diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..2e839779 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,5 @@ +.github/ +public/ +build/ +node_modules/ +backend/node_modules \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/.prettierrc @@ -0,0 +1 @@ +{} diff --git a/backend/create_database.js b/backend/create_database.js index 76d94647..46f8a053 100644 --- a/backend/create_database.js +++ b/backend/create_database.js @@ -1,37 +1,33 @@ -const { Client } = require('pg'); +const { Client } = require("pg"); -const _POSTGRES_USER=process.env.POSTGRES_USER; +const _POSTGRES_USER = process.env.POSTGRES_USER; const _POSTGRES_PASSWORD = process.env.POSTGRES_PASSWORD; -const _POSTGRES_IP=process.env.POSTGRES_IP; +const _POSTGRES_IP = process.env.POSTGRES_IP; const _POSTGRES_PORT = process.env.POSTGRES_PORT; - const client = new Client({ - host: _POSTGRES_IP, - user: _POSTGRES_USER, - password: _POSTGRES_PASSWORD, - port: _POSTGRES_PORT, + host: _POSTGRES_IP, + user: _POSTGRES_USER, + password: _POSTGRES_PASSWORD, + port: _POSTGRES_PORT, }); const createDatabase = async () => { - try { - await client.connect(); // gets connection - await client.query('CREATE DATABASE jfstat'); // sends queries - return true; - } catch (error) { - if(!error.stack.includes('already exists')) - { - console.error(error.stack); - } - - return false; - } finally { - await client.end(); // closes connection + try { + await client.connect(); // gets connection + await client.query("CREATE DATABASE jfstat"); // sends queries + return true; + } catch (error) { + if (!error.stack.includes("already exists")) { + console.error(error.stack); } -}; + return false; + } finally { + await client.end(); // closes connection + } +}; module.exports = { - createDatabase:createDatabase, - }; - \ No newline at end of file + createDatabase: createDatabase, +}; diff --git a/backend/db.js b/backend/db.js index 6c71d056..431354c0 100644 --- a/backend/db.js +++ b/backend/db.js @@ -1,137 +1,136 @@ -const { Pool } = require('pg'); +const { Pool } = require("pg"); const pgp = require("pg-promise")(); -const {update_query : update_query_map} = require("./models/bulk_insert_update_handler"); +const { + update_query: update_query_map, +} = require("./models/bulk_insert_update_handler"); - -const _POSTGRES_USER=process.env.POSTGRES_USER; +const _POSTGRES_USER = process.env.POSTGRES_USER; const _POSTGRES_PASSWORD = process.env.POSTGRES_PASSWORD; -const _POSTGRES_IP=process.env.POSTGRES_IP; +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('Error: Postgres details not defined'); +if ( + [_POSTGRES_USER, _POSTGRES_PASSWORD, _POSTGRES_IP, _POSTGRES_PORT].includes( + undefined, + ) +) { + console.log("Error: Postgres details not defined"); return; - } const pool = new Pool({ - user: (_POSTGRES_USER), - host:(_POSTGRES_IP), - database: 'jfstat', - password:(_POSTGRES_PASSWORD), - port: (_POSTGRES_PORT), + user: _POSTGRES_USER, + host: _POSTGRES_IP, + database: "jfstat", + password: _POSTGRES_PASSWORD, + port: _POSTGRES_PORT, }); -pool.on('error', (err, client) => { - console.error('Unexpected error on idle client', err); +pool.on("error", (err, client) => { + console.error("Unexpected error on idle client", err); return; - //process.exit(-1); }); async function deleteBulk(table_name, data) { const client = await pool.connect(); - let result='SUCCESS'; - let message=''; + let result = "SUCCESS"; + let message = ""; try { - await client.query('BEGIN'); + await client.query("BEGIN"); if (data && data.length !== 0) { - const deleteQuery = { - text: `DELETE FROM ${table_name} WHERE "Id" IN (${pgp.as.csv( - data - )})` + text: `DELETE FROM ${table_name} WHERE "Id" IN (${pgp.as.csv(data)})`, }; // console.log(deleteQuery); await client.query(deleteQuery); - } - - - await client.query('COMMIT'); - message=(data.length + " Rows removed."); + } + await client.query("COMMIT"); + message = data.length + " Rows removed."; } catch (error) { - await client.query('ROLLBACK'); - message=('Bulk delete error: '+ error); - result='ERROR'; + await client.query("ROLLBACK"); + message = "Bulk delete error: " + error; + result = "ERROR"; } finally { client.release(); } - return ({Result:result,message:''+message}); + return { Result: result, message: "" + message }; } -async function insertBulk(table_name, data,columns) { +async function insertBulk(table_name, data, columns) { //dedupe data -if (Array.isArray(data)) { - data= data.reduce((accumulator, currentItem) => { - const isDuplicate = accumulator.some(item => currentItem.Id ? (item.Id === currentItem.Id) : (item.rowid === currentItem.rowid)); - + if (Array.isArray(data)) { + data = data.reduce((accumulator, currentItem) => { + const isDuplicate = accumulator.some((item) => + currentItem.Id + ? item.Id === currentItem.Id + : item.rowid === currentItem.rowid, + ); + if (!isDuplicate) { accumulator.push(currentItem); } - + return accumulator; }, []); } - // const client = await pool.connect(); - let result='SUCCESS'; - let message=''; + let result = "SUCCESS"; + let message = ""; try { - await client.query("BEGIN"); - const update_query= update_query_map.find(query => query.table === table_name).query; - await client.query("COMMIT"); - const cs = new pgp.helpers.ColumnSet(columns, { table: table_name }); - const query = pgp.helpers.insert(data, cs) + update_query; // Update the column names accordingly - await client.query(query); - + await client.query("BEGIN"); + const update_query = update_query_map.find( + (query) => query.table === table_name, + ).query; + await client.query("COMMIT"); + const cs = new pgp.helpers.ColumnSet(columns, { table: table_name }); + const query = pgp.helpers.insert(data, cs) + update_query; // Update the column names accordingly + await client.query(query); } catch (error) { - await client.query('ROLLBACK'); - message=(''+ error); - result='ERROR'; + await client.query("ROLLBACK"); + message = "" + error; + result = "ERROR"; } finally { client.release(); } - return ({Result:result,message:message?'Bulk insert error: '+message:''}); + return { + Result: result, + message: message ? "Bulk insert error: " + message : "", + }; } -async function query(text, params) { +async function query(text, params) { try { - const result = await pool.query(text, params); - return result; + return await pool.query(text, params); } catch (error) { - - if(error?.routine==='auth_failed') - { - console.log('Error 401: Unable to Authenticate with Postgres DB'); - }else - if(error?.code==='ENOTFOUND') - { - console.log('Error: Unable to Connect to Postgres DB'); - }else - if(error?.code==='ERR_SOCKET_BAD_PORT') - { - console.log('Error: Invalid Postgres DB Port Range. Port should be >= 0 and < 65536.'); - }else - if(error?.code==='ECONNREFUSED') - { - console.log('Error: Postgres DB Connection refused at '+error.address+':'+error.port); - }else - { - console.error('Error occurred while executing query:', error); + if (error?.routine === "auth_failed") { + console.log("Error 401: Unable to Authenticate with Postgres DB"); + } else if (error?.code === "ENOTFOUND") { + console.log("Error: Unable to Connect to Postgres DB"); + } else if (error?.code === "ERR_SOCKET_BAD_PORT") { + console.log( + "Error: Invalid Postgres DB Port Range. Port should be >= 0 and < 65536.", + ); + } else if (error?.code === "ECONNREFUSED") { + console.log( + "Error: Postgres DB Connection refused at " + + error.address + + ":" + + error.port, + ); + } else { + console.error("Error occurred while executing query:", error); } return []; - // throw error; } } module.exports = { - query:query, + query: query, deleteBulk: deleteBulk, insertBulk: insertBulk, - // initDB: initDB, }; diff --git a/backend/logging/taskName.js b/backend/logging/taskName.js index 9634565d..7e13bb56 100644 --- a/backend/logging/taskName.js +++ b/backend/logging/taskName.js @@ -1,8 +1,8 @@ const task = { - sync: 'Jellyfin Sync', - backup: 'Backup', - restore: 'Restore', - import: 'Jellyfin Playback Reporting Plugin Sync', - }; - - module.exports = task; \ No newline at end of file + sync: "Jellyfin Sync", + backup: "Backup", + restore: "Restore", + import: "Jellyfin Playback Reporting Plugin Sync", +}; + +module.exports = task; diff --git a/backend/logging/taskstate.js b/backend/logging/taskstate.js index 57ef1f8e..25b83302 100644 --- a/backend/logging/taskstate.js +++ b/backend/logging/taskstate.js @@ -1,7 +1,7 @@ const taskstate = { - SUCCESS: 'Success', - FAILED: 'Failed', - RUNNING: 'Running', - }; - - module.exports = taskstate; \ No newline at end of file + SUCCESS: "Success", + FAILED: "Failed", + RUNNING: "Running", +}; + +module.exports = taskstate; diff --git a/backend/logging/triggertype.js b/backend/logging/triggertype.js index 5bac9498..b72793bb 100644 --- a/backend/logging/triggertype.js +++ b/backend/logging/triggertype.js @@ -1,6 +1,6 @@ const triggertype = { - Automatic: 'Automatic', - Manual: 'Manual' - }; - - module.exports = triggertype; \ No newline at end of file + Automatic: "Automatic", + Manual: "Manual", +}; + +module.exports = triggertype; diff --git a/backend/migrations.js b/backend/migrations.js index 38d54b54..24d18fdc 100644 --- a/backend/migrations.js +++ b/backend/migrations.js @@ -1,58 +1,29 @@ - module.exports = { - development: { - client: 'pg', - connection: { - host: process.env.POSTGRES_IP, - user: process.env.POSTGRES_USER, - password: process.env.POSTGRES_PASSWORD, - port:process.env.POSTGRES_PORT, - database: 'jfstat', - createDatabase: true, - }, - migrations: { - directory: __dirname + '/migrations', - migrationSource: { - // Use a sequential index naming convention for migration files - pattern: /^([0-9]+)_.*\.js$/, - sortDirsSeparately: true, - load: (filename) => { - const migration = require(filename); - if (migration.up && migration.down) { - return migration; - } else { - throw new Error(`Invalid migration file: ${filename}`); - } - } - } - } + development: { + client: "pg", + connection: { + host: process.env.POSTGRES_IP, + user: process.env.POSTGRES_USER, + password: process.env.POSTGRES_PASSWORD, + port: process.env.POSTGRES_PORT, + database: "jfstat", + createDatabase: true, }, - production: { - client: 'pg', - connection: { - host: process.env.DB_HOST, - user: process.env.DB_USER, - password: process.env.DB_PASSWORD, - port:process.env.POSTGRES_PORT, - database: "jfstat", - createDatabase: true, - }, - migrations: { - directory: __dirname + '/migrations', - migrationSource: { - // Use a sequential index naming convention for migration files - pattern: /^([0-9]+)_.*\.js$/, - sortDirsSeparately: true, - load: (filename) => { - const migration = require(filename); - if (migration.up && migration.down) { - return migration; - } else { - throw new Error(`Invalid migration file: ${filename}`); - } + migrations: { + directory: __dirname + "/migrations", + migrationSource: { + // Use a sequential index naming convention for migration files + pattern: /^([0-9]+)_.*\.js$/, + sortDirsSeparately: true, + load: (filename) => { + const migration = require(filename); + if (migration.up && migration.down) { + return migration; + } else { + throw new Error(`Invalid migration file: ${filename}`); } - } - } - } - }; - \ No newline at end of file + }, + }, + }, + }, +}; diff --git a/backend/migrations/001_app_config_table.js b/backend/migrations/001_app_config_table.js index 4fce2730..36c26bec 100644 --- a/backend/migrations/001_app_config_table.js +++ b/backend/migrations/001_app_config_table.js @@ -1,25 +1,26 @@ -exports.up = async function(knex) { - try - { - const hasTable = await knex.schema.hasTable('app_config'); - if (!hasTable) { - await knex.schema.createTable('app_config', function(table) { - table.increments('ID').primary(); - table.text('JF_HOST'); - table.text('JF_API_KEY'); - table.text('APP_USER'); - table.text('APP_PASSWORD'); - }); - await knex.raw(`ALTER TABLE app_config OWNER TO ${process.env.POSTGRES_USER};`); +exports.up = async function (knex) { + try { + const hasTable = await knex.schema.hasTable("app_config"); + if (!hasTable) { + await knex.schema.createTable("app_config", function (table) { + table.increments("ID").primary(); + table.text("JF_HOST"); + table.text("JF_API_KEY"); + table.text("APP_USER"); + table.text("APP_PASSWORD"); + }); + await knex.raw( + `ALTER TABLE app_config OWNER TO ${process.env.POSTGRES_USER};`, + ); + } + } catch (error) { + console.error(error); } -}catch (error) { - console.error(error); -} }; -exports.down = async function(knex) { +exports.down = async function (knex) { try { - await knex.schema.dropTableIfExists('app_config'); + await knex.schema.dropTableIfExists("app_config"); } catch (error) { console.error(error); } diff --git a/backend/migrations/002_jf_activity_watchdog_table.js b/backend/migrations/002_jf_activity_watchdog_table.js index 39ddee79..dccee44a 100644 --- a/backend/migrations/002_jf_activity_watchdog_table.js +++ b/backend/migrations/002_jf_activity_watchdog_table.js @@ -1,35 +1,37 @@ -exports.up = async function(knex) { +exports.up = async function (knex) { try { - const hasTable = await knex.schema.hasTable('jf_activity_watchdog'); + const hasTable = await knex.schema.hasTable("jf_activity_watchdog"); if (!hasTable) { - await knex.schema.createTable('jf_activity_watchdog', function(table) { - table.text('Id').primary(); - table.boolean('IsPaused').defaultTo(false); - table.text('UserId'); - table.text('UserName'); - table.text('Client'); - table.text('DeviceName'); - table.text('DeviceId'); - table.text('ApplicationVersion'); - table.text('NowPlayingItemId'); - table.text('NowPlayingItemName'); - table.text('SeasonId'); - table.text('SeriesName'); - table.text('EpisodeId'); - table.bigInteger('PlaybackDuration'); - table.timestamp('ActivityDateInserted').defaultTo(knex.fn.now()); - table.text('PlayMethod'); + await knex.schema.createTable("jf_activity_watchdog", function (table) { + table.text("Id").primary(); + table.boolean("IsPaused").defaultTo(false); + table.text("UserId"); + table.text("UserName"); + table.text("Client"); + table.text("DeviceName"); + table.text("DeviceId"); + table.text("ApplicationVersion"); + table.text("NowPlayingItemId"); + table.text("NowPlayingItemName"); + table.text("SeasonId"); + table.text("SeriesName"); + table.text("EpisodeId"); + table.bigInteger("PlaybackDuration"); + table.timestamp("ActivityDateInserted").defaultTo(knex.fn.now()); + table.text("PlayMethod"); }); - await knex.raw(`ALTER TABLE jf_activity_watchdog OWNER TO ${process.env.POSTGRES_USER};`); + await knex.raw( + `ALTER TABLE jf_activity_watchdog OWNER TO ${process.env.POSTGRES_USER};`, + ); } } catch (error) { console.error(error); } }; -exports.down = async function(knex) { +exports.down = async function (knex) { try { - await knex.schema.dropTableIfExists('jf_activity_watchdog'); + await knex.schema.dropTableIfExists("jf_activity_watchdog"); } catch (error) { console.error(error); } diff --git a/backend/migrations/003_jf_libraries_table.js b/backend/migrations/003_jf_libraries_table.js index bb12d11f..e35d74c7 100644 --- a/backend/migrations/003_jf_libraries_table.js +++ b/backend/migrations/003_jf_libraries_table.js @@ -1,28 +1,29 @@ -exports.up = async function(knex) { - try { - const hasTable = await knex.schema.hasTable('jf_libraries'); - if (!hasTable) { - await knex.schema.createTable('jf_libraries', function(table) { - table.text('Id').primary(); - table.text('Name').notNullable(); - table.text('ServerId'); - table.boolean('IsFolder').notNullable().defaultTo(true); - table.text('Type').notNullable().defaultTo('CollectionFolder'); - table.text('CollectionType').notNullable(); - table.text('ImageTagsPrimary'); - }); - await knex.raw(`ALTER TABLE jf_libraries OWNER TO ${process.env.POSTGRES_USER};`); - } - } catch (error) { - console.error(error); +exports.up = async function (knex) { + try { + const hasTable = await knex.schema.hasTable("jf_libraries"); + if (!hasTable) { + await knex.schema.createTable("jf_libraries", function (table) { + table.text("Id").primary(); + table.text("Name").notNullable(); + table.text("ServerId"); + table.boolean("IsFolder").notNullable().defaultTo(true); + table.text("Type").notNullable().defaultTo("CollectionFolder"); + table.text("CollectionType").notNullable(); + table.text("ImageTagsPrimary"); + }); + await knex.raw( + `ALTER TABLE jf_libraries OWNER TO ${process.env.POSTGRES_USER};`, + ); } - }; - - exports.down = async function(knex) { - try { - await knex.schema.dropTableIfExists('jf_libraries'); - } catch (error) { - console.error(error); - } - }; - \ No newline at end of file + } catch (error) { + console.error(error); + } +}; + +exports.down = async function (knex) { + try { + await knex.schema.dropTableIfExists("jf_libraries"); + } catch (error) { + console.error(error); + } +}; diff --git a/backend/migrations/004_jf_library_items_table.js b/backend/migrations/004_jf_library_items_table.js index bba1de7d..7c0a33a6 100644 --- a/backend/migrations/004_jf_library_items_table.js +++ b/backend/migrations/004_jf_library_items_table.js @@ -1,38 +1,45 @@ -exports.up = async function(knex) { +exports.up = async function (knex) { try { - const hasTable = await knex.schema.hasTable('jf_library_items'); + const hasTable = await knex.schema.hasTable("jf_library_items"); if (!hasTable) { - await knex.schema.createTable('jf_library_items', function(table) { - table.text('Id').primary(); - table.text('Name').notNullable(); - table.text('ServerId'); - table.timestamp('PremiereDate'); - table.timestamp('EndDate'); - table.double('CommunityRating'); - table.bigInteger('RunTimeTicks'); - table.integer('ProductionYear'); - table.boolean('IsFolder'); - table.text('Type').notNullable(); - table.text('Status'); - table.text('ImageTagsPrimary'); - table.text('ImageTagsBanner'); - table.text('ImageTagsLogo'); - table.text('ImageTagsThumb'); - table.text('BackdropImageTags'); - table.text('ParentId').notNullable().references('Id').inTable('jf_libraries').onDelete('SET NULL').onUpdate('NO ACTION'); - table.text('PrimaryImageHash'); + await knex.schema.createTable("jf_library_items", function (table) { + table.text("Id").primary(); + table.text("Name").notNullable(); + table.text("ServerId"); + table.timestamp("PremiereDate"); + table.timestamp("EndDate"); + table.double("CommunityRating"); + table.bigInteger("RunTimeTicks"); + table.integer("ProductionYear"); + table.boolean("IsFolder"); + table.text("Type").notNullable(); + table.text("Status"); + table.text("ImageTagsPrimary"); + table.text("ImageTagsBanner"); + table.text("ImageTagsLogo"); + table.text("ImageTagsThumb"); + table.text("BackdropImageTags"); + table + .text("ParentId") + .notNullable() + .references("Id") + .inTable("jf_libraries") + .onDelete("SET NULL") + .onUpdate("NO ACTION"); + table.text("PrimaryImageHash"); }); - await knex.raw(`ALTER TABLE IF EXISTS jf_library_items OWNER TO ${process.env.POSTGRES_USER};`); - + await knex.raw( + `ALTER TABLE IF EXISTS jf_library_items OWNER TO ${process.env.POSTGRES_USER};`, + ); } } catch (error) { console.error(error); } }; -exports.down = async function(knex) { +exports.down = async function (knex) { try { - await knex.schema.dropTableIfExists('jf_library_items'); + await knex.schema.dropTableIfExists("jf_library_items"); } catch (error) { console.error(error); } diff --git a/backend/migrations/005_jf_library_seasons_table.js b/backend/migrations/005_jf_library_seasons_table.js index 0d8271a7..643e209c 100644 --- a/backend/migrations/005_jf_library_seasons_table.js +++ b/backend/migrations/005_jf_library_seasons_table.js @@ -1,33 +1,34 @@ -exports.up = async function(knex) { +exports.up = async function (knex) { try { - const tableExists = await knex.schema.hasTable('jf_library_seasons'); + const tableExists = await knex.schema.hasTable("jf_library_seasons"); if (!tableExists) { - await knex.schema.createTable('jf_library_seasons', function(table) { - table.text('Id').notNullable().primary(); - table.text('Name'); - table.text('ServerId'); - table.integer('IndexNumber'); - table.text('Type'); - table.text('ParentLogoItemId'); - table.text('ParentBackdropItemId'); - table.text('ParentBackdropImageTags'); - table.text('SeriesName'); - table.text('SeriesId'); - table.text('SeriesPrimaryImageTag'); + await knex.schema.createTable("jf_library_seasons", function (table) { + table.text("Id").notNullable().primary(); + table.text("Name"); + table.text("ServerId"); + table.integer("IndexNumber"); + table.text("Type"); + table.text("ParentLogoItemId"); + table.text("ParentBackdropItemId"); + table.text("ParentBackdropImageTags"); + table.text("SeriesName"); + table.text("SeriesId"); + table.text("SeriesPrimaryImageTag"); }); - await knex.raw(`ALTER TABLE IF EXISTS jf_library_seasons OWNER TO ${process.env.POSTGRES_USER};`); + await knex.raw( + `ALTER TABLE IF EXISTS jf_library_seasons OWNER TO ${process.env.POSTGRES_USER};`, + ); } } catch (error) { console.error(error); } }; -exports.down = async function(knex) { +exports.down = async function (knex) { try { - await knex.schema.dropTableIfExists('jf_library_seasons'); + await knex.schema.dropTableIfExists("jf_library_seasons"); } catch (error) { console.error(error); } }; - diff --git a/backend/migrations/006_jf_library_episodes_table.js b/backend/migrations/006_jf_library_episodes_table.js index f97158a2..cf3e12aa 100644 --- a/backend/migrations/006_jf_library_episodes_table.js +++ b/backend/migrations/006_jf_library_episodes_table.js @@ -1,37 +1,38 @@ -exports.up = async function(knex) { +exports.up = async function (knex) { try { - const tableExists = await knex.schema.hasTable('jf_library_episodes'); + const tableExists = await knex.schema.hasTable("jf_library_episodes"); if (!tableExists) { - await knex.schema.createTable('jf_library_episodes', function(table) { - table.text('Id').primary(); - table.text('EpisodeId').notNullable(); - table.text('Name'); - table.text('ServerId'); - table.timestamp('PremiereDate'); - table.text('OfficialRating'); - table.double('CommunityRating'); - table.bigInteger('RunTimeTicks'); - table.integer('ProductionYear'); - table.integer('IndexNumber'); - table.integer('ParentIndexNumber'); - table.text('Type'); - table.text('ParentLogoItemId'); - table.text('ParentBackdropItemId'); - table.text('ParentBackdropImageTags'); - table.text('SeriesId'); - table.text('SeasonId'); - table.text('SeasonName'); - table.text('SeriesName'); + await knex.schema.createTable("jf_library_episodes", function (table) { + table.text("Id").primary(); + table.text("EpisodeId").notNullable(); + table.text("Name"); + table.text("ServerId"); + table.timestamp("PremiereDate"); + table.text("OfficialRating"); + table.double("CommunityRating"); + table.bigInteger("RunTimeTicks"); + table.integer("ProductionYear"); + table.integer("IndexNumber"); + table.integer("ParentIndexNumber"); + table.text("Type"); + table.text("ParentLogoItemId"); + table.text("ParentBackdropItemId"); + table.text("ParentBackdropImageTags"); + table.text("SeriesId"); + table.text("SeasonId"); + table.text("SeasonName"); + table.text("SeriesName"); }); - await knex.raw(`ALTER TABLE IF EXISTS jf_library_episodes OWNER TO ${process.env.POSTGRES_USER};`); + await knex.raw( + `ALTER TABLE IF EXISTS jf_library_episodes OWNER TO ${process.env.POSTGRES_USER};`, + ); } } catch (error) { console.error(error); } }; - // exports.down = function(knex) { // return knex.schema.hasTable('jf_library_episodes').then(function(exists) { // if (exists) { @@ -42,11 +43,10 @@ exports.up = async function(knex) { // }); // }; -exports.down = async function(knex) { +exports.down = async function (knex) { try { - await knex.schema.dropTableIfExists('jf_library_episodes'); + await knex.schema.dropTableIfExists("jf_library_episodes"); } catch (error) { console.error(error); } }; - diff --git a/backend/migrations/007_jf_playback_activity_table.js b/backend/migrations/007_jf_playback_activity_table.js index 25455558..68508799 100644 --- a/backend/migrations/007_jf_playback_activity_table.js +++ b/backend/migrations/007_jf_playback_activity_table.js @@ -1,38 +1,39 @@ -exports.up = async function(knex) { +exports.up = async function (knex) { try { - const tableExists = await knex.schema.hasTable('jf_playback_activity'); + const tableExists = await knex.schema.hasTable("jf_playback_activity"); if (!tableExists) { - await knex.schema.createTable('jf_playback_activity', function(table) { - table.text('Id').notNullable().primary(); - table.boolean('IsPaused').defaultTo(false); - table.text('UserId'); - table.text('UserName'); - table.text('Client'); - table.text('DeviceName'); - table.text('DeviceId'); - table.text('ApplicationVersion'); - table.text('NowPlayingItemId'); - table.text('NowPlayingItemName'); - table.text('SeasonId'); - table.text('SeriesName'); - table.text('EpisodeId'); - table.bigInteger('PlaybackDuration'); - table.timestamp('ActivityDateInserted').defaultTo(knex.fn.now()); - table.text('PlayMethod'); + await knex.schema.createTable("jf_playback_activity", function (table) { + table.text("Id").notNullable().primary(); + table.boolean("IsPaused").defaultTo(false); + table.text("UserId"); + table.text("UserName"); + table.text("Client"); + table.text("DeviceName"); + table.text("DeviceId"); + table.text("ApplicationVersion"); + table.text("NowPlayingItemId"); + table.text("NowPlayingItemName"); + table.text("SeasonId"); + table.text("SeriesName"); + table.text("EpisodeId"); + table.bigInteger("PlaybackDuration"); + table.timestamp("ActivityDateInserted").defaultTo(knex.fn.now()); + table.text("PlayMethod"); }); - await knex.raw(`ALTER TABLE IF EXISTS jf_playback_activity OWNER TO ${process.env.POSTGRES_USER};`); + await knex.raw( + `ALTER TABLE IF EXISTS jf_playback_activity OWNER TO ${process.env.POSTGRES_USER};`, + ); } } catch (error) { console.error(error); } }; -exports.down = async function(knex) { +exports.down = async function (knex) { try { - await knex.schema.dropTableIfExists('jf_playback_activity'); + await knex.schema.dropTableIfExists("jf_playback_activity"); } catch (error) { console.error(error); } }; - diff --git a/backend/migrations/008_js_users_table.js b/backend/migrations/008_js_users_table.js index 72bd0be4..4b2e4f27 100644 --- a/backend/migrations/008_js_users_table.js +++ b/backend/migrations/008_js_users_table.js @@ -1,29 +1,29 @@ - exports.up = async function(knex) { - try { - const tableExists = await knex.schema.hasTable('jf_users'); - if (!tableExists) { - await knex.schema.createTable('jf_users', function(table) { - table.text('Id').primary().notNullable().collate('default'); - table.text('Name').collate('default'); - table.text('PrimaryImageTag').collate('default'); - table.timestamp('LastLoginDate', { useTz: true }); - table.timestamp('LastActivityDate', { useTz: true }); - table.boolean('IsAdministrator'); - }); - - await knex.raw(`ALTER TABLE IF EXISTS jf_users OWNER TO ${process.env.POSTGRES_USER};`);; - } - } catch (error) { - console.error(error); +exports.up = async function (knex) { + try { + const tableExists = await knex.schema.hasTable("jf_users"); + if (!tableExists) { + await knex.schema.createTable("jf_users", function (table) { + table.text("Id").primary().notNullable().collate("default"); + table.text("Name").collate("default"); + table.text("PrimaryImageTag").collate("default"); + table.timestamp("LastLoginDate", { useTz: true }); + table.timestamp("LastActivityDate", { useTz: true }); + table.boolean("IsAdministrator"); + }); + + await knex.raw( + `ALTER TABLE IF EXISTS jf_users OWNER TO ${process.env.POSTGRES_USER};`, + ); } - }; - - exports.down = async function(knex) { - try { - await knex.schema.dropTableIfExists('jf_users'); - } catch (error) { - console.error(error); - } - }; - - \ No newline at end of file + } catch (error) { + console.error(error); + } +}; + +exports.down = async function (knex) { + try { + await knex.schema.dropTableIfExists("jf_users"); + } catch (error) { + console.error(error); + } +}; diff --git a/backend/migrations/009_jf_all_user_activity_view.js b/backend/migrations/009_jf_all_user_activity_view.js index a23dda92..fe86378d 100644 --- a/backend/migrations/009_jf_all_user_activity_view.js +++ b/backend/migrations/009_jf_all_user_activity_view.js @@ -1,5 +1,7 @@ -exports.up = async function(knex) { - await knex.raw(` +exports.up = async function (knex) { + await knex + .raw( + ` CREATE OR REPLACE VIEW jf_all_user_activity AS SELECT u."Id" AS "UserId", @@ -51,12 +53,13 @@ exports.up = async function(knex) { WHERE jf_playback_activity."UserId" = u."Id" ) plays ON true ORDER BY (now() - j."ActivityDateInserted"); - `).catch(function(error) { - console.error(error); - }); - }; - - exports.down = async function(knex) { - await knex.raw(`DROP VIEW jf_all_user_activity;`); - }; - \ No newline at end of file + `, + ) + .catch(function (error) { + console.error(error); + }); +}; + +exports.down = async function (knex) { + await knex.raw(`DROP VIEW jf_all_user_activity;`); +}; diff --git a/backend/migrations/010_jf_library_count_view.js b/backend/migrations/010_jf_library_count_view.js index 7dd1b959..98223a28 100644 --- a/backend/migrations/010_jf_library_count_view.js +++ b/backend/migrations/010_jf_library_count_view.js @@ -1,5 +1,7 @@ -exports.up = function(knex) { - return knex.raw(` +exports.up = function (knex) { + return knex + .raw( + ` CREATE OR REPLACE VIEW jf_library_count_view AS SELECT l."Id", @@ -14,14 +16,15 @@ exports.up = function(knex) { LEFT JOIN jf_library_episodes e ON e."SeasonId" = s."Id" GROUP BY l."Id", l."Name" ORDER BY (count(DISTINCT i."Id")) DESC; - `).catch(function(error) { - console.error(error); - }); - }; - - exports.down = function(knex) { - return knex.raw(` + `, + ) + .catch(function (error) { + console.error(error); + }); +}; + +exports.down = function (knex) { + return knex.raw(` DROP VIEW jf_library_count_view; `); - }; - \ No newline at end of file +}; diff --git a/backend/migrations/011_js_library_stats_overview_view.js b/backend/migrations/011_js_library_stats_overview_view.js index 279a8fb3..b6802626 100644 --- a/backend/migrations/011_js_library_stats_overview_view.js +++ b/backend/migrations/011_js_library_stats_overview_view.js @@ -1,4 +1,4 @@ -exports.up = function(knex) { +exports.up = function (knex) { const query = ` CREATE VIEW js_library_stats_overview AS SELECT DISTINCT ON (l."Id") l."Id", @@ -54,15 +54,12 @@ exports.up = function(knex) { LEFT JOIN jf_library_episodes e ON e."EpisodeId" = latest_activity."EpisodeId" ORDER BY l."Id", latest_activity."ActivityDateInserted" DESC; `; - - return knex.schema.raw(query).catch(function(error) { + + return knex.schema.raw(query).catch(function (error) { console.error(error); }); }; - - - exports.down = function(knex) { - return knex.schema.raw('DROP VIEW js_library_stats_overview'); - }; - \ No newline at end of file +exports.down = function (knex) { + return knex.schema.raw("DROP VIEW js_library_stats_overview"); +}; diff --git a/backend/migrations/012_fs_last_library_activity_function.js b/backend/migrations/012_fs_last_library_activity_function.js index d6cb3ce5..38d8e560 100644 --- a/backend/migrations/012_fs_last_library_activity_function.js +++ b/backend/migrations/012_fs_last_library_activity_function.js @@ -1,5 +1,7 @@ -exports.up = function(knex) { - return knex.schema.raw(` +exports.up = function (knex) { + return knex.schema + .raw( + ` CREATE OR REPLACE FUNCTION fs_last_library_activity( libraryid text) RETURNS TABLE("Id" text, "Name" text, "EpisodeName" text, "SeasonNumber" integer, "EpisodeNumber" integer, "PrimaryImageHash" text, "UserId" text, "UserName" text, "LastPlayed" interval) @@ -35,14 +37,15 @@ exports.up = function(knex) { LIMIT 15; END; $BODY$; - `).catch(function(error) { - console.error(error); - }); - }; - - exports.down = function(knex) { - return knex.schema.raw(` + `, + ) + .catch(function (error) { + console.error(error); + }); +}; + +exports.down = function (knex) { + return knex.schema.raw(` DROP FUNCTION IF EXISTS fs_last_library_activity(text); `); - }; - \ No newline at end of file +}; diff --git a/backend/migrations/013_fs_last_user_activity_function.js b/backend/migrations/013_fs_last_user_activity_function.js index 899877cf..a79c0390 100644 --- a/backend/migrations/013_fs_last_user_activity_function.js +++ b/backend/migrations/013_fs_last_user_activity_function.js @@ -1,5 +1,7 @@ -exports.up = function(knex) { - return knex.raw(` +exports.up = function (knex) { + return knex + .raw( + ` CREATE OR REPLACE FUNCTION fs_last_user_activity( userid text ) @@ -45,14 +47,15 @@ exports.up = function(knex) { ALTER FUNCTION fs_last_user_activity(text) OWNER TO ${process.env.POSTGRES_USER}; - `).catch(function(error) { - console.error(error); - }); - }; - - exports.down = function(knex) { - return knex.raw(` + `, + ) + .catch(function (error) { + console.error(error); + }); +}; + +exports.down = function (knex) { + return knex.raw(` DROP FUNCTION IF EXISTS fs_last_user_activity(text); `); - }; - \ No newline at end of file +}; diff --git a/backend/migrations/014_fs_library_stats_function.js b/backend/migrations/014_fs_library_stats_function.js index 2171cec2..3cd9a699 100644 --- a/backend/migrations/014_fs_library_stats_function.js +++ b/backend/migrations/014_fs_library_stats_function.js @@ -1,5 +1,7 @@ -exports.up = async function(knex) { - await knex.raw(` +exports.up = async function (knex) { + await knex + .raw( + ` CREATE OR REPLACE FUNCTION fs_library_stats( hours integer, libraryid text) @@ -29,14 +31,15 @@ exports.up = async function(knex) { ALTER FUNCTION fs_library_stats(integer, text) OWNER TO ${process.env.POSTGRES_USER}; - `).catch(function(error) { - console.error(error); - }); - }; - - exports.down = async function(knex) { - await knex.raw(` + `, + ) + .catch(function (error) { + console.error(error); + }); +}; + +exports.down = async function (knex) { + await knex.raw(` DROP FUNCTION IF EXISTS fs_library_stats(integer, text); `); - }; - \ No newline at end of file +}; diff --git a/backend/migrations/015_fs_most_active_user_function.js b/backend/migrations/015_fs_most_active_user_function.js index 428fdb43..9d1e4d58 100644 --- a/backend/migrations/015_fs_most_active_user_function.js +++ b/backend/migrations/015_fs_most_active_user_function.js @@ -1,5 +1,7 @@ -exports.up = function(knex) { - return knex.raw(` +exports.up = function (knex) { + return knex + .raw( + ` CREATE OR REPLACE FUNCTION fs_most_active_user( days integer) RETURNS TABLE("Plays" bigint, "UserId" text, "Name" text) @@ -21,12 +23,13 @@ exports.up = function(knex) { $BODY$; ALTER FUNCTION fs_most_active_user(integer) OWNER TO ${process.env.POSTGRES_USER}; - `).catch(function(error) { + `, + ) + .catch(function (error) { console.error(error); }); - }; - - exports.down = function(knex) { - return knex.raw('DROP FUNCTION IF EXISTS fs_most_active_user(integer)'); - }; - \ No newline at end of file +}; + +exports.down = function (knex) { + return knex.raw("DROP FUNCTION IF EXISTS fs_most_active_user(integer)"); +}; diff --git a/backend/migrations/016_fs_most_played_items_function.js b/backend/migrations/016_fs_most_played_items_function.js index 19de4893..69de9048 100644 --- a/backend/migrations/016_fs_most_played_items_function.js +++ b/backend/migrations/016_fs_most_played_items_function.js @@ -1,5 +1,7 @@ -exports.up = async function(knex) { - await knex.raw(` +exports.up = async function (knex) { + await knex + .raw( + ` CREATE OR REPLACE FUNCTION fs_most_played_items( days integer, itemtype text @@ -48,12 +50,13 @@ exports.up = async function(knex) { ALTER FUNCTION fs_most_played_items(integer, text) OWNER TO ${process.env.POSTGRES_USER}; - `).catch(function(error) { - console.error(error); - }); - }; - - exports.down = async function(knex) { - await knex.raw('DROP FUNCTION IF EXISTS fs_most_played_items(integer, text)'); - }; - \ No newline at end of file + `, + ) + .catch(function (error) { + console.error(error); + }); +}; + +exports.down = async function (knex) { + await knex.raw("DROP FUNCTION IF EXISTS fs_most_played_items(integer, text)"); +}; diff --git a/backend/migrations/017_fs_most_popular_items_function.js b/backend/migrations/017_fs_most_popular_items_function.js index 64a1c059..47b51f46 100644 --- a/backend/migrations/017_fs_most_popular_items_function.js +++ b/backend/migrations/017_fs_most_popular_items_function.js @@ -1,5 +1,7 @@ -exports.up = async function(knex) { - await knex.raw(` +exports.up = async function (knex) { + await knex + .raw( + ` CREATE OR REPLACE FUNCTION fs_most_popular_items( days integer, itemtype text @@ -54,12 +56,13 @@ exports.up = async function(knex) { $BODY$; ALTER FUNCTION fs_most_popular_items(integer, text) OWNER TO ${process.env.POSTGRES_USER}; - `).catch(function(error) { - console.error(error); - }); - }; - - exports.down = async function(knex) { - await knex.raw(`DROP FUNCTION fs_most_popular_items(integer, text);`); - }; - \ No newline at end of file + `, + ) + .catch(function (error) { + console.error(error); + }); +}; + +exports.down = async function (knex) { + await knex.raw(`DROP FUNCTION fs_most_popular_items(integer, text);`); +}; diff --git a/backend/migrations/018_fs_most_used_clients_function.js b/backend/migrations/018_fs_most_used_clients_function.js index 4d04361d..23c61592 100644 --- a/backend/migrations/018_fs_most_used_clients_function.js +++ b/backend/migrations/018_fs_most_used_clients_function.js @@ -1,5 +1,7 @@ -exports.up = async function(knex) { - await knex.raw(` +exports.up = async function (knex) { + await knex + .raw( + ` CREATE OR REPLACE FUNCTION fs_most_used_clients( days integer ) @@ -23,12 +25,13 @@ exports.up = async function(knex) { ALTER FUNCTION fs_most_used_clients(integer) OWNER TO ${process.env.POSTGRES_USER}; - `).catch(function(error) { + `, + ) + .catch(function (error) { console.error(error); }); - }; - - exports.down = async function(knex) { - await knex.raw(`DROP FUNCTION fs_most_used_clients(integer);`); - }; - \ No newline at end of file +}; + +exports.down = async function (knex) { + await knex.raw(`DROP FUNCTION fs_most_used_clients(integer);`); +}; diff --git a/backend/migrations/019_fs_most_viewed_libraries_function.js b/backend/migrations/019_fs_most_viewed_libraries_function.js index 80c8bd84..d5112d32 100644 --- a/backend/migrations/019_fs_most_viewed_libraries_function.js +++ b/backend/migrations/019_fs_most_viewed_libraries_function.js @@ -1,5 +1,7 @@ -exports.up = function(knex) { - return knex.raw(` +exports.up = function (knex) { + return knex + .raw( + ` CREATE OR REPLACE FUNCTION fs_most_viewed_libraries( days integer ) RETURNS TABLE( @@ -52,14 +54,15 @@ exports.up = function(knex) { ALTER FUNCTION fs_most_viewed_libraries(integer) OWNER TO ${process.env.POSTGRES_USER}; - `).catch(function(error) { - console.error(error); - }); - }; - - exports.down = function(knex) { - return knex.raw(` + `, + ) + .catch(function (error) { + console.error(error); + }); +}; + +exports.down = function (knex) { + return knex.raw(` DROP FUNCTION IF EXISTS fs_most_viewed_libraries(integer); `); - }; - \ No newline at end of file +}; diff --git a/backend/migrations/020_fs_user_stats_function.js b/backend/migrations/020_fs_user_stats_function.js index f36b40d6..13191440 100644 --- a/backend/migrations/020_fs_user_stats_function.js +++ b/backend/migrations/020_fs_user_stats_function.js @@ -1,5 +1,7 @@ -exports.up = async function(knex) { - await knex.raw(` +exports.up = async function (knex) { + await knex + .raw( + ` CREATE OR REPLACE FUNCTION fs_user_stats( hours integer, userid text @@ -33,12 +35,13 @@ exports.up = async function(knex) { ALTER FUNCTION fs_user_stats(integer, text) OWNER TO ${process.env.POSTGRES_USER}; - `).catch(function(error) { - console.error(error); - }); - }; - - exports.down = async function(knex) { - await knex.raw(`DROP FUNCTION IF EXISTS fs_user_stats(integer, text);`); - }; - \ No newline at end of file + `, + ) + .catch(function (error) { + console.error(error); + }); +}; + +exports.down = async function (knex) { + await knex.raw(`DROP FUNCTION IF EXISTS fs_user_stats(integer, text);`); +}; diff --git a/backend/migrations/021_fs_watch_stats_over_time_functions.js b/backend/migrations/021_fs_watch_stats_over_time_functions.js index 4a35c565..2f2540b7 100644 --- a/backend/migrations/021_fs_watch_stats_over_time_functions.js +++ b/backend/migrations/021_fs_watch_stats_over_time_functions.js @@ -1,5 +1,7 @@ exports.up = async function (knex) { - await knex.raw(` + await knex + .raw( + ` CREATE OR REPLACE FUNCTION fs_watch_stats_over_time( days integer ) @@ -49,14 +51,15 @@ exports.up = async function (knex) { ALTER FUNCTION fs_watch_stats_over_time(integer) OWNER TO ${process.env.POSTGRES_USER}; - `).catch(function(error) { - console.error(error); - }); - }; - - exports.down = async function (knex) { - await knex.raw(` + `, + ) + .catch(function (error) { + console.error(error); + }); +}; + +exports.down = async function (knex) { + await knex.raw(` DROP FUNCTION IF EXISTS fs_watch_stats_over_time(integer); `); - }; - \ No newline at end of file +}; diff --git a/backend/migrations/022_fs_watch_stats_popular_days_of_week_function.js b/backend/migrations/022_fs_watch_stats_popular_days_of_week_function.js index e3bef1d2..e409f892 100644 --- a/backend/migrations/022_fs_watch_stats_popular_days_of_week_function.js +++ b/backend/migrations/022_fs_watch_stats_popular_days_of_week_function.js @@ -1,5 +1,7 @@ -exports.up =async function(knex) { - return knex.raw(` +exports.up = async function (knex) { + return knex + .raw( + ` CREATE OR REPLACE FUNCTION fs_watch_stats_popular_days_of_week( days integer ) @@ -57,12 +59,15 @@ exports.up =async function(knex) { $BODY$; ALTER FUNCTION fs_watch_stats_popular_days_of_week(integer) OWNER TO ${process.env.POSTGRES_USER}; - `).catch(function(error) { - console.error(error); - }); - }; - - exports.down = function(knex) { - return knex.raw('DROP FUNCTION IF EXISTS fs_watch_stats_popular_days_of_week(integer)'); - }; - \ No newline at end of file + `, + ) + .catch(function (error) { + console.error(error); + }); +}; + +exports.down = function (knex) { + return knex.raw( + "DROP FUNCTION IF EXISTS fs_watch_stats_popular_days_of_week(integer)", + ); +}; diff --git a/backend/migrations/023_fs_watch_stats_popular_hour_of_day_function,js.js b/backend/migrations/023_fs_watch_stats_popular_hour_of_day_function,js.js index 54ff0475..4ee6a853 100644 --- a/backend/migrations/023_fs_watch_stats_popular_hour_of_day_function,js.js +++ b/backend/migrations/023_fs_watch_stats_popular_hour_of_day_function,js.js @@ -1,5 +1,7 @@ -exports.up =async function(knex) { - return knex.raw(` +exports.up = async function (knex) { + return knex + .raw( + ` CREATE OR REPLACE FUNCTION fs_watch_stats_popular_hour_of_day( days integer ) RETURNS TABLE("Hour" integer, "Count" integer, "Library" text) @@ -43,12 +45,15 @@ exports.up =async function(knex) { $BODY$; ALTER FUNCTION fs_watch_stats_popular_hour_of_day(integer) OWNER TO ${process.env.POSTGRES_USER}; - `).catch(function(error) { - console.error(error); - }); - }; - - exports.down = function(knex) { - return knex.raw('DROP FUNCTION IF EXISTS fs_watch_stats_popular_hour_of_day(integer)'); - }; - \ No newline at end of file + `, + ) + .catch(function (error) { + console.error(error); + }); +}; + +exports.down = function (knex) { + return knex.raw( + "DROP FUNCTION IF EXISTS fs_watch_stats_popular_hour_of_day(integer)", + ); +}; diff --git a/backend/migrations/024_jf_item_info_table.js b/backend/migrations/024_jf_item_info_table.js index 9fe83140..74cd157d 100644 --- a/backend/migrations/024_jf_item_info_table.js +++ b/backend/migrations/024_jf_item_info_table.js @@ -1,29 +1,30 @@ -exports.up = async function(knex) { +exports.up = async function (knex) { try { - const tableExists = await knex.schema.hasTable('jf_item_info'); + const tableExists = await knex.schema.hasTable("jf_item_info"); if (!tableExists) { - await knex.schema.createTable('jf_item_info', function(table) { - table.text('Id').notNullable().primary(); - table.text('Path'); - table.text('Name'); - table.bigInteger('Size'); - table.bigInteger('Bitrate'); - table.json('MediaStreams'); - table.text('Type'); + await knex.schema.createTable("jf_item_info", function (table) { + table.text("Id").notNullable().primary(); + table.text("Path"); + table.text("Name"); + table.bigInteger("Size"); + table.bigInteger("Bitrate"); + table.json("MediaStreams"); + table.text("Type"); }); - await knex.raw(`ALTER TABLE IF EXISTS jf_item_info OWNER TO ${process.env.POSTGRES_USER};`); + await knex.raw( + `ALTER TABLE IF EXISTS jf_item_info OWNER TO ${process.env.POSTGRES_USER};`, + ); } } catch (error) { console.error(error); } }; -exports.down = async function(knex) { +exports.down = async function (knex) { try { - await knex.schema.dropTableIfExists('jf_item_info'); + await knex.schema.dropTableIfExists("jf_item_info"); } catch (error) { console.error(error); } }; - diff --git a/backend/migrations/025_fs_last_library_activity_function.js b/backend/migrations/025_fs_last_library_activity_function.js index 7b1a52cb..cc5473f7 100644 --- a/backend/migrations/025_fs_last_library_activity_function.js +++ b/backend/migrations/025_fs_last_library_activity_function.js @@ -1,5 +1,7 @@ -exports.up = function(knex) { - return knex.schema.raw(` +exports.up = function (knex) { + return knex.schema + .raw( + ` DROP FUNCTION IF EXISTS fs_last_library_activity(text); CREATE OR REPLACE FUNCTION public.fs_last_library_activity( libraryid text) @@ -39,13 +41,15 @@ exports.up = function(knex) { $BODY$; - `).catch(function(error) { - console.error(error); - }); - }; - - exports.down = function(knex) { - return knex.schema.raw(` + `, + ) + .catch(function (error) { + console.error(error); + }); +}; + +exports.down = function (knex) { + return knex.schema.raw(` DROP FUNCTION IF EXISTS fs_last_library_activity(text); CREATE OR REPLACE FUNCTION fs_last_library_activity( libraryid text) @@ -83,5 +87,4 @@ exports.up = function(knex) { END; $BODY$; `); - }; - \ No newline at end of file +}; diff --git a/backend/migrations/026_fs_last_user_activity_function.js b/backend/migrations/026_fs_last_user_activity_function.js index 2ce4bba5..d15bfceb 100644 --- a/backend/migrations/026_fs_last_user_activity_function.js +++ b/backend/migrations/026_fs_last_user_activity_function.js @@ -1,5 +1,7 @@ -exports.up = function(knex) { - return knex.raw(` +exports.up = function (knex) { + return knex + .raw( + ` DROP FUNCTION IF EXISTS fs_last_user_activity(text); CREATE OR REPLACE FUNCTION public.fs_last_user_activity( userid text) @@ -38,13 +40,15 @@ exports.up = function(knex) { ALTER FUNCTION fs_last_user_activity(text) OWNER TO ${process.env.POSTGRES_USER}; - `).catch(function(error) { - console.error(error); - }); - }; - - exports.down = function(knex) { - return knex.raw(` + `, + ) + .catch(function (error) { + console.error(error); + }); +}; + +exports.down = function (knex) { + return knex.raw(` DROP FUNCTION IF EXISTS fs_last_user_activity(text); CREATE OR REPLACE FUNCTION fs_last_user_activity( userid text @@ -92,5 +96,4 @@ exports.up = function(knex) { ALTER FUNCTION fs_last_user_activity(text) OWNER TO ${process.env.POSTGRES_USER}; `); - }; - \ No newline at end of file +}; diff --git a/backend/migrations/027_js_library_metadata_view.js b/backend/migrations/027_js_library_metadata_view.js index 4dbd60bc..240fa205 100644 --- a/backend/migrations/027_js_library_metadata_view.js +++ b/backend/migrations/027_js_library_metadata_view.js @@ -1,5 +1,7 @@ -exports.up = async function(knex) { - await knex.raw(` +exports.up = async function (knex) { + await knex + .raw( + ` CREATE OR REPLACE VIEW public.js_library_metadata AS select l."Id", @@ -14,12 +16,13 @@ exports.up = async function(knex) { left join jf_item_info ii on (ii."Id"=i."Id" or ii."Id"=e."EpisodeId") group by l."Id",l."Name" - `).catch(function(error) { - console.error(error); - }); - }; - - exports.down = async function(knex) { - await knex.raw(`DROP VIEW js_library_metadata;`); - }; - \ No newline at end of file + `, + ) + .catch(function (error) { + console.error(error); + }); +}; + +exports.down = async function (knex) { + await knex.raw(`DROP VIEW js_library_metadata;`); +}; diff --git a/backend/migrations/028_jf_playback_reporting_plugin_data_table.js b/backend/migrations/028_jf_playback_reporting_plugin_data_table.js index de64448a..86e0b6e9 100644 --- a/backend/migrations/028_jf_playback_reporting_plugin_data_table.js +++ b/backend/migrations/028_jf_playback_reporting_plugin_data_table.js @@ -1,32 +1,38 @@ -exports.up = async function(knex) { +exports.up = async function (knex) { try { - const tableExists = await knex.schema.hasTable('jf_playback_reporting_plugin_data'); + const tableExists = await knex.schema.hasTable( + "jf_playback_reporting_plugin_data", + ); if (!tableExists) { - await knex.schema.createTable('jf_playback_reporting_plugin_data', function(table) { - table.bigInteger('rowid').notNullable().primary(); - table.timestamp('DateCreated', { useTz: true }); - table.text('UserId'); - table.text('ItemId'); - table.text('ItemType'); - table.text('ItemName'); - table.text('PlaybackMethod'); - table.text('ClientName'); - table.text('DeviceName'); - table.bigInteger('PlayDuration'); - }); + await knex.schema.createTable( + "jf_playback_reporting_plugin_data", + function (table) { + table.bigInteger("rowid").notNullable().primary(); + table.timestamp("DateCreated", { useTz: true }); + table.text("UserId"); + table.text("ItemId"); + table.text("ItemType"); + table.text("ItemName"); + table.text("PlaybackMethod"); + table.text("ClientName"); + table.text("DeviceName"); + table.bigInteger("PlayDuration"); + }, + ); - await knex.raw(`ALTER TABLE IF EXISTS jf_playback_reporting_plugin_data OWNER TO ${process.env.POSTGRES_USER};`); + await knex.raw( + `ALTER TABLE IF EXISTS jf_playback_reporting_plugin_data OWNER TO ${process.env.POSTGRES_USER};`, + ); } } catch (error) { console.error(error); } }; -exports.down = async function(knex) { +exports.down = async function (knex) { try { - await knex.schema.dropTableIfExists('jf_playback_reporting_plugin_data'); + await knex.schema.dropTableIfExists("jf_playback_reporting_plugin_data"); } catch (error) { console.error(error); } }; - diff --git a/backend/migrations/029_jf_all_user_activity_view.js b/backend/migrations/029_jf_all_user_activity_view.js index 334b1d12..2aff3175 100644 --- a/backend/migrations/029_jf_all_user_activity_view.js +++ b/backend/migrations/029_jf_all_user_activity_view.js @@ -1,5 +1,7 @@ -exports.up = async function(knex) { - await knex.raw(` +exports.up = async function (knex) { + await knex + .raw( + ` DROP VIEW jf_all_user_activity; CREATE OR REPLACE VIEW jf_all_user_activity AS SELECT u."Id" AS "UserId", @@ -55,12 +57,13 @@ exports.up = async function(knex) { WHERE jf_playback_activity."UserId" = u."Id" ) plays ON true ORDER BY (now() - j."ActivityDateInserted"); - `).catch(function(error) { - console.error(error); - }); - }; - - exports.down = async function(knex) { - await knex.raw(`DROP VIEW jf_all_user_activity;`); - }; - \ No newline at end of file + `, + ) + .catch(function (error) { + console.error(error); + }); +}; + +exports.down = async function (knex) { + await knex.raw(`DROP VIEW jf_all_user_activity;`); +}; diff --git a/backend/migrations/030_jf_logging_table.js b/backend/migrations/030_jf_logging_table.js index e45a7415..26b52c7d 100644 --- a/backend/migrations/030_jf_logging_table.js +++ b/backend/migrations/030_jf_logging_table.js @@ -1,29 +1,30 @@ -exports.up = async function(knex) { - try { - const hasTable = await knex.schema.hasTable('jf_logging'); - if (!hasTable) { - await knex.schema.createTable('jf_logging', function(table) { - table.text('Id').primary(); - table.text('Name').notNullable(); - table.text('Type').notNullable(); - table.text('ExecutionType'); - table.text('Duration').notNullable(); - table.timestamp('TimeRun').defaultTo(knex.fn.now()); - table.json('Log'); - table.text('Result'); - }); - await knex.raw(`ALTER TABLE jf_logging OWNER TO ${process.env.POSTGRES_USER};`); - } - } catch (error) { - console.error(error); +exports.up = async function (knex) { + try { + const hasTable = await knex.schema.hasTable("jf_logging"); + if (!hasTable) { + await knex.schema.createTable("jf_logging", function (table) { + table.text("Id").primary(); + table.text("Name").notNullable(); + table.text("Type").notNullable(); + table.text("ExecutionType"); + table.text("Duration").notNullable(); + table.timestamp("TimeRun").defaultTo(knex.fn.now()); + table.json("Log"); + table.text("Result"); + }); + await knex.raw( + `ALTER TABLE jf_logging OWNER TO ${process.env.POSTGRES_USER};`, + ); } - }; - - exports.down = async function(knex) { - try { - await knex.schema.dropTableIfExists('jf_logging'); - } catch (error) { - console.error(error); - } - }; - \ No newline at end of file + } catch (error) { + console.error(error); + } +}; + +exports.down = async function (knex) { + try { + await knex.schema.dropTableIfExists("jf_logging"); + } catch (error) { + console.error(error); + } +}; diff --git a/backend/migrations/031_jd_remove_orphaned_data.js b/backend/migrations/031_jd_remove_orphaned_data.js index d875bbf8..489bddc3 100644 --- a/backend/migrations/031_jd_remove_orphaned_data.js +++ b/backend/migrations/031_jd_remove_orphaned_data.js @@ -1,5 +1,7 @@ -exports.up = function(knex) { - return knex.schema.raw(` +exports.up = function (knex) { + return knex.schema + .raw( + ` CREATE OR REPLACE PROCEDURE jd_remove_orphaned_data() AS $$ BEGIN DELETE FROM public.jf_library_episodes @@ -30,14 +32,15 @@ exports.up = function(knex) { END; $$ LANGUAGE plpgsql; - `).catch(function(error) { - console.error(error); - }); - }; - - exports.down = function(knex) { - return knex.schema.raw(` + `, + ) + .catch(function (error) { + console.error(error); + }); +}; + +exports.down = function (knex) { + return knex.schema.raw(` DROP PROCEDURE jd_remove_orphaned_data; `); - }; - \ No newline at end of file +}; diff --git a/backend/migrations/032_app_config_table_add_auth_flag.js b/backend/migrations/032_app_config_table_add_auth_flag.js index b15064fe..9f18d718 100644 --- a/backend/migrations/032_app_config_table_add_auth_flag.js +++ b/backend/migrations/032_app_config_table_add_auth_flag.js @@ -1,21 +1,20 @@ -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.boolean('REQUIRE_LOGIN').defaultTo(true); - }); +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.boolean("REQUIRE_LOGIN").defaultTo(true); + }); + } + } catch (error) { + console.error(error); } -}catch (error) { - console.error(error); -} }; -exports.down = async function(knex) { +exports.down = async function (knex) { try { - await knex.schema.alterTable('app_config', function(table) { - table.dropColumn('REQUIRE_LOGIN'); + await knex.schema.alterTable("app_config", function (table) { + table.dropColumn("REQUIRE_LOGIN"); }); } catch (error) { console.error(error); diff --git a/backend/migrations/033_js_library_stats_overview_view.js b/backend/migrations/033_js_library_stats_overview_view.js index e99a9102..4b93f3d4 100644 --- a/backend/migrations/033_js_library_stats_overview_view.js +++ b/backend/migrations/033_js_library_stats_overview_view.js @@ -1,4 +1,4 @@ -exports.up = function(knex) { +exports.up = function (knex) { const query = ` CREATE OR REPLACE VIEW public.js_library_stats_overview AS @@ -55,16 +55,14 @@ exports.up = function(knex) { LEFT JOIN jf_library_episodes e ON e."EpisodeId" = latest_activity."EpisodeId" ORDER BY l."Id", latest_activity."ActivityDateInserted" DESC; `; - - return knex.schema.raw(query).catch(function(error) { + + return knex.schema.raw(query).catch(function (error) { console.error(error); }); }; - - - exports.down = function(knex) { - return knex.schema.raw(` +exports.down = function (knex) { + return knex.schema.raw(` CREATE VIEW js_library_stats_overview AS SELECT DISTINCT ON (l."Id") l."Id", l."Name", @@ -119,5 +117,4 @@ exports.up = function(knex) { LEFT JOIN jf_library_episodes e ON e."EpisodeId" = latest_activity."EpisodeId" ORDER BY l."Id", latest_activity."ActivityDateInserted" DESC; `); - }; - \ No newline at end of file +}; diff --git a/backend/migrations/034_jf_libraries_table_add_stat_columns.js b/backend/migrations/034_jf_libraries_table_add_stat_columns.js index 1ae2ac53..50a8953c 100644 --- a/backend/migrations/034_jf_libraries_table_add_stat_columns.js +++ b/backend/migrations/034_jf_libraries_table_add_stat_columns.js @@ -1,27 +1,26 @@ -exports.up = async function(knex) { - try - { - const hasTable = await knex.schema.hasTable('jf_libraries'); - if (hasTable) { - await knex.schema.alterTable('jf_libraries', function(table) { - table.bigInteger('total_play_time'); - table.bigInteger('item_count'); - table.bigInteger('season_count'); - table.bigInteger('episode_count'); - }); +exports.up = async function (knex) { + try { + const hasTable = await knex.schema.hasTable("jf_libraries"); + if (hasTable) { + await knex.schema.alterTable("jf_libraries", function (table) { + table.bigInteger("total_play_time"); + table.bigInteger("item_count"); + table.bigInteger("season_count"); + table.bigInteger("episode_count"); + }); + } + } catch (error) { + console.error(error); } -}catch (error) { - console.error(error); -} }; -exports.down = async function(knex) { +exports.down = async function (knex) { try { - await knex.schema.alterTable('jf_libraries', function(table) { - table.dropColumn('total_play_time'); - table.dropColumn('item_count'); - table.dropColumn('season_count'); - table.dropColumn('episode_count'); + await knex.schema.alterTable("jf_libraries", function (table) { + table.dropColumn("total_play_time"); + table.dropColumn("item_count"); + table.dropColumn("season_count"); + table.dropColumn("episode_count"); }); } catch (error) { console.error(error); diff --git a/backend/migrations/035_ju_update_library_stats_data.js b/backend/migrations/035_ju_update_library_stats_data.js index 7496419a..2ca19668 100644 --- a/backend/migrations/035_ju_update_library_stats_data.js +++ b/backend/migrations/035_ju_update_library_stats_data.js @@ -1,5 +1,7 @@ -exports.up = function(knex) { - return knex.schema.raw(` +exports.up = function (knex) { + return knex.schema + .raw( + ` CREATE OR REPLACE PROCEDURE ju_update_library_stats_data() LANGUAGE plpgsql AS $$ @@ -24,14 +26,15 @@ exports.up = function(knex) { END; $$; - `).catch(function(error) { - console.error(error); - }); - }; - - exports.down = function(knex) { - return knex.schema.raw(` + `, + ) + .catch(function (error) { + console.error(error); + }); +}; + +exports.down = function (knex) { + return knex.schema.raw(` DROP PROCEDURE ju_update_library_stats_data; `); - }; - \ No newline at end of file +}; diff --git a/backend/migrations/036_js_library_stats_overview_view.js b/backend/migrations/036_js_library_stats_overview_view.js index e703eaba..65266b94 100644 --- a/backend/migrations/036_js_library_stats_overview_view.js +++ b/backend/migrations/036_js_library_stats_overview_view.js @@ -1,4 +1,4 @@ -exports.up = function(knex) { +exports.up = function (knex) { const query = ` DROP VIEW js_library_stats_overview; CREATE OR REPLACE VIEW public.js_library_stats_overview @@ -57,16 +57,14 @@ CREATE OR REPLACE VIEW public.js_library_stats_overview LEFT JOIN jf_library_episodes e ON e."EpisodeId" = latest_activity."EpisodeId" ORDER BY l."Id", latest_activity."ActivityDateInserted" DESC; `; - - return knex.schema.raw(query).catch(function(error) { + + return knex.schema.raw(query).catch(function (error) { console.error(error); }); }; - - - exports.down = function(knex) { - return knex.schema.raw(` +exports.down = function (knex) { + return knex.schema.raw(` CREATE OR REPLACE VIEW public.js_library_stats_overview AS SELECT DISTINCT ON (l."Id") l."Id", @@ -122,5 +120,4 @@ CREATE OR REPLACE VIEW public.js_library_stats_overview LEFT JOIN jf_library_episodes e ON e."EpisodeId" = latest_activity."EpisodeId" ORDER BY l."Id", latest_activity."ActivityDateInserted" DESC; `); - }; - \ No newline at end of file +}; diff --git a/backend/migrations/037_jf_library_items_with_playcount_playtime.js b/backend/migrations/037_jf_library_items_with_playcount_playtime.js index 3f1bdc9e..4349d035 100644 --- a/backend/migrations/037_jf_library_items_with_playcount_playtime.js +++ b/backend/migrations/037_jf_library_items_with_playcount_playtime.js @@ -1,4 +1,4 @@ -exports.up = function(knex) { +exports.up = function (knex) { const query = ` CREATE OR REPLACE VIEW jf_library_items_with_playcount_playtime AS SELECT @@ -28,15 +28,14 @@ exports.up = function(knex) { group by i."Id" order by times_played desc `; - - return knex.schema.raw(query).catch(function(error) { + + return knex.schema.raw(query).catch(function (error) { console.error(error); }); }; - - - exports.down = function(knex) { - return knex.schema.raw(`DROP VIEW public.jf_library_items_with_playcount_playtime;`); - }; - \ No newline at end of file +exports.down = function (knex) { + return knex.schema.raw( + `DROP VIEW public.jf_library_items_with_playcount_playtime;`, + ); +}; diff --git a/backend/migrations/038_jf_playback_activity_add_stream_data_columns.js b/backend/migrations/038_jf_playback_activity_add_stream_data_columns.js index 219df436..be283fb9 100644 --- a/backend/migrations/038_jf_playback_activity_add_stream_data_columns.js +++ b/backend/migrations/038_jf_playback_activity_add_stream_data_columns.js @@ -1,29 +1,28 @@ -exports.up = async function(knex) { - try - { - const hasTable = await knex.schema.hasTable('jf_playback_activity'); - if (hasTable) { - await knex.schema.alterTable('jf_playback_activity', function(table) { - table.json('MediaStreams'); - table.json('TranscodingInfo'); - table.json('PlayState'); - table.text('OriginalContainer'); - table.text('RemoteEndPoint'); - }); +exports.up = async function (knex) { + try { + const hasTable = await knex.schema.hasTable("jf_playback_activity"); + if (hasTable) { + await knex.schema.alterTable("jf_playback_activity", function (table) { + table.json("MediaStreams"); + table.json("TranscodingInfo"); + table.json("PlayState"); + table.text("OriginalContainer"); + table.text("RemoteEndPoint"); + }); + } + } catch (error) { + console.error(error); } -}catch (error) { - console.error(error); -} }; -exports.down = async function(knex) { +exports.down = async function (knex) { try { - await knex.schema.alterTable('jf_playback_activity', function(table) { - table.dropColumn('MediaStreams'); - table.dropColumn('TranscodingInfo'); - table.dropColumn('PlayState'); - table.dropColumn('OriginalContainer'); - table.dropColumn('RemoteEndPoint'); + await knex.schema.alterTable("jf_playback_activity", function (table) { + table.dropColumn("MediaStreams"); + table.dropColumn("TranscodingInfo"); + table.dropColumn("PlayState"); + table.dropColumn("OriginalContainer"); + table.dropColumn("RemoteEndPoint"); }); } catch (error) { console.error(error); diff --git a/backend/migrations/039_jf_activity_watchdog_add_stream_data_columns.js b/backend/migrations/039_jf_activity_watchdog_add_stream_data_columns.js index 2d2834e8..aedc4cdb 100644 --- a/backend/migrations/039_jf_activity_watchdog_add_stream_data_columns.js +++ b/backend/migrations/039_jf_activity_watchdog_add_stream_data_columns.js @@ -1,29 +1,28 @@ -exports.up = async function(knex) { - try - { - const hasTable = await knex.schema.hasTable('jf_activity_watchdog'); - if (hasTable) { - await knex.schema.alterTable('jf_activity_watchdog', function(table) { - table.json('MediaStreams'); - table.json('TranscodingInfo'); - table.json('PlayState'); - table.text('OriginalContainer'); - table.text('RemoteEndPoint'); - }); +exports.up = async function (knex) { + try { + const hasTable = await knex.schema.hasTable("jf_activity_watchdog"); + if (hasTable) { + await knex.schema.alterTable("jf_activity_watchdog", function (table) { + table.json("MediaStreams"); + table.json("TranscodingInfo"); + table.json("PlayState"); + table.text("OriginalContainer"); + table.text("RemoteEndPoint"); + }); + } + } catch (error) { + console.error(error); } -}catch (error) { - console.error(error); -} }; -exports.down = async function(knex) { +exports.down = async function (knex) { try { - await knex.schema.alterTable('jf_activity_watchdog', function(table) { - table.dropColumn('MediaStreams'); - table.dropColumn('TranscodingInfo'); - table.dropColumn('PlayState'); - table.dropColumn('OriginalContainer'); - table.dropColumn('RemoteEndPoint'); + await knex.schema.alterTable("jf_activity_watchdog", function (table) { + table.dropColumn("MediaStreams"); + table.dropColumn("TranscodingInfo"); + table.dropColumn("PlayState"); + table.dropColumn("OriginalContainer"); + table.dropColumn("RemoteEndPoint"); }); } catch (error) { console.error(error); diff --git a/backend/migrations/040_app_config_add_general_settings_column.js b/backend/migrations/040_app_config_add_general_settings_column.js index b61e6195..103f11e0 100644 --- a/backend/migrations/040_app_config_add_general_settings_column.js +++ b/backend/migrations/040_app_config_add_general_settings_column.js @@ -1,21 +1,20 @@ -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('settings').defaultTo({time_format:'12hr'}); - }); +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("settings").defaultTo({ time_format: "12hr" }); + }); + } + } catch (error) { + console.error(error); } -}catch (error) { - console.error(error); -} }; -exports.down = async function(knex) { +exports.down = async function (knex) { try { - await knex.schema.alterTable('app_config', function(table) { - table.dropColumn('settings'); + await knex.schema.alterTable("app_config", function (table) { + table.dropColumn("settings"); }); } catch (error) { console.error(error); diff --git a/backend/migrations/041_js_library_stats_overview_view_optimizations.js b/backend/migrations/041_js_library_stats_overview_view_optimizations.js index 1e428f21..89b034de 100644 --- a/backend/migrations/041_js_library_stats_overview_view_optimizations.js +++ b/backend/migrations/041_js_library_stats_overview_view_optimizations.js @@ -1,4 +1,4 @@ -exports.up = function(knex) { +exports.up = function (knex) { const query = ` CREATE OR REPLACE VIEW public.js_library_stats_overview AS @@ -55,16 +55,14 @@ exports.up = function(knex) { LEFT JOIN jf_library_episodes e ON e."EpisodeId" = latest_activity."EpisodeId" ORDER BY l."Id", latest_activity."ActivityDateInserted" DESC; `; - - return knex.schema.raw(query).catch(function(error) { + + return knex.schema.raw(query).catch(function (error) { console.error(error); }); }; - - - exports.down = function(knex) { - return knex.schema.raw(` +exports.down = function (knex) { + return knex.schema.raw(` CREATE OR REPLACE VIEW public.js_library_stats_overview AS SELECT DISTINCT ON (l."Id") l."Id", @@ -121,5 +119,4 @@ exports.up = function(knex) { LEFT JOIN jf_library_episodes e ON e."EpisodeId" = latest_activity."EpisodeId" ORDER BY l."Id", latest_activity."ActivityDateInserted" DESC; `); - }; - \ No newline at end of file +}; diff --git a/backend/migrations/042_app_config_add_api_keys_column.js b/backend/migrations/042_app_config_add_api_keys_column.js index 9e0b7b9c..46cc89bc 100644 --- a/backend/migrations/042_app_config_add_api_keys_column.js +++ b/backend/migrations/042_app_config_add_api_keys_column.js @@ -1,21 +1,20 @@ -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'); - }); +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); } -}catch (error) { - console.error(error); -} }; -exports.down = async function(knex) { +exports.down = async function (knex) { try { - await knex.schema.alterTable('app_config', function(table) { - table.dropColumn('api_keys'); + await knex.schema.alterTable("app_config", function (table) { + table.dropColumn("api_keys"); }); } catch (error) { console.error(error); diff --git a/backend/migrations/043_jf_playback_activity_add_serverid_column.js b/backend/migrations/043_jf_playback_activity_add_serverid_column.js index f2fe8c77..4362c077 100644 --- a/backend/migrations/043_jf_playback_activity_add_serverid_column.js +++ b/backend/migrations/043_jf_playback_activity_add_serverid_column.js @@ -1,23 +1,22 @@ -exports.up = async function(knex) { - try - { - const hasTable = await knex.schema.hasTable('jf_playback_activity'); - if (hasTable) { - await knex.schema.alterTable('jf_playback_activity', function(table) { - table.text('ServerId'); - table.boolean('imported').defaultTo(false); - }); +exports.up = async function (knex) { + try { + const hasTable = await knex.schema.hasTable("jf_playback_activity"); + if (hasTable) { + await knex.schema.alterTable("jf_playback_activity", function (table) { + table.text("ServerId"); + table.boolean("imported").defaultTo(false); + }); + } + } catch (error) { + console.error(error); } -}catch (error) { - console.error(error); -} }; -exports.down = async function(knex) { +exports.down = async function (knex) { try { - await knex.schema.alterTable('jf_playback_activity', function(table) { - table.dropColumn('ServerId'); - table.dropColumn('imported'); + await knex.schema.alterTable("jf_playback_activity", function (table) { + table.dropColumn("ServerId"); + table.dropColumn("imported"); }); } catch (error) { console.error(error); diff --git a/backend/migrations/044_jf_activity_watchdog_add_serverid_column.js b/backend/migrations/044_jf_activity_watchdog_add_serverid_column.js index b7204d6b..0d870e3d 100644 --- a/backend/migrations/044_jf_activity_watchdog_add_serverid_column.js +++ b/backend/migrations/044_jf_activity_watchdog_add_serverid_column.js @@ -1,21 +1,20 @@ -exports.up = async function(knex) { - try - { - const hasTable = await knex.schema.hasTable('jf_activity_watchdog'); - if (hasTable) { - await knex.schema.alterTable('jf_activity_watchdog', function(table) { - table.text('ServerId'); - }); +exports.up = async function (knex) { + try { + const hasTable = await knex.schema.hasTable("jf_activity_watchdog"); + if (hasTable) { + await knex.schema.alterTable("jf_activity_watchdog", function (table) { + table.text("ServerId"); + }); + } + } catch (error) { + console.error(error); } -}catch (error) { - console.error(error); -} }; -exports.down = async function(knex) { +exports.down = async function (knex) { try { - await knex.schema.alterTable('jf_activity_watchdog', function(table) { - table.dropColumn('ServerId'); + await knex.schema.alterTable("jf_activity_watchdog", function (table) { + table.dropColumn("ServerId"); }); } catch (error) { console.error(error); diff --git a/backend/migrations/045_ji_insert_playback_plugin_data_to_activity_table.js b/backend/migrations/045_ji_insert_playback_plugin_data_to_activity_table.js index bc237260..a7ba8548 100644 --- a/backend/migrations/045_ji_insert_playback_plugin_data_to_activity_table.js +++ b/backend/migrations/045_ji_insert_playback_plugin_data_to_activity_table.js @@ -1,5 +1,7 @@ -exports.up = function(knex) { - return knex.schema.raw(` +exports.up = function (knex) { + return knex.schema + .raw( + ` CREATE OR REPLACE PROCEDURE ji_insert_playback_plugin_data_to_activity_table() AS $$ BEGIN insert into jf_playback_activity @@ -46,14 +48,15 @@ exports.up = function(knex) { ); END; $$ LANGUAGE plpgsql; - `).catch(function(error) { - console.error(error); - }); - }; - - exports.down = function(knex) { - return knex.schema.raw(` + `, + ) + .catch(function (error) { + console.error(error); + }); +}; + +exports.down = function (knex) { + return knex.schema.raw(` DROP PROCEDURE ji_insert_playback_plugin_data_to_activity_table; `); - }; - \ No newline at end of file +}; diff --git a/backend/models/bulk_insert_update_handler.js b/backend/models/bulk_insert_update_handler.js index b39024aa..08a5f5ef 100644 --- a/backend/models/bulk_insert_update_handler.js +++ b/backend/models/bulk_insert_update_handler.js @@ -1,17 +1,49 @@ - - - const update_query = [ - {table:'jf_activity_watchdog',query:' ON CONFLICT ("Id") DO UPDATE SET "TranscodingInfo" = EXCLUDED."TranscodingInfo", "MediaStreams" = EXCLUDED."MediaStreams", "PlayMethod" = EXCLUDED."PlayMethod"'}, - {table:'jf_item_info',query:' ON CONFLICT ("Id") DO UPDATE SET "Path" = EXCLUDED."Path", "Name" = EXCLUDED."Name", "Size" = EXCLUDED."Size", "Bitrate" = EXCLUDED."Bitrate", "MediaStreams" = EXCLUDED."MediaStreams"'}, - {table:'jf_libraries',query:' ON CONFLICT ("Id") DO UPDATE SET "Name" = EXCLUDED."Name", "Type" = EXCLUDED."Type", "CollectionType" = EXCLUDED."CollectionType", "ImageTagsPrimary" = EXCLUDED."ImageTagsPrimary"'}, - {table:'jf_library_episodes',query:' ON CONFLICT ("Id") DO UPDATE SET "Name" = EXCLUDED."Name", "PremiereDate" = EXCLUDED."PremiereDate", "OfficialRating" = EXCLUDED."OfficialRating", "CommunityRating" = EXCLUDED."CommunityRating", "RunTimeTicks" = EXCLUDED."RunTimeTicks", "ProductionYear" = EXCLUDED."ProductionYear", "IndexNumber" = EXCLUDED."IndexNumber", "ParentIndexNumber" = EXCLUDED."ParentIndexNumber", "Type" = EXCLUDED."Type", "ParentLogoItemId" = EXCLUDED."ParentLogoItemId", "ParentBackdropItemId" = EXCLUDED."ParentBackdropItemId", "ParentBackdropImageTags" = EXCLUDED."ParentBackdropImageTags", "SeriesId" = EXCLUDED."SeriesId", "SeasonId" = EXCLUDED."SeasonId", "SeasonName" = EXCLUDED."SeasonName", "SeriesName" = EXCLUDED."SeriesName"'}, - {table:'jf_library_items',query:' ON CONFLICT ("Id") DO UPDATE SET "Name" = EXCLUDED."Name", "PremiereDate" = EXCLUDED."PremiereDate", "EndDate" = EXCLUDED."EndDate", "CommunityRating" = EXCLUDED."CommunityRating", "RunTimeTicks" = EXCLUDED."RunTimeTicks", "ProductionYear" = EXCLUDED."ProductionYear", "Type" = EXCLUDED."Type", "Status" = EXCLUDED."Status", "ImageTagsPrimary" = EXCLUDED."ImageTagsPrimary", "ImageTagsBanner" = EXCLUDED."ImageTagsBanner", "ImageTagsLogo" = EXCLUDED."ImageTagsLogo", "ImageTagsThumb" = EXCLUDED."ImageTagsThumb", "BackdropImageTags" = EXCLUDED."BackdropImageTags", "ParentId" = EXCLUDED."ParentId", "PrimaryImageHash" = EXCLUDED."PrimaryImageHash"'}, - {table:'jf_library_seasons',query:' ON CONFLICT ("Id") DO UPDATE SET "Name" = EXCLUDED."Name", "ParentLogoItemId" = EXCLUDED."ParentLogoItemId", "ParentBackdropItemId" = EXCLUDED."ParentBackdropItemId", "ParentBackdropImageTags" = EXCLUDED."ParentBackdropImageTags", "SeriesPrimaryImageTag" = EXCLUDED."SeriesPrimaryImageTag"'}, - {table:'jf_logging',query:` ON CONFLICT ("Id") DO UPDATE SET "Duration" = EXCLUDED."Duration", "Log"=EXCLUDED."Log", "Result"=EXCLUDED."Result" WHERE "jf_logging"."Result"='Running'`}, - {table:'jf_playback_activity',query:' ON CONFLICT DO NOTHING'}, - {table:'jf_playback_reporting_plugin_data',query:' ON CONFLICT DO NOTHING'}, - {table:'jf_users',query:' ON CONFLICT ("Id") DO UPDATE SET "Name" = EXCLUDED."Name", "PrimaryImageTag" = EXCLUDED."PrimaryImageTag", "LastLoginDate" = EXCLUDED."LastLoginDate", "LastActivityDate" = EXCLUDED."LastActivityDate"'} - ]; - module.exports = { - update_query - }; \ No newline at end of file +const update_query = [ + { + table: "jf_activity_watchdog", + query: + ' ON CONFLICT ("Id") DO UPDATE SET "TranscodingInfo" = EXCLUDED."TranscodingInfo", "MediaStreams" = EXCLUDED."MediaStreams", "PlayMethod" = EXCLUDED."PlayMethod"', + }, + { + table: "jf_item_info", + query: + ' ON CONFLICT ("Id") DO UPDATE SET "Path" = EXCLUDED."Path", "Name" = EXCLUDED."Name", "Size" = EXCLUDED."Size", "Bitrate" = EXCLUDED."Bitrate", "MediaStreams" = EXCLUDED."MediaStreams"', + }, + { + table: "jf_libraries", + query: + ' ON CONFLICT ("Id") DO UPDATE SET "Name" = EXCLUDED."Name", "Type" = EXCLUDED."Type", "CollectionType" = EXCLUDED."CollectionType", "ImageTagsPrimary" = EXCLUDED."ImageTagsPrimary"', + }, + { + table: "jf_library_episodes", + query: + ' ON CONFLICT ("Id") DO UPDATE SET "Name" = EXCLUDED."Name", "PremiereDate" = EXCLUDED."PremiereDate", "OfficialRating" = EXCLUDED."OfficialRating", "CommunityRating" = EXCLUDED."CommunityRating", "RunTimeTicks" = EXCLUDED."RunTimeTicks", "ProductionYear" = EXCLUDED."ProductionYear", "IndexNumber" = EXCLUDED."IndexNumber", "ParentIndexNumber" = EXCLUDED."ParentIndexNumber", "Type" = EXCLUDED."Type", "ParentLogoItemId" = EXCLUDED."ParentLogoItemId", "ParentBackdropItemId" = EXCLUDED."ParentBackdropItemId", "ParentBackdropImageTags" = EXCLUDED."ParentBackdropImageTags", "SeriesId" = EXCLUDED."SeriesId", "SeasonId" = EXCLUDED."SeasonId", "SeasonName" = EXCLUDED."SeasonName", "SeriesName" = EXCLUDED."SeriesName"', + }, + { + table: "jf_library_items", + query: + ' ON CONFLICT ("Id") DO UPDATE SET "Name" = EXCLUDED."Name", "PremiereDate" = EXCLUDED."PremiereDate", "EndDate" = EXCLUDED."EndDate", "CommunityRating" = EXCLUDED."CommunityRating", "RunTimeTicks" = EXCLUDED."RunTimeTicks", "ProductionYear" = EXCLUDED."ProductionYear", "Type" = EXCLUDED."Type", "Status" = EXCLUDED."Status", "ImageTagsPrimary" = EXCLUDED."ImageTagsPrimary", "ImageTagsBanner" = EXCLUDED."ImageTagsBanner", "ImageTagsLogo" = EXCLUDED."ImageTagsLogo", "ImageTagsThumb" = EXCLUDED."ImageTagsThumb", "BackdropImageTags" = EXCLUDED."BackdropImageTags", "ParentId" = EXCLUDED."ParentId", "PrimaryImageHash" = EXCLUDED."PrimaryImageHash"', + }, + { + table: "jf_library_seasons", + query: + ' ON CONFLICT ("Id") DO UPDATE SET "Name" = EXCLUDED."Name", "ParentLogoItemId" = EXCLUDED."ParentLogoItemId", "ParentBackdropItemId" = EXCLUDED."ParentBackdropItemId", "ParentBackdropImageTags" = EXCLUDED."ParentBackdropImageTags", "SeriesPrimaryImageTag" = EXCLUDED."SeriesPrimaryImageTag"', + }, + { + table: "jf_logging", + query: ` ON CONFLICT ("Id") DO UPDATE SET "Duration" = EXCLUDED."Duration", "Log"=EXCLUDED."Log", "Result"=EXCLUDED."Result" WHERE "jf_logging"."Result"='Running'`, + }, + { table: "jf_playback_activity", query: " ON CONFLICT DO NOTHING" }, + { + table: "jf_playback_reporting_plugin_data", + query: " ON CONFLICT DO NOTHING", + }, + { + table: "jf_users", + query: + ' ON CONFLICT ("Id") DO UPDATE SET "Name" = EXCLUDED."Name", "PrimaryImageTag" = EXCLUDED."PrimaryImageTag", "LastLoginDate" = EXCLUDED."LastLoginDate", "LastActivityDate" = EXCLUDED."LastActivityDate"', + }, +]; +module.exports = { + update_query, +}; diff --git a/backend/models/jf_activity_watchdog.js b/backend/models/jf_activity_watchdog.js index db9026df..2d169c12 100644 --- a/backend/models/jf_activity_watchdog.js +++ b/backend/models/jf_activity_watchdog.js @@ -1,59 +1,72 @@ -const moment = require('moment'); - - +const moment = require("moment"); const jf_activity_watchdog_columns = [ - "Id", - "IsPaused", - "UserId", - "UserName", - "Client", - "DeviceName", - "DeviceId", - "ApplicationVersion", - "NowPlayingItemId", - "NowPlayingItemName", - "EpisodeId", - "SeasonId", - "SeriesName", - "PlaybackDuration", - "PlayMethod", - "ActivityDateInserted", - { name: 'MediaStreams', mod: ':json' }, - { name: 'TranscodingInfo', mod: ':json' }, - { name: 'PlayState', mod: ':json' }, - "OriginalContainer", - "RemoteEndPoint", - "ServerId", - ]; - + "Id", + "IsPaused", + "UserId", + "UserName", + "Client", + "DeviceName", + "DeviceId", + "ApplicationVersion", + "NowPlayingItemId", + "NowPlayingItemName", + "EpisodeId", + "SeasonId", + "SeriesName", + "PlaybackDuration", + "PlayMethod", + "ActivityDateInserted", + { name: "MediaStreams", mod: ":json" }, + { name: "TranscodingInfo", mod: ":json" }, + { name: "PlayState", mod: ":json" }, + "OriginalContainer", + "RemoteEndPoint", + "ServerId", +]; - const jf_activity_watchdog_mapping = (item) => ({ - Id: item.Id , - IsPaused: item.PlayState.IsPaused !== undefined ? item.PlayState.IsPaused : item.IsPaused, - UserId: item.UserId, - UserName: item.UserName, - Client: item.Client, - DeviceName: item.DeviceName, - DeviceId: item.DeviceId, - ApplicationVersion: item.ApplicationVersion, - NowPlayingItemId: item.NowPlayingItem.SeriesId !== undefined ? item.NowPlayingItem.SeriesId : item.NowPlayingItem.Id, - NowPlayingItemName: item.NowPlayingItem.Name, - EpisodeId: item.NowPlayingItem.SeriesId !== undefined ? item.NowPlayingItem.Id: null, - SeasonId: item.NowPlayingItem.SeasonId || null, - SeriesName: item.NowPlayingItem.SeriesName || null, - PlaybackDuration: item.PlaybackDuration !== undefined ? item.PlaybackDuration: 0, - PlayMethod:item.PlayState.PlayMethod, - ActivityDateInserted: item.ActivityDateInserted !== undefined ? item.ActivityDateInserted: moment().format('YYYY-MM-DD HH:mm:ss.SSSZ'), - MediaStreams: item.NowPlayingItem.MediaStreams ? item.NowPlayingItem.MediaStreams : null , - TranscodingInfo: item.TranscodingInfo? item.TranscodingInfo : null, - PlayState: item.PlayState? item.PlayState : null, - OriginalContainer: item.NowPlayingItem && item.NowPlayingItem.Container ? item.NowPlayingItem.Container : null, - RemoteEndPoint: item.RemoteEndPoint || null, - ServerId: item.ServerId || null, - }); +const jf_activity_watchdog_mapping = (item) => ({ + Id: item.Id, + IsPaused: + item.PlayState.IsPaused !== undefined + ? item.PlayState.IsPaused + : item.IsPaused, + UserId: item.UserId, + UserName: item.UserName, + Client: item.Client, + DeviceName: item.DeviceName, + DeviceId: item.DeviceId, + ApplicationVersion: item.ApplicationVersion, + NowPlayingItemId: + item.NowPlayingItem.SeriesId !== undefined + ? item.NowPlayingItem.SeriesId + : item.NowPlayingItem.Id, + NowPlayingItemName: item.NowPlayingItem.Name, + EpisodeId: + item.NowPlayingItem.SeriesId !== undefined ? item.NowPlayingItem.Id : null, + SeasonId: item.NowPlayingItem.SeasonId || null, + SeriesName: item.NowPlayingItem.SeriesName || null, + PlaybackDuration: + item.PlaybackDuration !== undefined ? item.PlaybackDuration : 0, + PlayMethod: item.PlayState.PlayMethod, + ActivityDateInserted: + item.ActivityDateInserted !== undefined + ? item.ActivityDateInserted + : moment().format("YYYY-MM-DD HH:mm:ss.SSSZ"), + MediaStreams: item.NowPlayingItem.MediaStreams + ? item.NowPlayingItem.MediaStreams + : null, + TranscodingInfo: item.TranscodingInfo ? item.TranscodingInfo : null, + PlayState: item.PlayState ? item.PlayState : null, + OriginalContainer: + item.NowPlayingItem && item.NowPlayingItem.Container + ? item.NowPlayingItem.Container + : null, + RemoteEndPoint: item.RemoteEndPoint || null, + ServerId: item.ServerId || null, +}); - module.exports = { - jf_activity_watchdog_columns, - jf_activity_watchdog_mapping - }; \ No newline at end of file +module.exports = { + jf_activity_watchdog_columns, + jf_activity_watchdog_mapping, +}; diff --git a/backend/models/jf_item_info.js b/backend/models/jf_item_info.js index 44a17d77..93615224 100644 --- a/backend/models/jf_item_info.js +++ b/backend/models/jf_item_info.js @@ -1,25 +1,24 @@ +const jf_item_info_columns = [ + "Id", + "Path", + "Name", + "Size", + "Bitrate", + "MediaStreams", + "Type", +]; - const jf_item_info_columns = [ - "Id", - "Path", - "Name", - "Size", - "Bitrate", - "MediaStreams", - "Type", - ]; +const jf_item_info_mapping = (item, typeOverride) => ({ + Id: item.EpisodeId || item.Id, + Path: item.Path, + Name: item.Name, + Size: item.Size, + Bitrate: item.Bitrate, + MediaStreams: JSON.stringify(item.MediaStreams), + Type: typeOverride !== undefined ? typeOverride : item.Type, +}); - const jf_item_info_mapping = (item, typeOverride) => ({ - Id: item.EpisodeId || item.Id, - Path: item.Path, - Name: item.Name, - Size: item.Size, - Bitrate: item.Bitrate, - MediaStreams:JSON.stringify(item.MediaStreams), - Type: typeOverride !== undefined ? typeOverride : item.Type, - }); - - module.exports = { - jf_item_info_columns, - jf_item_info_mapping, - }; \ No newline at end of file +module.exports = { + jf_item_info_columns, + jf_item_info_mapping, +}; diff --git a/backend/models/jf_libraries.js b/backend/models/jf_libraries.js index aeab06fa..63699276 100644 --- a/backend/models/jf_libraries.js +++ b/backend/models/jf_libraries.js @@ -1,26 +1,26 @@ - ////////////////////////// pn delete move to playback - const jf_libraries_columns = [ - "Id", - "Name", - "ServerId", - "IsFolder", - "Type", - "CollectionType", - "ImageTagsPrimary", - ]; +////////////////////////// pn delete move to playback +const jf_libraries_columns = [ + "Id", + "Name", + "ServerId", + "IsFolder", + "Type", + "CollectionType", + "ImageTagsPrimary", +]; - const jf_libraries_mapping = (item) => ({ - Id: item.Id, - Name: item.Name, - ServerId: item.ServerId, - IsFolder: item.IsFolder, - Type: item.Type, - CollectionType: item.CollectionType? item.CollectionType : 'mixed', - ImageTagsPrimary: - item.ImageTags && item.ImageTags.Primary ? item.ImageTags.Primary : null, - }); +const jf_libraries_mapping = (item) => ({ + Id: item.Id, + Name: item.Name, + ServerId: item.ServerId, + IsFolder: item.IsFolder, + Type: item.Type, + CollectionType: item.CollectionType ? item.CollectionType : "mixed", + ImageTagsPrimary: + item.ImageTags && item.ImageTags.Primary ? item.ImageTags.Primary : null, +}); - module.exports = { - jf_libraries_columns, - jf_libraries_mapping, - }; \ No newline at end of file +module.exports = { + jf_libraries_columns, + jf_libraries_mapping, +}; diff --git a/backend/models/jf_library_episodes.js b/backend/models/jf_library_episodes.js index 100d1da7..22e050bf 100644 --- a/backend/models/jf_library_episodes.js +++ b/backend/models/jf_library_episodes.js @@ -1,52 +1,52 @@ - ////////////////////////// pn delete move to playback - const jf_library_episodes_columns = [ - "Id", - "EpisodeId", - "Name", - "ServerId", - "PremiereDate", - "OfficialRating", - "CommunityRating", - "RunTimeTicks", - "ProductionYear", - "IndexNumber", - "ParentIndexNumber", - "Type", - "ParentLogoItemId", - "ParentBackdropItemId", - "ParentBackdropImageTags", - "SeriesId", - "SeasonId", - "SeasonName", - "SeriesName", - ]; - - const jf_library_episodes_mapping = (item) => ({ - Id: item.Id + item.SeasonId, - EpisodeId: item.Id, - Name: item.Name, - ServerId: item.ServerId, - PremiereDate: item.PremiereDate, - OfficialRating: item.OfficialRating, - CommunityRating: item.CommunityRating, - RunTimeTicks: item.RunTimeTicks, - ProductionYear: item.ProductionYear, - IndexNumber: item.IndexNumber, - ParentIndexNumber: item.ParentIndexNumber, - Type: item.Type, - ParentLogoItemId: item.ParentLogoItemId, - ParentBackdropItemId: item.ParentBackdropItemId, - ParentBackdropImageTags: - item.ParentBackdropImageTags !== undefined - ? item.ParentBackdropImageTags[0] - : null, - SeriesId: item.SeriesId, - SeasonId: item.SeasonId, - SeasonName: item.SeasonName, - SeriesName: item.SeriesName, - }); +////////////////////////// pn delete move to playback +const jf_library_episodes_columns = [ + "Id", + "EpisodeId", + "Name", + "ServerId", + "PremiereDate", + "OfficialRating", + "CommunityRating", + "RunTimeTicks", + "ProductionYear", + "IndexNumber", + "ParentIndexNumber", + "Type", + "ParentLogoItemId", + "ParentBackdropItemId", + "ParentBackdropImageTags", + "SeriesId", + "SeasonId", + "SeasonName", + "SeriesName", +]; - module.exports = { - jf_library_episodes_columns, - jf_library_episodes_mapping, - }; \ No newline at end of file +const jf_library_episodes_mapping = (item) => ({ + Id: item.Id + item.SeasonId, + EpisodeId: item.Id, + Name: item.Name, + ServerId: item.ServerId, + PremiereDate: item.PremiereDate, + OfficialRating: item.OfficialRating, + CommunityRating: item.CommunityRating, + RunTimeTicks: item.RunTimeTicks, + ProductionYear: item.ProductionYear, + IndexNumber: item.IndexNumber, + ParentIndexNumber: item.ParentIndexNumber, + Type: item.Type, + ParentLogoItemId: item.ParentLogoItemId, + ParentBackdropItemId: item.ParentBackdropItemId, + ParentBackdropImageTags: + item.ParentBackdropImageTags !== undefined + ? item.ParentBackdropImageTags[0] + : null, + SeriesId: item.SeriesId, + SeasonId: item.SeasonId, + SeasonName: item.SeasonName, + SeriesName: item.SeriesName, +}); + +module.exports = { + jf_library_episodes_columns, + jf_library_episodes_mapping, +}; diff --git a/backend/models/jf_library_items.js b/backend/models/jf_library_items.js index c996423e..74f603d6 100644 --- a/backend/models/jf_library_items.js +++ b/backend/models/jf_library_items.js @@ -1,51 +1,57 @@ +const jf_library_items_columns = [ + "Id", + "Name", + "ServerId", + "PremiereDate", + "EndDate", + "CommunityRating", + "RunTimeTicks", + "ProductionYear", + "IsFolder", + "Type", + "Status", + "ImageTagsPrimary", + "ImageTagsBanner", + "ImageTagsLogo", + "ImageTagsThumb", + "BackdropImageTags", + "ParentId", + "PrimaryImageHash", +]; - const jf_library_items_columns = [ - "Id", - "Name", - "ServerId", - "PremiereDate", - "EndDate", - "CommunityRating", - "RunTimeTicks", - "ProductionYear", - "IsFolder", - "Type", - "Status", - "ImageTagsPrimary", - "ImageTagsBanner", - "ImageTagsLogo", - "ImageTagsThumb", - "BackdropImageTags", - "ParentId", - "PrimaryImageHash", - ]; +const jf_library_items_mapping = (item) => ({ + Id: item.Id, + Name: item.Name, + ServerId: item.ServerId, + PremiereDate: item.PremiereDate, + EndDate: item.EndDate, + CommunityRating: item.CommunityRating, + RunTimeTicks: item.RunTimeTicks, + ProductionYear: item.ProductionYear, + IsFolder: item.IsFolder, + Type: item.Type, + Status: item.Status, + ImageTagsPrimary: + item.ImageTags && item.ImageTags.Primary ? item.ImageTags.Primary : null, + ImageTagsBanner: + item.ImageTags && item.ImageTags.Banner ? item.ImageTags.Banner : null, + ImageTagsLogo: + item.ImageTags && item.ImageTags.Logo ? item.ImageTags.Logo : null, + ImageTagsThumb: + item.ImageTags && item.ImageTags.Thumb ? item.ImageTags.Thumb : null, + BackdropImageTags: item.BackdropImageTags[0], + ParentId: item.ParentId, + PrimaryImageHash: + item.ImageTags && + item.ImageTags.Primary && + item.ImageBlurHashes && + item.ImageBlurHashes.Primary && + item.ImageBlurHashes.Primary[item.ImageTags["Primary"]] + ? item.ImageBlurHashes.Primary[item.ImageTags["Primary"]] + : null, +}); - const jf_library_items_mapping = (item) => ({ - Id: item.Id, - Name: item.Name, - ServerId: item.ServerId, - PremiereDate: item.PremiereDate, - EndDate: item.EndDate, - CommunityRating: item.CommunityRating, - RunTimeTicks: item.RunTimeTicks, - ProductionYear: item.ProductionYear, - IsFolder: item.IsFolder, - Type: item.Type, - Status: item.Status, - ImageTagsPrimary: - item.ImageTags && item.ImageTags.Primary ? item.ImageTags.Primary : null, - ImageTagsBanner: - item.ImageTags && item.ImageTags.Banner ? item.ImageTags.Banner : null, - ImageTagsLogo: - item.ImageTags && item.ImageTags.Logo ? item.ImageTags.Logo : null, - ImageTagsThumb: - item.ImageTags && item.ImageTags.Thumb ? item.ImageTags.Thumb : null, - BackdropImageTags: item.BackdropImageTags[0], - ParentId: item.ParentId, - PrimaryImageHash: item.ImageTags && item.ImageTags.Primary && item.ImageBlurHashes && item.ImageBlurHashes.Primary && item.ImageBlurHashes.Primary[item.ImageTags["Primary"]] ? item.ImageBlurHashes.Primary[item.ImageTags["Primary"]] : null, - }); - - module.exports = { - jf_library_items_columns, - jf_library_items_mapping, - }; \ No newline at end of file +module.exports = { + jf_library_items_columns, + jf_library_items_mapping, +}; diff --git a/backend/models/jf_library_seasons.js b/backend/models/jf_library_seasons.js index b92eed93..05feda96 100644 --- a/backend/models/jf_library_seasons.js +++ b/backend/models/jf_library_seasons.js @@ -1,36 +1,38 @@ - ////////////////////////// pn delete move to playback - const jf_library_seasons_columns = [ - "Id", - "Name", - "ServerId", - "IndexNumber", - "Type", - "ParentLogoItemId", - "ParentBackdropItemId", - "ParentBackdropImageTags", - "SeriesName", - "SeriesId", - "SeriesPrimaryImageTag", - ]; - - const jf_library_seasons_mapping = (item) => ({ - Id: item.Id, - Name: item.Name, - ServerId: item.ServerId, - IndexNumber: item.IndexNumber, - Type: item.Type, - ParentLogoItemId: item.ParentLogoItemId, - ParentBackdropItemId: item.ParentBackdropItemId, - ParentBackdropImageTags: - item.ParentBackdropImageTags !== undefined - ? item.ParentBackdropImageTags[0] - : null, - SeriesName: item.SeriesName, - SeriesId: item.SeriesId, - SeriesPrimaryImageTag: item.SeriesPrimaryImageTag ? item.SeriesPrimaryImageTag : null, - }); +////////////////////////// pn delete move to playback +const jf_library_seasons_columns = [ + "Id", + "Name", + "ServerId", + "IndexNumber", + "Type", + "ParentLogoItemId", + "ParentBackdropItemId", + "ParentBackdropImageTags", + "SeriesName", + "SeriesId", + "SeriesPrimaryImageTag", +]; - module.exports = { - jf_library_seasons_columns, - jf_library_seasons_mapping, - }; \ No newline at end of file +const jf_library_seasons_mapping = (item) => ({ + Id: item.Id, + Name: item.Name, + ServerId: item.ServerId, + IndexNumber: item.IndexNumber, + Type: item.Type, + ParentLogoItemId: item.ParentLogoItemId, + ParentBackdropItemId: item.ParentBackdropItemId, + ParentBackdropImageTags: + item.ParentBackdropImageTags !== undefined + ? item.ParentBackdropImageTags[0] + : null, + SeriesName: item.SeriesName, + SeriesId: item.SeriesId, + SeriesPrimaryImageTag: item.SeriesPrimaryImageTag + ? item.SeriesPrimaryImageTag + : null, +}); + +module.exports = { + jf_library_seasons_columns, + jf_library_seasons_mapping, +}; diff --git a/backend/models/jf_logging.js b/backend/models/jf_logging.js index d3313de2..9c862f56 100644 --- a/backend/models/jf_logging.js +++ b/backend/models/jf_logging.js @@ -1,26 +1,26 @@ - const jf_logging_columns = [ - "Id", - "Name", - "Type", - "ExecutionType", - "Duration", - "TimeRun", - "Log", - "Result" - ]; +const jf_logging_columns = [ + "Id", + "Name", + "Type", + "ExecutionType", + "Duration", + "TimeRun", + "Log", + "Result", +]; - const jf_logging_mapping = (item) => ({ - Id: item.Id, - Name: item.Name, - Type: item.Type, - ExecutionType: item.ExecutionType || '', - Duration: item.Duration, - TimeRun: item.TimeRun || '', - Log: item.Log, - Result: item.Result || '', - }); +const jf_logging_mapping = (item) => ({ + Id: item.Id, + Name: item.Name, + Type: item.Type, + ExecutionType: item.ExecutionType || "", + Duration: item.Duration, + TimeRun: item.TimeRun || "", + Log: item.Log, + Result: item.Result || "", +}); - module.exports = { - jf_logging_columns, - jf_logging_mapping, - }; \ No newline at end of file +module.exports = { + jf_logging_columns, + jf_logging_mapping, +}; diff --git a/backend/models/jf_playback_activity.js b/backend/models/jf_playback_activity.js index 2908635f..4fa711a7 100644 --- a/backend/models/jf_playback_activity.js +++ b/backend/models/jf_playback_activity.js @@ -1,55 +1,68 @@ - const columnsPlayback = [ - "Id", - "IsPaused", - "UserId", - "UserName", - "Client", - "DeviceName", - "DeviceId", - "ApplicationVersion", - "NowPlayingItemId", - "NowPlayingItemName", - "EpisodeId", - "SeasonId", - "SeriesName", - "PlaybackDuration", - "PlayMethod", - "ActivityDateInserted", - { name: 'MediaStreams', mod: ':json' }, - { name: 'TranscodingInfo', mod: ':json' }, - { name: 'PlayState', mod: ':json' }, - "OriginalContainer", - "RemoteEndPoint", - "ServerId" - ]; +const columnsPlayback = [ + "Id", + "IsPaused", + "UserId", + "UserName", + "Client", + "DeviceName", + "DeviceId", + "ApplicationVersion", + "NowPlayingItemId", + "NowPlayingItemName", + "EpisodeId", + "SeasonId", + "SeriesName", + "PlaybackDuration", + "PlayMethod", + "ActivityDateInserted", + { name: "MediaStreams", mod: ":json" }, + { name: "TranscodingInfo", mod: ":json" }, + { name: "PlayState", mod: ":json" }, + "OriginalContainer", + "RemoteEndPoint", + "ServerId", +]; +const mappingPlayback = (item) => ({ + Id: item.Id, + IsPaused: + item.PlayState.IsPaused !== undefined + ? item.PlayState.IsPaused + : item.IsPaused, + UserId: item.UserId, + UserName: item.UserName, + Client: item.Client, + DeviceName: item.DeviceName, + DeviceId: item.DeviceId, + ApplicationVersion: item.ApplicationVersion, + NowPlayingItemId: + item.NowPlayingItem.SeriesId !== undefined + ? item.NowPlayingItem.SeriesId + : item.NowPlayingItem.Id, + NowPlayingItemName: item.NowPlayingItem.Name, + EpisodeId: + item.NowPlayingItem.SeriesId !== undefined ? item.NowPlayingItem.Id : null, + SeasonId: item.NowPlayingItem.SeasonId || null, + SeriesName: item.NowPlayingItem.SeriesName || null, + PlaybackDuration: + item.PlaybackDuration !== undefined ? item.PlaybackDuration : 0, + PlayMethod: + item.PlayState.PlayMethod !== undefined + ? item.PlayState.PlayMethod + : item.PlayMethod, + ActivityDateInserted: + item.ActivityDateInserted !== undefined + ? item.ActivityDateInserted + : new Date().toISOString(), + MediaStreams: item.MediaStreams ? item.MediaStreams : null, + TranscodingInfo: item.TranscodingInfo ? item.TranscodingInfo : null, + PlayState: item.PlayState ? item.PlayState : null, + OriginalContainer: item.OriginalContainer ? item.OriginalContainer : null, + RemoteEndPoint: item.RemoteEndPoint ? item.RemoteEndPoint : null, + ServerId: item.ServerId ? item.ServerId : null, +}); - const mappingPlayback = (item) => ({ - Id: item.Id , - IsPaused: item.PlayState.IsPaused !== undefined ? item.PlayState.IsPaused : item.IsPaused, - UserId: item.UserId, - UserName: item.UserName, - Client: item.Client, - DeviceName: item.DeviceName, - DeviceId: item.DeviceId, - ApplicationVersion: item.ApplicationVersion, - NowPlayingItemId: item.NowPlayingItem.SeriesId !== undefined ? item.NowPlayingItem.SeriesId : item.NowPlayingItem.Id, - NowPlayingItemName: item.NowPlayingItem.Name, - EpisodeId: item.NowPlayingItem.SeriesId !== undefined ? item.NowPlayingItem.Id: null, - SeasonId: item.NowPlayingItem.SeasonId || null, - SeriesName: item.NowPlayingItem.SeriesName || null, - PlaybackDuration: item.PlaybackDuration !== undefined ? item.PlaybackDuration: 0, - PlayMethod: item.PlayState.PlayMethod !== undefined ? item.PlayState.PlayMethod : item.PlayMethod , - ActivityDateInserted: item.ActivityDateInserted !== undefined ? item.ActivityDateInserted: new Date().toISOString(), - MediaStreams: item.MediaStreams ? item.MediaStreams : null , - TranscodingInfo: item.TranscodingInfo? item.TranscodingInfo : null, - PlayState: item.PlayState? item.PlayState : null, - OriginalContainer: item.OriginalContainer ? item.OriginalContainer : null, - RemoteEndPoint: item.RemoteEndPoint ? item.RemoteEndPoint : null, - ServerId: item.ServerId ? item.ServerId : null, - }); - - module.exports = { - columnsPlayback, - mappingPlayback, - }; \ No newline at end of file +module.exports = { + columnsPlayback, + mappingPlayback, +}; diff --git a/backend/models/jf_playback_reporting_plugin_data.js b/backend/models/jf_playback_reporting_plugin_data.js index f00277c3..9c51a7b4 100644 --- a/backend/models/jf_playback_reporting_plugin_data.js +++ b/backend/models/jf_playback_reporting_plugin_data.js @@ -1,32 +1,31 @@ - ////////////////////////// pn delete move to playback - const columnsPlaybackReporting = [ - "rowid", - "DateCreated", - "UserId", - "ItemId", - "ItemType", - "ItemName", - "PlaybackMethod", - "ClientName", - "DeviceName", - "PlayDuration", - ]; +////////////////////////// pn delete move to playback +const columnsPlaybackReporting = [ + "rowid", + "DateCreated", + "UserId", + "ItemId", + "ItemType", + "ItemName", + "PlaybackMethod", + "ClientName", + "DeviceName", + "PlayDuration", +]; +const mappingPlaybackReporting = (item) => ({ + rowid: item[0], + DateCreated: item[1], + UserId: item[2], + ItemId: item[3], + ItemType: item[4], + ItemName: item[5], + PlaybackMethod: item[6], + ClientName: item[7], + DeviceName: item[8], + PlayDuration: item[9], +}); - const mappingPlaybackReporting = (item) => ({ - rowid:item[0] , - DateCreated:item[1] , - UserId:item[2] , - ItemId:item[3] , - ItemType:item[4] , - ItemName:item[5] , - PlaybackMethod:item[6] , - ClientName:item[7] , - DeviceName:item[8] , - PlayDuration:item[9] , - }); - - module.exports = { - columnsPlaybackReporting, - mappingPlaybackReporting, - }; \ No newline at end of file +module.exports = { + columnsPlaybackReporting, + mappingPlaybackReporting, +}; diff --git a/backend/models/jf_users.js b/backend/models/jf_users.js index 1e882bae..79d26cb0 100644 --- a/backend/models/jf_users.js +++ b/backend/models/jf_users.js @@ -1,23 +1,26 @@ - ////////////////////////// pn delete move to playback - const jf_users_columns = [ - "Id", - "Name", - "PrimaryImageTag", - "LastLoginDate", - "LastActivityDate", - "IsAdministrator" - ]; +////////////////////////// pn delete move to playback +const jf_users_columns = [ + "Id", + "Name", + "PrimaryImageTag", + "LastLoginDate", + "LastActivityDate", + "IsAdministrator", +]; - const jf_users_mapping = (item) => ({ - Id: item.Id, - Name: item.Name, - PrimaryImageTag: item.PrimaryImageTag, - LastLoginDate: item.LastLoginDate, - LastActivityDate: item.LastActivityDate, - IsAdministrator: item.Policy && item.Policy.IsAdministrator ? item.Policy.IsAdministrator : false, - }); +const jf_users_mapping = (item) => ({ + Id: item.Id, + Name: item.Name, + PrimaryImageTag: item.PrimaryImageTag, + LastLoginDate: item.LastLoginDate, + LastActivityDate: item.LastActivityDate, + IsAdministrator: + item.Policy && item.Policy.IsAdministrator + ? item.Policy.IsAdministrator + : false, +}); - module.exports = { - jf_users_columns, - jf_users_mapping, - }; \ No newline at end of file +module.exports = { + jf_users_columns, + jf_users_mapping, +}; diff --git a/backend/routes/api.js b/backend/routes/api.js index bbb6d9c6..d36666f3 100644 --- a/backend/routes/api.js +++ b/backend/routes/api.js @@ -3,7 +3,7 @@ const express = require("express"); const axios = require("axios"); const db = require("../db"); const https = require("https"); -const { randomUUID } = require('crypto'); +const { randomUUID } = require("crypto"); const agent = new https.Agent({ rejectUnauthorized: @@ -22,12 +22,11 @@ router.get("/test", async (req, res) => { res.send("Backend Responded Succesfully"); }); - //Settings and config endpoints router.get("/getconfig", async (req, res) => { try { const { rows } = await db.query( - 'SELECT "JF_HOST","APP_USER","REQUIRE_LOGIN", settings FROM app_config where "ID"=1' + 'SELECT "JF_HOST","APP_USER","REQUIRE_LOGIN", settings FROM app_config where "ID"=1', ); res.send(rows); } catch (error) { @@ -40,7 +39,7 @@ router.post("/setconfig", async (req, res) => { const { JF_HOST, JF_API_KEY } = req.body; const { rows: getConfig } = await db.query( - 'SELECT * FROM app_config where "ID"=1' + 'SELECT * FROM app_config where "ID"=1', ); let query = @@ -62,38 +61,33 @@ router.post("/setPreferredAdmin", async (req, res) => { try { const { userid, username } = req.body; const { rows: config } = await db.query( - 'SELECT * FROM app_config where "ID"=1' + 'SELECT * FROM app_config where "ID"=1', ); - - if ( - config[0].JF_HOST === null || - config[0].JF_API_KEY === null - ) { + + if (config[0].JF_HOST === null || config[0].JF_API_KEY === null) { res.status(404); res.send({ error: "Config Details Not Found" }); return; } - + const settingsjson = await db .query('SELECT settings FROM app_config where "ID"=1') .then((res) => res.rows); - + if (settingsjson.length > 0) { const settings = settingsjson[0].settings || {}; - settings.preferred_admin = {userid:userid,username:username}; - + settings.preferred_admin = { userid: userid, username: username }; + let query = 'UPDATE app_config SET settings=$1 where "ID"=1'; - + const { rows } = await db.query(query, [settings]); - + res.send("Settings updated succesfully"); - }else - { - res.status(404) + } else { + res.status(404); res.send("Settings not found"); } - } catch (error) { console.log(error); } @@ -128,7 +122,7 @@ router.post("/updatePassword", async (req, res) => { try { const { rows } = await db.query( - `SELECT "JF_HOST","JF_API_KEY","APP_USER" FROM app_config where "ID"=1 AND "APP_PASSWORD"='${current_password}' ` + `SELECT "JF_HOST","JF_API_KEY","APP_USER" FROM app_config where "ID"=1 AND "APP_PASSWORD"='${current_password}' `, ); if (rows && rows.length > 0) { @@ -137,7 +131,7 @@ router.post("/updatePassword", async (req, res) => { result.errorMessage = "New Password cannot be the same as Old Password"; } else { await db.query( - `UPDATE app_config SET "APP_PASSWORD"='${new_password}' where "ID"=1 AND "APP_PASSWORD"='${current_password}' ` + `UPDATE app_config SET "APP_PASSWORD"='${new_password}' where "ID"=1 AND "APP_PASSWORD"='${current_password}' `, ); } } else { @@ -154,7 +148,7 @@ router.post("/updatePassword", async (req, res) => { router.get("/TrackedLibraries", async (req, res) => { const { rows: config } = await db.query( - 'SELECT * FROM app_config where "ID"=1' + 'SELECT * FROM app_config where "ID"=1', ); if (config[0].JF_HOST === null || config[0].JF_API_KEY === null) { @@ -163,25 +157,23 @@ router.get("/TrackedLibraries", async (req, res) => { } let url = `${config[0].JF_HOST}/Library/MediaFolders`; - try - { - + try { const response_data = await axios_instance.get(url, { headers: { "X-MediaBrowser-Token": config[0].JF_API_KEY, }, }); - + const filtered_items = response_data.data.Items.filter( - (type) => !["boxsets", "playlists"].includes(type.CollectionType) + (type) => !["boxsets", "playlists"].includes(type.CollectionType), ); - + const excluded_libraries = await db .query('SELECT settings FROM app_config where "ID"=1') .then((res) => res.rows); if (excluded_libraries.length > 0) { const libraries = excluded_libraries[0].settings?.ExcludedLibraries || []; - + const librariesWithTrackedStatus = filtered_items.map((items) => ({ ...items, ...{ Tracked: !libraries.includes(items.Id) }, @@ -191,19 +183,17 @@ router.get("/TrackedLibraries", async (req, res) => { res.status(404); res.send({ error: "Settings Not Found" }); } - }catch(error) - { - res.status(503); - res.send({ error: "Error: "+error }); + } catch (error) { + res.status(503); + res.send({ error: "Error: " + error }); } - }); router.post("/setExcludedLibraries", async (req, res) => { const { libraryID } = req.body; const { rows: config } = await db.query( - 'SELECT * FROM app_config where "ID"=1' + 'SELECT * FROM app_config where "ID"=1', ); if ( @@ -219,7 +209,7 @@ router.post("/setExcludedLibraries", async (req, res) => { const settingsjson = await db .query('SELECT settings FROM app_config where "ID"=1') .then((res) => res.rows); - + if (settingsjson.length > 0) { const settings = settingsjson[0].settings || {}; @@ -236,23 +226,18 @@ router.post("/setExcludedLibraries", async (req, res) => { const { rows } = await db.query(query, [settings]); res.send("Settings updated succesfully"); - }else - { - res.status(404) + } else { + res.status(404); res.send("Settings not found"); } - }); -router.get("/keys", async (req,res) => { +router.get("/keys", async (req, res) => { const { rows: config } = await db.query( - 'SELECT * FROM app_config where "ID"=1' + 'SELECT * FROM app_config where "ID"=1', ); - if ( - config[0]?.JF_HOST === null || - config[0]?.JF_API_KEY === null - ) { + if (config[0]?.JF_HOST === null || config[0]?.JF_API_KEY === null) { res.status(404); res.send({ error: "Config Details Not Found" }); return; @@ -265,30 +250,27 @@ router.get("/keys", async (req,res) => { if (keysjson) { const keys = keysjson || []; res.send(keys); - }else - { - res.status(404) + } else { + res.status(404); res.send("Settings not found"); } - }); -router.delete("/keys", async (req,res) => { - const { key } = req.body; - - if(!key) - { +router.delete("/keys", async (req, res) => { + const { key } = req.body; + + if (!key) { res.status(400); res.send({ error: "No API key provided to remove" }); return; } const { rows: config } = await db.query( - 'SELECT * FROM app_config where "ID"=1' + 'SELECT * FROM app_config where "ID"=1', ); if ( - config.length===0 || + config.length === 0 || config[0].JF_HOST === null || config[0].JF_API_KEY === null ) { @@ -301,51 +283,39 @@ router.delete("/keys", async (req,res) => { .query('SELECT api_keys FROM app_config where "ID"=1') .then((res) => res.rows[0].api_keys); - if (keysjson) { const keys = keysjson || []; - const keyExists = keys.some(obj => obj.key === key); - if(keyExists) - { - const new_keys_array=keys.filter(obj => obj.key !== key); + const keyExists = keys.some((obj) => obj.key === key); + if (keyExists) { + const new_keys_array = keys.filter((obj) => obj.key !== key); let query = 'UPDATE app_config SET api_keys=$1 where "ID"=1'; - - await db.query(query, [JSON.stringify(new_keys_array)]); - return res.send('Key removed: '+key); - }else - { + await db.query(query, [JSON.stringify(new_keys_array)]); + return res.send("Key removed: " + key); + } else { res.status(404); - return res.send('API key does not exist'); + return res.send("API key does not exist"); } - - - }else - { - res.status(404) + } else { + res.status(404); return res.send("No API keys found"); } - }); router.post("/keys", async (req, res) => { const { name } = req.body; - if(!name) - { + if (!name) { res.status(400); res.send({ error: "A Name is required to generate a key" }); return; } const { rows: config } = await db.query( - 'SELECT * FROM app_config where "ID"=1' + 'SELECT * FROM app_config where "ID"=1', ); - if ( - config[0].JF_HOST === null || - config[0].JF_API_KEY === null - ) { + if (config[0].JF_HOST === null || config[0].JF_API_KEY === null) { res.status(404); res.send({ error: "Config Details Not Found" }); return; @@ -355,15 +325,14 @@ router.post("/keys", async (req, res) => { .query('SELECT api_keys FROM app_config where "ID"=1') .then((res) => res.rows[0].api_keys); - let keys=[]; - const uuid = randomUUID() - const new_key={name:name, key:uuid}; - + let keys = []; + const uuid = randomUUID(); + const new_key = { name: name, key: uuid }; + if (keysjson) { - keys = keysjson || []; + keys = keysjson || []; keys.push(new_key); - }else - { + } else { keys.push(new_key); } @@ -371,90 +340,70 @@ router.post("/keys", async (req, res) => { await db.query(query, [JSON.stringify(keys)]); res.send(keys); - }); router.get("/getTaskSettings", async (req, res) => { - - - try - { + try { const settingsjson = await db - .query('SELECT settings FROM app_config where "ID"=1') - .then((res) => res.rows); + .query('SELECT settings FROM app_config where "ID"=1') + .then((res) => res.rows); if (settingsjson.length > 0) { const settings = settingsjson[0].settings || {}; - + let tasksettings = settings.Tasks || {}; res.send(tasksettings); - - }else { + } else { res.status(404); res.send({ error: "Task Settings Not Found" }); } - - - }catch(error) - { - res.status(503); - res.send({ error: "Error: "+error }); + } catch (error) { + res.status(503); + res.send({ error: "Error: " + error }); } - }); router.post("/setTaskSettings", async (req, res) => { - const { taskname,Interval } = req.body; + const { taskname, Interval } = req.body; - try - { + try { const settingsjson = await db - .query('SELECT settings FROM app_config where "ID"=1') - .then((res) => res.rows); + .query('SELECT settings FROM app_config where "ID"=1') + .then((res) => res.rows); if (settingsjson.length > 0) { const settings = settingsjson[0].settings || {}; - if(!settings.Tasks) - { + if (!settings.Tasks) { settings.Tasks = {}; } - + let tasksettings = settings.Tasks; - if(!tasksettings[taskname]) - { - tasksettings[taskname]={}; + if (!tasksettings[taskname]) { + tasksettings[taskname] = {}; } - tasksettings[taskname].Interval=Interval; + tasksettings[taskname].Interval = Interval; - settings.Tasks=tasksettings; + settings.Tasks = tasksettings; let query = 'UPDATE app_config SET settings=$1 where "ID"=1'; await db.query(query, [settings]); res.status(200); res.send(tasksettings); - - }else { + } else { res.status(404); res.send({ error: "Task Settings Not Found" }); } - - - }catch(error) - { - res.status(503); - res.send({ error: "Error: "+error }); + } catch (error) { + res.status(503); + res.send({ error: "Error: " + error }); } - - - }); - router.get("/dataValidator", async (req, res) => { try { const { rows: config } = await db.query( - 'SELECT * FROM app_config where "ID"=1' + 'SELECT * FROM app_config where "ID"=1', ); let payload = { @@ -493,19 +442,15 @@ router.get("/dataValidator", async (req, res) => { }); const admins = await response.data.filter( - (user) => user.Policy.IsAdministrator === true + (user) => user.Policy.IsAdministrator === true, ); - let userid=config[0].settings?.preferred_admin?.userid; + let userid = config[0].settings?.preferred_admin?.userid; - if(!userid) - { + if (!userid) { userid = admins[0].Id; } - - - //////////////////////// const db_libraries = await db .query('SELECT "Id" FROM jf_libraries') @@ -571,16 +516,16 @@ router.get("/dataValidator", async (req, res) => { (items) => ({ ...items, ...{ ParentId: library.Id }, - }) + }), ); movie_data.push( - ...libraryItemsWithParent.filter((item) => item.Type === "Movie") + ...libraryItemsWithParent.filter((item) => item.Type === "Movie"), ); show_data.push( - ...libraryItemsWithParent.filter((item) => item.Type === "Series") + ...libraryItemsWithParent.filter((item) => item.Type === "Series"), ); music_data.push( - ...libraryItemsWithParent.filter((item) => item.Type === "Audio") + ...libraryItemsWithParent.filter((item) => item.Type === "Audio"), ); raw_item_data.push(response_data_item.data); } @@ -607,7 +552,7 @@ router.get("/dataValidator", async (req, res) => { let raw_allEpisodes = []; const { rows: shows } = await db.query( - `SELECT "Id" FROM public.jf_library_items where "Type"='Series'` + `SELECT "Id" FROM public.jf_library_items where "Type"='Series'`, ); //loop for each show @@ -651,24 +596,24 @@ router.get("/dataValidator", async (req, res) => { //missing data section let missing_libraries = libraries.filter( - (library) => !db_libraries.includes(library.Id) + (library) => !db_libraries.includes(library.Id), ); let missing_movies = movie_data.filter( - (item) => !db_movies.includes(item.Id) && item.Type === "Movie" + (item) => !db_movies.includes(item.Id) && item.Type === "Movie", ); let missing_shows = show_data.filter( - (item) => !db_shows.includes(item.Id) && item.Type === "Series" + (item) => !db_shows.includes(item.Id) && item.Type === "Series", ); let missing_music = music_data.filter( - (item) => !db_music.includes(item.Id) && item.Type === "Audio" + (item) => !db_music.includes(item.Id) && item.Type === "Audio", ); let missing_seasons = allSeasons.filter( - (season) => !db_seasons.includes(season.Id) + (season) => !db_seasons.includes(season.Id), ); let missing_episodes = allEpisodes.filter( - (episode) => !db_episodes.includes(episode.Id) + (episode) => !db_episodes.includes(episode.Id), ); payload.missing_api_library_data = missing_libraries; @@ -688,12 +633,12 @@ router.get("/dataValidator", async (req, res) => { } }); -//DB Queries +//DB Queries router.post("/getUserDetails", async (req, res) => { try { const { userid } = req.body; const { rows } = await db.query( - `select * from jf_users where "Id"='${userid}'` + `select * from jf_users where "Id"='${userid}'`, ); res.send(rows[0]); } catch (error) { @@ -716,7 +661,7 @@ router.post("/getLibrary", async (req, res) => { try { const { libraryid } = req.body; const { rows } = await db.query( - `select * from jf_libraries where "Id"='${libraryid}'` + `select * from jf_libraries where "Id"='${libraryid}'`, ); res.send(rows[0]); } catch (error) { @@ -726,13 +671,13 @@ router.post("/getLibrary", async (req, res) => { } }); - router.post("/getLibraryItems", async (req, res) => { try { const { libraryid } = req.body; console.log(`ENDPOINT CALLED: /getLibraryItems: ` + libraryid); const { rows } = await db.query( - `SELECT * FROM jf_library_items where "ParentId"=$1`, [libraryid] + `SELECT * FROM jf_library_items where "ParentId"=$1`, + [libraryid], ); res.send(rows); } catch (error) { @@ -745,7 +690,8 @@ router.post("/getSeasons", async (req, res) => { const { Id } = req.body; const { rows } = await db.query( - `SELECT * FROM jf_library_seasons where "SeriesId"=$1`, [Id] + `SELECT * FROM jf_library_seasons where "SeriesId"=$1`, + [Id], ); console.log({ Id: Id }); res.send(rows); @@ -760,7 +706,8 @@ router.post("/getEpisodes", async (req, res) => { try { const { Id } = req.body; const { rows } = await db.query( - `SELECT * FROM jf_library_episodes where "SeasonId"=$1`, [Id] + `SELECT * FROM jf_library_episodes where "SeasonId"=$1`, + [Id], ); console.log({ Id: Id }); res.send(rows); @@ -808,7 +755,7 @@ router.post("/getItemDetails", async (req, res) => { router.get("/getHistory", async (req, res) => { try { const { rows } = await db.query( - `SELECT * FROM jf_playback_activity order by "ActivityDateInserted" desc` + `SELECT * FROM jf_playback_activity order by "ActivityDateInserted" desc`, ); const groupedResults = {}; @@ -829,7 +776,7 @@ router.get("/getHistory", async (req, res) => { if (row.results && row.results.length > 0) { row.PlaybackDuration = row.results.reduce( (acc, item) => acc + parseInt(item.PlaybackDuration), - 0 + 0, ); } }); @@ -844,7 +791,8 @@ router.post("/getLibraryHistory", async (req, res) => { try { const { libraryid } = req.body; 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] + `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], ); const groupedResults = {}; rows.forEach((row) => { @@ -875,7 +823,8 @@ router.post("/getItemHistory", async (req, res) => { `select jf_playback_activity.* from jf_playback_activity jf_playback_activity where - ("EpisodeId"=$1 OR "SeasonId"=$1 OR "NowPlayingItemId"=$1);`, [idemid] + ("EpisodeId"=$1 OR "SeasonId"=$1 OR "NowPlayingItemId"=$1);`, + [idemid], ); const groupedResults = rows.map((item) => ({ @@ -898,7 +847,8 @@ router.post("/getUserHistory", async (req, res) => { const { rows } = await db.query( `select jf_playback_activity.* from jf_playback_activity jf_playback_activity - where "UserId"=$1;`, [userid] + where "UserId"=$1;`, + [userid], ); const groupedResults = {}; @@ -922,7 +872,6 @@ router.post("/getUserHistory", async (req, res) => { } }); - //Jellyfin related functions router.post("/validateSettings", async (req, res) => { @@ -968,7 +917,4 @@ router.post("/validateSettings", async (req, res) => { res.send({ isValid: isValid, errorMessage: errorMessage }); }); - - - module.exports = router; diff --git a/backend/routes/auth.js b/backend/routes/auth.js index 33fb0f50..eef0552d 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -1,121 +1,101 @@ const express = require("express"); const db = require("../db"); -const jwt = require('jsonwebtoken'); - +const jwt = require("jsonwebtoken"); const JWT_SECRET = process.env.JWT_SECRET; if (JWT_SECRET === undefined) { - console.log('JWT Secret cannot be undefined'); + console.log("JWT Secret cannot be undefined"); process.exit(1); // end the program with error status code } const router = express.Router(); +router.post("/login", async (req, res) => { + try { + const { username, password } = req.body; -router.post('/login', async (req, res) => { - - try{ - const { username, password } = req.body; - - const query = 'SELECT * FROM app_config WHERE ("APP_USER" = $1 AND "APP_PASSWORD" = $2) OR "REQUIRE_LOGIN" = false'; - const values = [username, password]; - const { rows: login } = await db.query(query, values); - if(login.length>0) - { - const user = { id: 1, username: username }; - - jwt.sign({ user }, JWT_SECRET, (err, token) => { - if (err) { - console.log(err); - res.sendStatus(500); - } else { - res.json({ token }); - } - }); - }else{ - res.sendStatus(401); - } - - }catch(error) - { - console.log(error); + const query = + 'SELECT * FROM app_config WHERE ("APP_USER" = $1 AND "APP_PASSWORD" = $2) OR "REQUIRE_LOGIN" = false'; + const values = [username, password]; + const { rows: login } = await db.query(query, values); + if (login.length > 0) { + const user = { id: 1, username: username }; + + jwt.sign({ user }, JWT_SECRET, (err, token) => { + if (err) { + console.log(err); + res.sendStatus(500); + } else { + res.json({ token }); + } + }); + } else { + res.sendStatus(401); } - }); - - router.get('/isConfigured', async (req, res) => { - - try{ - const { rows : Configured } = await db.query(`SELECT * FROM app_config`); - - if(Configured.length>0) - { - if(Configured[0].JF_API_KEY && Configured[0].APP_USER && Configured[0].JF_API_KEY!==null && Configured[0].APP_USER!==null) - { - - res.status(200); - res.send({state:2}); - }else - if(Configured[0].APP_USER && Configured[0].APP_USER!==null) - { - + } catch (error) { + console.log(error); + } +}); + +router.get("/isConfigured", async (req, res) => { + try { + const { rows: Configured } = await db.query(`SELECT * FROM app_config`); + + if (Configured.length > 0) { + if ( + Configured[0].JF_API_KEY && + Configured[0].APP_USER && + Configured[0].JF_API_KEY !== null && + Configured[0].APP_USER !== null + ) { res.status(200); - res.send({state:1}); - }else - { + res.send({ state: 2 }); + } else if (Configured[0].APP_USER && Configured[0].APP_USER !== null) { res.status(200); - res.send({state:0}); - } - }else{ + res.send({ state: 1 }); + } else { res.status(200); - res.send({state:0}); + res.send({ state: 0 }); } - - }catch(error) - { - console.log(error); + } else { + res.status(200); + res.send({ state: 0 }); } - }); - - - router.post('/createuser', async (req, res) => { - - - try{ - const { username, password } = req.body; - const { rows : Configured } = await db.query(`SELECT * FROM app_config where "ID"=1`); - - if(Configured.length===0) - { - const user = { id: 1, username: username }; - - let query='INSERT INTO app_config ("JF_HOST","JF_API_KEY","APP_USER","APP_PASSWORD") VALUES (null,null,$1,$2)'; - console.log(query); - - const { rows } = await db.query( - query, - [username, password] - ); - - jwt.sign({ user }, JWT_SECRET, (err, token) => { - if (err) { - console.log(err); - res.sendStatus(500); - } else { - res.json({ token }); - } - }); - }else{ - res.sendStatus(403); - } - - }catch(error) - { - console.log(error); + } catch (error) { + console.log(error); + } +}); + +router.post("/createuser", async (req, res) => { + try { + const { username, password } = req.body; + const { rows: Configured } = await db.query( + `SELECT * FROM app_config where "ID"=1`, + ); + + if (Configured.length === 0) { + const user = { id: 1, username: username }; + + let query = + 'INSERT INTO app_config ("JF_HOST","JF_API_KEY","APP_USER","APP_PASSWORD") VALUES (null,null,$1,$2)'; + console.log(query); + + const { rows } = await db.query(query, [username, password]); + + jwt.sign({ user }, JWT_SECRET, (err, token) => { + if (err) { + console.log(err); + res.sendStatus(500); + } else { + res.json({ token }); + } + }); + } else { + res.sendStatus(403); } - - - - - }); + } catch (error) { + console.log(error); + } +}); -module.exports = router; \ No newline at end of file +module.exports = router; diff --git a/backend/routes/backup.js b/backend/routes/backup.js index cd07563d..e9d6ce06 100644 --- a/backend/routes/backup.js +++ b/backend/routes/backup.js @@ -1,17 +1,16 @@ -const { Router } = require('express'); -const { Pool } = require('pg'); -const fs = require('fs'); -const path = require('path'); -const moment = require('moment'); -const { randomUUID } = require('crypto'); -const multer = require('multer'); +const { Router } = require("express"); +const { Pool } = require("pg"); +const fs = require("fs"); +const path = require("path"); +const moment = require("moment"); +const { randomUUID } = require("crypto"); +const multer = require("multer"); // const wss = require("./WebsocketHandler"); -const Logging =require('./logging'); -const triggertype = require('../logging/triggertype'); -const taskstate = require('../logging/taskstate'); -const taskName = require('../logging/taskName'); - +const Logging = require("./logging"); +const triggertype = require("../logging/triggertype"); +const taskstate = require("../logging/taskstate"); +const taskName = require("../logging/taskName"); const router = Router(); @@ -20,16 +19,25 @@ const postgresUser = process.env.POSTGRES_USER; const postgresPassword = process.env.POSTGRES_PASSWORD; const postgresIp = process.env.POSTGRES_IP; const postgresPort = process.env.POSTGRES_PORT; -const postgresDatabase = process.env.POSTGRES_DATABASE || 'jfstat'; -const backupfolder='backup-data'; +const postgresDatabase = process.env.POSTGRES_DATABASE || "jfstat"; +const backupfolder = "backup-data"; // Tables to back up -const tables = ['jf_libraries', 'jf_library_items', 'jf_library_seasons','jf_library_episodes','jf_users','jf_playback_activity','jf_playback_reporting_plugin_data','jf_item_info']; +const tables = [ + "jf_libraries", + "jf_library_items", + "jf_library_seasons", + "jf_library_episodes", + "jf_users", + "jf_playback_activity", + "jf_playback_reporting_plugin_data", + "jf_item_info", +]; function checkFolderWritePermission(folderPath) { try { const testFile = `${folderPath}/.writableTest`; - fs.writeFileSync(testFile, ''); + fs.writeFileSync(testFile, ""); fs.unlinkSync(testFile); return true; } catch (error) { @@ -44,125 +52,148 @@ async function backup(refLog) { password: postgresPassword, host: postgresIp, port: postgresPort, - database: postgresDatabase + database: postgresDatabase, }); // Get data from each table and append it to the backup file - - - try{ - let now = moment(); - const backuppath='./'+backupfolder; + try { + let now = moment(); + const backuppath = "./" + backupfolder; - if (!fs.existsSync(backuppath)) { - fs.mkdirSync(backuppath); - console.log('Directory created successfully!'); - } - if (!checkFolderWritePermission(backuppath)) { - console.error('No write permissions for the folder:', backuppath); - refLog.logData.push({ color: "red", Message: "Backup Failed: No write permissions for the folder: "+backuppath }); - refLog.logData.push({ color: "red", Message: "Backup Failed with errors"}); - logging.updateLog(refLog.uuid,refLog.loggedData,taskstate.FAILED); - await pool.end(); - return; + if (!fs.existsSync(backuppath)) { + fs.mkdirSync(backuppath); + console.log("Directory created successfully!"); + } + if (!checkFolderWritePermission(backuppath)) { + console.error("No write permissions for the folder:", backuppath); + refLog.logData.push({ + color: "red", + Message: + "Backup Failed: No write permissions for the folder: " + backuppath, + }); + refLog.logData.push({ + color: "red", + Message: "Backup Failed with errors", + }); + logging.updateLog(refLog.uuid, refLog.loggedData, taskstate.FAILED); + await pool.end(); + return; + } - } - - - // const backupPath = `../backup-data/backup_${now.format('yyyy-MM-DD HH-mm-ss')}.json`; - const directoryPath = path.join(__dirname, '..', backupfolder,`backup_${now.format('yyyy-MM-DD HH-mm-ss')}.json`); - - const stream = fs.createWriteStream(directoryPath, { flags: 'a' }); - stream.on('error', (error) => { - refLog.logData.push({ color: "red", Message: "Backup Failed: "+error }); - logging.updateLog(refLog.uuid,refLog.loggedData,taskstate.FAILED); - return; - }); - const backup_data=[]; - - refLog.logData.push({ color: "yellow", Message: "Begin Backup "+directoryPath }); - for (let table of tables) { - const query = `SELECT * FROM ${table}`; + // const backupPath = `../backup-data/backup_${now.format('yyyy-MM-DD HH-mm-ss')}.json`; + const directoryPath = path.join( + __dirname, + "..", + backupfolder, + `backup_${now.format("yyyy-MM-DD HH-mm-ss")}.json`, + ); + + const stream = fs.createWriteStream(directoryPath, { flags: "a" }); + stream.on("error", (error) => { + refLog.logData.push({ color: "red", Message: "Backup Failed: " + error }); + logging.updateLog(refLog.uuid, refLog.loggedData, taskstate.FAILED); + return; + }); + const backup_data = []; - const { rows } = await pool.query(query); - refLog.logData.push({color: "dodgerblue",Message: `Saving ${rows.length} rows for table ${table}`}); + refLog.logData.push({ + color: "yellow", + Message: "Begin Backup " + directoryPath, + }); + for (let table of tables) { + const query = `SELECT * FROM ${table}`; - backup_data.push({[table]:rows}); - - } + const { rows } = await pool.query(query); + refLog.logData.push({ + color: "dodgerblue", + Message: `Saving ${rows.length} rows for table ${table}`, + }); + backup_data.push({ [table]: rows }); + } await stream.write(JSON.stringify(backup_data)); stream.end(); refLog.logData.push({ color: "lawngreen", Message: "Backup Complete" }); - refLog.logData.push({ color: "dodgerblue", Message: "Removing old backups" }); - - //Cleanup excess backups - let deleteCount=0; - const directoryPathDelete = path.join(__dirname, '..', backupfolder); - - const files = await new Promise((resolve, reject) => { - fs.readdir(directoryPathDelete, (err, files) => { - if (err) { - reject(err); - } else { - resolve(files); - } - }); - }); - - let fileData = files.filter(file => file.endsWith('.json')) - .map(file => { - const filePath = path.join(directoryPathDelete, file); - const stats = fs.statSync(filePath); - return { - name: file, - size: stats.size, - datecreated: stats.birthtime - }; + refLog.logData.push({ + color: "dodgerblue", + Message: "Removing old backups", }); - fileData = fileData.sort((a, b) => new Date(b.datecreated) - new Date(a.datecreated)).slice(5); - - for (var oldBackup of fileData) { - const oldBackupFile = path.join(__dirname, '..', backupfolder, oldBackup.name); + //Cleanup excess backups + let deleteCount = 0; + const directoryPathDelete = path.join(__dirname, "..", backupfolder); - await new Promise((resolve, reject) => { - fs.unlink(oldBackupFile, (err) => { + const files = await new Promise((resolve, reject) => { + fs.readdir(directoryPathDelete, (err, files) => { if (err) { reject(err); } else { - resolve(); + resolve(files); } }); }); - deleteCount += 1; - refLog.logData.push({ color: "yellow", Message: `${oldBackupFile} has been deleted.` }); - } + let fileData = files + .filter((file) => file.endsWith(".json")) + .map((file) => { + const filePath = path.join(directoryPathDelete, file); + const stats = fs.statSync(filePath); + return { + name: file, + size: stats.size, + datecreated: stats.birthtime, + }; + }); - refLog.logData.push({ color: "lawngreen", Message: deleteCount+" backups removed." }); + fileData = fileData + .sort((a, b) => new Date(b.datecreated) - new Date(a.datecreated)) + .slice(5); + + for (var oldBackup of fileData) { + const oldBackupFile = path.join( + __dirname, + "..", + backupfolder, + oldBackup.name, + ); + + await new Promise((resolve, reject) => { + fs.unlink(oldBackupFile, (err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); - }catch(error) - { + deleteCount += 1; + refLog.logData.push({ + color: "yellow", + Message: `${oldBackupFile} has been deleted.`, + }); + } + + refLog.logData.push({ + color: "lawngreen", + Message: deleteCount + " backups removed.", + }); + } catch (error) { console.log(error); - refLog.logData.push({ color: "red", Message: "Backup Failed: "+error }); - logging.updateLog(refLog.uuid,refLog.loggedData,taskstate.FAILED); + refLog.logData.push({ color: "red", Message: "Backup Failed: " + error }); + logging.updateLog(refLog.uuid, refLog.loggedData, taskstate.FAILED); } - await pool.end(); - - } // Restore function - function readFile(path) { return new Promise((resolve, reject) => { - fs.readFile(path, 'utf8', (err, data) => { + fs.readFile(path, "utf8", (err, data) => { if (err) { reject(err); return; @@ -173,220 +204,214 @@ function readFile(path) { }); } -async function restore(file,refLog) { - +async function restore(file, refLog) { refLog.logData.push({ color: "lawngreen", Message: "Starting Restore" }); - refLog.logData.push({ color: "yellow", Message: "Restoring from Backup: "+file }); + refLog.logData.push({ + color: "yellow", + Message: "Restoring from Backup: " + file, + }); const pool = new Pool({ user: postgresUser, password: postgresPassword, host: postgresIp, port: postgresPort, - database: postgresDatabase + database: postgresDatabase, }); - const backupPath = file; - - let jsonData; + const backupPath = file; - try { - // Use await to wait for the Promise to resolve - jsonData = await readFile(backupPath); - - } catch (err) { - refLog.logData.push({ color: "red",key:tableName ,Message: `Failed to read backup file`}); - Logging.updateLog(refLog.uuid,refLog.logData,taskstate.FAILED); - console.error(err); - } - - // console.log(jsonData); - if(!jsonData) - { - console.log('No Data'); - return; - } + let jsonData; - for(let table of jsonData) - { - const data = Object.values(table)[0]; - const tableName=Object.keys(table)[0]; - refLog.logData.push({ color: "dodgerblue",key:tableName ,Message: `Restoring ${tableName}`}); - for(let index in data) - { - const keysWithQuotes = Object.keys(data[index]).map(key => `"${key}"`); - const keyString = keysWithQuotes.join(", "); - - const valuesWithQuotes = Object.values(data[index]).map(col => { - if (col === null) { - return 'NULL'; - } else if (typeof col === 'string') { - return `'${col.replace(/'/g, "''")}'`; - }else if (typeof col === 'object') { - return `'${JSON.stringify(col).replace(/'/g, "''")}'`; - } else { - return `'${col}'`; - } - }); + try { + // Use await to wait for the Promise to resolve + jsonData = await readFile(backupPath); + } catch (err) { + refLog.logData.push({ + color: "red", + key: tableName, + Message: `Failed to read backup file`, + }); + Logging.updateLog(refLog.uuid, refLog.logData, taskstate.FAILED); + console.error(err); + } - const valueString = valuesWithQuotes.join(", "); - - - const query=`INSERT INTO ${tableName} (${keyString}) VALUES(${valueString}) ON CONFLICT DO NOTHING`; - const { rows } = await pool.query( query ); + // console.log(jsonData); + if (!jsonData) { + console.log("No Data"); + return; + } + for (let table of jsonData) { + const data = Object.values(table)[0]; + const tableName = Object.keys(table)[0]; + refLog.logData.push({ + color: "dodgerblue", + key: tableName, + Message: `Restoring ${tableName}`, + }); + for (let index in data) { + const keysWithQuotes = Object.keys(data[index]).map((key) => `"${key}"`); + const keyString = keysWithQuotes.join(", "); + + const valuesWithQuotes = Object.values(data[index]).map((col) => { + if (col === null) { + return "NULL"; + } else if (typeof col === "string") { + return `'${col.replace(/'/g, "''")}'`; + } else if (typeof col === "object") { + return `'${JSON.stringify(col).replace(/'/g, "''")}'`; + } else { + return `'${col}'`; + } + }); - } - + const valueString = valuesWithQuotes.join(", "); + const query = `INSERT INTO ${tableName} (${keyString}) VALUES(${valueString}) ON CONFLICT DO NOTHING`; + const { rows } = await pool.query(query); } - await pool.end(); - refLog.logData.push({ color: "lawngreen", Message: "Restore Complete" }); - } + await pool.end(); + refLog.logData.push({ color: "lawngreen", Message: "Restore Complete" }); +} // Route handler for backup endpoint -router.get('/beginBackup', async (req, res) => { +router.get("/beginBackup", async (req, res) => { try { - const last_execution=await db.query( `SELECT "Result" + const last_execution = await db + .query( + `SELECT "Result" FROM public.jf_logging WHERE "Name"='${taskName.backup}' ORDER BY "TimeRun" DESC - LIMIT 1`).then((res) => res.rows); - - if(last_execution.length!==0) - { - - if(last_execution[0].Result ===taskstate.RUNNING) - { - res.send(); - return; + LIMIT 1`, + ) + .then((res) => res.rows); + + if (last_execution.length !== 0) { + if (last_execution[0].Result === taskstate.RUNNING) { + res.send(); + return; } } - const uuid = randomUUID(); - let refLog={logData:[],uuid:uuid}; - Logging.insertLog(uuid,triggertype.Manual,taskName.backup); + let refLog = { logData: [], uuid: uuid }; + Logging.insertLog(uuid, triggertype.Manual, taskName.backup); await backup(refLog); - Logging.updateLog(uuid,refLog.logData,taskstate.SUCCESS); - res.send('Backup completed successfully'); + Logging.updateLog(uuid, refLog.logData, taskstate.SUCCESS); + res.send("Backup completed successfully"); } catch (error) { console.error(error); - res.status(500).send('Backup failed'); + res.status(500).send("Backup failed"); } }); -router.get('/restore/:filename', async (req, res) => { - - try { - const uuid = randomUUID(); - let refLog={logData:[],uuid:uuid}; - Logging.insertLog(uuid,triggertype.Manual,taskName.restore); - - const filePath = path.join(__dirname, '..', backupfolder, req.params.filename); - - await restore(filePath,refLog); - Logging.updateLog(uuid,refLog.logData,taskstate.SUCCESS); - - res.send('Restore completed successfully'); - } catch (error) { - console.error(error); - res.status(500).send('Restore failed'); - } +router.get("/restore/:filename", async (req, res) => { + try { + const uuid = randomUUID(); + let refLog = { logData: [], uuid: uuid }; + Logging.insertLog(uuid, triggertype.Manual, taskName.restore); - }); + const filePath = path.join( + __dirname, + "..", + backupfolder, + req.params.filename, + ); + await restore(filePath, refLog); + Logging.updateLog(uuid, refLog.logData, taskstate.SUCCESS); + res.send("Restore completed successfully"); + } catch (error) { + console.error(error); + res.status(500).send("Restore failed"); + } +}); - - router.get('/files', (req, res) => { - try - { - const directoryPath = path.join(__dirname, '..', backupfolder); +router.get("/files", (req, res) => { + try { + const directoryPath = path.join(__dirname, "..", backupfolder); fs.readdir(directoryPath, (err, files) => { if (err) { - res.status(500).send('Unable to read directory'); + res.status(500).send("Unable to read directory"); } else { - const fileData = files.filter(file => file.endsWith('.json')) - .map(file => { - const filePath = path.join(directoryPath, file); - const stats = fs.statSync(filePath); - return { - name: file, - size: stats.size, - datecreated: stats.birthtime - }; - }); + const fileData = files + .filter((file) => file.endsWith(".json")) + .map((file) => { + const filePath = path.join(directoryPath, file); + const stats = fs.statSync(filePath); + return { + name: file, + size: stats.size, + datecreated: stats.birthtime, + }; + }); res.json(fileData); } }); + } catch (error) { + console.log(error); + } +}); - }catch(error) - { - console.log(error); - } - - }); - - - //download backup file - router.get('/files/:filename', (req, res) => { - const filePath = path.join(__dirname, '..', backupfolder, req.params.filename); - res.download(filePath); - }); +//download backup file +router.get("/files/:filename", (req, res) => { + const filePath = path.join( + __dirname, + "..", + backupfolder, + req.params.filename, + ); + res.download(filePath); +}); - //delete backup - router.delete('/files/:filename', (req, res) => { +//delete backup +router.delete("/files/:filename", (req, res) => { + try { + const filePath = path.join( + __dirname, + "..", + backupfolder, + req.params.filename, + ); + + fs.unlink(filePath, (err) => { + if (err) { + console.error(err); + res.status(500).send("An error occurred while deleting the file."); + return; + } - try{ - const filePath = path.join(__dirname, '..', backupfolder, req.params.filename); - - fs.unlink(filePath, (err) => { - if (err) { - console.error(err); - res.status(500).send('An error occurred while deleting the file.'); - return; - } - - console.log(`${filePath} has been deleted.`); - res.status(200).send(`${filePath} has been deleted.`); - }); + console.log(`${filePath} has been deleted.`); + res.status(200).send(`${filePath} has been deleted.`); + }); + } catch (error) { + res.status(500).send("An error occurred while deleting the file."); + } +}); - }catch(error) - { - res.status(500).send('An error occurred while deleting the file.'); - } +const storage = multer.diskStorage({ + destination: function (req, file, cb) { + cb(null, path.join(__dirname, "..", backupfolder)); // Set the destination folder for uploaded files + }, + filename: function (req, file, cb) { + cb(null, file.originalname); // Set the file name + }, +}); - }); +const upload = multer({ storage: storage }); - - const storage = multer.diskStorage({ - destination: function (req, file, cb) { - cb(null, path.join(__dirname, '..', backupfolder)); // Set the destination folder for uploaded files - }, - filename: function (req, file, cb) { - cb(null, file.originalname); // Set the file name - }, - }); - - const upload = multer({ storage: storage }); - - - router.post("/upload", upload.single("file"), (req, res) => { - // Handle the uploaded file here - res.json({ - fileName: req.file.originalname, - filePath: req.file.path, - }); +router.post("/upload", upload.single("file"), (req, res) => { + // Handle the uploaded file here + res.json({ + fileName: req.file.originalname, + filePath: req.file.path, }); - - - - - +}); -module.exports = -{ +module.exports = { router, - backup + backup, }; diff --git a/backend/routes/logging.js b/backend/routes/logging.js index 9743c420..776816fe 100644 --- a/backend/routes/logging.js +++ b/backend/routes/logging.js @@ -1,56 +1,51 @@ - const db = require("../db"); -const moment = require('moment'); -const taskstate = require('../logging/taskstate'); - +const moment = require("moment"); +const taskstate = require("../logging/taskstate"); -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(); router.get("/getLogs", async (req, res) => { try { - const { rows } = await db.query(`SELECT * FROM jf_logging order by "TimeRun" desc LIMIT 50 `); + const { rows } = await db.query( + `SELECT * FROM jf_logging order by "TimeRun" desc LIMIT 50 `, + ); res.send(rows); } catch (error) { res.send(error); } }); - -async function insertLog(uuid,triggertype,taskType) -{ +async function insertLog(uuid, triggertype, taskType) { try { let startTime = moment(); - const log= - { - "Id":uuid, - "Name":taskType, - "Type":"Task", - "ExecutionType":triggertype, - "Duration":0, - "TimeRun":startTime, - "Log":JSON.stringify([{}]), - "Result":taskstate.RUNNING - + const log = { + Id: uuid, + Name: taskType, + Type: "Task", + ExecutionType: triggertype, + Duration: 0, + TimeRun: startTime, + Log: JSON.stringify([{}]), + Result: taskstate.RUNNING, }; - - let result=await db.insertBulk("jf_logging",log,jf_logging_columns); - // console.log(result); - - } catch (error) { - console.log(error); - return []; - } - + + let result = await db.insertBulk("jf_logging", log, jf_logging_columns); + // console.log(result); + } catch (error) { + console.log(error); + return []; + } } -async function updateLog(uuid,data,taskstate) -{ +async function updateLog(uuid, data, taskstate) { try { - - const { rows:task } = await db.query( - `SELECT "TimeRun" FROM jf_logging WHERE "Id" = '${uuid}';` + const { rows: task } = await db.query( + `SELECT "TimeRun" FROM jf_logging WHERE "Id" = '${uuid}';`, ); if (task.length === 0) { @@ -60,30 +55,24 @@ async function updateLog(uuid,data,taskstate) let endtime = moment(); let startTime = moment(task[0].TimeRun); - let duration = endtime.diff(startTime, 'seconds'); - const log= - { - "Id":uuid, - "Name":"NULL Placeholder", - "Type":"Task", - "ExecutionType":"NULL Placeholder", - "Duration":duration, - "TimeRun":startTime, - "Log":JSON.stringify(data), - "Result":taskstate - + let duration = endtime.diff(startTime, "seconds"); + const log = { + Id: uuid, + Name: "NULL Placeholder", + Type: "Task", + ExecutionType: "NULL Placeholder", + Duration: duration, + TimeRun: startTime, + Log: JSON.stringify(data), + Result: taskstate, }; - - let result=await db.insertBulk("jf_logging",log,jf_logging_columns); - // console.log(result); - - } catch (error) { - console.log(error); - return []; - } - -} + let result = await db.insertBulk("jf_logging", log, jf_logging_columns); + // console.log(result); + } catch (error) { + console.log(error); + return []; + } +} -module.exports = -{router,insertLog,updateLog}; +module.exports = { router, insertLog, updateLog }; diff --git a/backend/routes/proxy.js b/backend/routes/proxy.js index ef572416..9b53cf32 100644 --- a/backend/routes/proxy.js +++ b/backend/routes/proxy.js @@ -1,285 +1,304 @@ -const express = require('express'); +const express = require("express"); const axios = require("axios"); const db = require("../db"); -const https = require('https'); +const https = require("https"); const agent = new https.Agent({ - rejectUnauthorized: (process.env.REJECT_SELF_SIGNED_CERTIFICATES || 'true').toLowerCase() ==='true' + rejectUnauthorized: + (process.env.REJECT_SELF_SIGNED_CERTIFICATES || "true").toLowerCase() === + "true", }); - const axios_instance = axios.create({ - httpsAgent: agent + httpsAgent: agent, }); const router = express.Router(); -router.get('/web/assets/img/devices/', async(req, res) => { +router.get("/web/assets/img/devices/", async (req, res) => { const { devicename } = req.query; // Get the image URL from the query string - const { rows: config } = await db.query('SELECT * FROM app_config where "ID"=1'); - - if (config[0].JF_HOST === null || config[0].JF_API_KEY === null || devicename===undefined) { + const { rows: config } = await db.query( + 'SELECT * FROM app_config where "ID"=1', + ); + + if ( + config[0].JF_HOST === null || + config[0].JF_API_KEY === null || + devicename === undefined + ) { res.send({ error: "Config Details Not Found" }); return; } - let url=`${config[0].JF_HOST}/web/assets/img/devices/${devicename}.svg`; - - axios_instance.get(url, { - responseType: 'arraybuffer' - }) - .then((response) => { - res.set('Content-Type', 'image/svg+xml'); - res.status(200); - - if (response.headers['content-type'].startsWith('image/')) { - res.send(response.data); - } else { - res.status(500).send('Error fetching image'); - } - - return; // Add this line - }) - .catch((error) => { - console.error(error); - res.status(500).send('Error fetching image: '+error); - }); - -}); + let url = `${config[0].JF_HOST}/web/assets/img/devices/${devicename}.svg`; + axios_instance + .get(url, { + responseType: "arraybuffer", + }) + .then((response) => { + res.set("Content-Type", "image/svg+xml"); + res.status(200); + if (response.headers["content-type"].startsWith("image/")) { + res.send(response.data); + } else { + res.status(500).send("Error fetching image"); + } -router.get('/Items/Images/Backdrop/', async(req, res) => { - const { id,fillWidth,quality,blur } = req.query; // Get the image URL from the query string - const { rows: config } = await db.query('SELECT * FROM app_config where "ID"=1'); + return; // Add this line + }) + .catch((error) => { + console.error(error); + res.status(500).send("Error fetching image: " + error); + }); +}); - if (config[0].JF_HOST === null || config[0].JF_API_KEY === null) { - res.send({ error: "Config Details Not Found" }); - return; - } - +router.get("/Items/Images/Backdrop/", async (req, res) => { + const { id, fillWidth, quality, blur } = req.query; // Get the image URL from the query string + const { rows: config } = await db.query( + 'SELECT * FROM app_config where "ID"=1', + ); - let url=`${config[0].JF_HOST}/Items/${id}/Images/Backdrop?fillWidth=${fillWidth || 800}&quality=${quality || 100}&blur=${blur || 0}`; + if (config[0].JF_HOST === null || config[0].JF_API_KEY === null) { + res.send({ error: "Config Details Not Found" }); + return; + } + let url = `${config[0].JF_HOST}/Items/${id}/Images/Backdrop?fillWidth=${ + fillWidth || 800 + }&quality=${quality || 100}&blur=${blur || 0}`; - axios_instance.get(url, { - responseType: 'arraybuffer' + axios_instance + .get(url, { + responseType: "arraybuffer", }) .then((response) => { - res.set('Content-Type', 'image/jpeg'); + res.set("Content-Type", "image/jpeg"); res.status(200); - if (response.headers['content-type'].startsWith('image/')) { + if (response.headers["content-type"].startsWith("image/")) { res.send(response.data); } else { - res.status(500).send('Error fetching image'); + res.status(500).send("Error fetching image"); } }) .catch((error) => { // console.error(error); - res.status(500).send('Error fetching image: '+error); + res.status(500).send("Error fetching image: " + error); }); - }); +}); - router.get('/Items/Images/Primary/', async(req, res) => { - const { id,fillWidth,quality } = req.query; // Get the image URL from the query string - const { rows: config } = await db.query('SELECT * FROM app_config where "ID"=1'); +router.get("/Items/Images/Primary/", async (req, res) => { + const { id, fillWidth, quality } = req.query; // Get the image URL from the query string + const { rows: config } = await db.query( + 'SELECT * FROM app_config where "ID"=1', + ); - if (config[0].JF_HOST === null || config[0].JF_API_KEY === null) { - res.send({ error: "Config Details Not Found" }); - return; - } - + if (config[0].JF_HOST === null || config[0].JF_API_KEY === null) { + res.send({ error: "Config Details Not Found" }); + return; + } - let url=`${config[0].JF_HOST}/Items/${id}/Images/Primary?fillWidth=${fillWidth || 400}&quality=${quality || 100}`; + let url = `${config[0].JF_HOST}/Items/${id}/Images/Primary?fillWidth=${ + fillWidth || 400 + }&quality=${quality || 100}`; - axios_instance.get(url, { - responseType: 'arraybuffer' + axios_instance + .get(url, { + responseType: "arraybuffer", }) .then((response) => { - res.set('Content-Type', 'image/jpeg'); + res.set("Content-Type", "image/jpeg"); res.status(200); - if (response.headers['content-type'].startsWith('image/')) { + if (response.headers["content-type"].startsWith("image/")) { res.send(response.data); } else { - res.status(500).send('Error fetching image'); + res.status(500).send("Error fetching image"); } }) .catch((error) => { // console.error(error); - res.status(500).send('Error fetching image: '+error); + res.status(500).send("Error fetching image: " + error); }); - }); +}); - - router.get('/Users/Images/Primary/', async(req, res) => { - const { id,fillWidth,quality } = req.query; // Get the image URL from the query string - const { rows: config } = await db.query('SELECT * FROM app_config where "ID"=1'); +router.get("/Users/Images/Primary/", async (req, res) => { + const { id, fillWidth, quality } = req.query; // Get the image URL from the query string + const { rows: config } = await db.query( + 'SELECT * FROM app_config where "ID"=1', + ); - if (config[0].JF_HOST === null || config[0].JF_API_KEY === null) { - res.send({ error: "Config Details Not Found" }); - return; - } - + if (config[0].JF_HOST === null || config[0].JF_API_KEY === null) { + res.send({ error: "Config Details Not Found" }); + return; + } - let url=`${config[0].JF_HOST}/Users/${id}/Images/Primary?fillWidth=${fillWidth || 100}&quality=${quality || 100}`; + let url = `${config[0].JF_HOST}/Users/${id}/Images/Primary?fillWidth=${ + fillWidth || 100 + }&quality=${quality || 100}`; - axios_instance.get(url, { - responseType: 'arraybuffer' + axios_instance + .get(url, { + responseType: "arraybuffer", }) .then((response) => { - res.set('Content-Type', 'image/jpeg'); + res.set("Content-Type", "image/jpeg"); res.status(200); - if (response.headers['content-type'].startsWith('image/')) { + if (response.headers["content-type"].startsWith("image/")) { res.send(response.data); } else { - res.status(500).send('Error fetching image'); + res.status(500).send("Error fetching image"); } }) .catch((error) => { // console.error(error); - res.status(500).send('Error fetching image: '+error); + 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, - }, +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, }); - res.send(response_data.data); - } catch (error) { + 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); + res.send({ error: "Config Details Not Found" }); + return; } - }); - - 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, { + + 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 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) { + + if ( + !response || + typeof response.data !== "object" || + !Array.isArray(response.data) + ) { res.status(503); - res.send({ error: "Config Details Not Found" }); + res.send({ + error: "Invalid Response from Users API Call.", + user_response: response, + }); return; } - - - let userid=config[0].settings?.preferred_admin?.userid; + const admins = response.data.filter( + (user) => user.Policy.IsAdministrator === true, + ); + userid = admins[0].Id; + } - 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); + 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); + } +}); module.exports = router; diff --git a/backend/routes/stats.js b/backend/routes/stats.js index 51fa31cf..2d6dee35 100644 --- a/backend/routes/stats.js +++ b/backend/routes/stats.js @@ -4,8 +4,6 @@ const db = require("../db"); const router = express.Router(); - - router.get("/getLibraryOverview", async (req, res) => { try { const { rows } = await db.query("SELECT * FROM jf_library_count_view"); @@ -18,24 +16,23 @@ router.get("/getLibraryOverview", async (req, res) => { router.post("/getMostViewedByType", async (req, res) => { try { - const { days,type } = req.body; + const { days, type } = req.body; - const valid_types=['Audio','Movie','Series']; + const valid_types = ["Audio", "Movie", "Series"]; let _days = days; if (days === undefined) { _days = 30; } - if(!valid_types.includes(type)) - { + if (!valid_types.includes(type)) { res.status(503); - return res.send('Invalid Type Value'); + return res.send("Invalid Type Value"); } - const { rows } = await db.query( - `select * from fs_most_played_items($1,'${type}') limit 5`, [_days-1] + `select * from fs_most_played_items($1,'${type}') limit 5`, + [_days - 1], ); res.send(rows); } catch (error) { @@ -46,23 +43,23 @@ router.post("/getMostViewedByType", async (req, res) => { router.post("/getMostPopularByType", async (req, res) => { try { - const { days,type } = req.body; + const { days, type } = req.body; - const valid_types=['Audio','Movie','Series']; + const valid_types = ["Audio", "Movie", "Series"]; let _days = days; if (days === undefined) { _days = 30; } - if(!valid_types.includes(type)) - { + if (!valid_types.includes(type)) { res.status(503); - return res.send('Invalid Type Value'); + return res.send("Invalid Type Value"); } const { rows } = await db.query( - `select * from fs_most_popular_items($1,$2) limit 5`, [_days-1, type] + `select * from fs_most_popular_items($1,$2) limit 5`, + [_days - 1, type], ); res.send(rows); } catch (error) { @@ -71,8 +68,6 @@ router.post("/getMostPopularByType", async (req, res) => { } }); - - router.post("/getMostViewedLibraries", async (req, res) => { try { const { days } = req.body; @@ -81,7 +76,8 @@ router.post("/getMostViewedLibraries", async (req, res) => { _days = 30; } const { rows } = await db.query( - `select * from fs_most_viewed_libraries($1)`, [_days-1] + `select * from fs_most_viewed_libraries($1)`, + [_days - 1], ); res.send(rows); } catch (error) { @@ -98,7 +94,8 @@ router.post("/getMostUsedClient", async (req, res) => { _days = 30; } const { rows } = await db.query( - `select * from fs_most_used_clients($1) limit 5`, [_days-1] + `select * from fs_most_used_clients($1) limit 5`, + [_days - 1], ); res.send(rows); } catch (error) { @@ -115,16 +112,16 @@ router.post("/getMostActiveUsers", async (req, res) => { _days = 30; } const { rows } = await db.query( - `select * from fs_most_active_user($1) limit 5`, [_days-1] + `select * from fs_most_active_user($1) limit 5`, + [_days - 1], ); - res.send(rows); + res.send(rows); } catch (error) { res.status(503); res.send(error); } }); - router.get("/getPlaybackActivity", async (req, res) => { try { const { rows } = await db.query("SELECT * FROM jf_playback_activity"); @@ -144,12 +141,12 @@ router.get("/getAllUserActivity", async (req, res) => { } }); - router.post("/getUserLastPlayed", async (req, res) => { try { const { userid } = req.body; const { rows } = await db.query( - `select * from fs_last_user_activity($1) limit 15`, [userId] + `select * from fs_last_user_activity($1) limit 15`, + [userId], ); res.send(rows); } catch (error) { @@ -162,14 +159,15 @@ router.post("/getUserLastPlayed", async (req, res) => { //Global Stats router.post("/getGlobalUserStats", async (req, res) => { try { - const { hours,userid } = req.body; + const { hours, userid } = req.body; let _hours = hours; if (hours === undefined) { _hours = 24; } - const { rows } = await db.query( - `select * from fs_user_stats($1,$2)`, [_hours, userid] - ); + const { rows } = await db.query(`select * from fs_user_stats($1,$2)`, [ + _hours, + userid, + ]); res.send(rows[0]); } catch (error) { console.log(error); @@ -180,7 +178,7 @@ router.post("/getGlobalUserStats", async (req, res) => { router.post("/getGlobalItemStats", async (req, res) => { try { - const { hours,itemid } = req.body; + const { hours, itemid } = req.body; let _hours = hours; if (hours === undefined) { _hours = 24; @@ -191,7 +189,8 @@ router.post("/getGlobalItemStats", async (req, res) => { from jf_playback_activity jf_playback_activity where ("EpisodeId"=$1 OR "SeasonId"=$1 OR "NowPlayingItemId"=$1) - AND jf_playback_activity."ActivityDateInserted" BETWEEN CURRENT_DATE - INTERVAL '1 hour' * $2 AND NOW();`, [itemid, _hours] + AND jf_playback_activity."ActivityDateInserted" BETWEEN CURRENT_DATE - INTERVAL '1 hour' * $2 AND NOW();`, + [itemid, _hours], ); res.send(rows[0]); } catch (error) { @@ -203,14 +202,15 @@ router.post("/getGlobalItemStats", async (req, res) => { router.post("/getGlobalLibraryStats", async (req, res) => { try { - const { hours,libraryid } = req.body; + const { hours, libraryid } = req.body; let _hours = hours; if (hours === undefined) { _hours = 24; } - const { rows } = await db.query( - `select * from fs_library_stats($1,$2)`, [_hours, libraryid] - ); + const { rows } = await db.query(`select * from fs_library_stats($1,$2)`, [ + _hours, + libraryid, + ]); res.send(rows[0]); } catch (error) { console.log(error); @@ -219,9 +219,6 @@ router.post("/getGlobalLibraryStats", async (req, res) => { } }); - - - router.get("/getLibraryCardStats", async (req, res) => { try { const { rows } = await db.query("select * from js_library_stats_overview"); @@ -243,29 +240,25 @@ router.get("/getLibraryMetadata", async (req, res) => { }); router.post("/getLibraryItemsWithStats", async (req, res) => { - try{ - const {libraryid} = req.body; - console.log(`ENDPOINT CALLED: /getLibraryItems: `+libraryid); + try { + const { libraryid } = req.body; + console.log(`ENDPOINT CALLED: /getLibraryItems: ` + libraryid); const { rows } = await db.query( - `SELECT * FROM jf_library_items_with_playcount_playtime where "ParentId"=$1`, [libraryid] + `SELECT * FROM jf_library_items_with_playcount_playtime where "ParentId"=$1`, + [libraryid], ); res.send(rows); - - - }catch(error) - { + } catch (error) { console.log(error); } - - }); - router.post("/getLibraryLastPlayed", async (req, res) => { try { const { libraryid } = req.body; const { rows } = await db.query( - `select * from fs_last_library_activity($1) limit 15`, [libraryid] + `select * from fs_last_library_activity($1) limit 15`, + [libraryid], ); res.send(rows); } catch (error) { @@ -275,44 +268,45 @@ router.post("/getLibraryLastPlayed", async (req, res) => { } }); - router.post("/getViewsOverTime", async (req, res) => { try { const { days } = req.body; let _days = days; - if (days=== undefined) { + if (days === undefined) { _days = 30; } - const { rows:stats } = await db.query( - `select * from fs_watch_stats_over_time($1)`, [_days] + const { rows: stats } = await db.query( + `select * from fs_watch_stats_over_time($1)`, + [_days], ); - const { rows:libraries } = await db.query( - `select distinct "Id","Name" from jf_libraries` + const { rows: libraries } = await db.query( + `select distinct "Id","Name" from jf_libraries`, ); - -const reorganizedData = {}; - -stats.forEach((item) => { - const library = item.Library; - const count = item.Count; - const date = new Date(item.Date).toLocaleDateString('en-US', { - year: 'numeric', - month: 'short', - day: '2-digit' - }); - - - if (!reorganizedData[date]) { - reorganizedData[date] = { - Key:date + const reorganizedData = {}; + + stats.forEach((item) => { + const library = item.Library; + const count = item.Count; + const date = new Date(item.Date).toLocaleDateString("en-US", { + year: "numeric", + month: "short", + day: "2-digit", + }); + + if (!reorganizedData[date]) { + reorganizedData[date] = { + Key: date, + }; + } + + reorganizedData[date] = { ...reorganizedData[date], [library]: count }; + }); + const finalData = { + libraries: libraries, + stats: Object.values(reorganizedData), }; - } - - reorganizedData[date]= { ...reorganizedData[date], [library]: count}; -}); -const finalData = {libraries:libraries,stats:Object.values(reorganizedData)}; res.send(finalData); } catch (error) { console.log(error); @@ -325,35 +319,37 @@ router.post("/getViewsByDays", async (req, res) => { try { const { days } = req.body; let _days = days; - if (days=== undefined) { + if (days === undefined) { _days = 30; } - const { rows:stats } = await db.query( - `select * from fs_watch_stats_popular_days_of_week($1)`, [_days] + const { rows: stats } = await db.query( + `select * from fs_watch_stats_popular_days_of_week($1)`, + [_days], ); - const { rows:libraries } = await db.query( - `select distinct "Id","Name" from jf_libraries` + const { rows: libraries } = await db.query( + `select distinct "Id","Name" from jf_libraries`, ); - -const reorganizedData = {}; + const reorganizedData = {}; -stats.forEach((item) => { - const library = item.Library; - const count = item.Count; - const day = item.Day; + stats.forEach((item) => { + const library = item.Library; + const count = item.Count; + const day = item.Day; + if (!reorganizedData[day]) { + reorganizedData[day] = { + Key: day, + }; + } - if (!reorganizedData[day]) { - reorganizedData[day] = { - Key:day + reorganizedData[day] = { ...reorganizedData[day], [library]: count }; + }); + const finalData = { + libraries: libraries, + stats: Object.values(reorganizedData), }; - } - - reorganizedData[day]= { ...reorganizedData[day], [library]: count}; -}); -const finalData = {libraries:libraries,stats:Object.values(reorganizedData)}; res.send(finalData); } catch (error) { console.log(error); @@ -366,35 +362,37 @@ router.post("/getViewsByHour", async (req, res) => { try { const { days } = req.body; let _days = days; - if (days=== undefined) { + if (days === undefined) { _days = 30; } - const { rows:stats } = await db.query( - `select * from fs_watch_stats_popular_hour_of_day($1)`, [_days] + const { rows: stats } = await db.query( + `select * from fs_watch_stats_popular_hour_of_day($1)`, + [_days], ); - const { rows:libraries } = await db.query( - `select distinct "Id","Name" from jf_libraries` + const { rows: libraries } = await db.query( + `select distinct "Id","Name" from jf_libraries`, ); - -const reorganizedData = {}; + const reorganizedData = {}; -stats.forEach((item) => { - const library = item.Library; - const count = item.Count; - const hour = item.Hour; + stats.forEach((item) => { + const library = item.Library; + const count = item.Count; + const hour = item.Hour; + if (!reorganizedData[hour]) { + reorganizedData[hour] = { + Key: hour, + }; + } - if (!reorganizedData[hour]) { - reorganizedData[hour] = { - Key:hour + reorganizedData[hour] = { ...reorganizedData[hour], [library]: count }; + }); + const finalData = { + libraries: libraries, + stats: Object.values(reorganizedData), }; - } - - reorganizedData[hour]= { ...reorganizedData[hour], [library]: count}; -}); -const finalData = {libraries:libraries,stats:Object.values(reorganizedData)}; res.send(finalData); } catch (error) { console.log(error); @@ -403,7 +401,4 @@ const finalData = {libraries:libraries,stats:Object.values(reorganizedData)}; } }); - - - module.exports = router; diff --git a/backend/routes/sync.js b/backend/routes/sync.js index e22c7f81..41ed3cb3 100644 --- a/backend/routes/sync.js +++ b/backend/routes/sync.js @@ -2,35 +2,52 @@ const express = require("express"); const pgp = require("pg-promise")(); const db = require("../db"); const axios = require("axios"); -const https = require('https'); -const moment = require('moment'); -const { randomUUID } = require('crypto'); +const https = require("https"); +const moment = require("moment"); +const { randomUUID } = require("crypto"); - -const logging=require("./logging"); -const taskName=require("../logging/taskName"); -const triggertype=require("../logging/triggertype"); +const logging = require("./logging"); +const taskName = require("../logging/taskName"); +const triggertype = require("../logging/triggertype"); const agent = new https.Agent({ - rejectUnauthorized: (process.env.REJECT_SELF_SIGNED_CERTIFICATES || 'true').toLowerCase() ==='true' + rejectUnauthorized: + (process.env.REJECT_SELF_SIGNED_CERTIFICATES || "true").toLowerCase() === + "true", }); - - const axios_instance = axios.create({ - httpsAgent: agent + httpsAgent: agent, }); 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_users_columns,jf_users_mapping,} = require("../models/jf_users"); +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 taskstate = require("../logging/taskstate"); let syncTask; @@ -39,11 +56,11 @@ let PlaybacksyncTask; /////////////////////////////////////////Functions function getErrorLineNumber(error) { - const stackTrace = error.stack.split('\n'); + const stackTrace = error.stack.split("\n"); const errorLine = stackTrace[1].trim(); const lineNumber = errorLine.substring( - errorLine.lastIndexOf('\\') + 1, - errorLine.lastIndexOf(')') + errorLine.lastIndexOf("\\") + 1, + errorLine.lastIndexOf(")"), ); return lineNumber; } @@ -78,56 +95,58 @@ class sync { }, }); - if(!response || typeof response.data !== 'object' || !Array.isArray(response.data)) - { + 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 }); + res.send({ + error: "Invalid Response from Users API Call.", + user_response: response, + }); return; } - + const adminUser = response.data.filter( - (user) => user.Policy.IsAdministrator === true + (user) => user.Policy.IsAdministrator === true, ); return adminUser || null; } catch (error) { console.log(error); - syncTask.loggedData.push({ Message: "Error Getting AdminId: "+error}); + syncTask.loggedData.push({ Message: "Error Getting AdminId: " + error }); return []; } } - async getItem(ids,params) { + async getItem(ids, params) { try { - - let url = `${this.hostUrl}/Items?ids=${ids}`; - let startIndex=params && params.startIndex ? params.startIndex :0; - let increment=params && params.increment ? params.startIndex :200; - let recursive=params && params.recursive!==undefined ? params.recursive :true; - let total=200; - - let final_response=[]; - while(startIndex !["boxsets", "playlists"].includes(type.CollectionType) + (type) => !["boxsets", "playlists"].includes(type.CollectionType), ); - - return filtered_libraries; } catch (error) { @@ -161,41 +175,39 @@ class sync { } } - async getItems(key,id,params) { + async getItems(key, id, params) { try { - - let url = `${this.hostUrl}/Items?${key}=${id}`; - let startIndex=params && params.startIndex ? params.startIndex :0; - let increment=params && params.increment ? params.startIndex :200; - let recursive=params && params.recursive!==undefined ? params.recursive :true; - let total=200; - - let final_response=[]; - while(startIndex !["boxsets","playlists"].includes(type.CollectionType)); + if (key === "userid") { + return final_response.filter( + (type) => !["boxsets", "playlists"].includes(type.CollectionType), + ); } else { // return final_response.filter((item) => item.ImageTags.Primary); return final_response; @@ -206,10 +218,8 @@ class sync { } } - - async getItemInfo(itemID,userid) { + async getItemInfo(itemID, userid) { try { - let url = `${this.hostUrl}/Items/${itemID}/playbackinfo?userId=${userid}`; const response = await axios_instance.get(url, { @@ -226,16 +236,14 @@ class sync { } } - async getExistingIDsforTable(tablename) - { - return await db + async getExistingIDsforTable(tablename) { + return await db .query(`SELECT "Id" FROM ${tablename}`) - .then((res) => res.rows.map((row) => row.Id)); + .then((res) => res.rows.map((row) => row.Id)); } - async insertData(tablename,dataToInsert,column_mappings) - { - let result = await db.insertBulk(tablename,dataToInsert,column_mappings); + async insertData(tablename, dataToInsert, column_mappings) { + let result = await db.insertBulk(tablename, dataToInsert, column_mappings); if (result.Result === "SUCCESS") { syncTask.loggedData.push(dataToInsert.length + " Rows Inserted."); } else { @@ -247,145 +255,178 @@ class sync { } } - async removeData(tablename,dataToRemove) - { - let result = await db.deleteBulk(tablename,dataToRemove); + async removeData(tablename, dataToRemove) { + let result = await db.deleteBulk(tablename, dataToRemove); if (result.Result === "SUCCESS") { syncTask.loggedData.push(dataToRemove.length + " Rows Removed."); } else { - syncTask.loggedData.push({color: "red",Message: "Error: "+result.message,}); + syncTask.loggedData.push({ + color: "red", + Message: "Error: " + result.message, + }); throw new Error("Error :" + result.message); } } } ////////////////////////////////////////API Methods -async function syncUserData() -{ +async function syncUserData() { const { rows } = await db.query('SELECT * FROM app_config where "ID"=1'); const _sync = new sync(rows[0].JF_HOST, rows[0].JF_API_KEY); const data = await _sync.getUsers(); - const existingIds = await _sync.getExistingIDsforTable('jf_users');// get existing user Ids from the db + const existingIds = await _sync.getExistingIDsforTable("jf_users"); // get existing user Ids from the db let dataToInsert = await data.map(jf_users_mapping); - if (dataToInsert.length > 0) { - await _sync.insertData("jf_users",dataToInsert,jf_users_columns); + await _sync.insertData("jf_users", dataToInsert, jf_users_columns); } - - const toDeleteIds = existingIds.filter((id) =>!data.some((row) => row.Id === id )); + + const toDeleteIds = existingIds.filter( + (id) => !data.some((row) => row.Id === id), + ); if (toDeleteIds.length > 0) { - await _sync.removeData("jf_users",toDeleteIds); + await _sync.removeData("jf_users", toDeleteIds); } //update usernames on log table where username does not match the user table - await db.query('UPDATE jf_playback_activity a SET "UserName" = u."Name" FROM jf_users u WHERE u."Id" = a."UserId" AND u."Name" <> a."UserName"'); - + await db.query( + 'UPDATE jf_playback_activity a SET "UserName" = u."Name" FROM jf_users u WHERE u."Id" = a."UserId" AND u."Name" <> a."UserName"', + ); } -async function syncLibraryFolders(data) -{ - const _sync = new sync(); - const existingIds = await _sync.getExistingIDsforTable('jf_libraries');// get existing library Ids from the db +async function syncLibraryFolders(data) { + const _sync = new sync(); + const existingIds = await _sync.getExistingIDsforTable("jf_libraries"); // get existing library Ids from the db + let dataToInsert = await data.map(jf_libraries_mapping); - let dataToInsert = await data.map(jf_libraries_mapping); + if (dataToInsert.length !== 0) { + await _sync.insertData("jf_libraries", dataToInsert, jf_libraries_columns); + } - if (dataToInsert.length !== 0) { - await _sync.insertData("jf_libraries",dataToInsert,jf_libraries_columns); + //----------------------DELETE FUNCTION + //GET EPISODES IN SEASONS + //GET SEASONS IN SHOWS + //GET SHOWS IN LIBRARY + //FINALY DELETE LIBRARY + const toDeleteIds = existingIds.filter( + (id) => !data.some((row) => row.Id === id), + ); + if (toDeleteIds.length > 0) { + const ItemsToDelete = await db + .query( + `SELECT "Id" FROM jf_library_items where "ParentId" in (${toDeleteIds + .map((id) => `'${id}'`) + .join(",")})`, + ) + .then((res) => res.rows.map((row) => row.Id)); + if (ItemsToDelete.length > 0) { + await _sync.removeData("jf_library_items", ItemsToDelete); } - -//----------------------DELETE FUNCTION - //GET EPISODES IN SEASONS - //GET SEASONS IN SHOWS - //GET SHOWS IN LIBRARY - //FINALY DELETE LIBRARY - const toDeleteIds = existingIds.filter((id) =>!data.some((row) => row.Id === id )); - if (toDeleteIds.length > 0) { - const ItemsToDelete=await db.query(`SELECT "Id" FROM jf_library_items where "ParentId" in (${toDeleteIds.map(id => `'${id}'`).join(',')})`).then((res) => res.rows.map((row) => row.Id)); - if (ItemsToDelete.length > 0) { - await _sync.removeData("jf_library_items",ItemsToDelete); - } - - await _sync.removeData("jf_libraries",toDeleteIds); - - } - + await _sync.removeData("jf_libraries", toDeleteIds); + } } -async function syncLibraryItems(data) -{ +async function syncLibraryItems(data) { const _sync = new sync(); - const existingLibraryIds = await _sync.getExistingIDsforTable('jf_libraries');// get existing library Ids from the db + const existingLibraryIds = await _sync.getExistingIDsforTable("jf_libraries"); // get existing library Ids from the db syncTask.loggedData.push({ color: "lawngreen", Message: "Syncing... 1/4" }); - syncTask.loggedData.push({color: "yellow",Message: "Beginning Library Item Sync",}); + syncTask.loggedData.push({ + color: "yellow", + Message: "Beginning Library Item Sync", + }); - data=data.filter((row) => existingLibraryIds.includes(row.ParentId)); + data = data.filter((row) => existingLibraryIds.includes(row.ParentId)); - const existingIds = await _sync.getExistingIDsforTable('jf_library_items'); + const existingIds = await _sync.getExistingIDsforTable("jf_library_items"); let dataToInsert = []; //filter fix if jf_libraries is empty dataToInsert = await data.map(jf_library_items_mapping); - dataToInsert=dataToInsert.filter((item)=>item.Id !== undefined); + dataToInsert = dataToInsert.filter((item) => item.Id !== undefined); if (dataToInsert.length > 0) { - await _sync.insertData("jf_library_items",dataToInsert,jf_library_items_columns); + await _sync.insertData( + "jf_library_items", + dataToInsert, + jf_library_items_columns, + ); } - const toDeleteIds = existingIds.filter((id) =>!data.some((row) => row.Id === id )); + const toDeleteIds = existingIds.filter( + (id) => !data.some((row) => row.Id === id), + ); if (toDeleteIds.length > 0) { - await _sync.removeData("jf_library_items",toDeleteIds); - } + await _sync.removeData("jf_library_items", toDeleteIds); + } - syncTask.loggedData.push({color: "dodgerblue",Message: `${dataToInsert.length-existingIds.length >0 ? dataToInsert.length-existingIds.length : 0} Rows Inserted. ${existingIds.length} Rows Updated.`,}); - syncTask.loggedData.push({color: "orange",Message: toDeleteIds.length + " Library Items Removed.",}); + syncTask.loggedData.push({ + color: "dodgerblue", + Message: `${ + dataToInsert.length - existingIds.length > 0 + ? dataToInsert.length - existingIds.length + : 0 + } Rows Inserted. ${existingIds.length} Rows Updated.`, + }); + syncTask.loggedData.push({ + color: "orange", + Message: toDeleteIds.length + " Library Items Removed.", + }); syncTask.loggedData.push({ color: "yellow", Message: "Item Sync Complete" }); } -async function syncShowItems(data) -{ - +async function syncShowItems(data) { syncTask.loggedData.push({ color: "lawngreen", Message: "Syncing... 2/4" }); - syncTask.loggedData.push({color: "yellow", Message: "Beginning Seasons and Episode sync",}); + syncTask.loggedData.push({ + color: "yellow", + Message: "Beginning Seasons and Episode sync", + }); - const { rows: shows } = await db.query(`SELECT * FROM public.jf_library_items where "Type"='Series'`); + const { rows: shows } = await db.query( + `SELECT * FROM public.jf_library_items where "Type"='Series'`, + ); let insertSeasonsCount = 0; let insertEpisodeCount = 0; let updateSeasonsCount = 0; let updateEpisodeCount = 0; - let deleteSeasonsCount = 0; let deleteEpisodeCount = 0; //loop for each show for (const show of shows) { - const allSeasons = data.filter((item) => item.Type==='Season' && item.SeriesId===show.Id); - const allEpisodes =data.filter((item) => item.Type==='Episode' && item.SeriesId===show.Id); - - const existingIdsSeasons = await db.query(`SELECT * FROM public.jf_library_seasons where "SeriesId" = '${show.Id}'`).then((res) => res.rows.map((row) => row.Id)); + const allSeasons = data.filter( + (item) => item.Type === "Season" && item.SeriesId === show.Id, + ); + const allEpisodes = data.filter( + (item) => item.Type === "Episode" && item.SeriesId === show.Id, + ); + + const existingIdsSeasons = await db + .query( + `SELECT * FROM public.jf_library_seasons where "SeriesId" = '${show.Id}'`, + ) + .then((res) => res.rows.map((row) => row.Id)); let existingIdsEpisodes = []; if (existingIdsSeasons.length > 0) { existingIdsEpisodes = await db - .query(`SELECT * FROM public.jf_library_episodes WHERE "SeasonId" IN (${existingIdsSeasons + .query( + `SELECT * FROM public.jf_library_episodes WHERE "SeasonId" IN (${existingIdsSeasons .filter((seasons) => seasons !== "") .map((seasons) => pgp.as.value(seasons)) .map((value) => "'" + value + "'") - .join(", ")})` + .join(", ")})`, ) .then((res) => res.rows.map((row) => row.EpisodeId)); } - - let seasonsToInsert = []; let episodesToInsert = []; @@ -394,215 +435,310 @@ async function syncShowItems(data) //Bulkinsert new data not on db if (seasonsToInsert.length !== 0) { - let result = await db.insertBulk("jf_library_seasons",seasonsToInsert,jf_library_seasons_columns); + let result = await db.insertBulk( + "jf_library_seasons", + seasonsToInsert, + jf_library_seasons_columns, + ); if (result.Result === "SUCCESS") { - insertSeasonsCount+=seasonsToInsert.length-existingIdsSeasons.length; - updateSeasonsCount+=existingIdsSeasons.length; - } else { + insertSeasonsCount += + seasonsToInsert.length - existingIdsSeasons.length; + updateSeasonsCount += existingIdsSeasons.length; + } else { syncTask.loggedData.push({ color: "red", Message: "Error performing bulk insert:" + result.message, }); - logging.updateLog(syncTask.uuid,syncTask.loggedData,taskstate.FAILED); + logging.updateLog(syncTask.uuid, syncTask.loggedData, taskstate.FAILED); } - } - const toDeleteIds = existingIdsSeasons.filter((id) =>!allSeasons.some((row) => row.Id === id )); + } + const toDeleteIds = existingIdsSeasons.filter( + (id) => !allSeasons.some((row) => row.Id === id), + ); //Bulk delete from db thats no longer on api if (toDeleteIds.length > 0) { - let result = await db.deleteBulk("jf_library_seasons",toDeleteIds); + let result = await db.deleteBulk("jf_library_seasons", toDeleteIds); if (result.Result === "SUCCESS") { - deleteSeasonsCount +=toDeleteIds.length; + deleteSeasonsCount += toDeleteIds.length; } else { - syncTask.loggedData.push({color: "red",Message: "Error: "+result.message,}); - logging.updateLog(syncTask.uuid,syncTask.loggedData,taskstate.FAILED); + syncTask.loggedData.push({ + color: "red", + Message: "Error: " + result.message, + }); + logging.updateLog(syncTask.uuid, syncTask.loggedData, taskstate.FAILED); } - - } + } //insert delete episodes //Bulkinsert new data not on db if (episodesToInsert.length !== 0) { - let result = await db.insertBulk("jf_library_episodes",episodesToInsert,jf_library_episodes_columns); + let result = await db.insertBulk( + "jf_library_episodes", + episodesToInsert, + jf_library_episodes_columns, + ); if (result.Result === "SUCCESS") { - insertEpisodeCount+=episodesToInsert.length-existingIdsEpisodes.length; - updateEpisodeCount+=existingIdsEpisodes.length; + insertEpisodeCount += + episodesToInsert.length - existingIdsEpisodes.length; + updateEpisodeCount += existingIdsEpisodes.length; } else { syncTask.loggedData.push({ color: "red", Message: "Error performing bulk insert:" + result.message, }); - logging.updateLog(syncTask.uuid,syncTask.loggedData,taskstate.FAILED); + logging.updateLog(syncTask.uuid, syncTask.loggedData, taskstate.FAILED); } - } + } - const toDeleteEpisodeIds = existingIdsEpisodes.filter((id) =>!allEpisodes.some((row) => row.Id=== id )); + const toDeleteEpisodeIds = existingIdsEpisodes.filter( + (id) => !allEpisodes.some((row) => row.Id === id), + ); //Bulk delete from db thats no longer on api if (toDeleteEpisodeIds.length > 0) { - let result = await db.deleteBulk("jf_library_episodes",toDeleteEpisodeIds); + let result = await db.deleteBulk( + "jf_library_episodes", + toDeleteEpisodeIds, + ); if (result.Result === "SUCCESS") { - deleteEpisodeCount +=toDeleteEpisodeIds.length; + deleteEpisodeCount += toDeleteEpisodeIds.length; } else { - syncTask.loggedData.push({color: "red",Message: "Error: "+result.message,}); - logging.updateLog(syncTask.uuid,syncTask.loggedData,taskstate.FAILED); + syncTask.loggedData.push({ + color: "red", + Message: "Error: " + result.message, + }); + logging.updateLog(syncTask.uuid, syncTask.loggedData, taskstate.FAILED); } - - } - - + } } - syncTask.loggedData.push({color: "dodgerblue",Message: `Seasons: ${insertSeasonsCount > 0 ? insertSeasonsCount : 0} Rows Inserted. ${updateSeasonsCount} Rows Updated.`}); - syncTask.loggedData.push({color: "orange",Message: deleteSeasonsCount + " Seasons Removed.",}); - syncTask.loggedData.push({color: "dodgerblue",Message: `Episodes: ${insertEpisodeCount > 0 ? insertEpisodeCount : 0} Rows Inserted. ${updateEpisodeCount} Rows Updated.`}); - syncTask.loggedData.push({color: "orange",Message: deleteEpisodeCount + " Episodes Removed.",}); + syncTask.loggedData.push({ + color: "dodgerblue", + Message: `Seasons: ${ + insertSeasonsCount > 0 ? insertSeasonsCount : 0 + } Rows Inserted. ${updateSeasonsCount} Rows Updated.`, + }); + syncTask.loggedData.push({ + color: "orange", + Message: deleteSeasonsCount + " Seasons Removed.", + }); + syncTask.loggedData.push({ + color: "dodgerblue", + Message: `Episodes: ${ + insertEpisodeCount > 0 ? insertEpisodeCount : 0 + } Rows Inserted. ${updateEpisodeCount} Rows Updated.`, + }); + syncTask.loggedData.push({ + color: "orange", + Message: deleteEpisodeCount + " Episodes Removed.", + }); syncTask.loggedData.push({ color: "yellow", Message: "Sync Complete" }); } -async function syncItemInfo() -{ +async function syncItemInfo() { syncTask.loggedData.push({ color: "lawngreen", Message: "Syncing... 3/4" }); - syncTask.loggedData.push({color: "yellow", Message: "Beginning File Info Sync",}); + syncTask.loggedData.push({ + color: "yellow", + Message: "Beginning File Info Sync", + }); - const { rows: config } = await db.query('SELECT * FROM app_config where "ID"=1'); + const { rows: config } = await db.query( + 'SELECT * FROM app_config where "ID"=1', + ); const _sync = new sync(config[0].JF_HOST, config[0].JF_API_KEY); - const { rows: Items } = await db.query(`SELECT * FROM public.jf_library_items where "Type" not in ('Series','Folder')`); - const { rows: Episodes } = await db.query(`SELECT * FROM public.jf_library_episodes`); + const { rows: Items } = await db.query( + `SELECT * FROM public.jf_library_items where "Type" not in ('Series','Folder')`, + ); + const { rows: Episodes } = await db.query( + `SELECT * FROM public.jf_library_episodes`, + ); let insertItemInfoCount = 0; let insertEpisodeInfoCount = 0; let updateItemInfoCount = 0; let updateEpisodeInfoCount = 0; - let deleteItemInfoCount = 0; + let deleteItemInfoCount = 0; let deleteEpisodeInfoCount = 0; const admins = await _sync.getAdminUser(); - if(admins.length===0) - { + if (admins.length === 0) { syncTask.loggedData.push({ color: "red", Message: "Error fetching Admin ID (syncItemInfo)", }); - logging.updateLog(syncTask.uuid,syncTask.loggedData,taskstate.FAILED); - throw new Error('Error fetching Admin ID (syncItemInfo)'); + logging.updateLog(syncTask.uuid, syncTask.loggedData, taskstate.FAILED); + throw new Error("Error fetching Admin ID (syncItemInfo)"); } const userid = admins[0].Id; - let current_item=0; - let all_items=Items.length; + let current_item = 0; + let all_items = Items.length; //loop for each Movie for (const Item of Items) { current_item++; - const data = await _sync.getItemInfo(Item.Id,userid); + const data = await _sync.getItemInfo(Item.Id, userid); - const existingItemInfo = await db.query(`SELECT * FROM public.jf_item_info where "Id" = '${Item.Id}'`).then((res) => res.rows.map((row) => row.Id)); - - let ItemInfoToInsert = await data.map(item => jf_item_info_mapping(item, 'Item')); + const existingItemInfo = await db + .query(`SELECT * FROM public.jf_item_info where "Id" = '${Item.Id}'`) + .then((res) => res.rows.map((row) => row.Id)); + let ItemInfoToInsert = await data.map((item) => + jf_item_info_mapping(item, "Item"), + ); if (ItemInfoToInsert.length !== 0) { - let result = await db.insertBulk("jf_item_info",ItemInfoToInsert,jf_item_info_columns); + let result = await db.insertBulk( + "jf_item_info", + ItemInfoToInsert, + jf_item_info_columns, + ); if (result.Result === "SUCCESS") { - insertItemInfoCount +=ItemInfoToInsert.length- existingItemInfo.length; - updateItemInfoCount+=existingItemInfo.length; - + insertItemInfoCount += + ItemInfoToInsert.length - existingItemInfo.length; + updateItemInfoCount += existingItemInfo.length; } else { syncTask.loggedData.push({ color: "red", Message: "Error performing bulk insert:" + result.message, }); - logging.updateLog(syncTask.uuid,syncTask.loggedData,taskstate.FAILED); + logging.updateLog(syncTask.uuid, syncTask.loggedData, taskstate.FAILED); } - } - const toDeleteItemInfoIds = existingItemInfo.filter((id) =>!data.some((row) => row.Id === id )); + } + const toDeleteItemInfoIds = existingItemInfo.filter( + (id) => !data.some((row) => row.Id === id), + ); //Bulk delete from db thats no longer on api if (toDeleteItemInfoIds.length > 0) { - let result = await db.deleteBulk("jf_item_info",toDeleteItemInfoIds); + let result = await db.deleteBulk("jf_item_info", toDeleteItemInfoIds); if (result.Result === "SUCCESS") { - deleteItemInfoCount +=toDeleteItemInfoIds.length; + deleteItemInfoCount += toDeleteItemInfoIds.length; } else { - syncTask.loggedData.push({color: "red",Message: "Error: "+result.message,}); - logging.updateLog(syncTask.uuid,syncTask.loggedData,taskstate.FAILED); + syncTask.loggedData.push({ + color: "red", + Message: "Error: " + result.message, + }); + logging.updateLog(syncTask.uuid, syncTask.loggedData, taskstate.FAILED); } - - } + } } - let current_episode=0; - let all_episodes=Episodes.length; - //loop for each Episode - for (const Episode of Episodes) { + let current_episode = 0; + let all_episodes = Episodes.length; + //loop for each Episode + for (const Episode of Episodes) { current_episode++; - const data = await _sync.getItemInfo(Episode.EpisodeId,userid); - + const data = await _sync.getItemInfo(Episode.EpisodeId, userid); - const existingEpisodeItemInfo = await db.query(`SELECT * FROM public.jf_item_info where "Id" = '${Episode.EpisodeId}'`).then((res) => res.rows.map((row) => row.Id)); + const existingEpisodeItemInfo = await db + .query( + `SELECT * FROM public.jf_item_info where "Id" = '${Episode.EpisodeId}'`, + ) + .then((res) => res.rows.map((row) => row.Id)); - - let EpisodeInfoToInsert = await data.map(item => jf_item_info_mapping(item, 'Episode')); + let EpisodeInfoToInsert = await data.map((item) => + jf_item_info_mapping(item, "Episode"), + ); //filter fix if jf_libraries is empty - if (EpisodeInfoToInsert.length !== 0) { - let result = await db.insertBulk("jf_item_info",EpisodeInfoToInsert,jf_item_info_columns); + let result = await db.insertBulk( + "jf_item_info", + EpisodeInfoToInsert, + jf_item_info_columns, + ); if (result.Result === "SUCCESS") { - insertEpisodeInfoCount += EpisodeInfoToInsert.length-existingEpisodeItemInfo.length; - updateEpisodeInfoCount+= existingEpisodeItemInfo.length; + insertEpisodeInfoCount += + EpisodeInfoToInsert.length - existingEpisodeItemInfo.length; + updateEpisodeInfoCount += existingEpisodeItemInfo.length; } else { syncTask.loggedData.push({ color: "red", Message: "Error performing bulk insert:" + result.message, }); - logging.updateLog(syncTask.uuid,syncTask.loggedData,taskstate.FAILED); + logging.updateLog(syncTask.uuid, syncTask.loggedData, taskstate.FAILED); } - } - const toDeleteEpisodeInfoIds = existingEpisodeItemInfo.filter((id) =>!data.some((row) => row.Id === id )); + } + const toDeleteEpisodeInfoIds = existingEpisodeItemInfo.filter( + (id) => !data.some((row) => row.Id === id), + ); //Bulk delete from db thats no longer on api if (toDeleteEpisodeInfoIds.length > 0) { - let result = await db.deleteBulk("jf_item_info",toDeleteEpisodeInfoIds); + let result = await db.deleteBulk("jf_item_info", toDeleteEpisodeInfoIds); if (result.Result === "SUCCESS") { - deleteEpisodeInfoCount +=toDeleteEpisodeInfoIds.length; + deleteEpisodeInfoCount += toDeleteEpisodeInfoIds.length; } else { - syncTask.loggedData.push({color: "red",Message: "Error: "+result.message,}); - logging.updateLog(syncTask.uuid,syncTask.loggedData,taskstate.FAILED); + syncTask.loggedData.push({ + color: "red", + Message: "Error: " + result.message, + }); + logging.updateLog(syncTask.uuid, syncTask.loggedData, taskstate.FAILED); } - } - // console.log(Episode.Name) + // console.log(Episode.Name) } - syncTask.loggedData.push({color: "dodgerblue",Message: (insertItemInfoCount >0 ? insertItemInfoCount : 0) + " Item Info inserted. "+updateItemInfoCount +" Item Info Updated"}); - syncTask.loggedData.push({color: "orange",Message: deleteItemInfoCount + " Item Info Removed.",}); - syncTask.loggedData.push({color: "dodgerblue",Message: (insertEpisodeInfoCount > 0 ? insertEpisodeInfoCount:0) + " Episodes Info inserted. "+updateEpisodeInfoCount +" Episodes Info Updated"}); - syncTask.loggedData.push({color: "orange",Message: deleteEpisodeInfoCount + " Episodes Info Removed.",}); + syncTask.loggedData.push({ + color: "dodgerblue", + Message: + (insertItemInfoCount > 0 ? insertItemInfoCount : 0) + + " Item Info inserted. " + + updateItemInfoCount + + " Item Info Updated", + }); + syncTask.loggedData.push({ + color: "orange", + Message: deleteItemInfoCount + " Item Info Removed.", + }); + syncTask.loggedData.push({ + color: "dodgerblue", + Message: + (insertEpisodeInfoCount > 0 ? insertEpisodeInfoCount : 0) + + " Episodes Info inserted. " + + updateEpisodeInfoCount + + " Episodes Info Updated", + }); + syncTask.loggedData.push({ + color: "orange", + Message: deleteEpisodeInfoCount + " Episodes Info Removed.", + }); syncTask.loggedData.push({ color: "yellow", Message: "Info Sync Complete" }); } -async function removeOrphanedData() -{ +async function removeOrphanedData() { syncTask.loggedData.push({ color: "lawngreen", Message: "Syncing... 4/4" }); - syncTask.loggedData.push({color: "yellow", Message: "Removing Orphaned FileInfo/Episode/Season Records",}); + syncTask.loggedData.push({ + color: "yellow", + Message: "Removing Orphaned FileInfo/Episode/Season Records", + }); - await db.query('CALL jd_remove_orphaned_data()'); + await db.query("CALL jd_remove_orphaned_data()"); - syncTask.loggedData.push({color: "dodgerblue",Message: "Orphaned FileInfo/Episode/Season Removed.",}); + syncTask.loggedData.push({ + color: "dodgerblue", + Message: "Orphaned FileInfo/Episode/Season Removed.", + }); syncTask.loggedData.push({ color: "Yellow", Message: "Sync Complete" }); - } -async function syncPlaybackPluginData() -{ - PlaybacksyncTask.loggedData.push({ color: "lawngreen", Message: "Syncing..." }); +async function syncPlaybackPluginData() { + PlaybacksyncTask.loggedData.push({ + color: "lawngreen", + Message: "Syncing...", + }); const { rows: config } = await db.query( - 'SELECT * FROM app_config where "ID"=1' + 'SELECT * FROM app_config where "ID"=1', ); - - if(config.length===0) - { - PlaybacksyncTask.loggedData.push({ Message: "Error: Config details not found!" }); - logging.updateLog(PlaybacksyncTask.uuid,PlaybacksyncTask.loggedData,taskstate.FAILED); + if (config.length === 0) { + PlaybacksyncTask.loggedData.push({ + Message: "Error: Config details not found!", + }); + logging.updateLog( + PlaybacksyncTask.uuid, + PlaybacksyncTask.loggedData, + taskstate.FAILED, + ); return; } @@ -610,168 +746,218 @@ async function syncPlaybackPluginData() const apiKey = config[0]?.JF_API_KEY; if (base_url === null || apiKey === null) { - PlaybacksyncTask.loggedData.push({ Message: "Error: Config details not found!" }); - logging.updateLog(PlaybacksyncTask.uuid,PlaybacksyncTask.loggedData,taskstate.FAILED); + PlaybacksyncTask.loggedData.push({ + Message: "Error: Config details not found!", + }); + logging.updateLog( + PlaybacksyncTask.uuid, + PlaybacksyncTask.loggedData, + taskstate.FAILED, + ); return; } //Playback Reporting Plugin Check const pluginURL = `${base_url}/plugins`; - const pluginResponse = await axios_instance.get(pluginURL, - { + const pluginResponse = await axios_instance.get(pluginURL, { headers: { "X-MediaBrowser-Token": apiKey, }, }); - - const hasPlaybackReportingPlugin=pluginResponse.data?.filter((plugins) => plugins?.ConfigurationFileName==='Jellyfin.Plugin.PlaybackReporting.xml'); + const hasPlaybackReportingPlugin = pluginResponse.data?.filter( + (plugins) => + plugins?.ConfigurationFileName === + "Jellyfin.Plugin.PlaybackReporting.xml", + ); - if(!hasPlaybackReportingPlugin || hasPlaybackReportingPlugin.length===0) - { - PlaybacksyncTask.loggedData.push({color: "lawngreen", Message: "Playback Reporting Plugin not detected. Skipping step.",}); - logging.updateLog(PlaybacksyncTask.uuid,PlaybacksyncTask.loggedData,taskstate.FAILED); + if (!hasPlaybackReportingPlugin || hasPlaybackReportingPlugin.length === 0) { + PlaybacksyncTask.loggedData.push({ + color: "lawngreen", + Message: "Playback Reporting Plugin not detected. Skipping step.", + }); + logging.updateLog( + PlaybacksyncTask.uuid, + PlaybacksyncTask.loggedData, + taskstate.FAILED, + ); return; } // - PlaybacksyncTask.loggedData.push({color: "dodgerblue", Message: "Determining query constraints.",}); + PlaybacksyncTask.loggedData.push({ + color: "dodgerblue", + Message: "Determining query constraints.", + }); const OldestPlaybackActivity = await db - .query('SELECT MIN("ActivityDateInserted") "OldestPlaybackActivity" FROM public.jf_playback_activity') - .then((res) => res.rows[0]?.OldestPlaybackActivity); + .query( + 'SELECT MIN("ActivityDateInserted") "OldestPlaybackActivity" FROM public.jf_playback_activity', + ) + .then((res) => res.rows[0]?.OldestPlaybackActivity); const MaxPlaybackReportingPluginID = await db - .query('SELECT MAX(rowid) "MaxRowId" FROM jf_playback_reporting_plugin_data') - .then((res) => res.rows[0]?.MaxRowId); - + .query( + 'SELECT MAX(rowid) "MaxRowId" FROM jf_playback_reporting_plugin_data', + ) + .then((res) => res.rows[0]?.MaxRowId); //Query Builder - let query=`SELECT rowid, * FROM PlaybackActivity`; + let query = `SELECT rowid, * FROM PlaybackActivity`; - if(OldestPlaybackActivity) - { - const formattedDateTime = moment(OldestPlaybackActivity).format('YYYY-MM-DD HH:mm:ss'); + if (OldestPlaybackActivity) { + const formattedDateTime = moment(OldestPlaybackActivity).format( + "YYYY-MM-DD HH:mm:ss", + ); - query=query+` WHERE DateCreated < '${formattedDateTime}'`; - - if(MaxPlaybackReportingPluginID) - { - - query=query+` AND rowid > ${MaxPlaybackReportingPluginID}`; + query = query + ` WHERE DateCreated < '${formattedDateTime}'`; + if (MaxPlaybackReportingPluginID) { + query = query + ` AND rowid > ${MaxPlaybackReportingPluginID}`; } - - }else if(MaxPlaybackReportingPluginID) - { - query=query+` WHERE rowid > ${MaxPlaybackReportingPluginID}`; + } else if (MaxPlaybackReportingPluginID) { + query = query + ` WHERE rowid > ${MaxPlaybackReportingPluginID}`; } - query+=' order by rowid'; + query += " order by rowid"; - PlaybacksyncTask.loggedData.push({color: "dodgerblue", Message: "Query built. Executing.",}); + PlaybacksyncTask.loggedData.push({ + color: "dodgerblue", + Message: "Query built. Executing.", + }); // const url = `${base_url}/user_usage_stats/submit_custom_query`; - const response = await axios_instance.post(url, { - CustomQueryString: query, - }, { - headers: { - "X-MediaBrowser-Token": apiKey, + const response = await axios_instance.post( + url, + { + CustomQueryString: query, }, - }); + { + headers: { + "X-MediaBrowser-Token": apiKey, + }, + }, + ); - const PlaybackData=response.data.results; + const PlaybackData = response.data.results; let DataToInsert = await PlaybackData.map(mappingPlaybackReporting); - if (DataToInsert.length > 0) { - PlaybacksyncTask.loggedData.push({color: "dodgerblue", Message: `Inserting ${DataToInsert.length} Rows.`,}); - let result=await db.insertBulk("jf_playback_reporting_plugin_data",DataToInsert,columnsPlaybackReporting); + PlaybacksyncTask.loggedData.push({ + color: "dodgerblue", + Message: `Inserting ${DataToInsert.length} Rows.`, + }); + let result = await db.insertBulk( + "jf_playback_reporting_plugin_data", + DataToInsert, + columnsPlaybackReporting, + ); if (result.Result === "SUCCESS") { - PlaybacksyncTask.loggedData.push({color: "dodgerblue", Message: `${DataToInsert.length} Rows have been inserted.`,}); - PlaybacksyncTask.loggedData.push({ color: "yellow", Message: "Running process to format data to be inserted into the Activity Table" }); - await db.query('CALL ji_insert_playback_plugin_data_to_activity_table()'); - PlaybacksyncTask.loggedData.push({color: "dodgerblue",Message: "Process complete. Data has been inserted.",}); - + PlaybacksyncTask.loggedData.push({ + color: "dodgerblue", + Message: `${DataToInsert.length} Rows have been inserted.`, + }); + PlaybacksyncTask.loggedData.push({ + color: "yellow", + Message: + "Running process to format data to be inserted into the Activity Table", + }); + await db.query("CALL ji_insert_playback_plugin_data_to_activity_table()"); + PlaybacksyncTask.loggedData.push({ + color: "dodgerblue", + Message: "Process complete. Data has been inserted.", + }); } else { - - PlaybacksyncTask.loggedData.push({color: "red",Message: "Error: "+result.message,}); - logging.updateLog(PlaybacksyncTask.uuid,PlaybacksyncTask.loggedData,taskstate.FAILED); + PlaybacksyncTask.loggedData.push({ + color: "red", + Message: "Error: " + result.message, + }); + logging.updateLog( + PlaybacksyncTask.uuid, + PlaybacksyncTask.loggedData, + taskstate.FAILED, + ); } - - - }else - { - PlaybacksyncTask.loggedData.push({color: "dodgerblue", Message: `No new data to insert.`,}); - } - + } else { + PlaybacksyncTask.loggedData.push({ + color: "dodgerblue", + Message: `No new data to insert.`, + }); + } - PlaybacksyncTask.loggedData.push({color: "lawngreen", Message: `Playback Reporting Plugin Sync Complete`,}); - - + PlaybacksyncTask.loggedData.push({ + color: "lawngreen", + Message: `Playback Reporting Plugin Sync Complete`, + }); } -async function updateLibraryStatsData() -{ - syncTask.loggedData.push({color: "yellow", Message: "Updating Library Stats",}); - - await db.query('CALL ju_update_library_stats_data()'); +async function updateLibraryStatsData() { + syncTask.loggedData.push({ + color: "yellow", + Message: "Updating Library Stats", + }); - syncTask.loggedData.push({color: "dodgerblue",Message: "Library Stats Updated.",}); + await db.query("CALL ju_update_library_stats_data()"); + syncTask.loggedData.push({ + color: "dodgerblue", + Message: "Library Stats Updated.", + }); } - -async function fullSync(triggertype) -{ - +async function fullSync(triggertype) { const uuid = randomUUID(); - syncTask={loggedData:[],uuid:uuid}; - try - { - logging.insertLog(uuid,triggertype,taskName.sync); + syncTask = { loggedData: [], uuid: uuid }; + try { + logging.insertLog(uuid, triggertype, taskName.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" }); syncTask.loggedData.push({ Message: "Error: Config details not found!" }); - logging.updateLog(syncTask.uuid,syncTask.loggedData,taskstate.FAILED); + logging.updateLog(syncTask.uuid, syncTask.loggedData, taskstate.FAILED); return; } const _sync = new sync(rows[0].JF_HOST, rows[0].JF_API_KEY); - const libraries = await _sync.getLibrariesFromApi(); - if(libraries.length===0) - { - syncTask.loggedData.push({ Message: "Error: No Libararies found to sync." }); - logging.updateLog(syncTask.uuid,syncTask.loggedData,taskstate.FAILED); + const libraries = await _sync.getLibrariesFromApi(); + if (libraries.length === 0) { + syncTask.loggedData.push({ + Message: "Error: No Libararies found to sync.", + }); + logging.updateLog(syncTask.uuid, syncTask.loggedData, taskstate.FAILED); return; } - const excluded_libraries= rows[0].settings.ExcludedLibraries||[]; + const excluded_libraries = rows[0].settings.ExcludedLibraries || []; - const filtered_libraries=libraries.filter((library)=> !excluded_libraries.includes(library.Id)); + const filtered_libraries = libraries.filter( + (library) => !excluded_libraries.includes(library.Id), + ); - const data=[]; + const data = []; //for each item in library run get item using that id as the ParentId (This gets the children of the parent id) - for (let i = 0; i < filtered_libraries.length; i++) { - const item = filtered_libraries[i]; - let libraryItems = await _sync.getItems('parentId',item.Id); - const libraryItemsWithParent = libraryItems.map((items) => ({ - ...items, - ...{ ParentId: item.Id }, - })); - data.push(...libraryItemsWithParent); - - } - const library_items=data.filter((item) => ['Movie','Audio','Series'].includes(item.Type)); - const seasons_and_episodes=data.filter((item) => ['Season','Episode'].includes(item.Type)); + for (let i = 0; i < filtered_libraries.length; i++) { + const item = filtered_libraries[i]; + let libraryItems = await _sync.getItems("parentId", item.Id); + const libraryItemsWithParent = libraryItems.map((items) => ({ + ...items, + ...{ ParentId: item.Id }, + })); + data.push(...libraryItemsWithParent); + } + const library_items = data.filter((item) => + ["Movie", "Audio", "Series"].includes(item.Type), + ); + const seasons_and_episodes = data.filter((item) => + ["Season", "Episode"].includes(item.Type), + ); //syncUserData await syncUserData(); @@ -793,157 +979,164 @@ async function fullSync(triggertype) await updateLibraryStatsData(); - logging.updateLog(syncTask.uuid,syncTask.loggedData,taskstate.SUCCESS); - - - - }catch(error) - { - syncTask.loggedData.push({color: "red",Message: getErrorLineNumber(error)+ ": Error: "+error,}); - logging.updateLog(syncTask.uuid,syncTask.loggedData,taskstate.FAILED); + logging.updateLog(syncTask.uuid, syncTask.loggedData, taskstate.SUCCESS); + } catch (error) { + syncTask.loggedData.push({ + color: "red", + Message: getErrorLineNumber(error) + ": Error: " + error, + }); + logging.updateLog(syncTask.uuid, syncTask.loggedData, taskstate.FAILED); } - - } - ////////////////////////////////////////API Calls ///////////////////////////////////////Sync All router.get("/beingSync", async (req, res) => { - 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 last_execution=await db.query( `SELECT "Result" + const last_execution = await db + .query( + `SELECT "Result" FROM public.jf_logging WHERE "Name"='${taskName.sync}' ORDER BY "TimeRun" DESC - LIMIT 1`).then((res) => res.rows); + LIMIT 1`, + ) + .then((res) => res.rows); - if(last_execution.length!==0) - { - - if(last_execution[0].Result ===taskstate.RUNNING) - { - res.send(); - return; + if (last_execution.length !== 0) { + if (last_execution[0].Result === taskstate.RUNNING) { + res.send(); + return; } } - await fullSync(triggertype.Manual); res.send(); - }); ///////////////////////////////////////Write Users router.post("/fetchItem", async (req, res) => { - try{ + try { const { itemId } = req.body; - if(itemId===undefined) - { + if (itemId === undefined) { res.status(400); - res.send('The itemId field is required.'); + res.send("The itemId field is required."); } - const { rows:config } = await db.query('SELECT * FROM app_config where "ID"=1'); - const { rows:temp_lib_id } = await db.query('SELECT "Id" FROM jf_libraries limit 1'); + const { rows: config } = await db.query( + 'SELECT * FROM app_config where "ID"=1', + ); + const { rows: temp_lib_id } = await db.query( + 'SELECT "Id" FROM jf_libraries limit 1', + ); if (config[0].JF_HOST === null || config[0].JF_API_KEY === null) { res.status(503); res.send({ error: "Config Details Not Found" }); return; } - + const _sync = new sync(config[0].JF_HOST, config[0].JF_API_KEY); - - let userid=config[0].settings?.preferred_admin?.userid; - if(!userid) - { + let userid = config[0].settings?.preferred_admin?.userid; + + if (!userid) { const admins = await _sync.getAdminUser(); userid = admins[0].Id; } - let item=await _sync.getItem(itemId); + let item = await _sync.getItem(itemId); const libraryItemWithParent = item.map((items) => ({ ...items, ...{ ParentId: temp_lib_id[0].Id }, })); + let item_info = await _sync.getItemInfo(itemId, userid); - let item_info= await _sync.getItemInfo(itemId,userid); - - - let itemToInsert = await libraryItemWithParent.map(jf_library_items_mapping); + let itemToInsert = await libraryItemWithParent.map( + jf_library_items_mapping, + ); let itemInfoToInsert = await item_info.map(jf_item_info_mapping); if (itemToInsert.length !== 0) { - let result = await db.insertBulk("jf_library_items",itemToInsert,jf_library_items_columns); + let result = await db.insertBulk( + "jf_library_items", + itemToInsert, + jf_library_items_columns, + ); if (result.Result === "SUCCESS") { - let result_info = await db.insertBulk("jf_item_info",itemInfoToInsert,jf_item_info_columns); + let result_info = await db.insertBulk( + "jf_item_info", + itemInfoToInsert, + jf_item_info_columns, + ); if (result_info.Result === "SUCCESS") { - res.send('Item Synced'); + res.send("Item Synced"); } else { res.status(500); - res.send('Unable to insert Item Info: '+result_info.message); + res.send("Unable to insert Item Info: " + result_info.message); } } else { res.status(500); - res.send('Unable to insert Item: '+result.message); + res.send("Unable to insert Item: " + result.message); } - }else - { + } else { res.status(404); - res.send('Unable to find Item'); + res.send("Unable to find Item"); } - - }catch(error) - { + } catch (error) { // console.log(error); res.status(500); res.send(error); } - }); - - ////////////////////////////////////// //////////////////////////////////////////////////////syncPlaybackPluginData router.get("/syncPlaybackPluginData", async (req, res) => { const uuid = randomUUID(); - PlaybacksyncTask={loggedData:[],uuid:uuid}; - try - { - logging.insertLog(uuid,triggertype.Manual,taskName.import); - + PlaybacksyncTask = { loggedData: [], uuid: uuid }; + try { + logging.insertLog(uuid, triggertype.Manual, taskName.import); + 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" }); - PlaybacksyncTask.loggedData.push({ Message: "Error: Config details not found!" }); - logging.updateLog(uuid,PlaybacksyncTask.loggedData,taskstate.FAILED); + PlaybacksyncTask.loggedData.push({ + Message: "Error: Config details not found!", + }); + logging.updateLog(uuid, PlaybacksyncTask.loggedData, taskstate.FAILED); return; } - + await sleep(5000); await syncPlaybackPluginData(); - - logging.updateLog(PlaybacksyncTask.uuid,PlaybacksyncTask.loggedData,taskstate.SUCCESS); + + logging.updateLog( + PlaybacksyncTask.uuid, + PlaybacksyncTask.loggedData, + taskstate.SUCCESS, + ); res.send("syncPlaybackPluginData Complete"); - }catch(error) - { - PlaybacksyncTask.loggedData.push({color: "red",Message: getErrorLineNumber(error)+ ": Error: "+error,}); - logging.updateLog(PlaybacksyncTask.uuid,PlaybacksyncTask.loggedData,taskstate.FAILED); + } catch (error) { + PlaybacksyncTask.loggedData.push({ + color: "red", + Message: getErrorLineNumber(error) + ": Error: " + error, + }); + logging.updateLog( + PlaybacksyncTask.uuid, + PlaybacksyncTask.loggedData, + taskstate.FAILED, + ); res.send("syncPlaybackPluginData Halted with Errors"); } - - }); function sleep(ms) { @@ -954,7 +1147,4 @@ function sleep(ms) { ////////////////////////////////////// - - -module.exports = -{router,fullSync}; +module.exports = { router, fullSync }; diff --git a/backend/server.js b/backend/server.js index edcf530e..7a846cef 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,119 +1,106 @@ -const express = require('express'); -const cors = require('cors'); -const jwt = require('jsonwebtoken'); -const knex = require('knex'); -const createdb = require('./create_database'); -const knexConfig = require('./migrations'); - -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('./routes/logging'); +const express = require("express"); +const cors = require("cors"); +const jwt = require("jsonwebtoken"); +const knex = require("knex"); +const createdb = require("./create_database"); +const knexConfig = require("./migrations"); + +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("./routes/logging"); const dbInstance = require("./db"); const path = require("path"); -const http = require('http'); - - - +const http = require("http"); const app = express(); const db = knex(knexConfig.development); const PORT = process.env.PORT || 3003; -const LISTEN_IP = '127.0.0.1'; +const LISTEN_IP = "127.0.0.1"; const JWT_SECRET = process.env.JWT_SECRET; if (JWT_SECRET === undefined) { - console.log('JWT Secret cannot be undefined'); + console.log("JWT Secret cannot be undefined"); process.exit(1); // end the program with error status code } app.use(express.json()); // middleware to parse JSON request bodies - const server = http.createServer(app); -app.use(express.static(path.join(__dirname, 'static'))); +app.use(express.static(path.join(__dirname, "static"))); // JWT middleware -async function authenticate (req, res, next) { - const token = req.headers.authorization; - const apiKey = req.headers['x-api-token'] || req.query.apiKey; - +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.' }); + 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') - { + 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' }); + return res.status(401).json({ message: "Invalid token" }); } } else { - - - 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' }); + if (!keysjson || Object.keys(keysjson).length === 0) { + return res.status(404).json({ message: "No API keys configured" }); } - const keys= keysjson || []; + const keys = keysjson || []; - const keyExists = keys.some(obj => obj.key === apiKey); + const keyExists = keys.some((obj) => obj.key === apiKey); - if(keyExists) - { + if (keyExists) { next(); - }else - { - return res.status(403).json({ message: 'Invalid API key' }); + } else { + return res.status(403).json({ message: "Invalid API key" }); } - - } - - } } -app.use('/auth', authRouter); // mount the API router at /auth -app.use('/proxy', proxyRouter); // mount the API router at /proxy -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 /backup, with JWT middleware -app.use('/logs', authenticate , logRouter); // mount the API router at /logs, with JWT middleware +app.use("/auth", authRouter); // mount the API router at /auth +app.use("/proxy", proxyRouter); // mount the API router at /proxy +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 /backup, with JWT middleware +app.use("/logs", authenticate, logRouter); // mount the API router at /logs, with JWT middleware -try{ +try { createdb.createDatabase().then((result) => { if (result) { - console.log('Database created'); + console.log("Database created"); } - + db.migrate.latest().then(() => { server.listen(PORT, async () => { console.log(`Server listening on http://${LISTEN_IP}:${PORT}`); @@ -124,10 +111,6 @@ try{ }); }); }); - - -}catch(error) -{ - console.log('An error has occured on startup: '+error); +} catch (error) { + console.log("An error has occured on startup: " + error); } - diff --git a/backend/tasks/ActivityMonitor.js b/backend/tasks/ActivityMonitor.js index 1e52cb97..dbfb76e2 100644 --- a/backend/tasks/ActivityMonitor.js +++ b/backend/tasks/ActivityMonitor.js @@ -2,41 +2,43 @@ const db = require("../db"); const pgp = require("pg-promise")(); const axios = require("axios"); -const moment = require('moment'); -const { columnsPlayback, mappingPlayback } = require('../models/jf_playback_activity'); -const { jf_activity_watchdog_columns, jf_activity_watchdog_mapping } = require('../models/jf_activity_watchdog'); -const { randomUUID } = require('crypto'); -const https = require('https'); +const moment = require("moment"); +const { + columnsPlayback, + mappingPlayback, +} = require("../models/jf_playback_activity"); +const { + jf_activity_watchdog_columns, + jf_activity_watchdog_mapping, +} = require("../models/jf_activity_watchdog"); +const { randomUUID } = require("crypto"); +const https = require("https"); const agent = new https.Agent({ - rejectUnauthorized: (process.env.REJECT_SELF_SIGNED_CERTIFICATES || 'true').toLowerCase() ==='true' + rejectUnauthorized: + (process.env.REJECT_SELF_SIGNED_CERTIFICATES || "true").toLowerCase() === + "true", }); - - const axios_instance = axios.create({ - httpsAgent: agent + httpsAgent: agent, }); async function ActivityMonitor(interval) { console.log("Activity Interval: " + interval); - - setInterval(async () => { try { const { rows: config } = await db.query( - 'SELECT * FROM app_config where "ID"=1' + 'SELECT * FROM app_config where "ID"=1', ); - - - if(!config || config.length===0) - { + + if (!config || config.length === 0) { return; } const base_url = config[0].JF_HOST; const apiKey = config[0].JF_API_KEY; - + if (base_url === null || config[0].JF_API_KEY === null) { return; } @@ -47,10 +49,14 @@ async function ActivityMonitor(interval) { "X-MediaBrowser-Token": apiKey, }, }); - const SessionData=response.data.filter(row => row.NowPlayingItem !== undefined); + const SessionData = response.data.filter( + (row) => row.NowPlayingItem !== undefined, + ); /////get data from jf_activity_monitor - const WatchdogData=await db.query('SELECT * FROM jf_activity_watchdog').then((res) => res.rows); + const WatchdogData = await db + .query("SELECT * FROM jf_activity_watchdog") + .then((res) => res.rows); // //compare to sessiondata @@ -60,80 +66,88 @@ async function ActivityMonitor(interval) { if (WatchdogData.length === 0) { // if there are no existing Ids in the table, map all items in the data array to the expected format - WatchdogDataToInsert = await SessionData.map(jf_activity_watchdog_mapping); + WatchdogDataToInsert = await SessionData.map( + jf_activity_watchdog_mapping, + ); } else { // otherwise, filter only new data to insert - WatchdogDataToInsert = await SessionData.filter((session) => !WatchdogData.map((wdData) => wdData.Id).includes(session.Id)) - .map(jf_activity_watchdog_mapping); - - WatchdogDataToUpdate = WatchdogData.filter((wdData) => { - const session = SessionData.find((sessionData) => sessionData.Id === wdData.Id); - if (session && session.PlayState) { - if (wdData.IsPaused !== session.PlayState.IsPaused) { - wdData.IsPaused = session.PlayState.IsPaused; - return true; - } + WatchdogDataToInsert = await SessionData.filter( + (session) => + !WatchdogData.map((wdData) => wdData.Id).includes(session.Id), + ).map(jf_activity_watchdog_mapping); + + WatchdogDataToUpdate = WatchdogData.filter((wdData) => { + const session = SessionData.find( + (sessionData) => sessionData.Id === wdData.Id, + ); + if (session && session.PlayState) { + if (wdData.IsPaused !== session.PlayState.IsPaused) { + wdData.IsPaused = session.PlayState.IsPaused; + return true; } - return false; - }); - - + } + return false; + }); } - - if (WatchdogDataToInsert.length !== 0) { - db.insertBulk("jf_activity_watchdog",WatchdogDataToInsert,jf_activity_watchdog_columns); + db.insertBulk( + "jf_activity_watchdog", + WatchdogDataToInsert, + jf_activity_watchdog_columns, + ); } - //update wd state - if(WatchdogDataToUpdate.length>0) - { - - - const WatchdogDataUpdated = WatchdogDataToUpdate.map(obj => { - - - - - let startTime = moment(obj.ActivityDateInserted, 'YYYY-MM-DD HH:mm:ss.SSSZ'); + if (WatchdogDataToUpdate.length > 0) { + const WatchdogDataUpdated = WatchdogDataToUpdate.map((obj) => { + let startTime = moment( + obj.ActivityDateInserted, + "YYYY-MM-DD HH:mm:ss.SSSZ", + ); let endTime = moment(); - let diffInSeconds = endTime.diff(startTime, 'seconds'); + let diffInSeconds = endTime.diff(startTime, "seconds"); - if(obj.IsPaused) { - obj.PlaybackDuration =parseInt(obj.PlaybackDuration)+ diffInSeconds; + if (obj.IsPaused) { + obj.PlaybackDuration = + parseInt(obj.PlaybackDuration) + diffInSeconds; } - obj.ActivityDateInserted = `'${endTime.format('YYYY-MM-DD HH:mm:ss.SSSZ')}'::timestamptz`; - - const {...rest } = obj; + obj.ActivityDateInserted = `'${endTime.format( + "YYYY-MM-DD HH:mm:ss.SSSZ", + )}'::timestamptz`; + + const { ...rest } = obj; return { ...rest }; }); - - await (async () => { try { await db.query("BEGIN"); const cs = new pgp.helpers.ColumnSet([ - '?Id', - 'IsPaused', - { name: 'PlaybackDuration', mod: ':raw' }, - { name: 'ActivityDateInserted', mod: ':raw' }, + "?Id", + "IsPaused", + { name: "PlaybackDuration", mod: ":raw" }, + { name: "ActivityDateInserted", mod: ":raw" }, ]); - - const updateQuery = pgp.helpers.update(WatchdogDataUpdated, cs,'jf_activity_watchdog' ) + ' WHERE v."Id" = t."Id"'; - await db.query(updateQuery) - .then(result => { + + const updateQuery = + pgp.helpers.update( + WatchdogDataUpdated, + cs, + "jf_activity_watchdog", + ) + ' WHERE v."Id" = t."Id"'; + await db + .query(updateQuery) + .then((result) => { // console.log('Update successful', result.rowCount, 'rows updated'); }) - .catch(error => { - console.error('Error updating rows', error); + .catch((error) => { + console.error("Error updating rows", error); }); - + await db.query("COMMIT"); } catch (error) { await db.query("ROLLBACK"); @@ -145,64 +159,59 @@ async function ActivityMonitor(interval) { //delete from db no longer in session data and insert into stats db (still to make) //Bulk delete from db thats no longer on api - const toDeleteIds = WatchdogData.filter((id) =>!SessionData.some((row) => row.Id === id.Id)).map((row) => row.Id); + const toDeleteIds = WatchdogData.filter( + (id) => !SessionData.some((row) => row.Id === id.Id), + ).map((row) => row.Id); + const playbackData = WatchdogData.filter( + (id) => !SessionData.some((row) => row.Id === id.Id), + ); - const playbackData = WatchdogData.filter((id) => !SessionData.some((row) => row.Id === id.Id)); + const playbackToInsert = playbackData.map((obj) => { + const uuid = randomUUID(); - - const playbackToInsert = playbackData.map(obj => { - const uuid = randomUUID() + obj.Id = uuid; - obj.Id=uuid; - - let startTime = moment(obj.ActivityDateInserted, 'YYYY-MM-DD HH:mm:ss.SSSZ'); + let startTime = moment( + obj.ActivityDateInserted, + "YYYY-MM-DD HH:mm:ss.SSSZ", + ); let endTime = moment(); - - let diffInSeconds = endTime.diff(startTime, 'seconds'); - + let diffInSeconds = endTime.diff(startTime, "seconds"); - if(!obj.IsPaused) { - obj.PlaybackDuration =parseInt(obj.PlaybackDuration)+ diffInSeconds; + if (!obj.IsPaused) { + obj.PlaybackDuration = parseInt(obj.PlaybackDuration) + diffInSeconds; } - obj.ActivityDateInserted =endTime.format('YYYY-MM-DD HH:mm:ss.SSSZ'); - const {...rest } = obj; + obj.ActivityDateInserted = endTime.format("YYYY-MM-DD HH:mm:ss.SSSZ"); + const { ...rest } = obj; return { ...rest }; }); - - - if(toDeleteIds.length>0) - { - let result=await db.deleteBulk('jf_activity_watchdog',toDeleteIds) + if (toDeleteIds.length > 0) { + let result = await db.deleteBulk("jf_activity_watchdog", toDeleteIds); // console.log(result); } - if(playbackToInsert.length>0) - { - let result=await db.insertBulk('jf_playback_activity',playbackToInsert,columnsPlayback); + if (playbackToInsert.length > 0) { + let result = await db.insertBulk( + "jf_playback_activity", + playbackToInsert, + columnsPlayback, + ); // console.log(result); } - /////////////////////////// - - - } catch (error) { - - if(error?.code==='ECONNREFUSED') - { - console.error('Error: Unable to connect to Jellyfin'); - }else if(error?.code==='ERR_BAD_RESPONSE') - { + if (error?.code === "ECONNREFUSED") { + console.error("Error: Unable to connect to Jellyfin"); + } else if (error?.code === "ERR_BAD_RESPONSE") { console.warn(error.response?.data); - }else - { + } else { console.error(error); - } + } return []; } }, interval); diff --git a/backend/tasks/BackupTask.js b/backend/tasks/BackupTask.js index 437e3d9a..c2280d2c 100644 --- a/backend/tasks/BackupTask.js +++ b/backend/tasks/BackupTask.js @@ -2,126 +2,118 @@ const db = require("../db"); const Logging = require("../routes/logging"); const backup = require("../routes/backup"); -const moment = require('moment'); -const { randomUUID } = require('crypto'); +const moment = require("moment"); +const { randomUUID } = require("crypto"); const taskstate = require("../logging/taskstate"); const taskName = require("../logging/taskName"); const triggertype = require("../logging/triggertype"); - async function BackupTask() { - try{ - + try { await db.query( - `UPDATE jf_logging SET "Result"='${taskstate.FAILED}' WHERE "Name"='${taskName.backup}' AND "Result"='${taskstate.RUNNING}'` - ); - } - catch(error) - { - console.log('Error Cleaning up Backup Tasks: '+error); - } -let interval=10000; -let taskDelay=1440; // 1 day in minutes - + `UPDATE jf_logging SET "Result"='${taskstate.FAILED}' WHERE "Name"='${taskName.backup}' AND "Result"='${taskstate.RUNNING}'`, + ); + } catch (error) { + console.log("Error Cleaning up Backup Tasks: " + error); + } + let interval = 10000; + let taskDelay = 1440; // 1 day in minutes -try{//get interval from db + try { + //get interval from db - - const settingsjson = await db - .query('SELECT settings FROM app_config where "ID"=1') - .then((res) => res.rows); - - if (settingsjson.length > 0) { - const settings = settingsjson[0].settings || {}; + const settingsjson = await db + .query('SELECT settings FROM app_config where "ID"=1') + .then((res) => res.rows); - let backuptasksettings = settings.Tasks?.Backup || {}; + if (settingsjson.length > 0) { + const settings = settingsjson[0].settings || {}; - if (backuptasksettings.Interval) { - taskDelay=backuptasksettings.Interval; - } else { - backuptasksettings.Interval=taskDelay; - } + let backuptasksettings = settings.Tasks?.Backup || {}; - if(!settings.Tasks) - { - settings.Tasks = {}; - } - if(!settings.Tasks.Backup) - { - settings.Tasks.Backup = {}; - } - settings.Tasks.Backup = backuptasksettings; + if (backuptasksettings.Interval) { + taskDelay = backuptasksettings.Interval; + } else { + backuptasksettings.Interval = taskDelay; + } + if (!settings.Tasks) { + settings.Tasks = {}; + } + if (!settings.Tasks.Backup) { + settings.Tasks.Backup = {}; + } + settings.Tasks.Backup = backuptasksettings; - let query = 'UPDATE app_config SET settings=$1 where "ID"=1'; + let query = 'UPDATE app_config SET settings=$1 where "ID"=1'; - await db.query(query, [settings]); + await db.query(query, [settings]); + } + } catch (error) { + console.log("Sync Task Settings Error: " + error); } -} -catch(error) -{ - console.log('Sync Task Settings Error: '+error); -} -async function intervalCallback() { - clearInterval(intervalTask); - try{ - let current_time = moment(); - 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) - { - return; - } - - - const last_execution=await db.query( `SELECT "TimeRun","Result" + async function intervalCallback() { + clearInterval(intervalTask); + try { + let current_time = moment(); + 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 + ) { + return; + } + + const last_execution = await db + .query( + `SELECT "TimeRun","Result" FROM public.jf_logging WHERE "Name"='${taskName.backup}' AND "Result" in ('${taskstate.SUCCESS}','${taskstate.RUNNING}') ORDER BY "TimeRun" DESC - LIMIT 1`).then((res) => res.rows); - - if(last_execution.length!==0) - { - let last_execution_time = moment(last_execution[0].TimeRun).add(taskDelay, 'minutes'); - - if(!current_time.isAfter(last_execution_time) || last_execution[0].Result ===taskstate.RUNNING) - { - - intervalTask = setInterval(intervalCallback, interval); - return; - } - } + LIMIT 1`, + ) + .then((res) => res.rows); + + if (last_execution.length !== 0) { + let last_execution_time = moment(last_execution[0].TimeRun).add( + taskDelay, + "minutes", + ); + + if ( + !current_time.isAfter(last_execution_time) || + last_execution[0].Result === taskstate.RUNNING + ) { + intervalTask = setInterval(intervalCallback, interval); + return; + } + } const uuid = randomUUID(); - let refLog={logData:[],uuid:uuid}; - - - console.log('Running Scheduled Backup'); + let refLog = { logData: [], uuid: uuid }; - Logging.insertLog(uuid,triggertype.Automatic,taskName.backup); - + console.log("Running Scheduled Backup"); - await backup.backup(refLog); - Logging.updateLog(uuid,refLog.logData,taskstate.SUCCESS); + Logging.insertLog(uuid, triggertype.Automatic, taskName.backup); + await backup.backup(refLog); + Logging.updateLog(uuid, refLog.logData, taskstate.SUCCESS); - console.log('Scheduled Backup Complete'); - - } catch (error) - { - console.log(error); - return []; - } - - intervalTask = setInterval(intervalCallback, interval); - } - - let intervalTask = setInterval(intervalCallback, interval); + console.log("Scheduled Backup Complete"); + } catch (error) { + console.log(error); + return []; + } + intervalTask = setInterval(intervalCallback, interval); + } + let intervalTask = setInterval(intervalCallback, interval); } module.exports = { diff --git a/backend/tasks/SyncTask.js b/backend/tasks/SyncTask.js index d582f5d6..5877fbad 100644 --- a/backend/tasks/SyncTask.js +++ b/backend/tasks/SyncTask.js @@ -1,115 +1,109 @@ const db = require("../db"); -const moment = require('moment'); +const moment = require("moment"); const sync = require("../routes/sync"); -const taskName=require('../logging/taskName'); +const taskName = require("../logging/taskName"); const taskstate = require("../logging/taskstate"); const triggertype = require("../logging/triggertype"); async function SyncTask() { - try{ - + try { await db.query( - `UPDATE jf_logging SET "Result"='${taskstate.FAILED}' WHERE "Name"='${taskName.sync}' AND "Result"='${taskstate.RUNNING}'` - ); - } - catch(error) - { - console.log('Error Cleaning up Sync Tasks: '+error); - } + `UPDATE jf_logging SET "Result"='${taskstate.FAILED}' WHERE "Name"='${taskName.sync}' AND "Result"='${taskstate.RUNNING}'`, + ); + } catch (error) { + console.log("Error Cleaning up Sync Tasks: " + error); + } -let interval=10000; + let interval = 10000; -let taskDelay=15; //in minutes + let taskDelay = 15; //in minutes + try { + //get interval from db -try{//get interval from db + const settingsjson = await db + .query('SELECT settings FROM app_config where "ID"=1') + .then((res) => res.rows); - - const settingsjson = await db - .query('SELECT settings FROM app_config where "ID"=1') - .then((res) => res.rows); - - if (settingsjson.length > 0) { - const settings = settingsjson[0].settings || {}; + if (settingsjson.length > 0) { + const settings = settingsjson[0].settings || {}; - let synctasksettings = settings.Tasks?.JellyfinSync || {}; + let synctasksettings = settings.Tasks?.JellyfinSync || {}; - if (synctasksettings.Interval) { - taskDelay=synctasksettings.Interval; - } else { - synctasksettings.Interval=taskDelay; - } - - if(!settings.Tasks) - { - settings.Tasks = {}; - } - if(!settings.Tasks.JellyfinSync) - { - settings.Tasks.JellyfinSync = {}; - } - settings.Tasks.JellyfinSync = synctasksettings; + if (synctasksettings.Interval) { + taskDelay = synctasksettings.Interval; + } else { + synctasksettings.Interval = taskDelay; + } + if (!settings.Tasks) { + settings.Tasks = {}; + } + if (!settings.Tasks.JellyfinSync) { + settings.Tasks.JellyfinSync = {}; + } + settings.Tasks.JellyfinSync = synctasksettings; - let query = 'UPDATE app_config SET settings=$1 where "ID"=1'; + let query = 'UPDATE app_config SET settings=$1 where "ID"=1'; - await db.query(query, [settings]); + await db.query(query, [settings]); + } + } catch (error) { + console.log("Sync Task Settings Error: " + error); } -} -catch(error) -{ - console.log('Sync Task Settings Error: '+error); -} - - -async function intervalCallback() { - clearInterval(intervalTask); - try{ - let current_time = moment(); - 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) - { + async function intervalCallback() { + clearInterval(intervalTask); + try { + let current_time = moment(); + 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 + ) { return; - } - - - const last_execution=await db.query( `SELECT "TimeRun","Result" + } + + const last_execution = await db + .query( + `SELECT "TimeRun","Result" FROM public.jf_logging WHERE "Name"='${taskName.sync}' ORDER BY "TimeRun" DESC - LIMIT 1`).then((res) => res.rows); - if(last_execution.length!==0) - { - let last_execution_time = moment(last_execution[0].TimeRun).add(taskDelay, 'minutes'); - - if(!current_time.isAfter(last_execution_time) || last_execution[0].Result ===taskstate.RUNNING) - { - intervalTask = setInterval(intervalCallback, interval); - return; + LIMIT 1`, + ) + .then((res) => res.rows); + if (last_execution.length !== 0) { + let last_execution_time = moment(last_execution[0].TimeRun).add( + taskDelay, + "minutes", + ); + + if ( + !current_time.isAfter(last_execution_time) || + last_execution[0].Result === taskstate.RUNNING + ) { + intervalTask = setInterval(intervalCallback, interval); + return; } + } + + console.log("Running Scheduled Sync"); + await sync.fullSync(triggertype.Automatic); + console.log("Scheduled Sync Complete"); + } catch (error) { + console.log(error); + return []; } - - console.log('Running Scheduled Sync'); - await sync.fullSync(triggertype.Automatic); - console.log('Scheduled Sync Complete'); - - } catch (error) - { - console.log(error); - return []; - } - - intervalTask = setInterval(intervalCallback, interval); + intervalTask = setInterval(intervalCallback, interval); } -let intervalTask = setInterval(intervalCallback, interval); - - + let intervalTask = setInterval(intervalCallback, interval); } module.exports = { diff --git a/package-lock.json b/package-lock.json index bf0002a9..e99fb1ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,10 +14,8 @@ "@mui/x-data-grid": "^6.2.1", "axios": "^1.3.4", "bootstrap": "^5.2.3", - "compare-versions": "^6.0.0-rc.1", "crypto-js": "^4.1.1", "file-saver": "^2.0.5", - "github-api": "^3.4.0", "knex": "^2.4.2", "moment": "^2.29.4", "multer": "^1.4.5-lts.1", @@ -34,7 +32,8 @@ }, "devDependencies": { "concurrently": "^7.6.0", - "http-proxy-middleware": "^2.0.6" + "http-proxy-middleware": "^2.0.6", + "prettier": "^3.0.3" } }, "node_modules/@ampproject/remapping": { @@ -5475,11 +5474,6 @@ "version": "1.0.1", "license": "MIT" }, - "node_modules/compare-versions": { - "version": "6.0.0-rc.1", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.0.0-rc.1.tgz", - "integrity": "sha512-cFhkjbGY1jLFWIV7KegECbfuyYPxSGvgGkdkfM+ibboQDoPwg2FRHm5BSNTOApiauRBzJIQH7qvOJs2sW5ueKQ==" - }, "node_modules/compressible": { "version": "2.0.18", "license": "MIT", @@ -8167,38 +8161,6 @@ "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.3.0.tgz", "integrity": "sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA==" }, - "node_modules/github-api": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/github-api/-/github-api-3.4.0.tgz", - "integrity": "sha512-2yYqYS6Uy4br1nw0D3VrlYWxtGTkUhIZrumBrcBwKdBOzMT8roAe8IvI6kjIOkxqxapKR5GkEsHtz3Du/voOpA==", - "dependencies": { - "axios": "^0.21.1", - "debug": "^2.2.0", - "js-base64": "^2.1.9", - "utf8": "^2.1.1" - } - }, - "node_modules/github-api/node_modules/axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "dependencies": { - "follow-redirects": "^1.14.0" - } - }, - "node_modules/github-api/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/github-api/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/glob": { "version": "7.2.3", "license": "ISC", @@ -11028,11 +10990,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/js-base64": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", - "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==" - }, "node_modules/js-sdsl": { "version": "4.3.0", "license": "MIT", @@ -13317,6 +13274,21 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", + "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-bytes": { "version": "5.6.0", "license": "MIT", @@ -15678,11 +15650,6 @@ "requires-port": "^1.0.0" } }, - "node_modules/utf8": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/utf8/-/utf8-2.1.2.tgz", - "integrity": "sha512-QXo+O/QkLP/x1nyi54uQiG0XrODxdysuQvE5dtVqv7F5K2Qb6FsN+qbr6KhF5wQ20tfcV3VQp0/2x1e1MRSPWg==" - }, "node_modules/util-deprecate": { "version": "1.0.2", "license": "MIT" diff --git a/package.json b/package.json index 57e0e12e..28087152 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,8 @@ "@mui/x-data-grid": "^6.2.1", "axios": "^1.3.4", "bootstrap": "^5.2.3", - "compare-versions": "^6.0.0-rc.1", "crypto-js": "^4.1.1", "file-saver": "^2.0.5", - "github-api": "^3.4.0", "knex": "^2.4.2", "moment": "^2.29.4", "multer": "^1.4.5-lts.1", @@ -29,7 +27,8 @@ }, "devDependencies": { "concurrently": "^7.6.0", - "http-proxy-middleware": "^2.0.6" + "http-proxy-middleware": "^2.0.6", + "prettier": "^3.0.3" }, "scripts": { "start": "react-scripts start", diff --git a/src/App.css b/src/App.css index cbef13e8..4d66cea4 100644 --- a/src/App.css +++ b/src/App.css @@ -1,16 +1,14 @@ -@import 'pages/css/variables.module.css'; -@import 'pages/css/variables.module.css'; -main{ - margin-inline: 20px; +@import "pages/css/variables.module.css"; +@import "pages/css/variables.module.css"; +main { + margin-inline: 20px; /* width: 100%; */ overflow: auto; - margin-inline: 20px; + margin-inline: 20px; /* width: 100%; */ overflow: auto; } - - .App-logo { height: 40vmin; pointer-events: none; @@ -22,49 +20,47 @@ main{ } } -body -{ +body { background-color: var(--background-color) !important; background-color: var(--background-color) !important; /* background-color: #17151a; */ color: white; } -h1{ +h1 { color: white; font-weight: lighter !important; margin: 0 !important; - margin-top: 0.5em; - margin-bottom: 0.5em; + margin-top: 0.5em; + margin-bottom: 0.5em; } -h2{ +h2 { color: white; font-weight: lighter !important; margin: 0 !important; - margin-top: 0.5em; - margin-bottom: 0.5em; + margin-top: 0.5em; + margin-bottom: 0.5em; } - - - - - - @keyframes fade-in { - from { opacity: 0; } - to { opacity: 1; } + from { + opacity: 0; + } + to { + opacity: 1; + } } @keyframes fade-out { - from { opacity: 1; } - to { opacity: 0; } + from { + opacity: 1; + } + to { + opacity: 0; + } } - - .App-header { - /* min-height: 100vh; */ padding-bottom: 10px; display: flex; @@ -79,7 +75,6 @@ h2{ color: #61dafb; } - @keyframes App-logo-spin { from { transform: rotate(0deg); @@ -89,64 +84,49 @@ h2{ } } - -.btn-outline-primary -{ - color: white!important; +.btn-outline-primary { + color: white !important; border-color: var(--primary-color) !important; background-color: var(--background-color) !important; } -.btn-outline-primary:hover -{ +.btn-outline-primary:hover { background-color: var(--primary-color) !important; } -.btn-outline-primary.active -{ +.btn-outline-primary.active { background-color: var(--primary-color) !important; - } -.btn-outline-primary:focus -{ +.btn-outline-primary:focus { background-color: var(--primary-color) !important; - } -.btn-primary -{ - color: white!important; +.btn-primary { + color: white !important; border-color: var(--primary-color) !important; background-color: var(--primary-color) !important; } -.btn-primary:hover -{ +.btn-primary:hover { background-color: var(--primary-dark-color) !important; } -.btn-primary.active -{ +.btn-primary.active { background-color: var(--primary-color) !important; - } -.btn-primary:focus -{ +.btn-primary:focus { background-color: var(--primary-color) !important; - } -.form-select -{ - background-color:var(--secondary-background-color) !important; - border-color:var(--secondary-background-color) !important ; +.form-select { + background-color: var(--secondary-background-color) !important; + border-color: var(--secondary-background-color) !important ; color: white !important; } -.form-select:focus -{ +.form-select:focus { box-shadow: none !important; border-color: var(--primary-color) !important; color: white !important; @@ -159,12 +139,7 @@ h2{ border-color: var(--primary-color) !important; } - .form-select option:hover { background-color: var(--primary-color) !important; color: white !important; } - - - - diff --git a/src/App.js b/src/App.js index 9509127a..4c421524 100644 --- a/src/App.js +++ b/src/App.js @@ -1,166 +1,134 @@ -// import logo from './logo.svg'; -import './App.css'; -import React, { useState, useEffect } from 'react'; +import "./App.css"; +import React, { useState, useEffect } from "react"; import { Routes, Route } from "react-router-dom"; -import axios from 'axios'; +import axios from "axios"; +import { ToastContainer, toast } from "react-toastify"; +import "react-toastify/dist/ReactToastify.css"; -import { ToastContainer, toast } from 'react-toastify'; -import 'react-toastify/dist/ReactToastify.css'; +import Config from "./lib/config"; +import Loading from "./pages/components/general/loading"; -import Config from './lib/config'; +import Signup from "./pages/signup"; +import Setup from "./pages/setup"; +import Login from "./pages/login"; -import Loading from './pages/components/general/loading'; +import Navbar from "./pages/components/general/navbar"; +import Home from "./pages/home"; +import Settings from "./pages/settings"; +import Users from "./pages/users"; +import UserInfo from "./pages/components/user-info"; +import Libraries from "./pages/libraries"; +import LibraryInfo from "./pages/components/library-info"; +import ItemInfo from "./pages/components/item-info"; +import ErrorPage from "./pages/components/general/error"; +import About from "./pages/about"; -import Signup from './pages/signup'; -import Setup from './pages/setup'; -import Login from './pages/login'; - - -import Navbar from './pages/components/general/navbar'; -import Home from './pages/home'; -import Settings from './pages/settings'; -import Users from './pages/users'; -import UserInfo from './pages/components/user-info'; -import Libraries from './pages/libraries'; -import LibraryInfo from './pages/components/library-info'; -import ItemInfo from './pages/components/item-info'; -import ErrorPage from './pages/components/general/error'; -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'; +import Testing from "./pages/testing"; +import Activity from "./pages/activity"; +import Statistics from "./pages/statistics"; +import Datadebugger from "./pages/data-debugger"; function App() { - const [setupState, setSetupState] = useState(0); const [config, setConfig] = useState(null); const [loading, setLoading] = useState(true); const [errorFlag, seterrorFlag] = useState(false); - const token = localStorage.getItem('token'); + const token = localStorage.getItem("token"); - - - const wsListeners=[ - {task:'PlaybackSyncTask',ref:React.useRef(null)}, - {task:'SyncTask',ref:React.useRef(null)}, - {task:'BackupTask',ref:React.useRef(null)}, - {task:'TaskError',ref:React.useRef(null)}, + const wsListeners = [ + { task: "PlaybackSyncTask", ref: React.useRef(null) }, + { task: "SyncTask", ref: React.useRef(null) }, + { task: "BackupTask", ref: React.useRef(null) }, + { task: "TaskError", ref: React.useRef(null) }, ]; useEffect(() => { - - const fetchConfig = async () => { - try { - const newConfig = await Config(); - if(!newConfig.response){ - setConfig(newConfig); - }else{ - if(newConfig.response.status!==403) - { - seterrorFlag(true); - } - - } - setLoading(false); - - } catch (error) { - console.log(error); + try { + const newConfig = await Config(); + if (!newConfig.response) { + setConfig(newConfig); + } else { + if (newConfig.response.status !== 403) { + seterrorFlag(true); + } } + setLoading(false); + } catch (error) { + console.log(error); + } }; - if(setupState===0) - { + if (setupState === 0) { setLoading(false); axios - .get("/auth/isConfigured") - .then(async (response) => { - if(response.status===200) - { - setSetupState(response.data.state); - } - - - }) - .catch((error) => { - console.log(error); - seterrorFlag(true); - - }); - + .get("/auth/isConfigured") + .then(async (response) => { + if (response.status === 200) { + setSetupState(response.data.state); + } + }) + .catch((error) => { + console.log(error); + seterrorFlag(true); + }); } - if (!config && setupState>=1) { - fetchConfig(); + if (!config && setupState >= 1) { + fetchConfig(); } + }, [config, setupState]); -}, [config,setupState]); - -if (loading) { - return ; -} + if (loading) { + return ; + } -if (errorFlag) { - return ; -} + if (errorFlag) { + return ( + + ); + } -if(!config && setupState===2) - { - if ((token===undefined || token===null) || !config) { + if (!config && setupState === 2) { + if (token === undefined || token === null || !config) { return ; } } - if (setupState===0) { + if (setupState === 0) { return ; } - if(setupState===1) - { - return ; - - } - - - - - + if (setupState === 1) { + return ; + } -if (config && setupState===2 && token!==null){ - return ( -
- -
- -
- - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - -
+ if (config && setupState === 2 && token !== null) { + return ( +
+
+ +
+ + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + +
+
-
- - ); -} - - - - + ); + } } export default App; diff --git a/src/index.css b/src/index.css index f823fdba..ca1cbb41 100644 --- a/src/index.css +++ b/src/index.css @@ -1,33 +1,30 @@ @font-face { - font-family: 'Railway'; - src: url('/src/pages/fonts/Raleway-VariableFont_wght.ttf') format('truetype'); + font-family: "Railway"; + src: url("/src/pages/fonts/Raleway-VariableFont_wght.ttf") format("truetype"); font-weight: normal; font-style: normal; } body { margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - color: white ; + color: white; } -* -{ - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',sans-serif !important; - - +* { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif !important; } code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; } - body { overflow: auto; /* show scrollbar when needed */ } @@ -48,4 +45,3 @@ body::-webkit-scrollbar-thumb { body::-webkit-scrollbar-thumb:hover { background-color: #88888883; /* set thumb color */ } - diff --git a/src/index.js b/src/index.js index be53609a..5d872ff9 100644 --- a/src/index.js +++ b/src/index.js @@ -1,16 +1,15 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import './index.css'; -import App from './App'; -import { BrowserRouter } from 'react-router-dom'; -import 'bootstrap/dist/css/bootstrap.min.css'; +import React from "react"; +import ReactDOM from "react-dom/client"; +import "./index.css"; +import App from "./App"; +import { BrowserRouter } from "react-router-dom"; +import "bootstrap/dist/css/bootstrap.min.css"; -const root = ReactDOM.createRoot(document.getElementById('root')); +const root = ReactDOM.createRoot(document.getElementById("root")); root.render( - + , ); - diff --git a/src/lib/config.js b/src/lib/config.js index 2c0b06cf..86e79442 100644 --- a/src/lib/config.js +++ b/src/lib/config.js @@ -1,23 +1,26 @@ -import axios from 'axios'; +import axios from "axios"; async function Config() { - const token = localStorage.getItem('token'); + const token = localStorage.getItem("token"); try { - const response = await axios.get('/api/getconfig', { + const response = await axios.get("/api/getconfig", { headers: { Authorization: `Bearer ${token}`, }, }); - if(response.data.length>0) - { - const { JF_HOST, APP_USER,REQUIRE_LOGIN, settings } = response.data[0]; - return { hostUrl: JF_HOST, username: APP_USER, token:token, requireLogin:REQUIRE_LOGIN, settings:settings }; + if (response.data.length > 0) { + const { JF_HOST, APP_USER, REQUIRE_LOGIN, settings } = response.data[0]; + return { + hostUrl: JF_HOST, + username: APP_USER, + token: token, + requireLogin: REQUIRE_LOGIN, + settings: settings, + }; } - return { hostUrl: null, username: null, token:token,requireLogin:true }; - + return { hostUrl: null, username: null, token: token, requireLogin: true }; } catch (error) { - // console.log(error); return error; } } diff --git a/src/lib/devices.js b/src/lib/devices.js index 486b59f6..66e6928a 100644 --- a/src/lib/devices.js +++ b/src/lib/devices.js @@ -1,3 +1,9 @@ - -export const clientData = ["android","ios","safari","chrome","firefox","edge","opera"] - +export const clientData = [ + "android", + "ios", + "safari", + "chrome", + "firefox", + "edge", + "opera", +]; diff --git a/src/lib/navdata.js b/src/lib/navdata.js index ef87ef3d..fdb0cd42 100644 --- a/src/lib/navdata.js +++ b/src/lib/navdata.js @@ -1,63 +1,54 @@ - - -import HomeFillIcon from 'remixicon-react/HomeFillIcon'; -// import FileListFillIcon from 'remixicon-react/FileListFillIcon'; -import BarChartFillIcon from 'remixicon-react/BarChartFillIcon'; -import HistoryFillIcon from 'remixicon-react/HistoryFillIcon'; -import SettingsFillIcon from 'remixicon-react/SettingsFillIcon'; -import GalleryFillIcon from 'remixicon-react/GalleryFillIcon'; -import UserFillIcon from 'remixicon-react/UserFillIcon'; -import InformationFillIcon from 'remixicon-react/InformationFillIcon'; - - -// import ReactjsFillIcon from 'remixicon-react/ReactjsFillIcon'; +import HomeFillIcon from "remixicon-react/HomeFillIcon"; +import BarChartFillIcon from "remixicon-react/BarChartFillIcon"; +import HistoryFillIcon from "remixicon-react/HistoryFillIcon"; +import SettingsFillIcon from "remixicon-react/SettingsFillIcon"; +import GalleryFillIcon from "remixicon-react/GalleryFillIcon"; +import UserFillIcon from "remixicon-react/UserFillIcon"; +import InformationFillIcon from "remixicon-react/InformationFillIcon"; export const navData = [ - { - id: 0, - icon: , - text: "Home", - link: "" - }, - { - id: 1, - icon: , - text: "Libraries", - link: "libraries" - }, - { - id: 2, - icon: , - text: "Users", - link: "users" - }, - { - id: 4, - icon: , - text: "Activity", - link: "activity" - }, - { - id: 5, - icon: , - text: "Statistics", - link: "statistics" - }, - - { - id: 6, - icon: , - text: "Settings", - link: "settings" - } - , - - { - id: 7, - icon: , - text: "About", - link: "about" - } - -] - + { + id: 0, + icon: , + text: "Home", + link: "", + }, + { + id: 1, + icon: , + text: "Libraries", + link: "libraries", + }, + { + id: 2, + icon: , + text: "Users", + link: "users", + }, + { + id: 4, + icon: , + text: "Activity", + link: "activity", + }, + { + id: 5, + icon: , + text: "Statistics", + link: "statistics", + }, + + { + id: 6, + icon: , + text: "Settings", + link: "settings", + }, + + { + id: 7, + icon: , + text: "About", + link: "about", + }, +]; diff --git a/src/lib/tasklist.js b/src/lib/tasklist.js index b70486f9..610531f3 100644 --- a/src/lib/tasklist.js +++ b/src/lib/tasklist.js @@ -1,26 +1,23 @@ - export const taskList = [ - { - id: 0, - name: "JellyfinSync", - description: "Synchronize with Jellyfin", - type: "Job", - link: "/sync/beingSync" - }, - { - id: 1, - name: "Jellyfin Playback Reporting Plugin Sync", - description: "Import Playback Reporting Plugin Data", - type: "Import", - link: "/sync/syncPlaybackPluginData" - }, - { - id: 2, - name: "Backup", - description: "Backup Jellystat", - type: "Job", - link: "/backup/beginBackup" - } - -] - + { + id: 0, + name: "JellyfinSync", + description: "Synchronize with Jellyfin", + type: "Job", + link: "/sync/beingSync", + }, + { + id: 1, + name: "Jellyfin Playback Reporting Plugin Sync", + description: "Import Playback Reporting Plugin Data", + type: "Import", + link: "/sync/syncPlaybackPluginData", + }, + { + id: 2, + name: "Backup", + description: "Backup Jellystat", + type: "Job", + link: "/backup/beginBackup", + }, +]; diff --git a/src/logo.svg b/src/logo.svg deleted file mode 100644 index 9dfc1c05..00000000 --- a/src/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/pages/about.js b/src/pages/about.js index 8526de1d..f4fc2c30 100644 --- a/src/pages/about.js +++ b/src/pages/about.js @@ -1,38 +1,36 @@ import React, { useState, useEffect } from "react"; import axios from "axios"; -import Row from 'react-bootstrap/Row'; -import Col from 'react-bootstrap/Col'; +import Row from "react-bootstrap/Row"; +import Col from "react-bootstrap/Col"; import Loading from "./components/general/loading"; - import "./css/about.css"; import { Card } from "react-bootstrap"; export default function SettingsAbout() { - - const token = localStorage.getItem('token'); + const token = localStorage.getItem("token"); const [data, setData] = useState(); - - - return ( -
-

About Jellystat

- - - - - - Github: - - - https://github.com/opspotes/Jellystat - - - - -
- ); - - + return ( +
+

About Jellystat

+ + + + Github: + + + {" "} + https://github.com/opspotes/Jellystat + + + + + +
+ ); } diff --git a/src/pages/activity.js b/src/pages/activity.js index 50518892..d046c6db 100644 --- a/src/pages/activity.js +++ b/src/pages/activity.js @@ -12,11 +12,7 @@ function Activity() { const [data, setData] = useState(); const [config, setConfig] = useState(null); - const [itemCount,setItemCount] = useState(10); - - - - + const [itemCount, setItemCount] = useState(10); useEffect(() => { const fetchConfig = async () => { @@ -47,9 +43,6 @@ function Activity() { }); }; - - - if (!data && config) { fetchLibraries(); } @@ -62,43 +55,45 @@ function Activity() { return () => clearInterval(intervalId); }, [data, config]); - if (!data) { return ; } - if (data.length === 0) { - return (
-
-

Activity

-
-
-

No Activity to display

+ return ( +
+
+

Activity

+
+
+

No Activity to display

+
-
); } return (
-

Activity

-
+

Activity

+
Items
- +
- - - -
+ +
); } diff --git a/src/pages/components/HomeStatisticCards.js b/src/pages/components/HomeStatisticCards.js index b1422cef..f99b4d85 100644 --- a/src/pages/components/HomeStatisticCards.js +++ b/src/pages/components/HomeStatisticCards.js @@ -1,8 +1,5 @@ import React, { useState } from "react"; - - - import MVLibraries from "./statCards/mv_libraries"; import MVMovies from "./statCards/mv_movies"; import MVSeries from "./statCards/mv_series"; @@ -48,22 +45,18 @@ function HomeStatisticCards() {
Days
- -
- - + + - - -
+ ); } diff --git a/src/pages/components/LibrarySelector/SelectionCard.js b/src/pages/components/LibrarySelector/SelectionCard.js index b1c08833..918c8216 100644 --- a/src/pages/components/LibrarySelector/SelectionCard.js +++ b/src/pages/components/LibrarySelector/SelectionCard.js @@ -1,8 +1,8 @@ -import React, {useState} from "react"; +import React, { useState } from "react"; import axios from "axios"; import "../../css/library/library-card.css"; -import { Form ,Card,Row,Col } from 'react-bootstrap'; +import { Form, Card, Row, Col } from "react-bootstrap"; import TvLineIcon from "remixicon-react/TvLineIcon"; import FilmLineIcon from "remixicon-react/FilmLineIcon"; @@ -12,86 +12,100 @@ import CheckboxMultipleBlankLineIcon from "remixicon-react/CheckboxMultipleBlank function SelectionCard(props) { const [imageLoaded, setImageLoaded] = useState(true); const [checked, setChecked] = useState(props.data.Tracked); - const SeriesIcon= ; - const MovieIcon= ; - const MusicIcon= ; - const MixedIcon= ; - const token = localStorage.getItem('token'); - - const default_image=
{props.data.CollectionType==='tvshows' ? SeriesIcon : props.data.CollectionType==='movies'? MovieIcon : props.data.CollectionType==='music'? MusicIcon : MixedIcon}
; + const SeriesIcon = ; + const MovieIcon = ; + const MusicIcon = ; + const MixedIcon = ( + + ); + const token = localStorage.getItem("token"); + + const default_image = ( +
+ {props.data.CollectionType === "tvshows" + ? SeriesIcon + : props.data.CollectionType === "movies" + ? MovieIcon + : props.data.CollectionType === "music" + ? MusicIcon + : MixedIcon}{" "} +
+ ); const handleChange = async () => { await axios - .post("/api/setExcludedLibraries", { - libraryID:props.data.Id - }, { - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - }) - .then(()=> - { - setChecked(!checked); - }) - .catch((error) => { - console.error(error); - }); - - - + .post( + "/api/setExcludedLibraries", + { + libraryID: props.data.Id, + }, + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }, + ) + .then(() => { + setChecked(!checked); + }) + .catch((error) => { + console.error(error); + }); }; - return ( - - -
- - {imageLoaded? - - setImageLoaded(false)} - /> - : - default_image - } -
- - - - - - Library - {props.data.Name} - - - - Type - {props.data.CollectionType==='tvshows' ? 'Series' : props.data.CollectionType==='movies'? "Movies" : props.data.CollectionType==='music'? "Music" : 'Mixed'} - - - - - Tracked - -
- - - -
- - -
- -
+ +
+ {imageLoaded ? ( + setImageLoaded(false)} + /> + ) : ( + default_image + )} +
+ + + + Library + {props.data.Name} + + + + Type + + {props.data.CollectionType === "tvshows" + ? "Series" + : props.data.CollectionType === "movies" + ? "Movies" + : props.data.CollectionType === "music" + ? "Music" + : "Mixed"} + + + + + Tracked + +
+ + + +
+
+
); } diff --git a/src/pages/components/activity/activity-table.js b/src/pages/components/activity/activity-table.js index 76e3abdb..78201cbe 100644 --- a/src/pages/components/activity/activity-table.js +++ b/src/pages/components/activity/activity-table.js @@ -1,48 +1,47 @@ -import React from 'react'; +import React from "react"; import { Link } from "react-router-dom"; -import { Button, ButtonGroup,Modal } from "react-bootstrap"; +import { Button, ButtonGroup, Modal } 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 Collapse from "@mui/material/Collapse"; +import TableSortLabel from "@mui/material/TableSortLabel"; +import IconButton from "@mui/material/IconButton"; +import Box from "@mui/material/Box"; +import { visuallyHidden } from "@mui/utils"; -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 Collapse from '@mui/material/Collapse'; -import TableSortLabel from '@mui/material/TableSortLabel'; -import IconButton from '@mui/material/IconButton'; -import Box from '@mui/material/Box'; -import { visuallyHidden } from '@mui/utils'; +import AddCircleFillIcon from "remixicon-react/AddCircleFillIcon"; +import IndeterminateCircleFillIcon from "remixicon-react/IndeterminateCircleFillIcon"; +import StreamInfo from "./stream_info"; -import AddCircleFillIcon from 'remixicon-react/AddCircleFillIcon'; -import IndeterminateCircleFillIcon from 'remixicon-react/IndeterminateCircleFillIcon'; - -import StreamInfo from './stream_info'; - -import '../../css/activity/activity-table.css'; +import "../../css/activity/activity-table.css"; // localStorage.setItem('hour12',true); - function formatTotalWatchTime(seconds) { const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const remainingSeconds = seconds % 60; - let timeString = ''; + let timeString = ""; if (hours > 0) { - timeString += `${hours} ${hours === 1 ? 'hr' : 'hrs'} `; + timeString += `${hours} ${hours === 1 ? "hr" : "hrs"} `; } if (minutes > 0) { - timeString += `${minutes} ${minutes === 1 ? 'min' : 'mins'} `; + timeString += `${minutes} ${minutes === 1 ? "min" : "mins"} `; } if (remainingSeconds > 0) { - timeString += `${remainingSeconds} ${remainingSeconds === 1 ? 'second' : 'seconds'}`; + timeString += `${remainingSeconds} ${ + remainingSeconds === 1 ? "second" : "seconds" + }`; } return timeString.trim(); @@ -51,22 +50,15 @@ function formatTotalWatchTime(seconds) { function Row(data) { const { row } = data; const [open, setOpen] = React.useState(false); - const twelve_hr = JSON.parse(localStorage.getItem('12hr')); + const twelve_hr = JSON.parse(localStorage.getItem("12hr")); - - const [modalState,setModalState]= React.useState(false); - const [modalData,setModalData]= React.useState(); + const [modalState, setModalState] = React.useState(false); + const [modalData, setModalData] = React.useState(); - const openModal = (data) => { setModalData(data); setModalState(!modalState); }; - - - - - const options = { day: "numeric", @@ -78,46 +70,86 @@ function Row(data) { hour12: twelve_hr, }; - return ( - - setModalState(false)} > + setModalState(false)}> - Stream Info: {!row.SeriesName ? row.NowPlayingItemName : row.SeriesName+' - '+ row.NowPlayingItemName} ({row.UserName}) + + Stream Info:{" "} + {!row.SeriesName + ? row.NowPlayingItemName + : row.SeriesName + " - " + row.NowPlayingItemName}{" "} + ({row.UserName}) + - + - - *': { borderBottom: 'unset' } }}> + *": { borderBottom: "unset" } }}> {if(row.results.length>1){setOpen(!open);}}} - > - {!open ? 1 ?1 : 0} cursor={row.results.length>1 ? "pointer":"default"}/> : } + onClick={() => { + if (row.results.length > 1) { + setOpen(!open); + } + }} + > + {!open ? ( + 1 ? 1 : 0} + cursor={row.results.length > 1 ? "pointer" : "default"} + /> + ) : ( + + )} - {row.UserName} - {row.RemoteEndPoint || '-'} - {!row.SeriesName ? row.NowPlayingItemName : row.SeriesName+' - '+ row.NowPlayingItemName} - openModal(row)}>{row.Client} - {Intl.DateTimeFormat('en-UK', options).format(new Date(row.ActivityDateInserted))} - {formatTotalWatchTime(row.PlaybackDuration) || '0 seconds'} - {row.results.length !==0 ? row.results.length : 1} + + + {row.UserName} + + + {row.RemoteEndPoint || "-"} + + + {!row.SeriesName + ? row.NowPlayingItemName + : row.SeriesName + " - " + row.NowPlayingItemName} + + + + openModal(row)}>{row.Client} + + + {Intl.DateTimeFormat("en-UK", options).format( + new Date(row.ActivityDateInserted), + )} + + + {formatTotalWatchTime(row.PlaybackDuration) || "0 seconds"} + + + {row.results.length !== 0 ? row.results.length : 1} + - - +
User @@ -130,17 +162,54 @@ function Row(data) { - {row.results.sort((a, b) => new Date(b.ActivityDateInserted) - new Date(a.ActivityDateInserted)).map((resultRow) => ( - - {resultRow.UserName} - {resultRow.RemoteEndPoint || '-'} - {!resultRow.SeriesName ? resultRow.NowPlayingItemName : resultRow.SeriesName+' - '+ resultRow.NowPlayingItemName} - openModal(resultRow)}>{resultRow.Client} - {Intl.DateTimeFormat('en-UK', options).format(new Date(resultRow.ActivityDateInserted))} - {formatTotalWatchTime(resultRow.PlaybackDuration) || '0 seconds'} + {row.results + .sort( + (a, b) => + new Date(b.ActivityDateInserted) - + new Date(a.ActivityDateInserted), + ) + .map((resultRow) => ( + + + + {resultRow.UserName} + + + {resultRow.RemoteEndPoint || "-"} + + + {!resultRow.SeriesName + ? resultRow.NowPlayingItemName + : resultRow.SeriesName + + " - " + + resultRow.NowPlayingItemName} + + + + openModal(resultRow)}> + {resultRow.Client} + + + + {Intl.DateTimeFormat("en-UK", options).format( + new Date(resultRow.ActivityDateInserted), + )} + + + {formatTotalWatchTime(resultRow.PlaybackDuration) || + "0 seconds"} + 1 - - ))} + + ))}
@@ -152,78 +221,76 @@ function Row(data) { } function EnhancedTableHead(props) { - const { order, orderBy, onRequestSort } = - props; + const { order, orderBy, onRequestSort } = props; const createSortHandler = (property) => (event) => { onRequestSort(event, property); }; const headCells = [ { - id: 'UserName', + id: "UserName", numeric: false, disablePadding: false, - label: 'User', + label: "User", }, { - id: 'RemoteEndPoint', + id: "RemoteEndPoint", numeric: false, disablePadding: false, - label: 'IP Address', + label: "IP Address", }, { - id: 'NowPlayingItemName', + id: "NowPlayingItemName", numeric: false, disablePadding: false, - label: 'Title', + label: "Title", }, { - id: 'Client', + id: "Client", numeric: false, disablePadding: false, - label: 'Client', + label: "Client", }, { - id: 'ActivityDateInserted', + id: "ActivityDateInserted", numeric: false, disablePadding: false, - label: 'Date', + label: "Date", }, { - id: 'PlaybackDuration', + id: "PlaybackDuration", numeric: false, disablePadding: false, - label: 'Total Playback', - }, + label: "Total Playback", + }, { - id: 'TotalPlays', + id: "TotalPlays", numeric: false, disablePadding: false, - label: 'Total Plays', + label: "Total Plays", }, ]; - return ( - + {headCells.map((headCell) => ( {headCell.label} {orderBy === headCell.id ? ( - {order === 'desc' ? 'sorted descending' : 'sorted ascending'} + {order === "desc" ? "sorted descending" : "sorted ascending"} ) : null} @@ -238,17 +305,14 @@ export default function ActivityTable(props) { const [page, setPage] = React.useState(0); const [rowsPerPage, setRowsPerPage] = React.useState(10); - const [order, setOrder] = React.useState('desc'); - const [orderBy, setOrderBy] = React.useState('ActivityDateInserted'); + const [order, setOrder] = React.useState("desc"); + const [orderBy, setOrderBy] = React.useState("ActivityDateInserted"); - - if(rowsPerPage!==props.itemCount) - { + if (rowsPerPage !== props.itemCount) { setRowsPerPage(props.itemCount); setPage(0); } - const handleNextPageClick = () => { setPage((prevPage) => prevPage + 1); }; @@ -257,101 +321,134 @@ export default function ActivityTable(props) { setPage((prevPage) => prevPage - 1); }; - - function descendingComparator(a, b, orderBy) { - if (b[orderBy] < a[orderBy]) { - return -1; - } - if (b[orderBy] > a[orderBy]) { - return 1; - } - return 0; + function descendingComparator(a, b, orderBy) { + if (b[orderBy] < a[orderBy]) { + return -1; } - - // eslint-disable-next-line - function getComparator(order, orderBy) { - return order === 'desc' - ? (a, b) => descendingComparator(a, b, orderBy) - : (a, b) => -descendingComparator(a, b, orderBy); + if (b[orderBy] > a[orderBy]) { + return 1; } - - - function stableSort(array, comparator) { - const stabilizedThis = array.map((el, index) => [el, index]); + return 0; + } - stabilizedThis.sort((a, b) => { + // eslint-disable-next-line + function getComparator(order, orderBy) { + return order === "desc" + ? (a, b) => descendingComparator(a, b, orderBy) + : (a, b) => -descendingComparator(a, b, orderBy); + } - const order = comparator(a[0], b[0]); - if (order !== 0) { - return order; - } - return a[1] - b[1]; - - }); + function stableSort(array, comparator) { + const stabilizedThis = array.map((el, index) => [el, index]); - return stabilizedThis.map((el) => el[0]); - } + stabilizedThis.sort((a, b) => { + const order = comparator(a[0], b[0]); + if (order !== 0) { + return order; + } + return a[1] - b[1]; + }); - const visibleRows = React.useMemo( - () => - stableSort(props.data, getComparator(order, orderBy)).slice( - page * rowsPerPage, - page * rowsPerPage + rowsPerPage, - ), - [order, orderBy, page, rowsPerPage, getComparator, props.data], - ); + return stabilizedThis.map((el) => el[0]); + } - const handleRequestSort = (event, property) => { - const isAsc = orderBy === property && order === 'asc'; - setOrder(isAsc ? 'desc' : 'asc'); - setOrderBy(property); - }; + const visibleRows = React.useMemo( + () => + stableSort(props.data, getComparator(order, orderBy)).slice( + page * rowsPerPage, + page * rowsPerPage + rowsPerPage, + ), + [order, orderBy, page, rowsPerPage, getComparator, props.data], + ); - - + const handleRequestSort = (event, property) => { + const isAsc = orderBy === property && order === "asc"; + setOrder(isAsc ? "desc" : "asc"); + setOrderBy(property); + }; return ( <> - - - + +
+ {visibleRows.map((row) => ( - - ))} - {props.data.length===0 ? :''} - + + ))} + {props.data.length === 0 ? ( + + + + ) : ( + "" + )}
No Activity Found
+ No Activity Found +
-
- - - - +
+ + -
{`${page *rowsPerPage + 1}-${Math.min((page * rowsPerPage+ 1 ) + (rowsPerPage - 1),props.data.length)} of ${props.data.length}`}
+ - +
{`${ + page * rowsPerPage + 1 + }-${Math.min( + page * rowsPerPage + 1 + (rowsPerPage - 1), + props.data.length, + )} of ${props.data.length}`}
+ + - -
-
- + +
+
); -} \ No newline at end of file +} diff --git a/src/pages/components/activity/stream_info.js b/src/pages/components/activity/stream_info.js index fa46890f..3873c6d8 100644 --- a/src/pages/components/activity/stream_info.js +++ b/src/pages/components/activity/stream_info.js @@ -2,175 +2,355 @@ import React from "react"; import "../../css/activity/stream-info.css"; // import { Button } from "react-bootstrap"; import Loading from "../general/loading"; -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 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"; function Row(logs) { - const { data } = logs; + const { data } = logs; function convertBitrate(bitrate) { - if(!bitrate) - { - return '-'; + if (!bitrate) { + return "-"; } const kbps = (bitrate / 1000).toFixed(1); const mbps = (bitrate / 1000000).toFixed(1); - + if (kbps >= 1000) { - return mbps+' Mbps'; + return mbps + " Mbps"; } else { - return kbps+' Kbps'; + return kbps + " Kbps"; } } - if(!data || !data.MediaStreams) - { + if (!data || !data.MediaStreams) { return null; } - - - - - return ( - - - - Media - - - - Bitrate - {convertBitrate(data.TranscodingInfo ? data.TranscodingInfo.Bitrate : (data.MediaStreams ? data.MediaStreams.find(stream => stream.Type === 'Video')?.BitRate : null))} - {convertBitrate(data.MediaStreams ? data.MediaStreams.find(stream => stream.Type === 'Video')?.BitRate : null)} - - - - Container - {data.TranscodingInfo ? data.TranscodingInfo.Container.toUpperCase() : data.OriginalContainer.toUpperCase()} - {data.OriginalContainer.toUpperCase()} - - - - Video - {data.TranscodingInfo ? (data.TranscodingInfo?.IsVideoDirect ? 'DIRECT' :'TRANSCODE'):'DIRECT'} - - - - Codec - {data.TranscodingInfo ? data.TranscodingInfo.VideoCodec?.toUpperCase() : data.MediaStreams ? data.MediaStreams.find(stream => stream.Type === 'Video')?.Codec?.toUpperCase() : '-'} - {data.MediaStreams ? data.MediaStreams.find(stream => stream.Type === 'Video')?.Codec?.toUpperCase() : '-'} - - - - Bitrate - {convertBitrate(data.TranscodingInfo ? data.TranscodingInfo.Bitrate : data.MediaStreams ? data.MediaStreams.find(stream => stream.Type === 'Video')?.BitRate : null)} - {convertBitrate(data.MediaStreams ? data.MediaStreams.find(stream => stream.Type === 'Video')?.BitRate : null)} - - - - Width - {data.TranscodingInfo ? data.TranscodingInfo.Width : data.MediaStreams ? data.MediaStreams.find(stream => stream.Type === 'Video')?.Width : '-'} - {data.MediaStreams ? data.MediaStreams.find(stream => stream.Type === 'Video')?.Width : '-'} - - - - Height - {data.TranscodingInfo? data.TranscodingInfo?.Height :data.MediaStreams?.find(stream => stream.Type === 'Video')?.Height } - {data.MediaStreams ? data.MediaStreams.find(stream => stream.Type === 'Video')?.Height : '-'} - - - - Framerate - {data.MediaStreams ? parseFloat(data.MediaStreams.find(stream => stream.Type === 'Video')?.RealFrameRate).toFixed(2) : '-'} - {data.MediaStreams ? parseFloat(data.MediaStreams.find(stream => stream.Type === 'Video')?.RealFrameRate).toFixed(2) : '-'} - - - - Dynamic Range - {data.MediaStreams ? data.MediaStreams.find(stream => stream.Type === 'Video')?.VideoRange : '-'} - {data.MediaStreams ? data.MediaStreams.find(stream => stream.Type === 'Video')?.VideoRange : '-'} - - - - Aspect Ratio - {data.MediaStreams ? data.MediaStreams.find(stream => stream.Type === 'Video')?.AspectRatio : '-'} - {data.MediaStreams ? data.MediaStreams.find(stream => stream.Type === 'Video')?.AspectRatio : '-'} - - - - Audio - {data.TranscodingInfo ? (data.TranscodingInfo?.IsAudioDirect ? 'DIRECT' :'TRANSCODE'):'DIRECT'} - - - - Codec - {data.TranscodingInfo?.IsAudioDirect ? data.MediaStreams?.find(stream => stream.Type === 'Audio' && stream.Index===data.PlayState?.AudioStreamIndex)?.Codec?.toUpperCase() : data.TranscodingInfo?.AudioCodec.toUpperCase()|| data.MediaStreams?.find(stream => stream.Type === 'Audio' && stream.Index===data.PlayState?.AudioStreamIndex)?.Codec?.toUpperCase()} - {data.MediaStreams ? data.MediaStreams.find(stream => stream.Type === 'Audio' && stream.Index===data.PlayState?.AudioStreamIndex)?.Codec?.toUpperCase() : '-'} - - - - Bitrate - {convertBitrate(data.MediaStreams ? data.MediaStreams.find(stream => stream.Type === 'Audio' && stream.Index===data.PlayState?.AudioStreamIndex)?.BitRate : null)} - {convertBitrate(data.MediaStreams ? data.MediaStreams.find(stream => stream.Type === 'Audio' && stream.Index===data.PlayState?.AudioStreamIndex)?.BitRate : null)} - - - - Channels - {data.TranscodingInfo?.IsAudioDirect ? data.TranscodingInfo?.AudioChannels: data.MediaStreams?.find(stream => stream.Type === 'Audio' && stream.Index===data.PlayState?.AudioStreamIndex)?.Channels} - {data.MediaStreams ? data.MediaStreams.find(stream => stream.Type === 'Audio' && stream.Index===data.PlayState?.AudioStreamIndex)?.Channels : null} - - - - Language - {data.MediaStreams ? data.MediaStreams.find(stream => stream.Type === 'Audio' && stream.Index===data.PlayState?.AudioStreamIndex)?.Language?.toUpperCase() : '-'} - {data.MediaStreams ? data.MediaStreams.find(stream => stream.Type === 'Audio' && stream.Index===data.PlayState?.AudioStreamIndex)?.Language?.toUpperCase() : '-'} - - - - - - ); - } - + return ( + + + + Media + + + + + Bitrate + + {convertBitrate( + data.TranscodingInfo + ? data.TranscodingInfo.Bitrate + : data.MediaStreams + ? data.MediaStreams.find((stream) => stream.Type === "Video") + ?.BitRate + : null, + )} + + + {convertBitrate( + data.MediaStreams + ? data.MediaStreams.find((stream) => stream.Type === "Video") + ?.BitRate + : null, + )} + + + + + Container + + {data.TranscodingInfo + ? data.TranscodingInfo.Container.toUpperCase() + : data.OriginalContainer.toUpperCase()} + + + {data.OriginalContainer.toUpperCase()} + + + + + + Video + + + + {data.TranscodingInfo + ? data.TranscodingInfo?.IsVideoDirect + ? "DIRECT" + : "TRANSCODE" + : "DIRECT"} + + + + + + Codec + + {data.TranscodingInfo + ? data.TranscodingInfo.VideoCodec?.toUpperCase() + : data.MediaStreams + ? data.MediaStreams.find( + (stream) => stream.Type === "Video", + )?.Codec?.toUpperCase() + : "-"} + + + {data.MediaStreams + ? data.MediaStreams.find( + (stream) => stream.Type === "Video", + )?.Codec?.toUpperCase() + : "-"} + + + + + Bitrate + + {convertBitrate( + data.TranscodingInfo + ? data.TranscodingInfo.Bitrate + : data.MediaStreams + ? data.MediaStreams.find((stream) => stream.Type === "Video") + ?.BitRate + : null, + )} + + + {convertBitrate( + data.MediaStreams + ? data.MediaStreams.find((stream) => stream.Type === "Video") + ?.BitRate + : null, + )} + + + + + Width + + {data.TranscodingInfo + ? data.TranscodingInfo.Width + : data.MediaStreams + ? data.MediaStreams.find((stream) => stream.Type === "Video")?.Width + : "-"} + + + {data.MediaStreams + ? data.MediaStreams.find((stream) => stream.Type === "Video")?.Width + : "-"} + + + + + Height + + {data.TranscodingInfo + ? data.TranscodingInfo?.Height + : data.MediaStreams?.find((stream) => stream.Type === "Video") + ?.Height} + + + {data.MediaStreams + ? data.MediaStreams.find((stream) => stream.Type === "Video") + ?.Height + : "-"} + + + + + Framerate + + {data.MediaStreams + ? parseFloat( + data.MediaStreams.find((stream) => stream.Type === "Video") + ?.RealFrameRate, + ).toFixed(2) + : "-"} + + + {data.MediaStreams + ? parseFloat( + data.MediaStreams.find((stream) => stream.Type === "Video") + ?.RealFrameRate, + ).toFixed(2) + : "-"} + + + + + Dynamic Range + + {data.MediaStreams + ? data.MediaStreams.find((stream) => stream.Type === "Video") + ?.VideoRange + : "-"} + + + {data.MediaStreams + ? data.MediaStreams.find((stream) => stream.Type === "Video") + ?.VideoRange + : "-"} + + + + + Aspect Ratio + + {data.MediaStreams + ? data.MediaStreams.find((stream) => stream.Type === "Video") + ?.AspectRatio + : "-"} + + + {data.MediaStreams + ? data.MediaStreams.find((stream) => stream.Type === "Video") + ?.AspectRatio + : "-"} + + + + + + Audio + + + + {data.TranscodingInfo + ? data.TranscodingInfo?.IsAudioDirect + ? "DIRECT" + : "TRANSCODE" + : "DIRECT"} + + + + + + Codec + + {data.TranscodingInfo?.IsAudioDirect + ? data.MediaStreams?.find( + (stream) => + stream.Type === "Audio" && + stream.Index === data.PlayState?.AudioStreamIndex, + )?.Codec?.toUpperCase() + : data.TranscodingInfo?.AudioCodec.toUpperCase() || + data.MediaStreams?.find( + (stream) => + stream.Type === "Audio" && + stream.Index === data.PlayState?.AudioStreamIndex, + )?.Codec?.toUpperCase()} + + + {data.MediaStreams + ? data.MediaStreams.find( + (stream) => + stream.Type === "Audio" && + stream.Index === data.PlayState?.AudioStreamIndex, + )?.Codec?.toUpperCase() + : "-"} + + + + + Bitrate + + {convertBitrate( + data.MediaStreams + ? data.MediaStreams.find( + (stream) => + stream.Type === "Audio" && + stream.Index === data.PlayState?.AudioStreamIndex, + )?.BitRate + : null, + )} + + + {convertBitrate( + data.MediaStreams + ? data.MediaStreams.find( + (stream) => + stream.Type === "Audio" && + stream.Index === data.PlayState?.AudioStreamIndex, + )?.BitRate + : null, + )} + + + + + Channels + + {data.TranscodingInfo?.IsAudioDirect + ? data.TranscodingInfo?.AudioChannels + : data.MediaStreams?.find( + (stream) => + stream.Type === "Audio" && + stream.Index === data.PlayState?.AudioStreamIndex, + )?.Channels} + + + {data.MediaStreams + ? data.MediaStreams.find( + (stream) => + stream.Type === "Audio" && + stream.Index === data.PlayState?.AudioStreamIndex, + )?.Channels + : null} + + + + + Language + + {data.MediaStreams + ? data.MediaStreams.find( + (stream) => + stream.Type === "Audio" && + stream.Index === data.PlayState?.AudioStreamIndex, + )?.Language?.toUpperCase() + : "-"} + + + {data.MediaStreams + ? data.MediaStreams.find( + (stream) => + stream.Type === "Audio" && + stream.Index === data.PlayState?.AudioStreamIndex, + )?.Language?.toUpperCase() + : "-"} + + + + ); +} function StreamInfo(props) { - - - if(!props && !props.data) - { - return ; + if (!props && !props.data) { + return ; } - - return (
- - - - - - Stream Details - Source Details - - - - - - -
-
- + + + + + Stream Details + Source Details + + + + + +
+
); } -export default StreamInfo; \ No newline at end of file +export default StreamInfo; diff --git a/src/pages/components/general/ComponentLoading.js b/src/pages/components/general/ComponentLoading.js index 055593a5..72482e2f 100644 --- a/src/pages/components/general/ComponentLoading.js +++ b/src/pages/components/general/ComponentLoading.js @@ -9,4 +9,4 @@ function ComponentLoading() { ); } -export default ComponentLoading; \ No newline at end of file +export default ComponentLoading; diff --git a/src/pages/components/general/ErrorBoundary.js b/src/pages/components/general/ErrorBoundary.js index 4e7cb4a4..568e4fb0 100644 --- a/src/pages/components/general/ErrorBoundary.js +++ b/src/pages/components/general/ErrorBoundary.js @@ -1,28 +1,27 @@ import React from "react"; export default class ErrorBoundary extends React.Component { - constructor(props) { - super(props); - this.state = { hasError: false }; - } - - static getDerivedStateFromError(error) { - // Update state to indicate an error has occurred - return { hasError: true }; - } - - componentDidCatch(error, errorInfo) { - // You can log the error or perform other actions here - console.error(error, errorInfo); - } - - render() { - if (this.state.hasError) { - // Render an error message or fallback UI - return <>; - } - - // Render the child components as normal - return this.props.children; + constructor(props) { + super(props); + this.state = { hasError: false }; + } + + static getDerivedStateFromError(error) { + // Update state to indicate an error has occurred + return { hasError: true }; + } + + componentDidCatch(error, errorInfo) { + // You can log the error or perform other actions here + console.error(error, errorInfo); + } + + render() { + if (this.state.hasError) { + // Render an error message or fallback UI + return <>; } + + // Render the child components as normal + return this.props.children; } - \ No newline at end of file +} diff --git a/src/pages/components/general/error.js b/src/pages/components/general/error.js index 49946e92..9ffed378 100644 --- a/src/pages/components/general/error.js +++ b/src/pages/components/general/error.js @@ -9,4 +9,4 @@ function ErrorPage(props) { ); } -export default ErrorPage; \ No newline at end of file +export default ErrorPage; diff --git a/src/pages/components/general/last-watched-card.js b/src/pages/components/general/last-watched-card.js index b18d8c14..c728a467 100644 --- a/src/pages/components/general/last-watched-card.js +++ b/src/pages/components/general/last-watched-card.js @@ -1,74 +1,89 @@ -import React, {useState} from "react"; +import React, { useState } from "react"; import { Link } from "react-router-dom"; -import { Blurhash } from 'react-blurhash'; +import { Blurhash } from "react-blurhash"; import "../../css/lastplayed.css"; function formatTime(time) { - - const units = { - days: ['Day', 'Days'], - hours: ['Hour', 'Hours'], - minutes: ['Minute', 'Minutes'], - seconds: ['Second', 'Seconds'] - }; - - 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} ago`; + const units = { + days: ["Day", "Days"], + hours: ["Hour", "Hours"], + minutes: ["Minute", "Minutes"], + seconds: ["Second", "Seconds"], + }; + + 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} ago`; +} function LastWatchedCard(props) { const [loaded, setLoaded] = useState(false); return (
- -
- {!loaded && props.data.PrimaryImageHash && props.data.PrimaryImageHash!=null ? : null} - setLoaded(true)} - style={loaded ? { backgroundImage: `url(path/to/image.jpg)` } : { display: 'none' }} - /> -
- + +
+ {!loaded && + props.data.PrimaryImageHash && + props.data.PrimaryImageHash != null ? ( + + ) : null} + setLoaded(true)} + style={ + loaded + ? { backgroundImage: `url(path/to/image.jpg)` } + : { display: "none" } + } + /> +
+
- {formatTime(props.data.LastPlayed)} + {formatTime(props.data.LastPlayed)}
- - {props.data.UserName} - + {props.data.UserName}
{props.data.Name}
{props.data.EpisodeName}
- {props.data.SeasonNumber ? -
S{props.data.SeasonNumber} - E{props.data.EpisodeNumber}
: - <> - } - + {props.data.SeasonNumber ? ( +
+ {" "} + S{props.data.SeasonNumber} - E{props.data.EpisodeNumber} +
+ ) : ( + <> + )}
); } diff --git a/src/pages/components/general/loading.js b/src/pages/components/general/loading.js index cb7bb12f..1637a33b 100644 --- a/src/pages/components/general/loading.js +++ b/src/pages/components/general/loading.js @@ -9,4 +9,4 @@ function Loading() { ); } -export default Loading; \ No newline at end of file +export default Loading; diff --git a/src/pages/components/general/navbar.js b/src/pages/components/general/navbar.js index 08a67e37..18bb6ae2 100644 --- a/src/pages/components/general/navbar.js +++ b/src/pages/components/general/navbar.js @@ -1,8 +1,8 @@ -import { Nav, Navbar as BootstrapNavbar } from "react-bootstrap"; +import { Nav, Navbar as BootstrapNavbar } from "react-bootstrap"; import { Link, useLocation } from "react-router-dom"; import { navData } from "../../../lib/navdata"; import LogoutBoxLineIcon from "remixicon-react/LogoutBoxLineIcon"; -import logo_dark from '../../images/icon-b-512.png'; +import logo_dark from "../../images/icon-b-512.png"; import "../../css/navbar.css"; import React from "react"; @@ -12,43 +12,57 @@ export default function Navbar() { window.location.reload(); }; - const location = useLocation(); + const location = useLocation(); return ( - -
- - - + +
+ + Jellystat - - - - -
- + +
); } diff --git a/src/pages/components/item-info.js b/src/pages/components/item-info.js index e26c85bb..87b30926 100644 --- a/src/pages/components/item-info.js +++ b/src/pages/components/item-info.js @@ -1,34 +1,30 @@ import React, { useState, useEffect } from "react"; import axios from "axios"; -import { useParams } from 'react-router-dom'; +import { useParams } from "react-router-dom"; import { Link } from "react-router-dom"; -import { Blurhash } from 'react-blurhash'; -import {Row, Col, Tabs, Tab, Button, ButtonGroup } from 'react-bootstrap'; +import { Blurhash } from "react-blurhash"; +import { Row, Col, Tabs, Tab, Button, ButtonGroup } from "react-bootstrap"; import ExternalLinkFillIcon from "remixicon-react/ExternalLinkFillIcon"; -import GlobalStats from './item-info/globalStats'; +import GlobalStats from "./item-info/globalStats"; import "../css/items/item-details.css"; import MoreItems from "./item-info/more-items"; import ItemActivity from "./item-info/item-activity"; import ItemNotFound from "./item-info/item-not-found"; - import Config from "../../lib/config"; import Loading from "./general/loading"; - - function ItemInfo() { const { Id } = useParams(); const [data, setData] = useState(); const [config, setConfig] = useState(); const [refresh, setRefresh] = useState(true); - const [activeTab, setActiveTab] = useState('tabOverview'); - - const [loaded, setLoaded] = useState(false); + const [activeTab, setActiveTab] = useState("tabOverview"); + const [loaded, setLoaded] = useState(false); function formatFileSize(sizeInBytes) { const sizeInMB = sizeInBytes / 1048576; // 1 MB = 1048576 bytes @@ -48,163 +44,230 @@ function ItemInfo() { const timeString = `${hours.toString().padStart(2, "0")}:${minutes .toString() .padStart(2, "0")}:${remainingSeconds.toString().padStart(2, "0")}`; - + return timeString; } const fetchData = async () => { - if(config){ - console.log('fetch'); + if (config) { setRefresh(true); - try { - const itemData = await axios.post(`/api/getItemDetails`, { - Id: Id - }, { - headers: { - Authorization: `Bearer ${config.token}`, - "Content-Type": "application/json", - }, - }); - - - setData(itemData.data[0]); - - } catch (error) { - setData({notfound:true, message:error.response.data}); - console.log(error); + try { + const itemData = await axios.post( + `/api/getItemDetails`, + { + Id: Id, + }, + { + headers: { + Authorization: `Bearer ${config.token}`, + "Content-Type": "application/json", + }, + }, + ); + + setData(itemData.data[0]); + } catch (error) { + setData({ notfound: true, message: error.response.data }); + console.log(error); + } + setRefresh(false); } - setRefresh(false); - } - }; - - -useEffect(() => { - - - const fetchConfig = async () => { + useEffect(() => { + const fetchConfig = async () => { try { const newConfig = await Config(); setConfig(newConfig); } catch (error) { - console.log(error); + console.log(error); } }; + fetchData(); - - fetchData(); - - if (!config) { + if (!config) { fetchConfig(); - } - - const intervalId = setInterval(fetchData, 60000 * 5); - return () => clearInterval(intervalId); -}, [config, Id]); - - - - - - + } -if(!data || refresh) -{ - return ; -} + const intervalId = setInterval(fetchData, 60000 * 5); + return () => clearInterval(intervalId); + }, [config, Id]); -if(data && data.notfound) -{ - return ; -} + if (!data || refresh) { + return ; + } -const cardStyle = { - backgroundImage: `url(/Proxy/Items/Images/Backdrop?id=${(["Episode","Season"].includes(data.Type)? data.SeriesId : data.Id)}&fillWidth=800&quality=90)`, - height:'100%', - backgroundSize: 'cover', -}; + if (data && data.notfound) { + return ( + + ); + } -const cardBgStyle = { - backgroundColor: 'rgb(0, 0, 0, 0.8)', - -}; + const cardStyle = { + backgroundImage: `url(/Proxy/Items/Images/Backdrop?id=${ + ["Episode", "Season"].includes(data.Type) ? data.SeriesId : data.Id + }&fillWidth=800&quality=90)`, + height: "100%", + backgroundSize: "cover", + }; + const cardBgStyle = { + backgroundColor: "rgb(0, 0, 0, 0.8)", + }; return (
- -
- - - {data.PrimaryImageHash && data.PrimaryImageHash!=null && !loaded ? : null} - setLoaded(true)} - /> - - - -
-
-

- {data.SeriesId? - {data.SeriesName || data.Name} - : - data.SeriesName || data.Name - } - -

- -
- -
- {data.Type==="Episode"?

{data.SeasonName} Episode {data.IndexNumber} - {data.Name}

: <> } - {data.Type==="Season"?

{data.Name}

: <> } - {data.FileName ?

File Name: {data.FileName}

:<>} - {data.Path ?

File Path: {data.Path}

:<>} - {data.RunTimeTicks ?

{data.Type==="Series"?"Average Runtime" : "Runtime"}: {ticksToTimeString(data.RunTimeTicks)}

:<>} - {data.Size ?

File Size: {formatFileSize(data.Size)}

:<>} - -
- - - - - - +
+ + + {data.PrimaryImageHash && + data.PrimaryImageHash != null && + !loaded ? ( + + ) : null} + setLoaded(true)} + /> + + + +
+
+

+ {data.SeriesId ? ( + + {data.SeriesName || data.Name} + + ) : ( + data.SeriesName || data.Name + )} +

+ + + +
+ +
+ {data.Type === "Episode" ? ( +

+ + {data.SeasonName} + {" "} + Episode {data.IndexNumber} - {data.Name} +

+ ) : ( + <> + )} + {data.Type === "Season" ?

{data.Name}

: <>} + {data.FileName ? ( +

+ File Name: {data.FileName} +

+ ) : ( + <> + )} + {data.Path ? ( +

+ File Path: {data.Path} +

+ ) : ( + <> + )} + {data.RunTimeTicks ? ( +

+ {data.Type === "Series" ? "Average Runtime" : "Runtime"}:{" "} + {ticksToTimeString(data.RunTimeTicks)} +

+ ) : ( + <> + )} + {data.Size ? ( +

+ File Size: {formatFileSize(data.Size)} +

+ ) : ( + <> + )} +
+ + + + +
+ +
- - - - - -
- - - - - {["Series","Season"].includes(data && data.Type)? - - : - <> - } - - - - - + + + + {["Series", "Season"].includes(data && data.Type) ? ( + + ) : ( + <> + )} + + + + +
); } diff --git a/src/pages/components/item-info/globalStats.js b/src/pages/components/item-info/globalStats.js index 61aea55e..82e25054 100644 --- a/src/pages/components/item-info/globalStats.js +++ b/src/pages/components/item-info/globalStats.js @@ -9,53 +9,69 @@ function GlobalStats(props) { const [weekStats, setWeekStats] = useState({}); const [monthStats, setMonthStats] = useState({}); const [allStats, setAllStats] = useState({}); - const token = localStorage.getItem('token'); + const token = localStorage.getItem("token"); useEffect(() => { const fetchData = async () => { try { - const dayData = await axios.post(`/stats/getGlobalItemStats`, { - hours: (24*1), - itemid: props.ItemId, - }, { - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", + const dayData = await axios.post( + `/stats/getGlobalItemStats`, + { + hours: 24 * 1, + itemid: props.ItemId, }, - }); + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }, + ); setDayStats(dayData.data); - const weekData = await axios.post(`/stats/getGlobalItemStats`, { - hours: (24*7), - itemid: props.ItemId, - }, { - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", + const weekData = await axios.post( + `/stats/getGlobalItemStats`, + { + hours: 24 * 7, + itemid: props.ItemId, + }, + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, }, - }); + ); setWeekStats(weekData.data); - const monthData = await axios.post(`/stats/getGlobalItemStats`, { - hours: (24*30), - itemid: props.ItemId, - }, { - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", + const monthData = await axios.post( + `/stats/getGlobalItemStats`, + { + hours: 24 * 30, + itemid: props.ItemId, }, - }); + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }, + ); setMonthStats(monthData.data); - const allData = await axios.post(`/stats/getGlobalItemStats`, { - hours: (24*999), - itemid: props.ItemId, - }, { - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", + const allData = await axios.post( + `/stats/getGlobalItemStats`, + { + hours: 24 * 999, + itemid: props.ItemId, + }, + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, }, - }); + ); setAllStats(allData.data); } catch (error) { console.log(error); @@ -65,14 +81,14 @@ function GlobalStats(props) { fetchData(); const intervalId = setInterval(fetchData, 60000 * 5); return () => clearInterval(intervalId); - }, [props.ItemId,token]); + }, [props.ItemId, token]); // console.log(dayStats); return (

Item Stats

-
+
diff --git a/src/pages/components/item-info/globalstats/watchtimestats.js b/src/pages/components/item-info/globalstats/watchtimestats.js index c18469ae..ed1b005c 100644 --- a/src/pages/components/item-info/globalstats/watchtimestats.js +++ b/src/pages/components/item-info/globalstats/watchtimestats.js @@ -3,14 +3,13 @@ import React from "react"; import "../../../css/globalstats.css"; function WatchTimeStats(props) { - function formatTime(totalSeconds, numberClassName, labelClassName) { const units = [ - { label: 'Day', seconds: 86400 }, - { label: 'Hour', seconds: 3600 }, - { label: 'Minute', seconds: 60 }, + { 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) { @@ -18,32 +17,30 @@ function WatchTimeStats(props) { const formattedLabel = ( {label} - {value === 1 ? '' : 's'} + {value === 1 ? "" : "s"} ); result.push( {formattedValue} {formattedLabel} - + , ); totalSeconds -= value * seconds; } return result; }, []); - + if (parts.length === 0) { return ( <> -

0

{' '} +

0

{" "}

Minutes

); } - + return parts; } - - return (
@@ -52,11 +49,16 @@ function WatchTimeStats(props) {
-

{props.data.Plays || 0}

-

Plays /

- - <>{formatTime(props.data.total_playback_duration || 0,'stat-value','stat-unit')} +

{props.data.Plays || 0}

+

Plays /

+ <> + {formatTime( + props.data.total_playback_duration || 0, + "stat-value", + "stat-unit", + )} +
); diff --git a/src/pages/components/item-info/item-activity.js b/src/pages/components/item-info/item-activity.js index aff713f8..893c0fd0 100644 --- a/src/pages/components/item-info/item-activity.js +++ b/src/pages/components/item-info/item-activity.js @@ -4,21 +4,24 @@ import ActivityTable from "../activity/activity-table"; function ItemActivity(props) { const [data, setData] = useState(); - const token = localStorage.getItem('token'); - const [itemCount,setItemCount] = useState(10); + const token = localStorage.getItem("token"); + const [itemCount, setItemCount] = useState(10); useEffect(() => { - const fetchData = async () => { try { - const itemData = await axios.post(`/api/getItemHistory`, { - itemid: props.itemid, - }, { - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", + const itemData = await axios.post( + `/api/getItemHistory`, + { + itemid: props.itemid, + }, + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, }, - }); + ); setData(itemData.data); } catch (error) { console.log(error); @@ -31,8 +34,7 @@ function ItemActivity(props) { const intervalId = setInterval(fetchData, 60000 * 5); return () => clearInterval(intervalId); - }, [data, props.itemid,token]); - + }, [data, props.itemid, token]); if (!data) { return <>; @@ -40,24 +42,27 @@ function ItemActivity(props) { return (
-
-

Item Activity

-
-
Items
- +
+

Item Activity

+
+
Items
+ +
+
+
+
-
- - - -
-
); } diff --git a/src/pages/components/item-info/item-not-found.js b/src/pages/components/item-info/item-not-found.js index 5be03af1..9e292545 100644 --- a/src/pages/components/item-info/item-not-found.js +++ b/src/pages/components/item-info/item-not-found.js @@ -1,4 +1,4 @@ -import React, {useState} from "react"; +import React, { useState } from "react"; import axios from "axios"; import "../../css/error.css"; import { Button } from "react-bootstrap"; @@ -6,50 +6,48 @@ import Loading from "../general/loading"; function ItemNotFound(props) { const [itemId] = useState(props.itemId); - const [loading,setLoading] = useState(false); - const token = localStorage.getItem('token'); + const [loading, setLoading] = useState(false); + const token = localStorage.getItem("token"); async function fetchItem() { setLoading(true); const result = await axios - .post("/sync/fetchItem", { - itemId:itemId - }, { - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - }) - .catch((error) => { - setLoading(false); - console.log(error); - }); - - if(result) - { - + .post( + "/sync/fetchItem", + { + itemId: itemId, + }, + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }, + ) + .catch((error) => { + setLoading(false); + console.log(error); + }); + + if (result) { await props.fetchdataMethod(); setLoading(false); - } - - } - if(loading) - { - return ; + if (loading) { + return ; } - - return (

{props.message}

- +
); } -export default ItemNotFound; \ No newline at end of file +export default ItemNotFound; diff --git a/src/pages/components/item-info/more-items.js b/src/pages/components/item-info/more-items.js index 46014fa2..25ad7e27 100644 --- a/src/pages/components/item-info/more-items.js +++ b/src/pages/components/item-info/more-items.js @@ -10,71 +10,73 @@ function MoreItems(props) { const [data, setData] = useState(); const [config, setConfig] = useState(); - useEffect(() => { - const fetchConfig = async () => { - try { - const newConfig = await Config(); - setConfig(newConfig); - } catch (error) { - console.log(error); - } - }; - + try { + const newConfig = await Config(); + setConfig(newConfig); + } catch (error) { + console.log(error); + } + }; const fetchData = async () => { - if(config) - { + if (config) { try { - let url=`/api/getSeasons`; - if(props.data.Type==='Season') - { - url=`/api/getEpisodes`; + let url = `/api/getSeasons`; + if (props.data.Type === "Season") { + url = `/api/getEpisodes`; } - - const itemData = await axios.post(url, { - Id: props.data.EpisodeId||props.data.Id - },{ - headers: { - Authorization: `Bearer ${config.token}`, - "Content-Type": "application/json", + + const itemData = await axios.post( + url, + { + Id: props.data.EpisodeId || props.data.Id, + }, + { + headers: { + Authorization: `Bearer ${config.token}`, + "Content-Type": "application/json", + }, }, - }); + ); setData(itemData.data); } catch (error) { console.log(error); } } - }; fetchData(); if (!config) { - fetchConfig(); + fetchConfig(); } const intervalId = setInterval(fetchData, 60000 * 5); return () => clearInterval(intervalId); }, [config, props]); - - if (!data || data.lenght===0) { + if (!data || data.lenght === 0) { return <>; } return (
-

{props.data.Type==="Season" ? "Episodes" : "Seasons"}

-
- - {data.sort((a,b) => a.IndexNumber-b.IndexNumber).map((item) => ( - +

+ {props.data.Type === "Season" ? "Episodes" : "Seasons"} +

+
+ {data + .sort((a, b) => a.IndexNumber - b.IndexNumber) + .map((item) => ( + ))} - -
- +
); } diff --git a/src/pages/components/item-info/more-items/more-items-card.js b/src/pages/components/item-info/more-items/more-items-card.js index 8604e81d..58b14171 100644 --- a/src/pages/components/item-info/more-items/more-items-card.js +++ b/src/pages/components/item-info/more-items/more-items-card.js @@ -1,64 +1,98 @@ -import React, {useState} from "react"; -import { Blurhash } from 'react-blurhash'; +import React, { useState } from "react"; +import { Blurhash } from "react-blurhash"; import { Link } from "react-router-dom"; -import { useParams } from 'react-router-dom'; +import { useParams } from "react-router-dom"; import "../../../css/lastplayed.css"; - - function MoreItemCards(props) { const { Id } = useParams(); const [loaded, setLoaded] = useState(false); const [fallback, setFallback] = useState(false); return ( -
- -
- {((props.data.ImageBlurHashes && props.data.ImageBlurHashes!=null) || (props.data.PrimaryImageHash && props.data.PrimaryImageHash!=null) ) && !loaded ? : null} - - {fallback ? - setLoaded(true)} - style={loaded ? { backgroundImage: `url(path/to/image.jpg)` } : { display: 'none' }} - /> - : - setLoaded(true)} - onError={() => setFallback(true)} - style={loaded ? { backgroundImage: `url(path/to/image.jpg)` } : { display: 'none' }} - /> - } +
+ +
+ {((props.data.ImageBlurHashes && + props.data.ImageBlurHashes != null) || + (props.data.PrimaryImageHash && + props.data.PrimaryImageHash != null)) && + !loaded ? ( + + ) : null} -
- + {fallback ? ( + setLoaded(true)} + style={ + loaded + ? { backgroundImage: `url(path/to/image.jpg)` } + : { display: "none" } + } + /> + ) : ( + setLoaded(true)} + onError={() => setFallback(true)} + style={ + loaded + ? { backgroundImage: `url(path/to/image.jpg)` } + : { display: "none" } + } + /> + )} +
+
{props.data.Name}
- {props.data.Type==="Episode"? -
S{props.data.ParentIndexNumber || 0} - E{props.data.IndexNumber || 0}
- : + {props.data.Type === "Episode" ? ( +
+ {" "} + S{props.data.ParentIndexNumber || 0} - E + {props.data.IndexNumber || 0} +
+ ) : ( <> - } - + )}
- -
); } diff --git a/src/pages/components/library-info.js b/src/pages/components/library-info.js index 6a1d1420..efb8838a 100644 --- a/src/pages/components/library-info.js +++ b/src/pages/components/library-info.js @@ -1,44 +1,42 @@ -import { useParams } from 'react-router-dom'; +import { useParams } from "react-router-dom"; import React, { 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 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"; 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", + console.log("getdata"); + 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); @@ -49,55 +47,72 @@ function LibraryInfo() { const intervalId = setInterval(fetchData, 60000 * 5); return () => clearInterval(intervalId); - }, [LibraryId,token]); + }, [LibraryId, token]); - if(!data) - { - return ; + if (!data) { + return ; } - - + return (
- - -
-
- {data.CollectionType==="tvshows" ? - - - : - - } -
-
-

{data.Name}

+
+
+ {data.CollectionType === "tvshows" ? ( + + ) : ( + + )} +
+
+

{data.Name}

- - - + + + +
- - - -
- - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + +
); } diff --git a/src/pages/components/library/RecentlyAdded/recently-added-card.js b/src/pages/components/library/RecentlyAdded/recently-added-card.js index bd38f900..2e7cce2b 100644 --- a/src/pages/components/library/RecentlyAdded/recently-added-card.js +++ b/src/pages/components/library/RecentlyAdded/recently-added-card.js @@ -1,38 +1,48 @@ -import React, {useState} from "react"; -import { Blurhash } from 'react-blurhash'; +import React, { useState } from "react"; +import { Blurhash } from "react-blurhash"; import { Link } from "react-router-dom"; import "../../../css/lastplayed.css"; - - function RecentlyAddedCard(props) { const [loaded, setLoaded] = useState(false); return (
- -
- {loaded ? null : } - setLoaded(true)} - style={loaded ? { } : { display: 'none' }} - /> -
- + +
+ {loaded ? null : ( + + )} + setLoaded(true)} + style={loaded ? {} : { display: "none" }} + /> +
+
- -
{(props.data.Type==="Episode"? props.data.SeriesName :props.data.Name)}
- +
+ {" "} + {props.data.Type === "Episode" + ? props.data.SeriesName + : props.data.Name} +
- -
); } diff --git a/src/pages/components/library/globalstats/watchtimestats.js b/src/pages/components/library/globalstats/watchtimestats.js index 9a661c62..4e49326e 100644 --- a/src/pages/components/library/globalstats/watchtimestats.js +++ b/src/pages/components/library/globalstats/watchtimestats.js @@ -3,14 +3,13 @@ import React from "react"; import "../../../css/globalstats.css"; function WatchTimeStats(props) { - function formatTime(totalSeconds, numberClassName, labelClassName) { const units = [ - { label: 'Day', seconds: 86400 }, - { label: 'Hour', seconds: 3600 }, - { label: 'Minute', seconds: 60 }, + { 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) { @@ -18,32 +17,30 @@ function WatchTimeStats(props) { const formattedLabel = ( {label} - {value === 1 ? '' : 's'} + {value === 1 ? "" : "s"} ); result.push( {formattedValue} {formattedLabel} - + , ); totalSeconds -= value * seconds; } return result; }, []); - + if (parts.length === 0) { return ( <> -

0

{' '} +

0

{" "}

Minutes

); } - + return parts; } - - return (
@@ -52,11 +49,16 @@ function WatchTimeStats(props) {
-

{props.data.Plays || 0}

-

Plays /

- - <>{formatTime(props.data.total_playback_duration || 0,'stat-value','stat-unit')} +

{props.data.Plays || 0}

+

Plays /

+ <> + {formatTime( + props.data.total_playback_duration || 0, + "stat-value", + "stat-unit", + )} +
); diff --git a/src/pages/components/library/last-watched.js b/src/pages/components/library/last-watched.js index fdd5a606..7cc29514 100644 --- a/src/pages/components/library/last-watched.js +++ b/src/pages/components/library/last-watched.js @@ -5,7 +5,6 @@ import axios from "axios"; import LastWatchedCard from "../general/last-watched-card"; - import Config from "../../../lib/config"; import "../../css/users/user-details.css"; @@ -13,47 +12,47 @@ function LibraryLastWatched(props) { const [data, setData] = useState(); const [config, setConfig] = useState(); - useEffect(() => { - const fetchConfig = async () => { - try { - const newConfig = await Config(); - setConfig(newConfig); - } catch (error) { - console.log(error); - } - }; + try { + const newConfig = await Config(); + setConfig(newConfig); + } catch (error) { + console.log(error); + } + }; const fetchData = async () => { try { - const itemData = await axios.post(`/stats/getLibraryLastPlayed`, { - libraryid: props.LibraryId, - }, { - headers: { - Authorization: `Bearer ${config.token}`, - "Content-Type": "application/json", + const itemData = await axios.post( + `/stats/getLibraryLastPlayed`, + { + libraryid: props.LibraryId, + }, + { + headers: { + Authorization: `Bearer ${config.token}`, + "Content-Type": "application/json", + }, }, - }); + ); setData(itemData.data); } catch (error) { console.log(error); } }; - if (!config) { - fetchConfig(); + fetchConfig(); } - + if (!data && config) { fetchData(); } const intervalId = setInterval(fetchData, 60000 * 5); return () => clearInterval(intervalId); - }, [data,config, props.LibraryId]); - + }, [data, config, props.LibraryId]); if (!data || !config) { return <>; @@ -61,14 +60,16 @@ function LibraryLastWatched(props) { return (
-

Last Watched

-
+

Last Watched

+
{data.map((item) => ( - - ))} - -
- + + ))} +
); } diff --git a/src/pages/components/library/library-activity.js b/src/pages/components/library/library-activity.js index aa8f002c..ffe20f80 100644 --- a/src/pages/components/library/library-activity.js +++ b/src/pages/components/library/library-activity.js @@ -5,21 +5,24 @@ import ActivityTable from "../activity/activity-table"; function LibraryActivity(props) { const [data, setData] = useState(); - const token = localStorage.getItem('token'); - const [itemCount,setItemCount] = useState(10); + const token = localStorage.getItem("token"); + const [itemCount, setItemCount] = useState(10); useEffect(() => { - const fetchData = async () => { try { - const libraryrData = await axios.post(`/api/getLibraryHistory`, { - libraryid: props.LibraryId, - }, { - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", + const libraryrData = await axios.post( + `/api/getLibraryHistory`, + { + libraryid: props.LibraryId, + }, + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, }, - }); + ); setData(libraryrData.data); } catch (error) { console.log(error); @@ -32,8 +35,7 @@ function LibraryActivity(props) { const intervalId = setInterval(fetchData, 60000 * 5); return () => clearInterval(intervalId); - }, [data, props.LibraryId,token]); - + }, [data, props.LibraryId, token]); if (!data) { return <>; @@ -41,24 +43,27 @@ function LibraryActivity(props) { return (
-
-

Library Activity

-
-
Items
- +
+

Library Activity

+
+
Items
+ +
+
+
+
-
- - - -
-
); } diff --git a/src/pages/components/library/library-card.js b/src/pages/components/library/library-card.js index 27845c07..7ad55d53 100644 --- a/src/pages/components/library/library-card.js +++ b/src/pages/components/library/library-card.js @@ -1,10 +1,10 @@ -import React, {useState} from "react"; +import React, { useState } from "react"; import { Link } from "react-router-dom"; import "../../css/library/library-card.css"; -import Card from 'react-bootstrap/Card'; -import Row from 'react-bootstrap/Row'; -import Col from 'react-bootstrap/Col'; +import Card from "react-bootstrap/Card"; +import Row from "react-bootstrap/Row"; +import Col from "react-bootstrap/Col"; import TvLineIcon from "remixicon-react/TvLineIcon"; import FilmLineIcon from "remixicon-react/FilmLineIcon"; @@ -13,12 +13,24 @@ import CheckboxMultipleBlankLineIcon from "remixicon-react/CheckboxMultipleBlank function LibraryCard(props) { const [imageLoaded, setImageLoaded] = useState(true); - const SeriesIcon= ; - const MovieIcon= ; - const MusicIcon= ; - const MixedIcon= ; + const SeriesIcon = ; + const MovieIcon = ; + const MusicIcon = ; + const MixedIcon = ( + + ); - const default_image=
{props.data.CollectionType==='tvshows' ? SeriesIcon : props.data.CollectionType==='movies'? MovieIcon : props.data.CollectionType==='music'? MusicIcon : MixedIcon}
; + const default_image = ( +
+ {props.data.CollectionType === "tvshows" + ? SeriesIcon + : props.data.CollectionType === "movies" + ? MovieIcon + : props.data.CollectionType === "music" + ? MusicIcon + : MixedIcon}{" "} +
+ ); function formatFileSize(sizeInBytes) { const sizeInKB = sizeInBytes / 1024; // 1 KB = 1024 bytes @@ -44,33 +56,30 @@ function LibraryCard(props) { } } } - - function formatTotalWatchTime(seconds) { const days = Math.floor(seconds / 86400); // 1 day = 86400 seconds const hours = Math.floor((seconds % 86400) / 3600); // 1 hour = 3600 seconds const minutes = Math.floor(((seconds % 86400) % 3600) / 60); // 1 minute = 60 seconds - - let formattedTime = ''; + + let formattedTime = ""; if (days) { - formattedTime += `${days} day${days > 1 ? 's' : ''}`; + formattedTime += `${days} day${days > 1 ? "s" : ""}`; } - + if (hours) { - formattedTime += ` ${hours} hour${hours > 1 ? 's' : ''}`; + formattedTime += ` ${hours} hour${hours > 1 ? "s" : ""}`; } - + if (minutes) { - formattedTime += ` ${minutes} minute${minutes > 1 ? 's' : ''}`; + formattedTime += ` ${minutes} minute${minutes > 1 ? "s" : ""}`; } - + if (!days && !hours && !minutes) { - formattedTime = '0 minutes'; + formattedTime = "0 minutes"; } - + return formattedTime; - } function ticksToTimeString(ticks) { const seconds = Math.floor(ticks / 10000000); @@ -78,133 +87,184 @@ function LibraryCard(props) { const days = Math.floor((seconds % (86400 * 30)) / 86400); // 1 day = 86400 seconds const hours = Math.floor((seconds % 86400) / 3600); // 1 hour = 3600 seconds const minutes = Math.floor((seconds % 3600) / 60); // 1 minute = 60 seconds - + const timeComponents = []; - + if (months) { - timeComponents.push(`${months} Month${months > 1 ? 's' : ''}`); + timeComponents.push(`${months} Month${months > 1 ? "s" : ""}`); } - + if (days) { - timeComponents.push(`${days} day${days > 1 ? 's' : ''}`); + timeComponents.push(`${days} day${days > 1 ? "s" : ""}`); } - + if (hours) { - timeComponents.push(`${hours} hour${hours > 1 ? 's' : ''}`); + timeComponents.push(`${hours} hour${hours > 1 ? "s" : ""}`); } - + if (!months && minutes) { - timeComponents.push(`${minutes} minute${minutes > 1 ? 's' : ''}`); + timeComponents.push(`${minutes} minute${minutes > 1 ? "s" : ""}`); } - - const formattedTime = timeComponents.length > 0 ? timeComponents.join(' ') : '0 minutes'; + + const formattedTime = + timeComponents.length > 0 ? timeComponents.join(" ") : "0 minutes"; return formattedTime; } - function formatLastActivityTime(time) { const units = { - days: ['Day', 'Days'], - hours: ['Hour', 'Hours'], - minutes: ['Minute', 'Minutes'] + days: ["Day", "Days"], + hours: ["Hour", "Hours"], + minutes: ["Minute", "Minutes"], }; - - let formattedTime = ''; - + + let formattedTime = ""; + for (const unit in units) { if (time[unit]) { const unitName = units[unit][time[unit] > 1 ? 1 : 0]; formattedTime += `${time[unit]} ${unitName} `; } } - + return `${formattedTime}ago`; } - + return ( - - -
- - {imageLoaded? - - setImageLoaded(false)} - /> - : - default_image + + +
+ {imageLoaded ? ( + - - - - - - Library - {props.data.Name} - - - - Type - {props.data.CollectionType==='tvshows' ? 'Series' : props.data.CollectionType==='movies'? "Movies" : props.data.CollectionType==='music'? "Music" : 'Mixed'} - - - - Total Time - {ticksToTimeString(props.data && props.data.total_play_time ? props.data.total_play_time:0)} - - - - Total Files - {props.metadata && props.metadata.files ? props.metadata.files :0} - - - - Library Size - {formatFileSize(props.metadata && props.metadata.Size ? props.metadata.Size:0)} - - - - Total Plays - {props.data.Plays} - - - - Total Playback - {formatTotalWatchTime(props.data.total_playback_duration)} - - - - Last Played - {props.data.ItemName || 'n/a'} - - - - Last Activity - {props.data.LastActivity ? formatLastActivityTime(props.data.LastActivity) : 'never'} - - - - {props.data.CollectionType==='tvshows' ? 'Series' : props.data.CollectionType==='movies'? "Movies" : props.data.CollectionType==='music'? "Songs" : 'Files'} - {props.data.Library_Count} - - - - Seasons - {props.data.CollectionType==='tvshows' ? props.data.Season_Count : ''} - - - - Episodes - {props.data.CollectionType==='tvshows' ? props.data.Episode_Count : ''} - - - - - + onError={() => setImageLoaded(false)} + /> + ) : ( + default_image + )} +
+ + + + + Library + {props.data.Name} + + + + Type + + {props.data.CollectionType === "tvshows" + ? "Series" + : props.data.CollectionType === "movies" + ? "Movies" + : props.data.CollectionType === "music" + ? "Music" + : "Mixed"} + + + + + Total Time + + {ticksToTimeString( + props.data && props.data.total_play_time + ? props.data.total_play_time + : 0, + )} + + + + + Total Files + + {props.metadata && props.metadata.files ? props.metadata.files : 0} + + + + + Library Size + + {formatFileSize( + props.metadata && props.metadata.Size ? props.metadata.Size : 0, + )} + + + + + Total Plays + {props.data.Plays} + + + + Total Playback + + {formatTotalWatchTime(props.data.total_playback_duration)} + + + + + Last Played + {props.data.ItemName || "n/a"} + + + + Last Activity + + {props.data.LastActivity + ? formatLastActivityTime(props.data.LastActivity) + : "never"} + + + + + + {props.data.CollectionType === "tvshows" + ? "Series" + : props.data.CollectionType === "movies" + ? "Movies" + : props.data.CollectionType === "music" + ? "Songs" + : "Files"} + + {props.data.Library_Count} + + + + Seasons + + {props.data.CollectionType === "tvshows" + ? props.data.Season_Count + : ""} + + + + + Episodes + + {props.data.CollectionType === "tvshows" + ? props.data.Episode_Count + : ""} + + + +
); } diff --git a/src/pages/components/library/library-items.js b/src/pages/components/library/library-items.js index 9fe752a5..5d8a2200 100644 --- a/src/pages/components/library/library-items.js +++ b/src/pages/components/library/library-items.js @@ -1,13 +1,11 @@ import React, { useState, useEffect } from "react"; import axios from "axios"; -import {FormControl, FormSelect, Button } from 'react-bootstrap'; -import SortAscIcon from 'remixicon-react/SortAscIcon'; -import SortDescIcon from 'remixicon-react/SortDescIcon'; - +import { FormControl, FormSelect, Button } from "react-bootstrap"; +import SortAscIcon from "remixicon-react/SortAscIcon"; +import SortDescIcon from "remixicon-react/SortDescIcon"; import MoreItemCards from "../item-info/more-items/more-items-card"; - import Config from "../../../lib/config"; import "../../css/library/media-items.css"; import "../../css/width_breakpoint_css.css"; @@ -16,73 +14,67 @@ import "../../css/radius_breakpoint_css.css"; function LibraryItems(props) { const [data, setData] = useState(); const [config, setConfig] = useState(); - const [searchQuery, setSearchQuery] = useState(''); - const [sortOrder, setSortOrder] = useState('Title'); + const [searchQuery, setSearchQuery] = useState(""); + const [sortOrder, setSortOrder] = useState("Title"); const [sortAsc, setSortAsc] = useState(true); - useEffect(() => { - const fetchConfig = async () => { - try { - const newConfig = await Config(); - setConfig(newConfig); - } catch (error) { - console.log(error); - } - }; + try { + const newConfig = await Config(); + setConfig(newConfig); + } catch (error) { + console.log(error); + } + }; const fetchData = async () => { try { - const itemData = await axios.post(`/stats/getLibraryItemsWithStats`, { - libraryid: props.LibraryId, - }, { - headers: { - Authorization: `Bearer ${config.token}`, - "Content-Type": "application/json", + const itemData = await axios.post( + `/stats/getLibraryItemsWithStats`, + { + libraryid: props.LibraryId, }, - }); + { + headers: { + Authorization: `Bearer ${config.token}`, + "Content-Type": "application/json", + }, + }, + ); setData(itemData.data); } catch (error) { console.log(error); } }; - if (!config) { - fetchConfig(); - }else{ - fetchData(); + fetchConfig(); + } else { + fetchData(); } - - + const intervalId = setInterval(fetchData, 60000 * 5); return () => clearInterval(intervalId); }, [config, props.LibraryId]); - function sortOrderLogic(_sortOrder) - { - if(_sortOrder!=='Title') - { + function sortOrderLogic(_sortOrder) { + if (_sortOrder !== "Title") { setSortAsc(false); - }else{ + } else { setSortAsc(true); } setSortOrder(_sortOrder); - } let filteredData = data; - if(searchQuery) - { + if (searchQuery) { filteredData = data.filter((item) => - item.Name.toLowerCase().includes(searchQuery.toLowerCase()) - ); + item.Name.toLowerCase().includes(searchQuery.toLowerCase()), + ); } - - if (!data || !config) { return <>; } @@ -92,70 +84,65 @@ function LibraryItems(props) {

Media

-
-
- sortOrderLogic(e.target.value) } className="my-md-3 w-100 rounded-0 rounded-start"> +
+ sortOrderLogic(e.target.value)} + className="my-md-3 w-100 rounded-0 rounded-start" + > - -
- setSearchQuery(e.target.value)} className="ms-md-3 my-3 w-sm-100 w-md-75" /> - -
- +
+ setSearchQuery(e.target.value)} + className="ms-md-3 my-3 w-sm-100 w-md-75" + />
+
+
+ {filteredData + .sort((a, b) => { + const titleA = a.Name.replace(/^(A |An |The )/i, ""); + const titleB = b.Name.replace(/^(A |An |The )/i, ""); - -
- {filteredData.sort((a, b) => - { - const titleA = a.Name.replace(/^(A |An |The )/i, ''); - const titleB = b.Name.replace(/^(A |An |The )/i, ''); - - if(sortOrder==='Title') - { - if(sortAsc) - { - return titleA.localeCompare(titleB); - } - return titleB.localeCompare(titleA); - }else if(sortOrder==='Views') - { - if(sortAsc) - { - return a.times_played-b.times_played; - } - return b.times_played-a.times_played; + if (sortOrder === "Title") { + if (sortAsc) { + return titleA.localeCompare(titleB); + } + return titleB.localeCompare(titleA); + } else if (sortOrder === "Views") { + if (sortAsc) { + return a.times_played - b.times_played; } - else - { - if(sortAsc) - { - return a.total_play_time-b.total_play_time; - } - return b.total_play_time-a.total_play_time; + return b.times_played - a.times_played; + } else { + if (sortAsc) { + return a.total_play_time - b.total_play_time; } - - - } - ).map((item) => ( - + return b.total_play_time - a.total_play_time; + } + }) + .map((item) => ( + ))} - -
- +
); } diff --git a/src/pages/components/library/library-stats.js b/src/pages/components/library/library-stats.js index 2e0a9b4d..b1aba874 100644 --- a/src/pages/components/library/library-stats.js +++ b/src/pages/components/library/library-stats.js @@ -9,53 +9,69 @@ function LibraryGlobalStats(props) { const [weekStats, setWeekStats] = useState({}); const [monthStats, setMonthStats] = useState({}); const [allStats, setAllStats] = useState({}); - const token = localStorage.getItem('token'); + const token = localStorage.getItem("token"); useEffect(() => { const fetchData = async () => { try { - const dayData = await axios.post(`/stats/getGlobalLibraryStats`, { - hours: (24*1), - libraryid: props.LibraryId, - }, { - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", + const dayData = await axios.post( + `/stats/getGlobalLibraryStats`, + { + hours: 24 * 1, + libraryid: props.LibraryId, }, - }); + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }, + ); setDayStats(dayData.data); - const weekData = await axios.post(`/stats/getGlobalLibraryStats`, { - hours: (24*7), - libraryid: props.LibraryId, - }, { - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", + const weekData = await axios.post( + `/stats/getGlobalLibraryStats`, + { + hours: 24 * 7, + libraryid: props.LibraryId, + }, + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, }, - }); + ); setWeekStats(weekData.data); - const monthData = await axios.post(`/stats/getGlobalLibraryStats`, { - hours: (24*30), - libraryid: props.LibraryId, - }, { - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", + const monthData = await axios.post( + `/stats/getGlobalLibraryStats`, + { + hours: 24 * 30, + libraryid: props.LibraryId, }, - }); + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }, + ); setMonthStats(monthData.data); - const allData = await axios.post(`/stats/getGlobalLibraryStats`, { - hours: (24*999), - libraryid: props.LibraryId, - }, { - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", + const allData = await axios.post( + `/stats/getGlobalLibraryStats`, + { + hours: 24 * 999, + libraryid: props.LibraryId, + }, + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, }, - }); + ); setAllStats(allData.data); } catch (error) { console.log(error); @@ -65,12 +81,12 @@ function LibraryGlobalStats(props) { fetchData(); const intervalId = setInterval(fetchData, 60000 * 5); return () => clearInterval(intervalId); - }, [props.LibraryId,token]); + }, [props.LibraryId, token]); return (

Library Stats

-
+
diff --git a/src/pages/components/library/recently-added.js b/src/pages/components/library/recently-added.js index 86ad1b36..edab38db 100644 --- a/src/pages/components/library/recently-added.js +++ b/src/pages/components/library/recently-added.js @@ -8,32 +8,33 @@ import ErrorBoundary from "../general/ErrorBoundary"; function RecentlyAdded(props) { const [data, setData] = useState(); - const token = localStorage.getItem('token'); - + const token = localStorage.getItem("token"); useEffect(() => { - - - - const fetchData = async () => { try { - let url=`/proxy/getRecentlyAdded`; - if(props.LibraryId) - { - url+=`?libraryid=${props.LibraryId}`; + let url = `/proxy/getRecentlyAdded`; + if (props.LibraryId) { + url += `?libraryid=${props.LibraryId}`; } - + const itemData = await axios.get(url, { headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, }); - - if(itemData && typeof itemData.data === 'object' && Array.isArray(itemData.data)) - { - setData(itemData.data.filter((item) => ["Series", "Movie","Audio","Episode"].includes(item.Type))); + + if ( + itemData && + typeof itemData.data === "object" && + Array.isArray(itemData.data) + ) { + setData( + itemData.data.filter((item) => + ["Series", "Movie", "Audio", "Episode"].includes(item.Type), + ), + ); } } catch (error) { console.log(error); @@ -44,12 +45,9 @@ function RecentlyAdded(props) { fetchData(); } - - const intervalId = setInterval(fetchData, 60000 * 5); return () => clearInterval(intervalId); - }, [data, props.LibraryId,token]); - + }, [data, props.LibraryId, token]); if (!data) { return <>; @@ -57,16 +55,15 @@ function RecentlyAdded(props) { return (
-

Recently Added

-
- {data && data.map((item) => ( - - - +

Recently Added

+
+ {data && + data.map((item) => ( + + + ))} - -
- +
); } diff --git a/src/pages/components/libraryOverview.js b/src/pages/components/libraryOverview.js index cef67caa..c5555cac 100644 --- a/src/pages/components/libraryOverview.js +++ b/src/pages/components/libraryOverview.js @@ -5,21 +5,19 @@ import Loading from "./general/loading"; import LibraryStatComponent from "./libraryStatCard/library-stat-component"; - import TvLineIcon from "remixicon-react/TvLineIcon"; import FilmLineIcon from "remixicon-react/FilmLineIcon"; import FileMusicLineIcon from "remixicon-react/FileMusicLineIcon"; import CheckboxMultipleBlankLineIcon from "remixicon-react/CheckboxMultipleBlankLineIcon"; export default function LibraryOverView() { - const token = localStorage.getItem('token'); - const SeriesIcon= ; - const MovieIcon= ; - const MusicIcon= ; - const MixedIcon= ; + const token = localStorage.getItem("token"); + const SeriesIcon = ; + const MovieIcon = ; + const MusicIcon = ; + const MixedIcon = ; const [data, setData] = useState(); - useEffect(() => { const fetchData = () => { const url = `/stats/getLibraryOverview`; @@ -37,7 +35,7 @@ export default function LibraryOverView() { if (!data || data.length === 0) { fetchData(); } - }, [data,token]); + }, [data, token]); if (!data) { return ; @@ -47,13 +45,31 @@ export default function LibraryOverView() {

Library Overview

- - stat.CollectionType === "movies")} heading={"MOVIE LIBRARIES"} units={"MOVIES"} icon={MovieIcon}/> - stat.CollectionType === "tvshows")} heading={"SHOW LIBRARIES"} units={"SERIES / SEASONS / EPISODES"} icon={SeriesIcon}/> - stat.CollectionType === "music")} heading={"MUSIC LIBRARIES"} units={"SONGS"} icon={MusicIcon}/> - stat.CollectionType === "mixed")} heading={"MIXED LIBRARIES"} units={"ITEMS"} icon={MixedIcon}/> - -
+ stat.CollectionType === "movies")} + heading={"MOVIE LIBRARIES"} + units={"MOVIES"} + icon={MovieIcon} + /> + stat.CollectionType === "tvshows")} + heading={"SHOW LIBRARIES"} + units={"SERIES / SEASONS / EPISODES"} + icon={SeriesIcon} + /> + stat.CollectionType === "music")} + heading={"MUSIC LIBRARIES"} + units={"SONGS"} + icon={MusicIcon} + /> + stat.CollectionType === "mixed")} + heading={"MIXED LIBRARIES"} + units={"ITEMS"} + icon={MixedIcon} + /> +
); } diff --git a/src/pages/components/libraryStatCard/library-stat-component.js b/src/pages/components/libraryStatCard/library-stat-component.js index 3e4000b6..2c4d4508 100644 --- a/src/pages/components/libraryStatCard/library-stat-component.js +++ b/src/pages/components/libraryStatCard/library-stat-component.js @@ -3,65 +3,75 @@ import { Link } from "react-router-dom"; import { Row, Col, Card } from "react-bootstrap"; function LibraryStatComponent(props) { - if (props.data.length === 0) { return <>; } const cardStyle = { backgroundImage: `url(${props.base_url}/Items/${props.data[0].Id}/Images/Backdrop/?fillWidth=300&quality=10), linear-gradient(to right, #00A4DC, #AA5CC3)`, - height:'100%', - backgroundSize: 'cover', + height: "100%", + backgroundSize: "cover", }; const cardBgStyle = { // backdropFilter: 'blur(5px)', - backgroundColor: 'rgb(0, 0, 0, 0.6)', - height:'100%', + backgroundColor: "rgb(0, 0, 0, 0.6)", + height: "100%", }; - return ( -
- - - -
- {props.icon} -
- +
+ + +
{props.icon}
+ - - - -
- {props.heading} -
-
- {props.units} -
-
- {props.data && - props.data.map((item, index) => ( -
- -
- {index + 1} - {item.Name} + + + +
+ + {props.heading} +
- - - {item.CollectionType ==='tvshows'? (item.Library_Count+' / '+item.Season_Count+' / '+item.Episode_Count): item.Library_Count} - - -
- ))} - - - -
- +
+ + {props.units} + +
+ + {props.data && + props.data.map((item, index) => ( +
+
+ + {index + 1} + + + {item.Name} + +
+ + + {item.CollectionType === "tvshows" + ? item.Library_Count + + " / " + + item.Season_Count + + " / " + + item.Episode_Count + : item.Library_Count} + +
+ ))} +
+ +
+
+ ); } diff --git a/src/pages/components/playbackactivity.js b/src/pages/components/playbackactivity.js index 4f2781b1..d87acb22 100644 --- a/src/pages/components/playbackactivity.js +++ b/src/pages/components/playbackactivity.js @@ -10,7 +10,7 @@ import Loading from "./loading"; function PlaybackActivity() { const [data, setData] = useState([]); -// const [config, setConfig] = useState(null); + // const [config, setConfig] = useState(null); const [sortConfig, setSortConfig] = useState({ key: null, direction: null }); function handleSort(key) { @@ -36,23 +36,19 @@ function PlaybackActivity() { } useEffect(() => { - - const fetchData = () => { - - axios - .get('/stats/getPlaybackActivity') - .then((data) => { - console.log("data"); - setData(data.data); - console.log(data); - }) - .catch((error) => { - console.log(error); - }); + axios + .get("/stats/getPlaybackActivity") + .then((data) => { + console.log("data"); + setData(data.data); + console.log(data); + }) + .catch((error) => { + console.log(error); + }); }; - if (data.length === 0) { fetchData(); } @@ -90,8 +86,12 @@ function PlaybackActivity() { handleSort("UserName")}>User handleSort("NowPlayingItemName")}>Watched handleSort("NowPlayingItemName")}>Episode - handleSort("PlaybackDuration")}>Playback Duration - handleSort("ActivityDateInserted")}>Playback Timestamp + handleSort("PlaybackDuration")}> + Playback Duration + + handleSort("ActivityDateInserted")}> + Playback Timestamp + @@ -99,9 +99,14 @@ function PlaybackActivity() { {item.UserName} {item.SeriesName || item.NowPlayingItemName} - {item.SeriesName ? item.NowPlayingItemName: '' } + {item.SeriesName ? item.NowPlayingItemName : ""} {convertSecondsToHMS(item.PlaybackDuration)} - {new Date(item.ActivityDateInserted).toLocaleString("en-GB", options)} + + {new Date(item.ActivityDateInserted).toLocaleString( + "en-GB", + options, + )} + ))} diff --git a/src/pages/components/sessions/session-card.js b/src/pages/components/sessions/session-card.js index fb7d2a72..b318b8a0 100644 --- a/src/pages/components/sessions/session-card.js +++ b/src/pages/components/sessions/session-card.js @@ -1,17 +1,16 @@ import React from "react"; -import { Link } from 'react-router-dom'; -import Card from 'react-bootstrap/Card'; -import Row from 'react-bootstrap/Row'; -import Col from 'react-bootstrap/Col'; -import Container from 'react-bootstrap/Container'; +import { Link } from "react-router-dom"; +import Card from "react-bootstrap/Card"; +import Row from "react-bootstrap/Row"; +import Col from "react-bootstrap/Col"; +import Container from "react-bootstrap/Container"; import AccountCircleFillIcon from "remixicon-react/AccountCircleFillIcon"; import PlayFillIcon from "remixicon-react/PlayFillIcon"; import PauseFillIcon from "remixicon-react/PauseFillIcon"; import { clientData } from "../../../lib/devices"; -import Tooltip from "@mui/material/Tooltip"; - +import Tooltip from "@mui/material/Tooltip"; function ticksToTimeString(ticks) { // Convert ticks to seconds @@ -28,180 +27,239 @@ function ticksToTimeString(ticks) { return timeString; } function convertBitrate(bitrate) { - if(!bitrate) - { - return 'N/A'; + if (!bitrate) { + return "N/A"; } const kbps = (bitrate / 1000).toFixed(1); const mbps = (bitrate / 1000000).toFixed(1); if (kbps >= 1000) { - return mbps+' Mbps'; + return mbps + " Mbps"; } else { - return kbps+' Kbps'; + return kbps + " Kbps"; } } function sessionCard(props) { const cardStyle = { - backgroundImage: `url(Proxy/Items/Images/Backdrop?id=${(props.data.session.NowPlayingItem.SeriesId ? props.data.session.NowPlayingItem.SeriesId : props.data.session.NowPlayingItem.Id)}&fillHeight=320&fillWidth=213&quality=80), linear-gradient(to right, #00A4DC, #AA5CC3)`, - height:'100%', - backgroundSize: 'cover', + backgroundImage: `url(Proxy/Items/Images/Backdrop?id=${ + props.data.session.NowPlayingItem.SeriesId + ? props.data.session.NowPlayingItem.SeriesId + : props.data.session.NowPlayingItem.Id + }&fillHeight=320&fillWidth=213&quality=80), linear-gradient(to right, #00A4DC, #AA5CC3)`, + height: "100%", + backgroundSize: "cover", }; const cardBgStyle = { - backdropFilter: 'blur(5px)', - backgroundColor: 'rgb(0, 0, 0, 0.6)', - height:'100%', + backdropFilter: "blur(5px)", + backgroundColor: "rgb(0, 0, 0, 0.6)", + height: "100%", }; - return ( -
- - - - - - - - - - - - +
+ + + + + + + + - {props.data.session.DeviceName} - {props.data.session.Client + " " + props.data.session.ApplicationVersion} - - {props.data.session.PlayState.PlayMethod} - {(props.data.session.NowPlayingItem.MediaStreams ? '( '+props.data.session.NowPlayingItem.MediaStreams.find(stream => stream.Type==='Video')?.Codec.toUpperCase()+(props.data.session.TranscodingInfo? ' - '+props.data.session.TranscodingInfo.VideoCodec.toUpperCase() : '')+' - '+convertBitrate(props.data.session.TranscodingInfo ? props.data.session.TranscodingInfo.Bitrate :props.data.session.NowPlayingItem.MediaStreams.find(stream => stream.Type==='Video')?.BitRate)+' )':'')} + + {" "} + {props.data.session.DeviceName} - - - - - - props.data.session.DeviceName.toLowerCase().includes(item)) || "other") - : - ( clientData.find(item => props.data.session.Client.toLowerCase().includes(item)) || "other") - )} - alt="" - /> - - - - - {props.data.session.NowPlayingItem.Type==='Episode' ? - - - - - {props.data.session.NowPlayingItem.SeriesName ? (props.data.session.NowPlayingItem.SeriesName+" - "+ props.data.session.NowPlayingItem.Name) : (props.data.session.NowPlayingItem.Name)} - - - - - : - <> - } - - - - - {props.data.session.NowPlayingItem.Type==='Episode' ? - - - {'S'+props.data.session.NowPlayingItem.ParentIndexNumber +' - E'+ props.data.session.NowPlayingItem.IndexNumber} - + + {" "} + {props.data.session.Client + + " " + + props.data.session.ApplicationVersion} + + + + {props.data.session.PlayState.PlayMethod} - - : - - - - {props.data.session.NowPlayingItem.SeriesName ? (props.data.session.NowPlayingItem.SeriesName+" - "+ props.data.session.NowPlayingItem.Name) : (props.data.session.NowPlayingItem.Name)} - - + + {props.data.session.NowPlayingItem.MediaStreams + ? "( " + + props.data.session.NowPlayingItem.MediaStreams.find( + (stream) => stream.Type === "Video", + )?.Codec.toUpperCase() + + (props.data.session.TranscodingInfo + ? " - " + + props.data.session.TranscodingInfo.VideoCodec.toUpperCase() + : "") + + " - " + + convertBitrate( + props.data.session.TranscodingInfo + ? props.data.session.TranscodingInfo.Bitrate + : props.data.session.NowPlayingItem.MediaStreams.find( + (stream) => stream.Type === "Video", + )?.BitRate, + ) + + " )" + : ""} - } - - + + - {props.data.session.UserPrimaryImageTag !== undefined ? ( + + props.data.session.DeviceName.toLowerCase().includes( + item, + ), + ) || "other" + : clientData.find((item) => + props.data.session.Client.toLowerCase().includes( + item, + ), + ) || "other") } alt="" /> - ) : ( - - )} - - - {props.data.session.UserName} - - - - + + - + {props.data.session.NowPlayingItem.Type === "Episode" ? ( + + + + + {props.data.session.NowPlayingItem.SeriesName + ? props.data.session.NowPlayingItem.SeriesName + + " - " + + props.data.session.NowPlayingItem.Name + : props.data.session.NowPlayingItem.Name} + + + + + ) : ( + <> + )} - - + + {props.data.session.NowPlayingItem.Type === "Episode" ? ( + + + {"S" + + props.data.session.NowPlayingItem.ParentIndexNumber + + " - E" + + props.data.session.NowPlayingItem.IndexNumber} + + + ) : ( + + + + {props.data.session.NowPlayingItem.SeriesName + ? props.data.session.NowPlayingItem.SeriesName + + " - " + + props.data.session.NowPlayingItem.Name + : props.data.session.NowPlayingItem.Name} + + + + )} - {props.data.session.PlayState.IsPaused ? - - : - - } + + {props.data.session.UserPrimaryImageTag !== undefined ? ( + + ) : ( + + )} + + + + {props.data.session.UserName} + + + + + - + + + {props.data.session.PlayState.IsPaused ? ( + + ) : ( + + )} + - - - {ticksToTimeString(props.data.session.PlayState.PositionTicks)} / - {ticksToTimeString(props.data.session.NowPlayingItem.RunTimeTicks)} - - - + + + {ticksToTimeString( + props.data.session.PlayState.PositionTicks, + )}{" "} + / + {ticksToTimeString( + props.data.session.NowPlayingItem.RunTimeTicks, + )} + + + - - - - - -
-
+ + + + + +
+
+
+ +
- -
-
- + ); } diff --git a/src/pages/components/sessions/sessions.js b/src/pages/components/sessions/sessions.js index ffe0e35b..a176b286 100644 --- a/src/pages/components/sessions/sessions.js +++ b/src/pages/components/sessions/sessions.js @@ -1,5 +1,5 @@ import React, { useState, useEffect } from "react"; -import axios from 'axios'; +import axios from "axios"; import Config from "../../../lib/config"; // import API from "../../../classes/jellyfin-api"; @@ -14,81 +14,83 @@ function Sessions() { const [config, setConfig] = useState(); useEffect(() => { - const fetchConfig = async () => { try { const newConfig = await Config(); setConfig(newConfig); } catch (error) { - console.log(error); + console.log(error); } }; const fetchData = () => { - if (config) { const url = `/proxy/getSessions`; axios - .get(url, { - headers: { - Authorization: `Bearer ${config.token}`, - "Content-Type": "application/json", - }, - }) + .get(url, { + headers: { + Authorization: `Bearer ${config.token}`, + "Content-Type": "application/json", + }, + }) .then((data) => { - if(data && typeof data.data === 'object' && Array.isArray(data.data)) - { - setData(data.data.filter(row => row.NowPlayingItem !== undefined)); + if ( + data && + typeof data.data === "object" && + Array.isArray(data.data) + ) { + setData( + data.data.filter((row) => row.NowPlayingItem !== undefined), + ); } - }) .catch((error) => { console.log(error); }); } }; - if (!config) { fetchConfig(); - }else - if(!data) - { + } else if (!data) { fetchData(); } const intervalId = setInterval(fetchData, 1000); return () => clearInterval(intervalId); - }, [data,config]); + }, [data, config]); if (!data && !config) { return ; } - if ((!data && config) || data.length === 0) { - return( -
-

Sessions

-
- No Active Sessions Found + return ( +
+

Sessions

+
+ No Active Sessions Found +
-
); + ); } return (
-

Sessions

+

Sessions

- {data && data.length>0 && + {data && + data.length > 0 && data .sort((a, b) => - a.Id.padStart(12, "0").localeCompare(b.Id.padStart(12, "0")) + a.Id.padStart(12, "0").localeCompare(b.Id.padStart(12, "0")), ) .map((session) => ( - - + + ))}
diff --git a/src/pages/components/settings/TerminalComponent.js b/src/pages/components/settings/TerminalComponent.js index d5eef1aa..b1082295 100644 --- a/src/pages/components/settings/TerminalComponent.js +++ b/src/pages/components/settings/TerminalComponent.js @@ -1,21 +1,26 @@ -import React, { useState } from 'react'; -import '../../css/websocket/websocket.css'; +import React, { useState } from "react"; +import "../../css/websocket/websocket.css"; -function TerminalComponent(props){ +function TerminalComponent(props) { const [messages] = useState(props.data); - return ( -
+
- {messages && messages.map((message, index) => ( -
-
{message.Message}
-
- ))} + {messages && + messages.map((message, index) => ( +
+
+                {message.Message}
+              
+
+ ))}
); -}; +} export default TerminalComponent; diff --git a/src/pages/components/settings/apiKeys.js b/src/pages/components/settings/apiKeys.js index e7f2d5c3..3106baf4 100644 --- a/src/pages/components/settings/apiKeys.js +++ b/src/pages/components/settings/apiKeys.js @@ -1,92 +1,90 @@ -import React, { useState,useEffect } from "react"; +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 { 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'); - +const token = localStorage.getItem("token"); function CustomRow(key) { const { data } = key; - - async function deleteKey(keyvalue) { - const url=`/api/keys`; + 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); - }); - - + .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 ( - *': { borderBottom: 'unset' } }}> + *": { borderBottom: "unset" } }}> {data.name} {data.key} - +
- -
- + +
- ); } - 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}); -} - + 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(() => { + useEffect(() => { const fetchData = async () => { try { const apiKeyData = await axios.get(`/api/keys`, { @@ -107,121 +105,161 @@ useEffect(() => { return () => clearInterval(intervalId); }, []); - -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); + 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, }); -} + }; - - - - return ( -
-

API Keys

- {showAlert && showAlert.visible && ( - - {showAlert.title} -

- {showAlert.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); + }); + } -
- - - - KEY NAME - - - - - - - - - - - -
- - - - - - Name - Key - - - - - {keys && keys.sort((a, b) => a.name-b.name).slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) - .map((apikey,index) => ( - - ))} - {keys.length===0 ? :''} - - -
No Keys Found
-
- -
- - - - - - - - -
{`${page *rowsPerPage + 1}-${Math.min((page * rowsPerPage+ 1 ) + (rowsPerPage - 1),keys.length)} of ${keys.length}`}
- - - - -
-
-
- ); - - -} \ No newline at end of file + return ( +
+

API Keys

+ {showAlert && showAlert.visible && ( + + {showAlert.title} +

{showAlert.message}

+
+ )} + +
+ + KEY NAME + + + + + + + +
+ + + + + + Name + Key + + + + + {keys && + keys + .sort((a, b) => a.name - b.name) + .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) + .map((apikey, index) => ( + + ))} + {keys.length === 0 ? ( + + + + ) : ( + "" + )} + +
+ No Keys Found +
+
+ +
+ + + + + +
{`${ + page * rowsPerPage + 1 + }-${Math.min( + page * rowsPerPage + 1 + (rowsPerPage - 1), + keys.length, + )} of ${keys.length}`}
+ + + + +
+
+
+ ); +} diff --git a/src/pages/components/settings/backupfiles.js b/src/pages/components/settings/backupfiles.js index c598e678..e4d2f1e6 100644 --- a/src/pages/components/settings/backupfiles.js +++ b/src/pages/components/settings/backupfiles.js @@ -1,96 +1,113 @@ -import React, { useState,useEffect } from "react"; +import React, { useState, useEffect } from "react"; import axios from "axios"; -import {Form, DropdownButton, Dropdown,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 { + Form, + DropdownButton, + Dropdown, + 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'); - +const token = localStorage.getItem("token"); function Row(file) { const { data } = file; const [disabled, setDisabled] = useState(false); async function downloadBackup(filename) { - const url=`/backup/files/${filename}`; + const url = `/backup/files/${filename}`; axios({ - url: url, - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - method: 'GET', - responseType: 'blob', - }).then(response => { - const url = window.URL.createObjectURL(new Blob([response.data])); - const link = document.createElement('a'); - link.href = url; - link.setAttribute('download', filename); - document.body.appendChild(link); - link.click(); - link.parentNode.removeChild(link); - }); - } - - async function restoreBackup(filename) { - const url=`/backup/restore/${filename}`; - setDisabled(true); - axios - .get(url, { + url: url, headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, - }) - .then((response) => { - const alert={visible:true,title:'Success',type:'success',message:response.data}; - setDisabled(false); - file.handleRowActionMessage(alert); - }) - .catch((error) => { - const alert={visible:true,title:'Error',type:'danger',message:error.response.data}; - setDisabled(false); - file.handleRowActionMessage(alert); + method: "GET", + responseType: "blob", + }).then((response) => { + const url = window.URL.createObjectURL(new Blob([response.data])); + const link = document.createElement("a"); + link.href = url; + link.setAttribute("download", filename); + document.body.appendChild(link); + link.click(); + link.parentNode.removeChild(link); }); + } - + async function restoreBackup(filename) { + const url = `/backup/restore/${filename}`; + setDisabled(true); + axios + .get(url, { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }) + .then((response) => { + const alert = { + visible: true, + title: "Success", + type: "success", + message: response.data, + }; + setDisabled(false); + file.handleRowActionMessage(alert); + }) + .catch((error) => { + const alert = { + visible: true, + title: "Error", + type: "danger", + message: error.response.data, + }; + setDisabled(false); + file.handleRowActionMessage(alert); + }); } async function deleteBackup(filename) { - const url=`/backup/files/${filename}`; + const url = `/backup/files/${filename}`; setDisabled(true); axios - .delete(url, { - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - }) - .then((response) => { - const alert={visible:true,title:'Success',type:'success',message:response.data}; - setDisabled(false); - file.handleRowActionMessage(alert); - }) - .catch((error) => { - const alert={visible:true,title:'Error',type:'danger',message:error.response.data}; - setDisabled(false); - file.handleRowActionMessage(alert); - }); - - + .delete(url, { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }) + .then((response) => { + const alert = { + visible: true, + title: "Success", + type: "success", + message: response.data, + }; + setDisabled(false); + file.handleRowActionMessage(alert); + }) + .catch((error) => { + const alert = { + visible: true, + title: "Error", + type: "danger", + message: error.response.data, + }; + setDisabled(false); + file.handleRowActionMessage(alert); + }); } function formatFileSize(sizeInBytes) { @@ -118,7 +135,7 @@ function Row(file) { } } - const twelve_hr = JSON.parse(localStorage.getItem('12hr')); + const twelve_hr = JSON.parse(localStorage.getItem("12hr")); const options = { day: "numeric", @@ -130,73 +147,94 @@ function Row(file) { hour12: twelve_hr, }; - - return ( - *': { borderBottom: 'unset' } }}> + *": { borderBottom: "unset" } }}> {data.name} - {Intl.DateTimeFormat('en-UK', options).format(new Date(data.datecreated))} + + {Intl.DateTimeFormat("en-UK", options).format( + new Date(data.datecreated), + )} + {formatFileSize(data.size)}
- - downloadBackup(data.name)}>Download - restoreBackup(data.name)}>Restore - - deleteBackup(data.name)}>Delete + + downloadBackup(data.name)} + > + Download + + restoreBackup(data.name)} + > + Restore + + + deleteBackup(data.name)} + > + Delete + -
- +
- ); } - export default function BackupFiles() { - const [files, setFiles] = useState([]); - const [showAlert, setshowAlert] = useState({visible:false,type:'danger',title:'Error',message:''}); - const [rowsPerPage] = React.useState(10); - const [page, setPage] = React.useState(0); - const [progress, setProgress] = useState(0); - - - - -function handleCloseAlert() { - setshowAlert({visible:false}); -} - -const uploadFile = (file, onUploadProgress) => { - const formData = new FormData(); - formData.append("file", file); - - return axios.post("/backup/upload", formData, { - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "multipart/form-data", - }, - onUploadProgress, + const [files, setFiles] = useState([]); + const [showAlert, setshowAlert] = useState({ + visible: false, + type: "danger", + title: "Error", + message: "", }); -}; + const [rowsPerPage] = React.useState(10); + const [page, setPage] = React.useState(0); + const [progress, setProgress] = useState(0); - -const handleFileSelect = (event) => { - setProgress(0); - if (event.target.files[0]) { - uploadFile(event.target.files[0], (progressEvent) => { - setProgress(Math.round((progressEvent.loaded / progressEvent.total) * 100)); - }); + function handleCloseAlert() { + setshowAlert({ visible: false }); } -}; + const uploadFile = (file, onUploadProgress) => { + const formData = new FormData(); + formData.append("file", file); + return axios.post("/backup/upload", formData, { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "multipart/form-data", + }, + onUploadProgress, + }); + }; + const handleFileSelect = (event) => { + setProgress(0); + if (event.target.files[0]) { + uploadFile(event.target.files[0], (progressEvent) => { + setProgress( + Math.round((progressEvent.loaded / progressEvent.total) * 100), + ); + }); + } + }; -useEffect(() => { + useEffect(() => { const fetchData = async () => { try { const backupFiles = await axios.get(`/backup/files`, { @@ -217,89 +255,140 @@ useEffect(() => { return () => clearInterval(intervalId); }, []); + const handleNextPageClick = () => { + setPage((prevPage) => prevPage + 1); + }; -const handleNextPageClick = () => { - setPage((prevPage) => prevPage + 1); -}; - -const handlePreviousPageClick = () => { - setPage((prevPage) => prevPage - 1); -}; - -const handleRowActionMessage= (alertState) => { - setshowAlert({visible:alertState.visible,title:alertState.title,type:alertState.type,message:alertState.message}); -}; + const handlePreviousPageClick = () => { + setPage((prevPage) => prevPage - 1); + }; - + const handleRowActionMessage = (alertState) => { + setshowAlert({ + visible: alertState.visible, + title: alertState.title, + type: alertState.type, + message: alertState.message, + }); + }; - - return ( -
-

Backups

- {showAlert && showAlert.visible && ( - - {showAlert.title} -

- {showAlert.message} -

-
+ return ( +
+

Backups

+ {showAlert && showAlert.visible && ( + + {showAlert.title} +

{showAlert.message}

+
+ )} + + + + + + File Name + Date Created + Size + + + + + {files && + files + .sort( + (a, b) => new Date(b.datecreated) - new Date(a.datecreated), + ) + .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) + .map((file, index) => ( + + ))} + {files.length === 0 ? ( + + + + ) : ( + "" )} - - -
+ No Backups Found +
- - - File Name - Date Created - Size - - - - - {files && files.sort((a, b) =>new Date(b.datecreated) - new Date(a.datecreated)).slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) - .map((file,index) => ( - - ))} - {files.length===0 ? :''} - - - - - - - - - - -
No Backups Found
-
- -
- - - - - - - - -
{`${page *rowsPerPage + 1}-${Math.min((page * rowsPerPage+ 1 ) + (rowsPerPage - 1),files.length)} of ${files.length}`}
- - - - -
-
-
- ); - - -} \ No newline at end of file + + + + + + + + + + + + +
+ + + + + +
{`${ + page * rowsPerPage + 1 + }-${Math.min( + page * rowsPerPage + 1 + (rowsPerPage - 1), + files.length, + )} of ${files.length}`}
+ + + + +
+
+
+ ); +} diff --git a/src/pages/components/settings/logs.js b/src/pages/components/settings/logs.js index 3d052848..ef58b210 100644 --- a/src/pages/components/settings/logs.js +++ b/src/pages/components/settings/logs.js @@ -1,35 +1,31 @@ import React, { useEffect } from "react"; import axios from "axios"; -import {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 Collapse from '@mui/material/Collapse'; -import IconButton from '@mui/material/IconButton'; -import Box from '@mui/material/Box'; - -import AddCircleFillIcon from 'remixicon-react/AddCircleFillIcon'; -import IndeterminateCircleFillIcon from 'remixicon-react/IndeterminateCircleFillIcon'; +import { 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 Collapse from "@mui/material/Collapse"; +import IconButton from "@mui/material/IconButton"; +import Box from "@mui/material/Box"; +import AddCircleFillIcon from "remixicon-react/AddCircleFillIcon"; +import IndeterminateCircleFillIcon from "remixicon-react/IndeterminateCircleFillIcon"; import "../../css/settings/backups.css"; import TerminalComponent from "./TerminalComponent"; -const token = localStorage.getItem('token'); - +const token = localStorage.getItem("token"); function Row(logs) { const { data } = logs; const [open, setOpen] = React.useState(false); - - const twelve_hr = JSON.parse(localStorage.getItem('12hr')); + const twelve_hr = JSON.parse(localStorage.getItem("12hr")); const options = { day: "numeric", @@ -41,67 +37,89 @@ function Row(logs) { hour12: twelve_hr, }; + function formatDurationTime(seconds) { + if (seconds === "0") { + return "0 second"; + } - -function formatDurationTime(seconds) { - if(seconds==='0') - { - return '0 second'; - } + const hours = Math.floor(seconds / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + const remainingSeconds = seconds % 60; - const hours = Math.floor(seconds / 3600); - const minutes = Math.floor((seconds % 3600) / 60); - const remainingSeconds = seconds % 60; + let timeString = ""; - let timeString = ''; + if (hours > 0) { + timeString += `${hours} ${hours === 1 ? "hr" : "hrs"} `; + } - if (hours > 0) { - timeString += `${hours} ${hours === 1 ? 'hr' : 'hrs'} `; - } + if (minutes > 0) { + timeString += `${minutes} ${minutes === 1 ? "min" : "mins"} `; + } - if (minutes > 0) { - timeString += `${minutes} ${minutes === 1 ? 'min' : 'mins'} `; - } + if (remainingSeconds > 0) { + timeString += `${remainingSeconds} ${ + remainingSeconds === 1 ? "second" : "seconds" + }`; + } - if (remainingSeconds > 0) { - timeString += `${remainingSeconds} ${remainingSeconds === 1 ? 'second' : 'seconds'}`; + return timeString.trim(); } - return timeString.trim(); -} - - return ( - *': { borderBottom: 'unset' } }}> + *": { borderBottom: "unset" } }}> {if(data.Log.length>0){setOpen(!open);}}} - > - {!open ? 0 ?1 : 0} cursor={data.Log.length>1 ? "pointer":"default"}/> : } + onClick={() => { + if (data.Log.length > 0) { + setOpen(!open); + } + }} + > + {!open ? ( + 0 ? 1 : 0} + cursor={data.Log.length > 1 ? "pointer" : "default"} + /> + ) : ( + + )} {data.Name} {data.Type} - {Intl.DateTimeFormat('en-UK', options).format(new Date(data.TimeRun))} + + {Intl.DateTimeFormat("en-UK", options).format(new Date(data.TimeRun))} + {formatDurationTime(data.Duration)} {data.ExecutionType} -
{data.Result}
- + +
+ {data.Result} +
+
- - - +
- - - + + + + +
@@ -112,18 +130,12 @@ function formatDurationTime(seconds) { ); } - export default function Logs() { + const [data, setData] = React.useState([]); + const [rowsPerPage] = React.useState(10); + const [page, setPage] = React.useState(0); - const [data, setData]=React.useState([]); - const [rowsPerPage] = React.useState(10); - const [page, setPage] = React.useState(0); - - - - - -useEffect(() => { + useEffect(() => { const fetchData = async () => { try { const logs = await axios.get(`/logs/getLogs`, { @@ -144,72 +156,100 @@ useEffect(() => { return () => clearInterval(intervalId); }, []); + const handleNextPageClick = () => { + setPage((prevPage) => prevPage + 1); + }; + + const handlePreviousPageClick = () => { + setPage((prevPage) => prevPage - 1); + }; -const handleNextPageClick = () => { - setPage((prevPage) => prevPage + 1); -}; - -const handlePreviousPageClick = () => { - setPage((prevPage) => prevPage - 1); -}; - - - - return ( -
-

Logs

- - - - - - - Name - Type - Date Created - Duration - Execution Type - Result - - - - {data && data.sort((a, b) =>new Date(b.TimeRun) - new Date(a.TimeRun)).slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) - .map((log,index) => ( - - ))} - {data.length===0 ? :''} - - - -
No Logs Found
-
- -
- - - - - - - - -
{`${page *rowsPerPage + 1}-${Math.min((page * rowsPerPage+ 1 ) + (rowsPerPage - 1),data.length)} of ${data.length}`}
- - - - -
-
-
- ); - - -} \ No newline at end of file + return ( +
+

Logs

+ + + + + + + Name + Type + Date Created + Duration + Execution Type + Result + + + + {data && + data + .sort((a, b) => new Date(b.TimeRun) - new Date(a.TimeRun)) + .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) + .map((log, index) => )} + {data.length === 0 ? ( + + + + ) : ( + "" + )} + +
+ No Logs Found +
+
+ +
+ + + + + +
{`${ + page * rowsPerPage + 1 + }-${Math.min( + page * rowsPerPage + 1 + (rowsPerPage - 1), + data.length, + )} of ${data.length}`}
+ + + + +
+
+
+ ); +} diff --git a/src/pages/components/settings/security.js b/src/pages/components/settings/security.js index d8c3d5c2..38c0e19d 100644 --- a/src/pages/components/settings/security.js +++ b/src/pages/components/settings/security.js @@ -1,21 +1,18 @@ -import React, { useState,useEffect } from "react"; +import React, { useState, useEffect } from "react"; import axios from "axios"; -import Form from 'react-bootstrap/Form'; -import Row from 'react-bootstrap/Row'; -import Col from 'react-bootstrap/Col'; -import Button from 'react-bootstrap/Button'; -import Alert from 'react-bootstrap/Alert'; -import ToggleButton from 'react-bootstrap/ToggleButton'; -import ToggleButtonGroup from 'react-bootstrap/ToggleButtonGroup'; -import CryptoJS from 'crypto-js'; -import EyeFillIcon from 'remixicon-react/EyeFillIcon'; -import EyeOffFillIcon from 'remixicon-react/EyeOffFillIcon'; +import Form from "react-bootstrap/Form"; +import Row from "react-bootstrap/Row"; +import Col from "react-bootstrap/Col"; +import Button from "react-bootstrap/Button"; +import Alert from "react-bootstrap/Alert"; +import ToggleButton from "react-bootstrap/ToggleButton"; +import ToggleButtonGroup from "react-bootstrap/ToggleButtonGroup"; +import CryptoJS from "crypto-js"; +import EyeFillIcon from "remixicon-react/EyeFillIcon"; +import EyeOffFillIcon from "remixicon-react/EyeOffFillIcon"; import Config from "../../../lib/config"; - - - import "../../css/settings/settings.css"; import { InputGroup } from "react-bootstrap"; @@ -27,181 +24,201 @@ export default function SettingsConfig() { const [isSubmitted, setisSubmitted] = useState(""); const [submissionMessage, setsubmissionMessage] = useState(""); - const token = localStorage.getItem('token'); + const token = localStorage.getItem("token"); useEffect(() => { - - const fetchConfig = async () => { - try { - const newConfig = await Config(); - setuse_password(newConfig.requireLogin); - } catch (error) { - console.log(error); - } - }; - - - + try { + const newConfig = await Config(); + setuse_password(newConfig.requireLogin); + } catch (error) { + console.log(error); + } + }; + fetchConfig(); - + const intervalId = setInterval(fetchConfig, 60000 * 5); return () => clearInterval(intervalId); }, []); - -async function updatePassword(_current_password, _new_password) { + async function updatePassword(_current_password, _new_password) { const result = await axios - .post("/api/updatePassword", { - current_password:_current_password, - new_password: _new_password - - }, { - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - }) - .catch((error) => { - // let errorMessage= `Error : ${error}`; - }); - - let data=result.data; - return { isValid:data.isValid, errorMessage:data.errorMessage} ; + .post( + "/api/updatePassword", + { + current_password: _current_password, + new_password: _new_password, + }, + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }, + ) + .catch((error) => { + // let errorMessage= `Error : ${error}`; + }); + + let data = result.data; + return { isValid: data.isValid, errorMessage: data.errorMessage }; } async function setRequireLogin(requireLogin) { - await axios - .post("/api/setRequireLogin", { - REQUIRE_LOGIN:requireLogin - - }, { - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - }) - .then((data)=> - { + await axios + .post( + "/api/setRequireLogin", + { + REQUIRE_LOGIN: requireLogin, + }, + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }, + ) + .then((data) => { setuse_password(requireLogin); - } - ) - .catch((error) => { - // let errorMessage= `Error : ${error}`; - - }); - + }) + .catch((error) => { + // let errorMessage= `Error : ${error}`; + }); } - - async function handleFormSubmit(event) { event.preventDefault(); setisSubmitted(""); - if(!formValues.JS_PASSWORD || formValues.JS_PASSWORD.length<6) - { - setisSubmitted("Failed"); - setsubmissionMessage("Unable to update password: New Password Must be at least 6 characters long"); - return; + if (!formValues.JS_PASSWORD || formValues.JS_PASSWORD.length < 6) { + setisSubmitted("Failed"); + setsubmissionMessage( + "Unable to update password: New Password Must be at least 6 characters long", + ); + return; } - let hashedOldPassword= CryptoJS.SHA3(formValues.JS_C_PASSWORD).toString(); - let hashedNewPassword= CryptoJS.SHA3(formValues.JS_PASSWORD).toString(); - let result = await updatePassword( - hashedOldPassword, - hashedNewPassword - ); + let hashedOldPassword = CryptoJS.SHA3(formValues.JS_C_PASSWORD).toString(); + let hashedNewPassword = CryptoJS.SHA3(formValues.JS_PASSWORD).toString(); + let result = await updatePassword(hashedOldPassword, hashedNewPassword); if (result.isValid) { - setisSubmitted("Success"); - setsubmissionMessage("Successfully updated password"); - return; - }else - { - setisSubmitted("Failed"); - setsubmissionMessage("Unable to update password: "+ result.errorMessage); - return; + setisSubmitted("Success"); + setsubmissionMessage("Successfully updated password"); + return; + } else { + setisSubmitted("Failed"); + setsubmissionMessage("Unable to update password: " + result.errorMessage); + return; } - } function handleFormChange(event) { setFormValues({ ...formValues, [event.target.name]: event.target.value }); } - - function togglePasswordRequired(isRequired){ + function togglePasswordRequired(isRequired) { // console.log(isRequired); setRequireLogin(isRequired); - }; - - - - - return ( -
-

Security

-
- - - Current Password - - - - - - - - - - - - New Password - - - - - - - - - - {isSubmitted !== "" ? ( - - isSubmitted === "Failed" ? - - {submissionMessage} - - : - - {submissionMessage} - - ) : ( - <> - )} -
- -
- -
- -
- - Require Login - - - {togglePasswordRequired(true);}}>Yes - {togglePasswordRequired(false);}}>No - - - - -
- - - - -
- ); - + } -} \ No newline at end of file + return ( +
+

Security

+
+ + + Current Password + + + + + + + + + + + + New Password + + + + + + + + + + {isSubmitted !== "" ? ( + isSubmitted === "Failed" ? ( + {submissionMessage} + ) : ( + {submissionMessage} + ) + ) : ( + <> + )} +
+ +
+
+ +
+ + + Require Login + + + + { + togglePasswordRequired(true); + }} + > + Yes + + { + togglePasswordRequired(false); + }} + > + No + + + + +
+
+ ); +} diff --git a/src/pages/components/settings/settingsConfig.js b/src/pages/components/settings/settingsConfig.js index 10b0b930..4c76f522 100644 --- a/src/pages/components/settings/settingsConfig.js +++ b/src/pages/components/settings/settingsConfig.js @@ -2,42 +2,41 @@ import React, { useState, useEffect } from "react"; import axios from "axios"; import Config from "../../../lib/config"; import Loading from "../general/loading"; -import Form from 'react-bootstrap/Form'; -import Row from 'react-bootstrap/Row'; -import Col from 'react-bootstrap/Col'; -import Button from 'react-bootstrap/Button'; -import Alert from 'react-bootstrap/Alert'; -import ToggleButton from 'react-bootstrap/ToggleButton'; -import ToggleButtonGroup from 'react-bootstrap/ToggleButtonGroup'; -import Dropdown from 'react-bootstrap/Dropdown'; - -import EyeFillIcon from 'remixicon-react/EyeFillIcon'; -import EyeOffFillIcon from 'remixicon-react/EyeOffFillIcon'; - - +import Form from "react-bootstrap/Form"; +import Row from "react-bootstrap/Row"; +import Col from "react-bootstrap/Col"; +import Button from "react-bootstrap/Button"; +import Alert from "react-bootstrap/Alert"; +import ToggleButton from "react-bootstrap/ToggleButton"; +import ToggleButtonGroup from "react-bootstrap/ToggleButtonGroup"; +import Dropdown from "react-bootstrap/Dropdown"; + +import EyeFillIcon from "remixicon-react/EyeFillIcon"; +import EyeOffFillIcon from "remixicon-react/EyeOffFillIcon"; import "../../css/settings/settings.css"; -import { InputGroup } from "react-bootstrap"; +import { InputGroup } from "react-bootstrap"; export default function SettingsConfig() { const [config, setConfig] = useState(null); const [admins, setAdmins] = useState(); - const [selectedAdmin, setSelectedAdmin] = useState(''); + const [selectedAdmin, setSelectedAdmin] = useState(""); const [showKey, setKeyState] = useState(false); const [formValues, setFormValues] = useState({}); const [isSubmitted, setisSubmitted] = useState(""); const [loadSate, setloadSate] = useState("Loading"); const [submissionMessage, setsubmissionMessage] = useState(""); - const token = localStorage.getItem('token'); - const [twelve_hr, set12hr] = useState(localStorage.getItem('12hr') === 'true'); + const token = localStorage.getItem("token"); + const [twelve_hr, set12hr] = useState( + localStorage.getItem("12hr") === "true", + ); - const storage_12hr = localStorage.getItem('12hr'); + const storage_12hr = localStorage.getItem("12hr"); - if(storage_12hr===null) - { - localStorage.setItem('12hr',false); + if (storage_12hr === null) { + localStorage.setItem("12hr", false); set12hr(false); - }else if(twelve_hr===null){ + } else if (twelve_hr === null) { set12hr(Boolean(storage_12hr)); } @@ -53,60 +52,57 @@ export default function SettingsConfig() { console.log("Error updating config:", error); setloadSate("Critical"); setsubmissionMessage( - "Error Retrieving Configuration. Unable to contact Backend Server" + "Error Retrieving Configuration. Unable to contact Backend Server", ); }); - - const fetchAdmins = async () => { - try { - - const adminData = await axios.get(`/proxy/getAdminUsers`, { - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - }); - setAdmins(adminData.data); - - } catch (error) { - console.log(error); - } - }; - - fetchAdmins(); - - - + const fetchAdmins = async () => { + try { + const adminData = await axios.get(`/proxy/getAdminUsers`, { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }); + setAdmins(adminData.data); + } catch (error) { + console.log(error); + } + }; + + fetchAdmins(); }, [token]); async function validateSettings(_url, _apikey) { const result = await axios - .post("/api/validateSettings", { - url:_url, - apikey: _apikey - - }, { - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - }) - .catch((error) => { - // let errorMessage= `Error : ${error}`; - }); + .post( + "/api/validateSettings", + { + url: _url, + apikey: _apikey, + }, + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }, + ) + .catch((error) => { + // let errorMessage= `Error : ${error}`; + }); - let data=result.data; - return { isValid:data.isValid, errorMessage:data.errorMessage} ; + let data = result.data; + return { isValid: data.isValid, errorMessage: data.errorMessage }; } async function handleFormSubmit(event) { event.preventDefault(); let validation = await validateSettings( formValues.JF_HOST, - formValues.JF_API_KEY + formValues.JF_API_KEY, ); - + if (!validation.isValid) { setisSubmitted("Failed"); setsubmissionMessage(validation.errorMessage); @@ -138,36 +134,36 @@ export default function SettingsConfig() { } function updateAdmin(event) { - - const username=event.target.textContent; - const userid=event.target.getAttribute('value'); - + const username = event.target.textContent; + const userid = event.target.getAttribute("value"); axios - .post("/api/setPreferredAdmin/", - { - userid:userid, - username:username - }, { - headers: { - Authorization: `Bearer ${config.token}`, - "Content-Type": "application/json", - }, - }) - .then((response) => { - console.log("Config updated successfully:", response.data); - setisSubmitted("Success"); - setsubmissionMessage("Successfully updated configuration"); - setSelectedAdmin({username:username, userid:userid}); - }) - .catch((error) => { - console.log("Error updating config:", error); - setisSubmitted("Failed"); - setsubmissionMessage("Error Updating Configuration: ", error); - }); + .post( + "/api/setPreferredAdmin/", + { + userid: userid, + username: username, + }, + { + headers: { + Authorization: `Bearer ${config.token}`, + "Content-Type": "application/json", + }, + }, + ) + .then((response) => { + console.log("Config updated successfully:", response.data); + setisSubmitted("Success"); + setsubmissionMessage("Successfully updated configuration"); + setSelectedAdmin({ username: username, userid: userid }); + }) + .catch((error) => { + console.log("Error updating config:", error); + setisSubmitted("Failed"); + setsubmissionMessage("Error Updating Configuration: ", error); + }); } - if (loadSate === "Loading") { return ; } @@ -176,100 +172,134 @@ export default function SettingsConfig() { return
{submissionMessage}
; } - - function toggle12Hr(is_12_hr){ + function toggle12Hr(is_12_hr) { set12hr(is_12_hr); - localStorage.setItem('12hr',is_12_hr); - }; - - - - - return ( -
-

Settings

-
- - - Jellyfin Url - - - - - + localStorage.setItem("12hr", is_12_hr); + } - - - API Key - - + return ( +
+

Settings

+ + + + Jellyfin Url + + + + + + + + + API Key + + - - - - - - {isSubmitted !== "" ? ( - - isSubmitted === "Failed" ? - - {submissionMessage} - - : - - {submissionMessage} - + + + + + + {isSubmitted !== "" ? ( + isSubmitted === "Failed" ? ( + {submissionMessage} ) : ( - <> - )} -
- -
- - -
- - Select Preferred Admin Account - - - - {selectedAdmin ? selectedAdmin.username : 'Select a Preferred Admin'} - - - - {admins && admins.sort((a, b) => a.Name - b.Name) - .map((admin) => ( - - {admin.Name} - ))} - - - - - - -
- -
- - Hour Format - - - {toggle12Hr(true);}}>12 Hours - {toggle12Hr(false);}}>24 Hours - - - - -
- - - - - - -
- ); - - -} \ No newline at end of file + {submissionMessage} + ) + ) : ( + <> + )} +
+ +
+ +
+ + + Select Preferred Admin Account + + + + + {selectedAdmin + ? selectedAdmin.username + : "Select a Preferred Admin"} + + + + {admins && + admins + .sort((a, b) => a.Name - b.Name) + .map((admin) => ( + + {admin.Name} + + ))} + + + + +
+ +
+ + + Hour Format + + + + { + toggle12Hr(true); + }} + > + 12 Hours + + { + toggle12Hr(false); + }} + > + 24 Hours + + + + +
+
+ ); +} diff --git a/src/pages/components/statCards/ItemStatComponent.js b/src/pages/components/statCards/ItemStatComponent.js index 3cec5c79..b7ea8483 100644 --- a/src/pages/components/statCards/ItemStatComponent.js +++ b/src/pages/components/statCards/ItemStatComponent.js @@ -1,121 +1,143 @@ -import React, {useState} from "react"; -import { Blurhash } from 'react-blurhash'; +import React, { useState } from "react"; +import { Blurhash } from "react-blurhash"; import { Link } from "react-router-dom"; -import Card from 'react-bootstrap/Card'; -import Row from 'react-bootstrap/Row'; -import Col from 'react-bootstrap/Col'; -import Tooltip from "@mui/material/Tooltip"; +import Card from "react-bootstrap/Card"; +import Row from "react-bootstrap/Row"; +import Col from "react-bootstrap/Col"; +import Tooltip from "@mui/material/Tooltip"; function ItemStatComponent(props) { const [loaded, setLoaded] = useState(false); const handleImageLoad = () => { setLoaded(true); - } + }; - const backgroundImage=`/proxy/Items/Images/Backdrop?id=${props.data[0].Id}&fillWidth=300&quality=10`; + const backgroundImage = `/proxy/Items/Images/Backdrop?id=${props.data[0].Id}&fillWidth=300&quality=10`; const cardStyle = { backgroundImage: `url(${backgroundImage}), linear-gradient(to right, #00A4DC, #AA5CC3)`, - height:'100%', - backgroundSize: 'cover', + height: "100%", + backgroundSize: "cover", }; const cardBgStyle = { - backdropFilter: props.base_url ? 'blur(5px)' : 'blur(0px)', - backgroundColor: 'rgb(0, 0, 0, 0.6)', - height:'100%', + backdropFilter: props.base_url ? "blur(5px)" : "blur(0px)", + backgroundColor: "rgb(0, 0, 0, 0.6)", + height: "100%", }; - if (props.data.length === 0) { return <>; } - return (
- {props.icon ? -
- {props.icon} -
- : + {props.icon ? ( +
{props.icon}
+ ) : ( <> - {props.data && props.data[0] && props.data[0].PrimaryImageHash && props.data[0].PrimaryImageHash!=null && !loaded && ( -
- -
- )} + {props.data && + props.data[0] && + props.data[0].PrimaryImageHash && + props.data[0].PrimaryImageHash != null && + !loaded && ( +
+ +
+ )} setLoaded(false)} /> - } - + )} - - - + + +
- {props.heading} + + {props.heading} +
- {props.units} + + {props.units} +
{props.data && - props.data.map((item, index) => ( -
- -
- {index + 1} - {item.UserId ? - - - {item.Name} - - - - : - !item.Client && !props.icon ? - - - - {item.Name} - - - : - !item.Client && props.icon ? - - - {item.Name} + props.data.map((item, index) => ( +
+
+ + {index + 1} + + {item.UserId ? ( + + + {item.Name} - : - + ) : !item.Client && !props.icon ? ( + + + {item.Name} + + + ) : !item.Client && props.icon ? ( + + + {item.Name} + + + ) : ( + {item.Client} - - } + + )} +
+ + + {item.Plays || item.unique_viewers} +
- - - {item.Plays || item.unique_viewers} - - -
- ))} + ))} -
- +
+
); } diff --git a/src/pages/components/statCards/most_active_users.js b/src/pages/components/statCards/most_active_users.js index 98dc3004..3bd44172 100644 --- a/src/pages/components/statCards/most_active_users.js +++ b/src/pages/components/statCards/most_active_users.js @@ -3,15 +3,13 @@ import axios from "axios"; import Config from "../../../lib/config"; import ItemStatComponent from "./ItemStatComponent"; - import AccountCircleFillIcon from "remixicon-react/AccountCircleFillIcon"; function MostActiveUsers(props) { const [data, setData] = useState(); - const [days, setDays] = useState(30); + const [days, setDays] = useState(30); const [config, setConfig] = useState(null); - const [loaded, setLoaded]= useState(true); - + const [loaded, setLoaded] = useState(true); useEffect(() => { const fetchConfig = async () => { @@ -30,12 +28,16 @@ function MostActiveUsers(props) { const url = `/stats/getMostActiveUsers`; axios - .post(url, {days:props.days}, { - headers: { - Authorization: `Bearer ${config.token}`, - "Content-Type": "application/json", - }, - }) + .post( + url, + { days: props.days }, + { + headers: { + Authorization: `Bearer ${config.token}`, + "Content-Type": "application/json", + }, + }, + ) .then((data) => { setData(data.data); }) @@ -44,7 +46,6 @@ function MostActiveUsers(props) { }); } }; - if (!config) { fetchConfig(); @@ -59,29 +60,33 @@ function MostActiveUsers(props) { fetchLibraries(); } - const intervalId = setInterval(fetchLibraries, 60000 * 5); return () => clearInterval(intervalId); - }, [data, config, days,props.days]); - + }, [data, config, days, props.days]); if (!data || data.length === 0) { - return <>; + return <>; } const UserImage = () => { return ( - setLoaded(false)} + setLoaded(false)} /> ); }; return ( - : } data={data} heading={"MOST ACTIVE USERS"} units={"Plays"}/> + : } + data={data} + heading={"MOST ACTIVE USERS"} + units={"Plays"} + /> ); } diff --git a/src/pages/components/statCards/most_used_client.js b/src/pages/components/statCards/most_used_client.js index 58557790..3e9b0c89 100644 --- a/src/pages/components/statCards/most_used_client.js +++ b/src/pages/components/statCards/most_used_client.js @@ -2,35 +2,35 @@ import React, { useState, useEffect } from "react"; import axios from "axios"; import ItemStatComponent from "./ItemStatComponent"; - - import ComputerLineIcon from "remixicon-react/ComputerLineIcon"; function MostUsedClient(props) { const [data, setData] = useState(); - const [days, setDays] = useState(30); - const token = localStorage.getItem('token'); + const [days, setDays] = useState(30); + const token = localStorage.getItem("token"); useEffect(() => { - const fetchLibraries = () => { - const url = `/stats/getMostUsedClient`; - - axios - .post(url, {days:props.days}, { - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", + const url = `/stats/getMostUsedClient`; + + axios + .post( + url, + { days: props.days }, + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, }, + ) + .then((data) => { + setData(data.data); }) - .then((data) => { - setData(data.data); - }) - .catch((error) => { - console.log(error); - }); + .catch((error) => { + console.log(error); + }); }; - if (!data) { fetchLibraries(); @@ -40,19 +40,21 @@ function MostUsedClient(props) { fetchLibraries(); } - const intervalId = setInterval(fetchLibraries, 60000 * 5); return () => clearInterval(intervalId); - }, [data, days,props.days,token]); + }, [data, days, props.days, token]); if (!data || data.length === 0) { - return <>; + return <>; } - - return ( - } data={data} heading={"MOST USED CLIENTS"} units={"Plays"}/> + } + data={data} + heading={"MOST USED CLIENTS"} + units={"Plays"} + /> ); } diff --git a/src/pages/components/statCards/mp_movies.js b/src/pages/components/statCards/mp_movies.js index b2515fbc..f2c1fff6 100644 --- a/src/pages/components/statCards/mp_movies.js +++ b/src/pages/components/statCards/mp_movies.js @@ -2,18 +2,15 @@ import React, { useState, useEffect } from "react"; import axios from "axios"; import Config from "../../../lib/config"; - - import ItemStatComponent from "./ItemStatComponent"; import Loading from "../general/loading"; function MPMovies(props) { const [data, setData] = useState(); - const [days, setDays] = useState(30); + const [days, setDays] = useState(30); const [config, setConfig] = useState(null); - useEffect(() => { const fetchConfig = async () => { try { @@ -31,12 +28,16 @@ function MPMovies(props) { const url = `/stats/getMostPopularByType`; axios - .post(url, {days:props.days, type:'Movie'}, { - headers: { - Authorization: `Bearer ${config.token}`, - "Content-Type": "application/json", - }, - }) + .post( + url, + { days: props.days, type: "Movie" }, + { + headers: { + Authorization: `Bearer ${config.token}`, + "Content-Type": "application/json", + }, + }, + ) .then((data) => { setData(data.data); }) @@ -45,7 +46,6 @@ function MPMovies(props) { }); } }; - if (!config) { fetchConfig(); @@ -59,23 +59,26 @@ function MPMovies(props) { setDays(props.days); fetchLibraries(); } - const intervalId = setInterval(fetchLibraries, 60000 * 5); return () => clearInterval(intervalId); - }, [data, config, days,props.days]); + }, [data, config, days, props.days]); if (!data || data.length === 0) { - return <>; + return <>; } - if(!config) - { - return ; + if (!config) { + return ; } return ( - + ); } diff --git a/src/pages/components/statCards/mp_music.js b/src/pages/components/statCards/mp_music.js index f7a3b02e..cbc4e5b9 100644 --- a/src/pages/components/statCards/mp_music.js +++ b/src/pages/components/statCards/mp_music.js @@ -5,11 +5,9 @@ import Config from "../../../lib/config"; import ItemStatComponent from "./ItemStatComponent"; - - function MPMusic(props) { const [data, setData] = useState(); - const [days, setDays] = useState(30); + const [days, setDays] = useState(30); const [config, setConfig] = useState(null); useEffect(() => { @@ -23,18 +21,22 @@ function MPMusic(props) { } } }; - + const fetchLibraries = () => { if (config) { const url = `/stats/getMostPopularByType`; - + axios - .post(url, { days: props.days, type:'Audio' }, { - headers: { - Authorization: `Bearer ${config.token}`, - "Content-Type": "application/json", + .post( + url, + { days: props.days, type: "Audio" }, + { + headers: { + Authorization: `Bearer ${config.token}`, + "Content-Type": "application/json", + }, }, - }) + ) .then((data) => { setData(data.data); }) @@ -43,11 +45,11 @@ function MPMusic(props) { }); } }; - + if (!config) { fetchConfig(); } - + if (!data) { fetchLibraries(); } @@ -56,20 +58,22 @@ function MPMusic(props) { setDays(props.days); fetchLibraries(); } - + const intervalId = setInterval(fetchLibraries, 60000 * 5); return () => clearInterval(intervalId); - }, [data, config, days,props.days]); - + }, [data, config, days, props.days]); + if (!data || data.length === 0) { - return <>; + return <>; } - - - return ( - + ); } diff --git a/src/pages/components/statCards/mp_series.js b/src/pages/components/statCards/mp_series.js index b74619fb..4fb28266 100644 --- a/src/pages/components/statCards/mp_series.js +++ b/src/pages/components/statCards/mp_series.js @@ -3,10 +3,9 @@ import axios from "axios"; import Config from "../../../lib/config"; import ItemStatComponent from "./ItemStatComponent"; - function MPSeries(props) { const [data, setData] = useState(); - const [days, setDays] = useState(30); + const [days, setDays] = useState(30); const [config, setConfig] = useState(null); @@ -21,18 +20,22 @@ function MPSeries(props) { } } }; - + const fetchLibraries = () => { if (config) { const url = `/stats/getMostPopularByType`; - + axios - .post(url, { days: props.days, type:'Series' }, { - headers: { - Authorization: `Bearer ${config.token}`, - "Content-Type": "application/json", + .post( + url, + { days: props.days, type: "Series" }, + { + headers: { + Authorization: `Bearer ${config.token}`, + "Content-Type": "application/json", + }, }, - }) + ) .then((data) => { setData(data.data); }) @@ -41,11 +44,11 @@ function MPSeries(props) { }); } }; - + if (!config) { fetchConfig(); } - + if (!data) { fetchLibraries(); } @@ -54,19 +57,22 @@ function MPSeries(props) { setDays(props.days); fetchLibraries(); } - + const intervalId = setInterval(fetchLibraries, 60000 * 5); return () => clearInterval(intervalId); - }, [data, config, days,props.days]); + }, [data, config, days, props.days]); if (!data || data.length === 0) { - return <>; + return <>; } - - return ( - + ); } diff --git a/src/pages/components/statCards/mv_libraries.js b/src/pages/components/statCards/mv_libraries.js index 5dfb8d40..6f785eba 100644 --- a/src/pages/components/statCards/mv_libraries.js +++ b/src/pages/components/statCards/mv_libraries.js @@ -1,7 +1,6 @@ import React, { useState, useEffect } from "react"; import axios from "axios"; - import ItemStatComponent from "./ItemStatComponent"; import TvLineIcon from "remixicon-react/TvLineIcon"; @@ -11,31 +10,32 @@ import CheckboxMultipleBlankLineIcon from "remixicon-react/CheckboxMultipleBlank function MVLibraries(props) { const [data, setData] = useState(); - const [days, setDays] = useState(30); + const [days, setDays] = useState(30); - const token = localStorage.getItem('token'); + const token = localStorage.getItem("token"); useEffect(() => { - const fetchLibraries = () => { - const url = `/stats/getMostViewedLibraries`; - - axios - .post(url, {days:props.days}, { - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", + const url = `/stats/getMostViewedLibraries`; + + axios + .post( + url, + { days: props.days }, + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, }, + ) + .then((data) => { + setData(data.data); }) - .then((data) => { - setData(data.data); - }) - .catch((error) => { - console.log(error); - }); + .catch((error) => { + console.log(error); + }); }; - - if (!data) { fetchLibraries(); @@ -47,20 +47,32 @@ function MVLibraries(props) { const intervalId = setInterval(fetchLibraries, 60000 * 5); return () => clearInterval(intervalId); - }, [data, days,props.days,token]); + }, [data, days, props.days, token]); if (!data || data.length === 0) { - return <>; + return <>; } - const SeriesIcon= ; - const MovieIcon= ; - const MusicIcon= ; - const MixedIcon= ; - + const SeriesIcon = ; + const MovieIcon = ; + const MusicIcon = ; + const MixedIcon = ; return ( - + ); } diff --git a/src/pages/components/statCards/mv_movies.js b/src/pages/components/statCards/mv_movies.js index 2e55c886..026cc868 100644 --- a/src/pages/components/statCards/mv_movies.js +++ b/src/pages/components/statCards/mv_movies.js @@ -3,18 +3,14 @@ import axios from "axios"; import Config from "../../../lib/config"; - import ItemStatComponent from "./ItemStatComponent"; - function MVMusic(props) { - const [data, setData] = useState(); const [days, setDays] = useState(30); const [config, setConfig] = useState(null); - useEffect(() => { const fetchConfig = async () => { try { @@ -32,12 +28,16 @@ function MVMusic(props) { const url = `/stats/getMostViewedByType`; axios - .post(url, {days:props.days, type:'Movie'}, { - headers: { - Authorization: `Bearer ${config.token}`, - "Content-Type": "application/json", - }, - }) + .post( + url, + { days: props.days, type: "Movie" }, + { + headers: { + Authorization: `Bearer ${config.token}`, + "Content-Type": "application/json", + }, + }, + ) .then((data) => { setData(data.data); }) @@ -47,7 +47,6 @@ function MVMusic(props) { } }; - if (!config) { fetchConfig(); } @@ -60,20 +59,21 @@ function MVMusic(props) { fetchLibraries(); } - const intervalId = setInterval(fetchLibraries, 60000 * 5); return () => clearInterval(intervalId); - }, [data, config, days,props.days]); + }, [data, config, days, props.days]); if (!data || data.length === 0) { - return <>; + return <>; } - - - return ( - + ); } diff --git a/src/pages/components/statCards/mv_music.js b/src/pages/components/statCards/mv_music.js index 657c472f..dcfefa6a 100644 --- a/src/pages/components/statCards/mv_music.js +++ b/src/pages/components/statCards/mv_music.js @@ -5,11 +5,10 @@ import ItemStatComponent from "./ItemStatComponent"; function MVMovies(props) { const [data, setData] = useState(); - const [days, setDays] = useState(30); + const [days, setDays] = useState(30); const [config, setConfig] = useState(null); - useEffect(() => { const fetchConfig = async () => { try { @@ -27,12 +26,16 @@ function MVMovies(props) { const url = `/stats/getMostViewedByType`; axios - .post(url, {days:props.days, type:'Audio'}, { - headers: { - Authorization: `Bearer ${config.token}`, - "Content-Type": "application/json", - }, - }) + .post( + url, + { days: props.days, type: "Audio" }, + { + headers: { + Authorization: `Bearer ${config.token}`, + "Content-Type": "application/json", + }, + }, + ) .then((data) => { setData(data.data); }) @@ -41,7 +44,6 @@ function MVMovies(props) { }); } }; - if (!config) { fetchConfig(); @@ -54,21 +56,22 @@ function MVMovies(props) { setDays(props.days); fetchLibraries(); } - const intervalId = setInterval(fetchLibraries, 60000 * 5); return () => clearInterval(intervalId); - }, [data, config, days,props.days]); + }, [data, config, days, props.days]); if (!data || data.length === 0) { - return <>; + return <>; } - - - return ( - + ); } diff --git a/src/pages/components/statCards/mv_series.js b/src/pages/components/statCards/mv_series.js index 30c9be8b..96cdb405 100644 --- a/src/pages/components/statCards/mv_series.js +++ b/src/pages/components/statCards/mv_series.js @@ -2,17 +2,14 @@ import React, { useState, useEffect } from "react"; import axios from "axios"; import Config from "../../../lib/config"; - import ItemStatComponent from "./ItemStatComponent"; - function MVSeries(props) { const [data, setData] = useState(); - const [days, setDays] = useState(30); + const [days, setDays] = useState(30); const [config, setConfig] = useState(null); - useEffect(() => { const fetchConfig = async () => { try { @@ -30,12 +27,16 @@ function MVSeries(props) { const url = `/stats/getMostViewedByType`; axios - .post(url, {days:props.days, type:'Series'}, { - headers: { - Authorization: `Bearer ${config.token}`, - "Content-Type": "application/json", - }, - }) + .post( + url, + { days: props.days, type: "Series" }, + { + headers: { + Authorization: `Bearer ${config.token}`, + "Content-Type": "application/json", + }, + }, + ) .then((data) => { setData(data.data); }) @@ -44,7 +45,6 @@ function MVSeries(props) { }); } }; - if (!config) { fetchConfig(); @@ -57,19 +57,22 @@ function MVSeries(props) { setDays(props.days); fetchLibraries(); } - const intervalId = setInterval(fetchLibraries, 60000 * 5); return () => clearInterval(intervalId); - }, [data, config, days,props.days]); + }, [data, config, days, props.days]); if (!data || data.length === 0) { - return <>; + return <>; } - return ( - + ); } diff --git a/src/pages/components/statistics/chart.js b/src/pages/components/statistics/chart.js index 0e11a1f6..37b097b1 100644 --- a/src/pages/components/statistics/chart.js +++ b/src/pages/components/statistics/chart.js @@ -1,5 +1,13 @@ -import React from 'react'; -import { ResponsiveContainer, AreaChart, Area, XAxis, YAxis, Tooltip, Legend } from 'recharts'; +import React from "react"; +import { + ResponsiveContainer, + AreaChart, + Area, + XAxis, + YAxis, + Tooltip, + Legend, +} from "recharts"; function Chart({ stats, libraries }) { const wordToColor = (word) => { @@ -13,12 +21,12 @@ function Chart({ stats, libraries }) { let hash = number; const str = number.toString(); for (let i = 0; i < str.length; i++) { - hash = ((hash << 5) + hash) + str.charCodeAt(i); + hash = (hash << 5) + hash + str.charCodeAt(i); } // Map the hash to an RGB color, with each channel restricted to a certain range const red = (hash % 256) * 4; - const green = ((hash >> 8) % 256)*2; - const blue = ((hash >> 16) % 256); + const green = ((hash >> 8) % 256) * 2; + const blue = (hash >> 16) % 256; // Return an RGB color string return `rgb(${red}, ${green}, ${blue})`; }; @@ -26,10 +34,16 @@ function Chart({ stats, libraries }) { const CustomTooltip = ({ payload, label, active }) => { if (active) { return ( -
-

{label}

+
+

{label}

{libraries.map((library, index) => ( -

{`${library.Name} : ${payload[index].value} Views`}

+

{`${library.Name} : ${payload[index].value} Views`}

))}
); @@ -38,45 +52,71 @@ function Chart({ stats, libraries }) { return null; }; - - const getMaxValue = () => { let max = 0; - if(stats) - { - stats.forEach(datum => { - Object.keys(datum).forEach(key => { + if (stats) { + stats.forEach((datum) => { + Object.keys(datum).forEach((key) => { if (key !== "Key") { max = Math.max(max, parseInt(datum[key])); } }); }); - } return max; - } + }; - const max = getMaxValue()+10; - + const max = getMaxValue() + 10; return ( - + {libraries.map((library) => ( - - - + + + ))} - + } /> {libraries.map((library) => ( - + ))} diff --git a/src/pages/components/statistics/daily-play-count.js b/src/pages/components/statistics/daily-play-count.js index 7eddd678..31b3aec2 100644 --- a/src/pages/components/statistics/daily-play-count.js +++ b/src/pages/components/statistics/daily-play-count.js @@ -6,15 +6,11 @@ import Chart from "./chart"; import "../../css/stats.css"; function DailyPlayStats(props) { - const [stats, setStats] = useState(); const [libraries, setLibraries] = useState(); const [days, setDays] = useState(20); const token = localStorage.getItem("token"); - - - useEffect(() => { const fetchLibraries = () => { const url = `/stats/getViewsOverTime`; @@ -28,7 +24,7 @@ function DailyPlayStats(props) { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, - } + }, ) .then((data) => { setStats(data.data.stats); @@ -49,7 +45,7 @@ function DailyPlayStats(props) { const intervalId = setInterval(fetchLibraries, 60000 * 5); return () => clearInterval(intervalId); - }, [stats,libraries, days, props.days, token]); + }, [stats, libraries, days, props.days, token]); if (!stats) { return <>; @@ -66,10 +62,12 @@ function DailyPlayStats(props) { } return (
-

Daily Play Count Per Library - Last {days} Days

+

+ Daily Play Count Per Library - Last {days} Days +

- +
); diff --git a/src/pages/components/statistics/play-stats-by-day.js b/src/pages/components/statistics/play-stats-by-day.js index 9710a591..05588909 100644 --- a/src/pages/components/statistics/play-stats-by-day.js +++ b/src/pages/components/statistics/play-stats-by-day.js @@ -23,7 +23,7 @@ function PlayStatsByDay(props) { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, - } + }, ) .then((data) => { setStats(data.data.stats); diff --git a/src/pages/components/statistics/play-stats-by-hour.js b/src/pages/components/statistics/play-stats-by-hour.js index 62054872..877ab286 100644 --- a/src/pages/components/statistics/play-stats-by-hour.js +++ b/src/pages/components/statistics/play-stats-by-hour.js @@ -22,7 +22,7 @@ function PlayStatsByHour(props) { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, - } + }, ) .then((data) => { setStats(data.data.stats); @@ -59,12 +59,11 @@ function PlayStatsByHour(props) { ); } - return (

Play Count By Hour - Last {days} Days

- +
); diff --git a/src/pages/components/user-info.js b/src/pages/components/user-info.js index 21dae39a..ee00fa8a 100644 --- a/src/pages/components/user-info.js +++ b/src/pages/components/user-info.js @@ -1,59 +1,57 @@ -import { useParams } from 'react-router-dom'; +import { useParams } from "react-router-dom"; import React, { useState, useEffect } from "react"; import axios from "axios"; import AccountCircleFillIcon from "remixicon-react/AccountCircleFillIcon"; import Config from "../../lib/config"; -import {Tabs, Tab, Button, ButtonGroup } from 'react-bootstrap'; +import { Tabs, Tab, Button, ButtonGroup } from "react-bootstrap"; -import GlobalStats from './user-info/globalStats'; -import LastPlayed from './user-info/lastplayed'; -import UserActivity from './user-info/user-activity'; +import GlobalStats from "./user-info/globalStats"; +import LastPlayed from "./user-info/lastplayed"; +import UserActivity from "./user-info/user-activity"; import "../css/users/user-details.css"; - - - - function UserInfo() { const { UserId } = useParams(); const [data, setData] = useState(); const [imgError, setImgError] = useState(false); const [config, setConfig] = useState(); - const [activeTab, setActiveTab] = useState('tabOverview'); + const [activeTab, setActiveTab] = useState("tabOverview"); useEffect(() => { - const fetchConfig = async () => { - try { - const newConfig = await Config(); - setConfig(newConfig); - } catch (error) { - console.log(error); - } - }; - - const fetchData = async () => { - if(config){ try { - const userData = await axios.post(`/api/getUserDetails`, { - userid: UserId, - }, { - headers: { - Authorization: `Bearer ${config.token}`, - "Content-Type": "application/json", - }, - }); - setData(userData.data); + const newConfig = await Config(); + setConfig(newConfig); } catch (error) { console.log(error); } - } + }; + const fetchData = async () => { + if (config) { + try { + const userData = await axios.post( + `/api/getUserDetails`, + { + userid: UserId, + }, + { + headers: { + Authorization: `Bearer ${config.token}`, + "Content-Type": "application/json", + }, + }, + ); + setData(userData.data); + } catch (error) { + console.log(error); + } + } }; fetchData(); if (!config) { - fetchConfig(); + fetchConfig(); } const intervalId = setInterval(fetchData, 60000 * 5); @@ -68,48 +66,58 @@ function UserInfo() { return <>; } - - return (
-
- {imgError ? ( - - ) : ( - - )} -
- -
-

{data.Name}

+
+ {imgError ? ( + + ) : ( + + )} +
+ +
+

{data.Name}

- - + + +
-
- - - - - - - - - - - + + + + + + + + +
); } diff --git a/src/pages/components/user-info/globalStats.js b/src/pages/components/user-info/globalStats.js index 8b6c5164..dce94a77 100644 --- a/src/pages/components/user-info/globalStats.js +++ b/src/pages/components/user-info/globalStats.js @@ -9,53 +9,69 @@ function GlobalStats(props) { const [weekStats, setWeekStats] = useState({}); const [monthStats, setMonthStats] = useState({}); const [allStats, setAllStats] = useState({}); - const token = localStorage.getItem('token'); + const token = localStorage.getItem("token"); useEffect(() => { const fetchData = async () => { try { - const dayData = await axios.post(`/stats/getGlobalUserStats`, { - hours: (24*1), - userid: props.UserId, - }, { - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", + const dayData = await axios.post( + `/stats/getGlobalUserStats`, + { + hours: 24 * 1, + userid: props.UserId, }, - }); + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }, + ); setDayStats(dayData.data); - const weekData = await axios.post(`/stats/getGlobalUserStats`, { - hours: (24*7), - userid: props.UserId, - }, { - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", + const weekData = await axios.post( + `/stats/getGlobalUserStats`, + { + hours: 24 * 7, + userid: props.UserId, + }, + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, }, - }); + ); setWeekStats(weekData.data); - const monthData = await axios.post(`/stats/getGlobalUserStats`, { - hours: (24*30), - userid: props.UserId, - }, { - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", + const monthData = await axios.post( + `/stats/getGlobalUserStats`, + { + hours: 24 * 30, + userid: props.UserId, }, - }); + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }, + ); setMonthStats(monthData.data); - const allData = await axios.post(`/stats/getGlobalUserStats`, { - hours: (24*999), - userid: props.UserId, - }, { - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", + const allData = await axios.post( + `/stats/getGlobalUserStats`, + { + hours: 24 * 999, + userid: props.UserId, + }, + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, }, - }); + ); setAllStats(allData.data); } catch (error) { console.log(error); @@ -65,12 +81,12 @@ function GlobalStats(props) { fetchData(); const intervalId = setInterval(fetchData, 60000 * 5); return () => clearInterval(intervalId); - }, [props.UserId,token]); + }, [props.UserId, token]); return (

User Stats

-
+
diff --git a/src/pages/components/user-info/globalstats/watchtimestats.js b/src/pages/components/user-info/globalstats/watchtimestats.js index 9a661c62..4e49326e 100644 --- a/src/pages/components/user-info/globalstats/watchtimestats.js +++ b/src/pages/components/user-info/globalstats/watchtimestats.js @@ -3,14 +3,13 @@ import React from "react"; import "../../../css/globalstats.css"; function WatchTimeStats(props) { - function formatTime(totalSeconds, numberClassName, labelClassName) { const units = [ - { label: 'Day', seconds: 86400 }, - { label: 'Hour', seconds: 3600 }, - { label: 'Minute', seconds: 60 }, + { 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) { @@ -18,32 +17,30 @@ function WatchTimeStats(props) { const formattedLabel = ( {label} - {value === 1 ? '' : 's'} + {value === 1 ? "" : "s"} ); result.push( {formattedValue} {formattedLabel} - + , ); totalSeconds -= value * seconds; } return result; }, []); - + if (parts.length === 0) { return ( <> -

0

{' '} +

0

{" "}

Minutes

); } - + return parts; } - - return (
@@ -52,11 +49,16 @@ function WatchTimeStats(props) {
-

{props.data.Plays || 0}

-

Plays /

- - <>{formatTime(props.data.total_playback_duration || 0,'stat-value','stat-unit')} +

{props.data.Plays || 0}

+

Plays /

+ <> + {formatTime( + props.data.total_playback_duration || 0, + "stat-value", + "stat-unit", + )} +
); diff --git a/src/pages/components/user-info/lastplayed.js b/src/pages/components/user-info/lastplayed.js index 9a2d4c34..5b695f78 100644 --- a/src/pages/components/user-info/lastplayed.js +++ b/src/pages/components/user-info/lastplayed.js @@ -11,35 +11,36 @@ function LastPlayed(props) { const [data, setData] = useState(); const [config, setConfig] = useState(); - useEffect(() => { - const fetchConfig = async () => { - try { - const newConfig = await Config(); - setConfig(newConfig); - } catch (error) { - console.log(error); - } - }; - - const fetchData = async () => { - if(config) - { try { - const itemData = await axios.post(`/stats/getUserLastPlayed`, { - userid: props.UserId, - }, { - headers: { - Authorization: `Bearer ${config.token}`, - "Content-Type": "application/json", - }, - }); - setData(itemData.data); + const newConfig = await Config(); + setConfig(newConfig); } catch (error) { console.log(error); - } - } + } + }; + + const fetchData = async () => { + if (config) { + try { + const itemData = await axios.post( + `/stats/getUserLastPlayed`, + { + userid: props.UserId, + }, + { + headers: { + Authorization: `Bearer ${config.token}`, + "Content-Type": "application/json", + }, + }, + ); + setData(itemData.data); + } catch (error) { + console.log(error); + } + } }; if (!data) { @@ -47,13 +48,12 @@ function LastPlayed(props) { } if (!config) { - fetchConfig(); + fetchConfig(); } const intervalId = setInterval(fetchData, 60000 * 5); return () => clearInterval(intervalId); - }, [data,config, props.UserId]); - + }, [data, config, props.UserId]); if (!data || !config) { return <>; @@ -61,17 +61,18 @@ function LastPlayed(props) { return (
-

Last Watched

-
+

Last Watched

+
{data.map((item) => ( - - - - - ))} - -
- + + + + ))} +
); } diff --git a/src/pages/components/user-info/user-activity.js b/src/pages/components/user-info/user-activity.js index 2725a7fa..ff424f95 100644 --- a/src/pages/components/user-info/user-activity.js +++ b/src/pages/components/user-info/user-activity.js @@ -4,21 +4,24 @@ import ActivityTable from "../activity/activity-table"; function UserActivity(props) { const [data, setData] = useState(); - const token = localStorage.getItem('token'); - const [itemCount,setItemCount] = useState(10); + const token = localStorage.getItem("token"); + const [itemCount, setItemCount] = useState(10); useEffect(() => { - const fetchData = async () => { try { - const itemData = await axios.post(`/api/getUserHistory`, { - userid: props.UserId, - }, { - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", + const itemData = await axios.post( + `/api/getUserHistory`, + { + userid: props.UserId, + }, + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, }, - }); + ); setData(itemData.data); } catch (error) { console.log(error); @@ -29,8 +32,7 @@ function UserActivity(props) { const intervalId = setInterval(fetchData, 60000 * 5); return () => clearInterval(intervalId); - }, [ props.UserId,token]); - + }, [props.UserId, token]); if (!data) { return <>; @@ -38,24 +40,27 @@ function UserActivity(props) { return (
-
-

User Activity

-
-
Items
- +
+

User Activity

+
+
Items
+ +
+
+
+
-
- - - -
-
); } diff --git a/src/pages/css/about.css b/src/pages/css/about.css index 0786f699..e824a2e4 100644 --- a/src/pages/css/about.css +++ b/src/pages/css/about.css @@ -1,20 +1,14 @@ -@import './variables.module.css'; -.about -{ - background-color: var(--second-background-color) !important; - border-color: transparent !important; - color: white !important; - - +@import "./variables.module.css"; +.about { + background-color: var(--second-background-color) !important; + border-color: transparent !important; + color: white !important; } -.about a -{ - text-decoration: none; +.about a { + text-decoration: none; } -.about a:hover -{ - text-decoration: underline; +.about a:hover { + text-decoration: underline; } - diff --git a/src/pages/css/activity.css b/src/pages/css/activity.css index 4765ffe7..d10e1667 100644 --- a/src/pages/css/activity.css +++ b/src/pages/css/activity.css @@ -1,3 +1,3 @@ -.Activity{ +.Activity { margin-top: 10px; -} \ No newline at end of file +} diff --git a/src/pages/css/activity/activity-table.css b/src/pages/css/activity/activity-table.css index 0247029b..02a82d1c 100644 --- a/src/pages/css/activity/activity-table.css +++ b/src/pages/css/activity/activity-table.css @@ -1,46 +1,34 @@ -@import '../variables.module.css'; +@import "../variables.module.css"; -.activity-table -{ - background-color: var(--secondary-background-color); - color: white !important; - +.activity-table { + background-color: var(--secondary-background-color); + color: white !important; } -td,th, td>button -{ +td, +th, +td > button { color: white !important; background-color: var(--tertiary-background-color); - } -th -{ +th { color: white !important; background-color: rgba(200, 200, 200, 0.2); - } -.activity-client:hover -{ +.activity-client:hover { color: var(--secondary-color) !important; - } -.activity-client:hover > span -{ - +.activity-client:hover > span { cursor: pointer; } - - -td > a -{ +td > a { color: white; } -td:hover > a -{ +td:hover > a { color: var(--secondary-color); } @@ -54,79 +42,63 @@ select option { outline: unset; width: 100%; border: none; - - } - -.pagination-range .items -{ -background-color: rgb(255, 255, 255, 0.1); -padding-inline: 10px; } - .pagination-range .header -{ -padding-inline: 10px; -align-self: center; +.pagination-range .items { + background-color: rgb(255, 255, 255, 0.1); + padding-inline: 10px; } -.pagination-range -{ -width: 130px; -height: 35px; -color: white; -display: flex; -background-color: var(--secondary-background-color); -border-radius: 8px; -font-size: 1.2em; -align-self: flex-end; -justify-content: space-between; +.pagination-range .header { + padding-inline: 10px; + align-self: center; } -.pagination-range select -{ - -height: 35px; -outline: none; -border: none; -border-radius: 8px; -background-color: rgb(255, 255, 255, 0.1); -color:white; -font-size: 1em; - +.pagination-range { + width: 130px; + height: 35px; + color: white; + display: flex; + background-color: var(--secondary-background-color); + border-radius: 8px; + font-size: 1.2em; + align-self: flex-end; + justify-content: space-between; +} +.pagination-range select { + height: 35px; + outline: none; + border: none; + border-radius: 8px; + background-color: rgb(255, 255, 255, 0.1); + color: white; + font-size: 1em; } -.page-btn -{ - background-color: var(--primary-color) !important; - border-color: var(--primary-color)!important; +.page-btn { + background-color: var(--primary-color) !important; + border-color: var(--primary-color) !important; } -.MuiTableCell-head > .Mui-active, .MuiTableSortLabel-icon -{ +.MuiTableCell-head > .Mui-active, +.MuiTableSortLabel-icon { color: var(--secondary-color) !important; } -.MuiTableCell-head :hover -{ +.MuiTableCell-head :hover { color: var(--secondary-color) !important; } -.modal-header -{ - color: white; +.modal-header { + color: white; border-bottom: none !important; } - -.modal-footer -{ +.modal-footer { border-top: none !important; } - -.modal-content -{ - background-color: var(--secondary-background-color) !important; +.modal-content { + background-color: var(--secondary-background-color) !important; } - diff --git a/src/pages/css/activity/stream-info.css b/src/pages/css/activity/stream-info.css index 4a3f69ce..ef8f74e7 100644 --- a/src/pages/css/activity/stream-info.css +++ b/src/pages/css/activity/stream-info.css @@ -1,10 +1,9 @@ -@import '../variables.module.css'; +@import "../variables.module.css"; -.ellipse -{ - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 1; - overflow: hidden; - text-overflow: ellipsis; +.ellipse { + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 1; + overflow: hidden; + text-overflow: ellipsis; } diff --git a/src/pages/css/error.css b/src/pages/css/error.css index bddc7eb6..c69938d9 100644 --- a/src/pages/css/error.css +++ b/src/pages/css/error.css @@ -1,29 +1,25 @@ -.error -{ - margin: 0px; - height: calc(100vh - 100px); - - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - /* z-index: 9999; */ - background-color: #1e1c22; - transition: opacity 800ms ease-in; - opacity: 1; - color: white; -} +.error { + margin: 0px; + height: calc(100vh - 100px); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + /* z-index: 9999; */ + background-color: #1e1c22; + transition: opacity 800ms ease-in; + opacity: 1; + color: white; +} -.error .message -{ - color:crimson; - font-size: 1.5em; - font-weight: 500; +.error .message { + color: crimson; + font-size: 1.5em; + font-weight: 500; } -.error-title -{ - color:crimson; - font-weight: 500; -} \ No newline at end of file +.error-title { + color: crimson; + font-weight: 500; +} diff --git a/src/pages/css/globalstats.css b/src/pages/css/globalstats.css index e01ab740..540b8914 100644 --- a/src/pages/css/globalstats.css +++ b/src/pages/css/globalstats.css @@ -1,52 +1,44 @@ - -@import './variables.module.css'; -.global-stats-container -{ - display: grid; - grid-template-columns: repeat(auto-fit, minmax(350px, 400px)); - grid-auto-rows: 120px; - background-color: var(--secondary-background-color); - padding: 20px; - border-radius: 8px; - font-size: 1.3em; +@import "./variables.module.css"; +.global-stats-container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(350px, 400px)); + grid-auto-rows: 120px; + background-color: var(--secondary-background-color); + padding: 20px; + border-radius: 8px; + font-size: 1.3em; } -.global-stats -{ - color: white; - width: fit-content; - max-width: 400px; - min-width: 350px; - height: 100px; - padding: 5px; - +.global-stats { + color: white; + width: fit-content; + max-width: 400px; + min-width: 350px; + height: 100px; + padding: 5px; } -.play-duration-stats -{ - padding-top: 20px; - display: flex; - flex-direction: row; - align-items: center; +.play-duration-stats { + padding-top: 20px; + display: flex; + flex-direction: row; + align-items: center; } -.stat-value -{ - text-align: right; - color: var(--secondary-color); - font-weight: 500; - font-size: 1.1em; - margin: 0; +.stat-value { + text-align: right; + color: var(--secondary-color); + font-weight: 500; + font-size: 1.1em; + margin: 0; } -.stat-unit -{ - padding-inline: 5px; - 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 +.time-part { + display: flex; + flex-direction: row; + align-items: center; +} diff --git a/src/pages/css/home.css b/src/pages/css/home.css index 2eadae4f..15790f99 100644 --- a/src/pages/css/home.css +++ b/src/pages/css/home.css @@ -1,5 +1,4 @@ -.Home -{ - color: white; - margin-bottom: 20px; -} \ No newline at end of file +.Home { + color: white; + margin-bottom: 20px; +} diff --git a/src/pages/css/items/item-details.css b/src/pages/css/items/item-details.css index 8de03232..2c00975a 100644 --- a/src/pages/css/items/item-details.css +++ b/src/pages/css/items/item-details.css @@ -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; - } \ No newline at end of file +.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; +} diff --git a/src/pages/css/lastplayed.css b/src/pages/css/lastplayed.css index f2d1186d..9b103b9d 100644 --- a/src/pages/css/lastplayed.css +++ b/src/pages/css/lastplayed.css @@ -1,127 +1,103 @@ -@import './variables.module.css'; +@import "./variables.module.css"; .last-played-container { - - display: flex; - overflow-x: scroll; - background-color: var(--secondary-background-color); - padding: 20px; - border-radius: 8px; - color: white; - margin-bottom: 20px; - min-height: 300px; - + display: flex; + overflow-x: scroll; + background-color: var(--secondary-background-color); + padding: 20px; + border-radius: 8px; + color: white; + margin-bottom: 20px; + min-height: 300px; } .last-played-container::-webkit-scrollbar { - width: 5px; /* set scrollbar width */ - } - - .last-played-container::-webkit-scrollbar-track { - background-color: transparent; /* set track color */ - } - - .last-played-container::-webkit-scrollbar-thumb { - background-color: #8888884d; /* set thumb color */ - border-radius: 5px; /* round corners */ - width: 5px; - } - - - - .last-played-container::-webkit-scrollbar-thumb:hover { - background-color: #88888883; /* set thumb color */ - } + width: 5px; /* set scrollbar width */ +} +.last-played-container::-webkit-scrollbar-track { + background-color: transparent; /* set track color */ +} +.last-played-container::-webkit-scrollbar-thumb { + background-color: #8888884d; /* set thumb color */ + border-radius: 5px; /* round corners */ + width: 5px; +} -.last-card -{ - display: flex; - flex-direction: column; - margin-right: 20px; +.last-played-container::-webkit-scrollbar-thumb:hover { + background-color: #88888883; /* set thumb color */ +} - width: 150px; - border-radius: 8px; - background-color: var(--background-color); +.last-card { + display: flex; + flex-direction: column; + margin-right: 20px; + width: 150px; + border-radius: 8px; + background-color: var(--background-color); } - -.episode{ +.episode { width: 220px !important; height: 128px !important; - } -.episode-card{ +.episode-card { width: 220px !important; - - } - .last-card-banner { width: 150px; height: 220px; transition: opacity 0.2s ease-in-out; } - - .last-card-banner:hover { opacity: 0.5; - } - .last-card-banner img { width: 100%; height: 100%; border-radius: 8px 8px 0px 0px; - } .last-item-details { - width: 90%; - position: relative; - margin: 10px; + width: 90%; + position: relative; + margin: 10px; } -.last-item-details a{ +.last-item-details a { text-decoration: none !important; color: white !important; } -.last-item-details a:hover{ - color: var(--secondary-color) !important; +.last-item-details a:hover { + color: var(--secondary-color) !important; } - .last-item-name { - overflow: hidden; - text-overflow: ellipsis; - + overflow: hidden; + text-overflow: ellipsis; } .last-item-episode { - - overflow: hidden; - text-overflow: ellipsis; - color:gray; - font-size: 0.8em; + overflow: hidden; + text-overflow: ellipsis; + color: gray; + font-size: 0.8em; } - .number{ - margin-inline: 10px; - padding-bottom: 10px; +.number { + margin-inline: 10px; + padding-bottom: 10px; } - - -.last-last-played{ - font-size: 0.8em; - margin-bottom: 5px; - color: var(--secondary-color); +.last-last-played { + font-size: 0.8em; + margin-bottom: 5px; + color: var(--secondary-color); } - diff --git a/src/pages/css/library/libraries.css b/src/pages/css/library/libraries.css index 0bff990b..d18389d6 100644 --- a/src/pages/css/library/libraries.css +++ b/src/pages/css/library/libraries.css @@ -1,10 +1,6 @@ -.libraries-container -{ - color: white; - display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); - grid-gap: 20px; - - +.libraries-container { + color: white; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + grid-gap: 20px; } - diff --git a/src/pages/css/library/library-card.css b/src/pages/css/library/library-card.css index 44787fb0..eb1ff8e0 100644 --- a/src/pages/css/library/library-card.css +++ b/src/pages/css/library/library-card.css @@ -1,92 +1,77 @@ -@import '../variables.module.css'; - -.lib-card{ - color: white; - /* max-width: 400px; */ +@import "../variables.module.css"; +.lib-card { + color: white; + /* max-width: 400px; */ } -.card-label -{ - color: var(--secondary-color); +.card-label { + color: var(--secondary-color); } -.card-row .col -{ - margin: 10px; +.card-row .col { + margin: 10px; } +.library-card-image { + max-height: 170px; -.library-card-image -{ - max-height: 170px; - - overflow: hidden; - border-radius: 8px 8px 0px 0px; - + overflow: hidden; + border-radius: 8px 8px 0px 0px; } -.library-card-banner -{ - object-fit: cover; - background-color: black; - background-repeat: no-repeat; - background-size: cover; - transition: all 0.2s ease-in-out; - max-height: 170px; - height: 170px; - +.library-card-banner { + object-fit: cover; + background-color: black; + background-repeat: no-repeat; + background-size: cover; + transition: all 0.2s ease-in-out; + max-height: 170px; + height: 170px; } -.library-card-banner-hover:hover, .default_library_image_hover:hover -{ - opacity: 0.5; +.library-card-banner-hover:hover, +.default_library_image_hover:hover { + opacity: 0.5; } -.library-card-details -{ - background-color: var(--secondary-background-color) !important; +.library-card-details { + background-color: var(--secondary-background-color) !important; } -.library-card-details-inv -{ - background-color: var(--background-color) !important; +.library-card-details-inv { + background-color: var(--background-color) !important; } - - -.default_library_image -{ - background-color: var(--secondary-background-color); - width: 100%; - height: 170px; - border-radius: 8px 8px 0px 0px; - transition: all 0.2s ease-in-out; +.default_library_image { + background-color: var(--secondary-background-color); + width: 100%; + height: 170px; + border-radius: 8px 8px 0px 0px; + transition: all 0.2s ease-in-out; } -.default_library_image-inv -{ - background-color: var(--background-color); - width: 100%; - height: 170px; - border-radius: 8px 8px 0px 0px; - transition: all 0.2s ease-in-out; +.default_library_image-inv { + background-color: var(--background-color); + width: 100%; + height: 170px; + border-radius: 8px 8px 0px 0px; + transition: all 0.2s ease-in-out; } .form-switch .form-check-input { - border-color: var(--primary-color) !important; + border-color: var(--primary-color) !important; } - + .form-switch .form-check-input:checked { background-color: var(--primary-color); } .form-switch .form-check-input:focus { - box-shadow: none !important; - border-color: var(--primary-color) !important; + box-shadow: none !important; + border-color: var(--primary-color) !important; } .form-switch .form-check-input:hover { - box-shadow: none !important; - border-color: var(--primary-color) !important; + box-shadow: none !important; + border-color: var(--primary-color) !important; } - diff --git a/src/pages/css/library/media-items.css b/src/pages/css/library/media-items.css index 68862fb4..ece0bdb3 100644 --- a/src/pages/css/library/media-items.css +++ b/src/pages/css/library/media-items.css @@ -1,48 +1,42 @@ -@import '../variables.module.css'; +@import "../variables.module.css"; .media-items-container { - - display: grid; - grid-template-columns: repeat(auto-fit, minmax(150px, 150px)); - grid-gap: 20px; - - background-color: var(--secondary-background-color); - padding: 20px; - border-radius: 8px; - color: white; - margin-bottom: 20px; - min-height: 300px; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 150px)); + grid-gap: 20px; + + background-color: var(--secondary-background-color); + padding: 20px; + border-radius: 8px; + color: white; + margin-bottom: 20px; + min-height: 300px; } .media-items-container::-webkit-scrollbar { - width: 5px; /* set scrollbar width */ - } - - .media-items-container::-webkit-scrollbar-track { - background-color: transparent; /* set track color */ - } - - .media-items-container::-webkit-scrollbar-thumb { - background-color: #8888884d; /* set thumb color */ - border-radius: 5px; /* round corners */ - width: 5px; - } + width: 5px; /* set scrollbar width */ +} +.media-items-container::-webkit-scrollbar-track { + background-color: transparent; /* set track color */ +} +.media-items-container::-webkit-scrollbar-thumb { + background-color: #8888884d; /* set thumb color */ + border-radius: 5px; /* round corners */ + width: 5px; +} - .media-items-container::-webkit-scrollbar-thumb:hover { - background-color: #88888883; /* set thumb color */ - } +.media-items-container::-webkit-scrollbar-thumb:hover { + background-color: #88888883; /* set thumb color */ +} - .library-items > div>div> .form-control - { - color: white !important; - background-color: var(--secondary-background-color) !important; - border-color: var(--secondary-background-color) !important; - } +.library-items > div > div > .form-control { + color: white !important; + background-color: var(--secondary-background-color) !important; + border-color: var(--secondary-background-color) !important; +} - - .library-items > div> div>.form-control:focus - { - box-shadow: none !important; - border-color: var(--primary-color) !important; - } \ No newline at end of file +.library-items > div > div > .form-control:focus { + box-shadow: none !important; + border-color: var(--primary-color) !important; +} diff --git a/src/pages/css/libraryOverview.css b/src/pages/css/libraryOverview.css index 414f64b1..6c30563a 100644 --- a/src/pages/css/libraryOverview.css +++ b/src/pages/css/libraryOverview.css @@ -1,110 +1,93 @@ -.overview-container -{ +.overview-container { display: grid; grid-template-columns: repeat(auto-fit, minmax(auto, 520px)); - grid-auto-rows: 200px;/* max-width+offset so 215 + 20*/ + grid-auto-rows: 200px; /* max-width+offset so 215 + 20*/ background-color: var(--secondary-background-color); border-radius: 8px; padding: 20px; } -.library-stat-card -{ - width: 500px; - height: 180px; - display: flex; - color: white; - /* box-shadow: 0 0 20px 5px rgba(0, 0, 0, 0.5); */ - background: linear-gradient(to right, #00A4DC, #AA5CC3); - background-size: cover; +.library-stat-card { + width: 500px; + height: 180px; + display: flex; + color: white; + /* box-shadow: 0 0 20px 5px rgba(0, 0, 0, 0.5); */ + background: linear-gradient(to right, #00a4dc, #aa5cc3); + background-size: cover; } - - -.library-icons -{ - display: flex; - justify-content: center; - align-items: center; - height: 100%; - +.library-icons { + display: flex; + justify-content: center; + align-items: center; + height: 100%; } - .library-header { - display: flex; - justify-content: space-between; - color: white; - font-weight: 500; - } - .library-header-count { - color: lightgray; - font-weight: 300; - - } - - .library-item { - display: flex; - justify-content: space-between; - width: 100%; - height: 20px; - margin-bottom: 5px; - - } - - .library-item-index { - padding-top: 3px; - font-size: 0.8em; - padding-right: 2px; - color: grey; +.library-header { + display: flex; + justify-content: space-between; + color: white; + font-weight: 500; +} +.library-header-count { + color: lightgray; + font-weight: 300; +} - text-align: right; - } - .library-item-name { - width: 35%; - } - - .library-item-count { - width: 60%; - text-align: right; - color: #00A4DC; - font-weight: 500; - font-size: 1.1em; +.library-item { + display: flex; + justify-content: space-between; + width: 100%; + height: 20px; + margin-bottom: 5px; +} - } +.library-item-index { + padding-top: 3px; + font-size: 0.8em; + padding-right: 2px; + color: grey; - .library-image - { - display: flex; - justify-content: center; - align-items: center; - height: 180px; - width: 180px; - background-color: rgb(0, 0, 0, 0.6); - } + text-align: right; +} +.library-item-name { + width: 35%; +} - .library-banner-image - { - height: 180px; - width: 120px; - } +.library-item-count { + width: 60%; + text-align: right; + color: #00a4dc; + font-weight: 500; + font-size: 1.1em; +} - - .library-user-image - { +.library-image { + display: flex; + justify-content: center; + align-items: center; + height: 180px; + width: 180px; + background-color: rgb(0, 0, 0, 0.6); +} - border-radius: 50%; - width: 80%; - object-fit: cover; - - } +.library-banner-image { + height: 180px; + width: 120px; +} +.library-user-image { + border-radius: 50%; + width: 80%; + object-fit: cover; +} - .library{ - width: 100%; - padding: 5px 20px; - backdrop-filter: blur(8px); - - background-color: rgb(0, 0, 0, 0.6); - } +.library { + width: 100%; + padding: 5px 20px; + backdrop-filter: blur(8px); + background-color: rgb(0, 0, 0, 0.6); +} diff --git a/src/pages/css/loading.css b/src/pages/css/loading.css index 0b6c9926..1bc8ed94 100644 --- a/src/pages/css/loading.css +++ b/src/pages/css/loading.css @@ -1,38 +1,34 @@ -@import './variables.module.css'; +@import "./variables.module.css"; .loading { + margin: 0px; + height: calc(100vh - 100px); - margin: 0px; - height: calc(100vh - 100px); - - display: flex; - justify-content: center; - align-items: center; - background-color: var(--background-color); - transition: opacity 800ms ease-in; - opacity: 1; - } - -.loading::before -{ + display: flex; + justify-content: center; + align-items: center; + background-color: var(--background-color); + transition: opacity 800ms ease-in; + opacity: 1; +} + +.loading::before { opacity: 0; } - - .loading__spinner { - width: 50px; - height: 50px; - border: 5px solid #ccc; - border-top-color: #333; - border-radius: 50%; - animation: spin 1s ease-in-out infinite; +.loading__spinner { + width: 50px; + height: 50px; + border: 5px solid #ccc; + border-top-color: #333; + border-radius: 50%; + animation: spin 1s ease-in-out infinite; +} + +@keyframes spin { + from { + transform: rotate(0deg); } - - @keyframes spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } + to { + transform: rotate(360deg); } - \ No newline at end of file +} diff --git a/src/pages/css/navbar.css b/src/pages/css/navbar.css index 3e152df8..bc8fa75c 100644 --- a/src/pages/css/navbar.css +++ b/src/pages/css/navbar.css @@ -1,4 +1,4 @@ -@import './variables.module.css'; +@import "./variables.module.css"; .navbar { background-color: var(--secondary-background-color); border-right: 1px solid #414141 !important; @@ -7,29 +7,25 @@ @media (min-width: 768px) { .navbar { min-height: 100vh; - border-bottom: 1px solid #414141 !important; - + border-bottom: 1px solid #414141 !important; } } -.navbar .navbar-brand{ +.navbar .navbar-brand { margin-top: 20px; font-size: 32px; font-weight: 500; } -.navbar .navbar-nav{ +.navbar .navbar-nav { width: 100%; margin-top: 20px; } -.logout -{ - +.logout { color: var(--secondary-color) !important; } -.navbar-toggler > .collapsed -{ +.navbar-toggler > .collapsed { right: 0; } /* .navbar-toggler-icon @@ -37,10 +33,7 @@ width: 100% !important; } */ - - .navitem { - color: white; font-size: 18px !important; text-decoration: none; @@ -62,24 +55,15 @@ background-color: var(--primary-color); } -.active -{ +.active { background-color: var(--primary-color); transition: background-color 0.2s ease-in-out; } - -.nav-link -{ +.nav-link { display: flex !important; } - .nav-text { margin-left: 10px; } - - - - - diff --git a/src/pages/css/radius_breakpoint_css.css b/src/pages/css/radius_breakpoint_css.css index 5721731d..1bb788c7 100644 --- a/src/pages/css/radius_breakpoint_css.css +++ b/src/pages/css/radius_breakpoint_css.css @@ -1,98 +1,92 @@ - /*based on https://drive.google.com/uc?export=view&id=1yTLwNiCZhIdCWolQldwq4spHQkgZDqkG */ /* Small devices (landscape phones, 576px and up)*/ @media (min-width: 576px) { - .rounded-sm { - border-radius: 8px !important; - } - + .rounded-sm { + border-radius: 8px !important; + } - .rounded-sm-start { - border-radius: 8px 0px 0px 8px !important; - } + .rounded-sm-start { + border-radius: 8px 0px 0px 8px !important; + } - .rounded-sm-end { - border-radius: 0px 8px 8px 0px !important; - } + .rounded-sm-end { + border-radius: 0px 8px 8px 0px !important; + } - .rounded-sm-top { - border-radius: 8px 8px 0px 0px !important; - } + .rounded-sm-top { + border-radius: 8px 8px 0px 0px !important; + } - .rounded-sm-bottom { - border-radius: 0px 0px 8px 8px !important; - } + .rounded-sm-bottom { + border-radius: 0px 0px 8px 8px !important; + } } - /* Medium devices (tablets, 768px and up)*/ @media (min-width: 768px) { - .rounded-md { - border-radius: 8px !important; - } - + .rounded-md { + border-radius: 8px !important; + } - .rounded-md-start { - border-radius: 8px 0px 0px 8px !important; - } + .rounded-md-start { + border-radius: 8px 0px 0px 8px !important; + } - .rounded-md-end { - border-radius: 0px 8px 8px 0px !important; - } + .rounded-md-end { + border-radius: 0px 8px 8px 0px !important; + } - .rounded-md-top { - border-radius: 8px 8px 0px 0px !important; - } + .rounded-md-top { + border-radius: 8px 8px 0px 0px !important; + } - .rounded-md-bottom { - border-radius: 0px 0px 8px 8px !important; - } + .rounded-md-bottom { + border-radius: 0px 0px 8px 8px !important; + } } /* Large devices (desktops, 992px and up)*/ @media (min-width: 992px) { - .rounded-lg { - border-radius: 8px !important; - } + .rounded-lg { + border-radius: 8px !important; + } + .rounded-lg-start { + border-radius: 8px 0px 0px 8px !important; + } - .rounded-lg-start { - border-radius: 8px 0px 0px 8px !important; - } + .rounded-lg-end { + border-radius: 0px 8px 8px 0px !important; + } - .rounded-lg-end { - border-radius: 0px 8px 8px 0px !important; - } + .rounded-lg-top { + border-radius: 8px 8px 0px 0px !important; + } - .rounded-lg-top { - border-radius: 8px 8px 0px 0px !important; - } - - .rounded-lg-bottom { - border-radius: 0px 0px 8px 8px !important; - } + .rounded-lg-bottom { + border-radius: 0px 0px 8px 8px !important; + } } /* Extra large devices (large desktops, 1200px and up)*/ @media (min-width: 1200px) { - .rounded-xl { - border-radius: 8px !important; - } - + .rounded-xl { + border-radius: 8px !important; + } - .rounded-xl-start { - border-radius: 8px 0px 0px 8px !important; - } + .rounded-xl-start { + border-radius: 8px 0px 0px 8px !important; + } - .rounded-xl-end { - border-radius: 0px 8px 8px 0px !important; - } + .rounded-xl-end { + border-radius: 0px 8px 8px 0px !important; + } - .rounded-xl-top { - border-radius: 8px 8px 0px 0px !important; - } + .rounded-xl-top { + border-radius: 8px 8px 0px 0px !important; + } - .rounded-xl-bottom { - border-radius: 0px 0px 8px 8px !important; - } -} \ No newline at end of file + .rounded-xl-bottom { + border-radius: 0px 0px 8px 8px !important; + } +} diff --git a/src/pages/css/recent.css b/src/pages/css/recent.css index 6bf99005..7426a3cb 100644 --- a/src/pages/css/recent.css +++ b/src/pages/css/recent.css @@ -1,66 +1,53 @@ .recent { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(185px, 200px)); - grid-auto-rows: 340px;/* max-width+offset so 215 + 20*/ - background-color: rgba(0,0,0,0.5); - padding: 20px; - border-radius: 8px; - margin-right: 20px; - color: white; - - + display: grid; + grid-template-columns: repeat(auto-fit, minmax(185px, 200px)); + grid-auto-rows: 340px; /* max-width+offset so 215 + 20*/ + background-color: rgba(0, 0, 0, 0.5); + padding: 20px; + border-radius: 8px; + margin-right: 20px; + color: white; } +.recent-card { + display: flex; + flex-direction: column; + box-shadow: 0 0 20px rgba(255, 255, 255, 0.05); -.recent-card -{ - display: flex; - flex-direction: column; - - box-shadow: 0 0 20px rgba(255, 255, 255, 0.05); - - height: 320px; - width: 185px; - border-radius: 8px; - + height: 320px; + width: 185px; + border-radius: 8px; } - .recent-card-banner { - width: 100%; - height: 70%; - background-size: cover; - background-repeat: no-repeat; - background-position: center top; - border-radius: 8px 8px 0px 0px; - + width: 100%; + height: 70%; + background-size: cover; + background-repeat: no-repeat; + background-position: center top; + border-radius: 8px 8px 0px 0px; } .recent-card-details { - - - width: 100%; - height: 30%; - position: relative; - + width: 100%; + height: 30%; + position: relative; } .recent-card-item-name { - width: 185px; - overflow: hidden; - text-overflow: ellipsis; - position: absolute; - margin: 0; - + width: 185px; + overflow: hidden; + text-overflow: ellipsis; + position: absolute; + margin: 0; } -.recent-card-last-played{ - width: 185px; - overflow: hidden; - text-overflow: ellipsis; - position: absolute; - bottom: 0; - margin: 0; +.recent-card-last-played { + width: 185px; + overflow: hidden; + text-overflow: ellipsis; + position: absolute; + bottom: 0; + margin: 0; } - diff --git a/src/pages/css/sessions.css b/src/pages/css/sessions.css index d657c512..46c1d3ab 100644 --- a/src/pages/css/sessions.css +++ b/src/pages/css/sessions.css @@ -1,208 +1,186 @@ - - .sessions-container { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(auto, 520px)); - grid-auto-rows: 200px;/* max-width+offset so 215 + 20*/ - /* margin-right: 20px; */ - + display: grid; + grid-template-columns: repeat(auto-fit, minmax(auto, 520px)); + grid-auto-rows: 200px; /* max-width+offset so 215 + 20*/ + /* margin-right: 20px; */ } .session-card { - display: flex; - color: white; - - background-color: grey; - /* box-shadow: 0 0 20px 5px rgba(0, 0, 0, 0.8); */ + display: flex; + color: white; - max-height: 180px; - max-width: 500px; + background-color: grey; + /* box-shadow: 0 0 20px 5px rgba(0, 0, 0, 0.8); */ - /* margin-left: 20px; */ - /* margin-bottom: 10px; */ + max-height: 180px; + max-width: 500px; - background-size: cover; - border-radius: 8px 8px 0px 8px; + /* margin-left: 20px; */ + /* margin-bottom: 10px; */ - display: grid; - grid-template-columns: auto 1fr; - grid-template-rows: auto auto 1fr; + background-size: cover; + border-radius: 8px 8px 0px 8px; + display: grid; + grid-template-columns: auto 1fr; + grid-template-rows: auto auto 1fr; } .progress-bar { - - /* grid-row: 2 / 3; + /* grid-row: 2 / 3; grid-column: 1/3; */ - height: 5px; - background-color: #101010 !important; - border-radius: 0px 0px 8px 8px; - - + height: 5px; + background-color: #101010 !important; + border-radius: 0px 0px 8px 8px; } .progress-custom { - height: 100%; - background-color: #00A4DC; - transition: width 0.2s ease-in-out; - border-radius: 0px 0px 0px 8px; + height: 100%; + background-color: #00a4dc; + transition: width 0.2s ease-in-out; + border-radius: 0px 0px 0px 8px; } .card-banner { - max-height: inherit; - height: 215px; - grid-row: 1 / 2; - grid-column-start: 1; - + max-height: inherit; + height: 215px; + grid-row: 1 / 2; + grid-column-start: 1; } .card-details { - padding-left: 5px; - max-height: inherit; - width: inherit; + padding-left: 5px; + max-height: inherit; + width: inherit; - grid-row: 1 / 3; + grid-row: 1 / 3; - grid-column: 2 / 3; - backdrop-filter: blur(1px); - background-color: rgb(0, 0, 0, 0.6); - border-radius: 0px 8px 0px 0px; + grid-column: 2 / 3; + backdrop-filter: blur(1px); + background-color: rgb(0, 0, 0, 0.6); + border-radius: 0px 8px 0px 0px; } - .card-banner-image { - border-radius: 8px 0px 0px 0px; - max-height: inherit; - /* box-shadow: 0 0 20px 5px rgba(0, 0, 0, 0.8); */ + border-radius: 8px 0px 0px 0px; + max-height: inherit; + /* box-shadow: 0 0 20px 5px rgba(0, 0, 0, 0.8); */ } .card-user { - display: grid; - grid-template-columns: auto 1fr; - grid-template-rows: auto auto; + display: grid; + grid-template-columns: auto 1fr; + grid-template-rows: auto auto; - bottom: 30px; - right: 0; - padding-right: 5px; - position: absolute; + bottom: 30px; + right: 0; + padding-right: 5px; + position: absolute; } - .session-card-user-image { - border-radius: 50%; - max-width: 30px; - max-height: 30px; - - + border-radius: 50%; + max-width: 30px; + max-height: 30px; } -.card-user-image-default -{ - /* width: 50px !important; */ - font-size: large; +.card-user-image-default { + /* width: 50px !important; */ + font-size: large; } - .card-username { - grid-row: 1 / 2; - grid-column: 2 / 3; + grid-row: 1 / 2; + grid-column: 2 / 3; } .card-username a { - text-decoration: none; - color: white; + text-decoration: none; + color: white; } - -.card-username:hover a{ - - color: rgb(0, 164, 219); - } - +.card-username:hover a { + color: rgb(0, 164, 219); +} .card-device { - margin-top: 5px; - display: grid; - grid-template-columns: auto 1fr; - grid-template-rows: auto auto; - /* grid-column-gap: 10px; */ + margin-top: 5px; + display: grid; + grid-template-columns: auto 1fr; + grid-template-rows: auto auto; + /* grid-column-gap: 10px; */ } .card-device-name { - font-size: small; - grid-row: 1 / 2; - grid-column: 2 / 3; + font-size: small; + grid-row: 1 / 2; + grid-column: 2 / 3; } .card-device-image { - max-width: 40px; - margin-top:5px; - width: 100%; - height: min-content; + max-width: 40px; + margin-top: 5px; + width: 100%; + height: min-content; } .card-client { - font-size: small; - grid-row: 2 / 3; - grid-column: 2 / 3; + font-size: small; + grid-row: 2 / 3; + grid-column: 2 / 3; } .card-item-name { - bottom: 45px; - margin-left: 5px; - max-width: 200px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - + bottom: 45px; + margin-left: 5px; + max-width: 200px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; - position: absolute; + position: absolute; } .card-playback-position { - bottom: 5px; - /* right: 5px; */ - /* text-align: right; */ - /* position: absolute; */ + bottom: 5px; + /* right: 5px; */ + /* text-align: right; */ + /* position: absolute; */ } .device-info { -margin-bottom: 100%; + margin-bottom: 100%; } .card-ip { - grid-row: 2 / 3; - grid-column: 2 / 3; + grid-row: 2 / 3; + grid-column: 2 / 3; } -.card-text >a{ - text-decoration: none !important; - color: white !important; - } - - .card-text a:hover{ - color: var(--secondary-color) !important; - } - - - .ellipse -{ - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 1; - overflow: hidden; - text-overflow: ellipsis; +.card-text > a { + text-decoration: none !important; + color: white !important; } +.card-text a:hover { + color: var(--secondary-color) !important; +} +.ellipse { + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 1; + overflow: hidden; + text-overflow: ellipsis; +} @media (max-width: 350px) { - .card-device-image { - display: none; - } - - .card-client-version, .session-card-user-image - { - display: none !important; - } - } \ No newline at end of file + .card-device-image { + display: none; + } + + .card-client-version, + .session-card-user-image { + display: none !important; + } +} diff --git a/src/pages/css/settings/backups.css b/src/pages/css/settings/backups.css index beb79644..d40e11ab 100644 --- a/src/pages/css/settings/backups.css +++ b/src/pages/css/settings/backups.css @@ -1,34 +1,31 @@ -@import '../variables.module.css'; -tr{ - color: white; +@import "../variables.module.css"; +tr { + color: white; } -th:hover{ - border-bottom: none !important; +th:hover { + border-bottom: none !important; } -th{ - border-bottom: none !important; - cursor: default !important; +th { + border-bottom: none !important; + cursor: default !important; } -.backup-file-download -{ - cursor: pointer; +.backup-file-download { + cursor: pointer; } -td{ - border-bottom: none !important; +td { + border-bottom: none !important; } -.upload-file -{ - background-color: var(--secondary-background-color) !important; - border-color: var(--secondary-background-color) !important; - color: white !important; +.upload-file { + background-color: var(--secondary-background-color) !important; + border-color: var(--secondary-background-color) !important; + color: white !important; } -.upload-file:focus -{ - box-shadow: none !important; +.upload-file:focus { + box-shadow: none !important; } diff --git a/src/pages/css/settings/settings.css b/src/pages/css/settings/settings.css index 80e037ce..09edc1b3 100644 --- a/src/pages/css/settings/settings.css +++ b/src/pages/css/settings/settings.css @@ -1,95 +1,74 @@ -@import '../variables.module.css'; -.show-key -{ - margin-bottom: 20px;; +@import "../variables.module.css"; +.show-key { + margin-bottom: 20px; } - -.settings{ +.settings { background-color: var(--secondary-background-color); padding: 20px; border-radius: 8px; } .tasks { - color: white; /* margin-inline: 10px; */ - } - .settings-form { + color: white; + margin-top: 20px; + margin-inline: 10px; +} - color: white; - margin-top: 20px; - margin-inline: 10px; - - } - - - .form-row - { - margin-top: 20px; - display: grid; - grid-template-columns: repeat(3,minmax(0,1fr)); - align-items: flex-start; - gap: 20px; - } - - .form-row label { - font-weight: bold; - font-size: 18px; - } - - .form-row input { - padding: 5px; - border-radius: 5px; - border: none; - max-width: 700px; - } - - - .submit - { - font-weight: bold; - margin-bottom: 5px; - } +.form-row { + margin-top: 20px; + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + align-items: flex-start; + gap: 20px; +} +.form-row label { + font-weight: bold; + font-size: 18px; +} +.form-row input { + padding: 5px; + border-radius: 5px; + border: none; + max-width: 700px; +} +.submit { + font-weight: bold; + margin-bottom: 5px; +} - .settings-form > div> div> .form-control, - .settings-form > div> div> .input-group> .form-control - { - color: white !important; - background-color: var(--background-color) !important; - border-color: var(--background-color) !important; - } +.settings-form > div > div > .form-control, +.settings-form > div > div > .input-group > .form-control { + color: white !important; + background-color: var(--background-color) !important; + border-color: var(--background-color) !important; +} - .settings-form > div> div> .input-group> .btn - { - border: none !important; - } +.settings-form > div > div > .input-group > .btn { + border: none !important; +} - - .settings-form > div> div> .form-control:focus, - .settings-form > div> div> .input-group> .form-control:focus - { - box-shadow: none !important; - border-color: var(--primary-color) !important; - } +.settings-form > div > div > .form-control:focus, +.settings-form > div > div > .input-group > .form-control:focus { + box-shadow: none !important; + border-color: var(--primary-color) !important; +} -.dropdown-item, .dropdown-menu - { +.dropdown-item, +.dropdown-menu { background-color: var(--background-color) !important; color: white !important; +} - } - - .dropdown-item:hover - { +.dropdown-item:hover { background-color: var(--primary-color) !important; color: white !important; - } - \ No newline at end of file +} diff --git a/src/pages/css/settings/version.css b/src/pages/css/settings/version.css index 3d9787e9..4a366d73 100644 --- a/src/pages/css/settings/version.css +++ b/src/pages/css/settings/version.css @@ -1,46 +1,37 @@ -@import '../variables.module.css'; -.version -{ - background-color: var(--background-color) !important; - - color: white !important; - position: fixed !important; - bottom: 0; - max-width: 200px; - text-align: center; - width: 100%; - - +@import "../variables.module.css"; +.version { + background-color: var(--background-color) !important; + + color: white !important; + position: fixed !important; + bottom: 0; + max-width: 200px; + text-align: center; + width: 100%; } - -.version a -{ - text-decoration: none; +.version a { + text-decoration: none; } -.version a:hover -{ - text-decoration: underline; +.version a:hover { + text-decoration: underline; } -.nav-pills > .nav-item , .nav-pills > .nav-item > .nav-link -{ - color: white !important; +.nav-pills > .nav-item, +.nav-pills > .nav-item > .nav-link { + color: white !important; } -.nav-pills > .nav-item .active -{ - background-color: var(--primary-color) !important; - color: white !important; +.nav-pills > .nav-item .active { + background-color: var(--primary-color) !important; + color: white !important; } -.nav-pills > .nav-item :hover -{ - background-color: var(--primary-color) !important; +.nav-pills > .nav-item :hover { + background-color: var(--primary-color) !important; } -.nav-pills > .nav-item .active> .nav-link -{ - color: white; -} \ No newline at end of file +.nav-pills > .nav-item .active > .nav-link { + color: white; +} diff --git a/src/pages/css/setup.css b/src/pages/css/setup.css index 467e7f22..e97acd93 100644 --- a/src/pages/css/setup.css +++ b/src/pages/css/setup.css @@ -1,150 +1,137 @@ -@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@500&display=swap'); -@import './variables.module.css'; +@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@500&display=swap"); +@import "./variables.module.css"; /* *{ margin: 0; padding: 0; font-family: 'poppins',sans-serif; } */ -.login-show-password -{ - background-color: transparent !important; - border: 0 !important; -} - - -section{ - display: flex; - justify-content: center; - align-items: center; - min-height: 100vh; - width: 100%; - background-position: center; - background-size: cover; -} -.form-box{ - position: relative; - width: 400px; - height: 600px; - background: var(--secondary-background-color); - /* border: 2px solid rgba(255,255,255,0.5); */ - border-radius: 20px; - backdrop-filter: blur(15px); - display: flex; - justify-content: center; - align-items: center; - -} -h2{ - font-size: 2em; - color: #fff; - text-align: center; -} -.inputbox{ - position: relative; - margin: 30px 0; - width: 310px; - border-bottom: 2px solid #fff; -} -.inputbox .form-label{ - position: absolute; - top: 50%; - transform: translateY(-50%); - color: #fff !important; - font-size: 1em; - pointer-events: none; - transition: .2s; +.login-show-password { + background-color: transparent !important; + border: 0 !important; +} + +section { + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + width: 100%; + background-position: center; + background-size: cover; +} +.form-box { + position: relative; + width: 400px; + height: 600px; + background: var(--secondary-background-color); + /* border: 2px solid rgba(255,255,255,0.5); */ + border-radius: 20px; + backdrop-filter: blur(15px); + display: flex; + justify-content: center; + align-items: center; +} +h2 { + font-size: 2em; + color: #fff; + text-align: center; +} +.inputbox { + position: relative; + margin: 30px 0; + width: 310px; + border-bottom: 2px solid #fff; +} +.inputbox .form-label { + position: absolute; + top: 50%; + transform: translateY(-50%); + color: #fff !important; + font-size: 1em; + pointer-events: none; + transition: 0.2s; } .inputbox input:focus ~ .form-label, -.inputbox input:not(:placeholder-shown) ~ .form-label -{ -top: -10px; +.inputbox input:not(:placeholder-shown) ~ .form-label { + top: -10px; } - .inputbox input:hover { - - background: transparent !important; - box-shadow: none !important; - + background: transparent !important; + box-shadow: none !important; } .inputbox input:focus { - - background: transparent !important; - box-shadow: none !important; - color: #fff; - + background: transparent !important; + box-shadow: none !important; + color: #fff; } - .inputbox input { + height: 50px; + background: transparent; + border: none; + outline: none; - height: 50px; - background: transparent; - border: none; - outline: none; - - color: #fff; + color: #fff; } -.forget{ - margin: -15px 0 15px ; - font-size: .9em; - color: #fff; - display: flex; - justify-content: space-between; +.forget { + margin: -15px 0 15px; + font-size: 0.9em; + color: #fff; + display: flex; + justify-content: space-between; } -.forget label input{ - margin-right: 3px; - -} -.forget label a{ - color: #fff; - text-decoration: none; -} -.forget label a:hover{ - text-decoration: underline; -} -.setup-button{ - color: white !important; - width: 100%; - height: 40px; - border-radius: 40px !important; - background: var(--primary-color) !important; - border: none !important; - outline: none !important; - font-size: 1em !important; - font-weight: 600 !important; +.forget label input { + margin-right: 3px; +} +.forget label a { + color: #fff; + text-decoration: none; +} +.forget label a:hover { + text-decoration: underline; +} +.setup-button { + color: white !important; + width: 100%; + height: 40px; + border-radius: 40px !important; + background: var(--primary-color) !important; + border: none !important; + outline: none !important; + font-size: 1em !important; + font-weight: 600 !important; } -.setup-button:hover{ - color: black !important; - width: 100%; - height: 40px; - border-radius: 40px !important; - background: var(--secondary-color) !important; - border: none !important; - outline: none !important; - font-size: 1em !important; - font-weight: 600 !important; -} -.register{ - font-size: .9em; - color: #fff; - text-align: center; - margin: 25px 0 10px; -} -.register p a{ - text-decoration: none; - color: #fff; - font-weight: 600; -} -.register p a:hover{ - text-decoration: underline; +.setup-button:hover { + color: black !important; + width: 100%; + height: 40px; + border-radius: 40px !important; + background: var(--secondary-color) !important; + border: none !important; + outline: none !important; + font-size: 1em !important; + font-weight: 600 !important; +} +.register { + font-size: 0.9em; + color: #fff; + text-align: center; + margin: 25px 0 10px; +} +.register p a { + text-decoration: none; + color: #fff; + font-weight: 600; +} +.register p a:hover { + text-decoration: underline; } -.fts-text -{ - color: var(--secondary-color); -} \ No newline at end of file +.fts-text { + color: var(--secondary-color); +} diff --git a/src/pages/css/statCard.css b/src/pages/css/statCard.css index ccd4e524..d05a684e 100644 --- a/src/pages/css/statCard.css +++ b/src/pages/css/statCard.css @@ -1,50 +1,40 @@ -@import './variables.module.css'; -.grid-stat-cards -{ +@import "./variables.module.css"; +.grid-stat-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(auto, 520px)); - grid-auto-rows: 200px;/* max-width+offset so 215 + 20*/ + grid-auto-rows: 200px; /* max-width+offset so 215 + 20*/ margin-top: 8px; background-color: var(--secondary-background-color); border-radius: 8px; padding: 20px; } -.stat-card{ +.stat-card { border: 0 !important; - background-color: var(--background-color)!important; + background-color: var(--background-color) !important; color: white; max-width: 500px; max-height: 180px; } -.stat-card-banner -{ +.stat-card-banner { max-width: 120px !important; } - - - - .stat-card-image { width: 120px !important; height: 180px; } -.stat-card-icon -{ +.stat-card-icon { width: 120px !important; - position: relative; top: 50%; left: 65%; transform: translate(-50%, -50%); } - -.stat-items -{ +.stat-items { color: white; } @@ -54,79 +44,67 @@ } .stat-item-count { text-align: right; - color: var(--secondary-color); + color: var(--secondary-color); font-weight: 500; font-size: 1.1em; - } - -.Heading -{ +.Heading { display: flex; flex-direction: row; } -.Heading h1 -{ +.Heading h1 { padding-right: 10px; } -.date-range -{ +.date-range { width: 220px; height: 35px; color: white; display: flex; - background-color: var(--secondary-background-color); + background-color: var(--secondary-background-color); border-radius: 8px; font-size: 1.2em; align-self: flex-end; justify-content: space-evenly; - } - -.date-range .days input -{ +.date-range .days input { height: 35px; outline: none; border: none; - background-color:transparent; - color:white; + background-color: transparent; + color: white; font-size: 1em; width: 40px; } -.date-range .days -{ +.date-range .days { background-color: rgb(255, 255, 255, 0.1); - padding-inline: 10px; + padding-inline: 10px; } - -input[type=number]::-webkit-outer-spin-button, -input[type=number]::-webkit-inner-spin-button { +input[type="number"]::-webkit-outer-spin-button, +input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } - -input[type=number] { + +input[type="number"] { -moz-appearance: textfield; } - .date-range .header, -.date-range .trailer -{ +.date-range .trailer { padding-inline: 10px; align-self: center; } -.stat-items div a{ +.stat-items div a { text-decoration: none !important; color: white !important; } -.stat-items div a:hover{ - color: var(--secondary-color) !important; +.stat-items div a:hover { + color: var(--secondary-color) !important; } diff --git a/src/pages/css/stats.css b/src/pages/css/stats.css index ca7761ba..0163f0f0 100644 --- a/src/pages/css/stats.css +++ b/src/pages/css/stats.css @@ -1,65 +1,52 @@ -@import './variables.module.css'; -.watch-stats -{ +@import "./variables.module.css"; +.watch-stats { margin-top: 10px; } -.graph -{ - - height: 700px; - color:black !important; - background-color: var(--secondary-background-color); - padding:10px; - border-radius:8px; +.graph { + height: 700px; + color: black !important; + background-color: var(--secondary-background-color); + padding: 10px; + border-radius: 8px; } - - -.small -{ - height: 500px; - +.small { + height: 500px; } -.statistics-graphs -{ - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - grid-gap: 20px; - margin-bottom: 20px; - - +.statistics-graphs { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + grid-gap: 20px; + margin-bottom: 20px; } -.main-widget -{ - flex: 1; +.main-widget { + flex: 1; } -.main-widget h1 -{ +.main-widget h1 { margin-bottom: 10px !important; } - -.statistics-widget h1{ +.statistics-widget h1 { margin-bottom: 10px !important; } .chart-canvas { - width: 100%; - height: 400px; - } + width: 100%; + height: 400px; +} - @media (min-width: 768px) { - .chart-canvas { - height: 500px; - } +@media (min-width: 768px) { + .chart-canvas { + height: 500px; } +} - @media (min-width: 992px) { - .chart-canvas { - height: 600px; - } - } \ No newline at end of file +@media (min-width: 992px) { + .chart-canvas { + height: 600px; + } +} diff --git a/src/pages/css/users/user-details.css b/src/pages/css/users/user-details.css index 3439add4..03a53931 100644 --- a/src/pages/css/users/user-details.css +++ b/src/pages/css/users/user-details.css @@ -1,38 +1,31 @@ -@import '../variables.module.css'; +@import "../variables.module.css"; -.user-detail-container -{ - color:white; - background-color: var(--secondary-background-color); - padding: 20px; - margin: 20px 0; - border-radius: 8px; +.user-detail-container { + color: white; + background-color: var(--secondary-background-color); + padding: 20px; + margin: 20px 0; + border-radius: 8px; - display: flex; - align-items: center; - + display: flex; + align-items: center; } -.user-name -{ - font-size: 2.5em; - font-weight: 500; - margin: 0; +.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 var(--secondary-background-color); +.user-image { + width: 100px; + height: 100px; + border-radius: 50%; + object-fit: cover; + box-shadow: 0 0 10px 5px var(--secondary-background-color); } -.user-image-container -{ - width: 100px; - height: 100px; - margin-right: 20px; - - -} \ No newline at end of file +.user-image-container { + width: 100px; + height: 100px; + margin-right: 20px; +} diff --git a/src/pages/css/users/users.css b/src/pages/css/users/users.css index cc423c21..9e0a516c 100644 --- a/src/pages/css/users/users.css +++ b/src/pages/css/users/users.css @@ -1,9 +1,6 @@ -.card-user-image -{ - border-radius: 50%; - width: 30px; - height: 30px; - object-fit: cover; - +.card-user-image { + border-radius: 50%; + width: 30px; + height: 30px; + object-fit: cover; } - diff --git a/src/pages/css/variables.module.css b/src/pages/css/variables.module.css index 4b9c8ecf..e8664277 100644 --- a/src/pages/css/variables.module.css +++ b/src/pages/css/variables.module.css @@ -1,7 +1,7 @@ :root { - --primary-color: #5a2da5; - --secondary-color: #00A4DC; - --background-color: #1e1c22; - --secondary-background-color: #2c2a2f; - --tertiary-background-color: #2f2e31; - } \ No newline at end of file + --primary-color: #5a2da5; + --secondary-color: #00a4dc; + --background-color: #1e1c22; + --secondary-background-color: #2c2a2f; + --tertiary-background-color: #2f2e31; +} diff --git a/src/pages/css/websocket/websocket.css b/src/pages/css/websocket/websocket.css index 0f4181fa..880a4d54 100644 --- a/src/pages/css/websocket/websocket.css +++ b/src/pages/css/websocket/websocket.css @@ -20,8 +20,6 @@ overflow-x: hidden; } - - .console-container::-webkit-scrollbar { width: 10px; /* set scrollbar width */ } diff --git a/src/pages/css/width_breakpoint_css.css b/src/pages/css/width_breakpoint_css.css index 8fbf6d07..d14117a3 100644 --- a/src/pages/css/width_breakpoint_css.css +++ b/src/pages/css/width_breakpoint_css.css @@ -1,142 +1,140 @@ - /*sourced from https://drive.google.com/uc?export=view&id=1yTLwNiCZhIdCWolQldwq4spHQkgZDqkG */ /* Small devices (landscape phones, 576px and up)*/ @media (min-width: 576px) { - .w-sm-100 { - width: 100% !important; - } + .w-sm-100 { + width: 100% !important; + } - .w-sm-75 { - width: 75% !important; - } + .w-sm-75 { + width: 75% !important; + } - .w-sm-50 { - width: 50% !important; - } + .w-sm-50 { + width: 50% !important; + } - .w-sm-25 { - width: 25% !important; - } + .w-sm-25 { + width: 25% !important; + } - .h-sm-100 { - height: 100% !important; - } + .h-sm-100 { + height: 100% !important; + } - .h-sm-75 { - height: 75% !important; - } + .h-sm-75 { + height: 75% !important; + } - .h-sm-50 { - height: 50% !important; - } + .h-sm-50 { + height: 50% !important; + } - .h-sm-25 { - height: 25% !important; - } + .h-sm-25 { + height: 25% !important; + } } - /* Medium devices (tablets, 768px and up)*/ @media (min-width: 768px) { - .w-md-100 { - width: 100% !important; - } + .w-md-100 { + width: 100% !important; + } - .w-md-75 { - width: 75% !important; - } + .w-md-75 { + width: 75% !important; + } - .w-md-50 { - width: 50% !important; - } + .w-md-50 { + width: 50% !important; + } - .w-md-25 { - width: 25% !important; - } + .w-md-25 { + width: 25% !important; + } - .h-md-100 { - height: 100% !important; - } + .h-md-100 { + height: 100% !important; + } - .h-md-75 { - height: 75% !important; - } + .h-md-75 { + height: 75% !important; + } - .h-md-50 { - height: 50% !important; - } + .h-md-50 { + height: 50% !important; + } - .h-md-25 { - height: 25% !important; - } + .h-md-25 { + height: 25% !important; + } } /* Large devices (desktops, 992px and up)*/ @media (min-width: 992px) { - .w-lg-100 { - width: 100% !important; - } + .w-lg-100 { + width: 100% !important; + } - .w-lg-75 { - width: 75% !important; - } + .w-lg-75 { + width: 75% !important; + } - .w-lg-50 { - width: 50% !important; - } + .w-lg-50 { + width: 50% !important; + } - .w-lg-25 { - width: 25% !important; - } + .w-lg-25 { + width: 25% !important; + } - .h-lg-100 { - height: 100% !important; - } + .h-lg-100 { + height: 100% !important; + } - .h-lg-75 { - height: 75% !important; - } + .h-lg-75 { + height: 75% !important; + } - .h-lg-50 { - height: 50% !important; - } + .h-lg-50 { + height: 50% !important; + } - .h-lg-25 { - height: 25% !important; - } + .h-lg-25 { + height: 25% !important; + } } /* Extra large devices (large desktops, 1200px and up)*/ @media (min-width: 1200px) { - .w-xl-100 { - width: 100% !important; - } - - .w-xl-75 { - width: 75% !important; - } - - .w-xl-50 { - width: 50% !important; - } - - .w-xl-25 { - width: 25% !important; - } - - .h-xl-100 { - height: 100% !important; - } - - .h-xl-75 { - height: 75% !important; - } - - .h-xl-50 { - height: 50% !important; - } - - .h-xl-25 { - height: 25% !important; - } -} \ No newline at end of file + .w-xl-100 { + width: 100% !important; + } + + .w-xl-75 { + width: 75% !important; + } + + .w-xl-50 { + width: 50% !important; + } + + .w-xl-25 { + width: 25% !important; + } + + .h-xl-100 { + height: 100% !important; + } + + .h-xl-75 { + height: 75% !important; + } + + .h-xl-50 { + height: 50% !important; + } + + .h-xl-25 { + height: 25% !important; + } +} diff --git a/src/pages/data-debugger.js b/src/pages/data-debugger.js index 5f1ec41e..438a86be 100644 --- a/src/pages/data-debugger.js +++ b/src/pages/data-debugger.js @@ -1,43 +1,33 @@ import React, { useState, useEffect } from "react"; -import axios from 'axios'; +import axios from "axios"; -import {Button, ButtonGroup } from 'react-bootstrap'; +import { Button, ButtonGroup } 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 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'; +import { saveAs } from "file-saver"; import Loading from "./components/general/loading"; - - - - - function Datadebugger() { const [data, setData] = useState(); - const token = localStorage.getItem('token'); - + const token = localStorage.getItem("token"); useEffect(() => { - const fetchData = async () => { try { - - const libraryData = await axios.get(`/api/dataValidator`, { + const libraryData = await axios.get(`/api/dataValidator`, { headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, }); setData(libraryData.data); - } catch (error) { console.log(error); } @@ -49,156 +39,261 @@ function Datadebugger() { return () => clearInterval(intervalId); }, [token]); - const handleDownload = (jsonData,filename) => { + 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'); + const blob = new Blob([jsonString], { type: "application/json" }); + + saveAs(blob, filename + ".json"); }; - if(!data) - { - return ; + if (!data) { + return ; } - return ( -
+

Data Debugger

-
+
{/*

{data? JSON.stringify(data):''}

*/} - - - - - Data Type - Database Count - API Count - Difference - Counts from Jellyfin* - Difference - Export Missing Data - - - - - - Libraries - {data ? data.existing_library_count:''} - {data ? data.api_library_count:''} - {data ? data.api_library_count-data.existing_library_count:''} - - - {data && data.api_library_count>data.existing_library_count ? - : - ''} - - - - - Movies - {data ? data.existing_movie_count:''} - {data ? data.api_movie_count:''} - {data ? data.api_movie_count-data.existing_movie_count:''} - {data ? data.count_from_api.MovieCount:''} - {data ? data.count_from_api.MovieCount-data.existing_movie_count:''} - {data && data.api_movie_count>data.existing_movie_count ? - : - ''} - - - - - Shows - {data ? data.existing_show_count:''} - {data ? data.api_show_count:''} - {data ? data.api_show_count-data.existing_show_count:''} - {data ? data.count_from_api.SeriesCount:''} - {data ? data.count_from_api.SeriesCount-data.existing_show_count:''} - {data && data.api_show_count>data.existing_show_count ? - : - ''} - - - - - Music - {data ? data.existing_music_count:''} - {data ? data.api_music_count:''} - {data ? data.api_music_count-data.existing_music_count:''} - {data ? data.count_from_api.SongCount:''} - {data ? data.count_from_api.SongCount-data.existing_music_count:''} - {data && data.api_music_count>data.existing_music_count ? - : - ''} - - - - - Seasons - {data ? data.existing_season_count:''} - {data ? data.api_season_count:''} - {data ? data.api_season_count-data.existing_season_count:''} - - - {data && data.api_season_count>data.existing_season_count ? - : - ''} - - - - - Episodes - {data ? data.existing_episode_count:''} - {data ? data.api_episode_count:''} - {data ? data.api_episode_count-data.existing_episode_count:''} - {data ? data.count_from_api.EpisodeCount:''} - {data ? data.count_from_api.EpisodeCount-data.existing_episode_count:''} - {data && data.api_episode_count>data.existing_episode_count ? - : - ''} - - - - -
-
- - - - - - - - - - - + + + + + Data Type + Database Count + API Count + Difference + Counts from Jellyfin* + Difference + Export Missing Data + + + + + Libraries + {data ? data.existing_library_count : ""} + {data ? data.api_library_count : ""} + + {data + ? data.api_library_count - data.existing_library_count + : ""} + + + + + {data && + data.api_library_count > data.existing_library_count ? ( + + ) : ( + "" + )} + + + + + Movies + {data ? data.existing_movie_count : ""} + {data ? data.api_movie_count : ""} + + {data ? data.api_movie_count - data.existing_movie_count : ""} + + + {data ? data.count_from_api.MovieCount : ""} + + + {data + ? data.count_from_api.MovieCount - data.existing_movie_count + : ""} + + + {data && data.api_movie_count > data.existing_movie_count ? ( + + ) : ( + "" + )} + + + + + Shows + {data ? data.existing_show_count : ""} + {data ? data.api_show_count : ""} + + {data ? data.api_show_count - data.existing_show_count : ""} + + + {data ? data.count_from_api.SeriesCount : ""} + + + {data + ? data.count_from_api.SeriesCount - data.existing_show_count + : ""} + + + {data && data.api_show_count > data.existing_show_count ? ( + + ) : ( + "" + )} + + + + + Music + {data ? data.existing_music_count : ""} + {data ? data.api_music_count : ""} + + {data ? data.api_music_count - data.existing_music_count : ""} + + {data ? data.count_from_api.SongCount : ""} + + {data + ? data.count_from_api.SongCount - data.existing_music_count + : ""} + + + {data && data.api_music_count > data.existing_music_count ? ( + + ) : ( + "" + )} + + + + + Seasons + {data ? data.existing_season_count : ""} + {data ? data.api_season_count : ""} + + {data ? data.api_season_count - data.existing_season_count : ""} + + + + + {data && data.api_season_count > data.existing_season_count ? ( + + ) : ( + "" + )} + + + + + Episodes + {data ? data.existing_episode_count : ""} + {data ? data.api_episode_count : ""} + + {data + ? data.api_episode_count - data.existing_episode_count + : ""} + + + {data ? data.count_from_api.EpisodeCount : ""} + + + {data + ? data.count_from_api.EpisodeCount - + data.existing_episode_count + : ""} + + + {data && + data.api_episode_count > data.existing_episode_count ? ( + + ) : ( + "" + )} + + + +
+
+ + + + + + + + + +
- ); } diff --git a/src/pages/home.js b/src/pages/home.js index 31f30b0e..52d38a9d 100644 --- a/src/pages/home.js +++ b/src/pages/home.js @@ -1,27 +1,24 @@ -import React from 'react' +import React from "react"; -import './css/home.css' +import "./css/home.css"; -import Sessions from './components/sessions/sessions' -import HomeStatisticCards from './components/HomeStatisticCards' -import LibraryOverView from './components/libraryOverview' -import RecentlyAdded from './components/library/recently-added' -import ErrorBoundary from './components/general/ErrorBoundary' +import Sessions from "./components/sessions/sessions"; +import HomeStatisticCards from "./components/HomeStatisticCards"; +import LibraryOverView from "./components/libraryOverview"; +import RecentlyAdded from "./components/library/recently-added"; +import ErrorBoundary from "./components/general/ErrorBoundary"; export default function Home() { return ( -
- - +
- + - + - - - + +
- ) -} \ No newline at end of file + ); +} diff --git a/src/pages/libraries.js b/src/pages/libraries.js index 13e841f1..7cbb33e6 100644 --- a/src/pages/libraries.js +++ b/src/pages/libraries.js @@ -7,7 +7,6 @@ import Loading from "./components/general/loading"; import LibraryCard from "./components/library/library-card"; import ErrorBoundary from "./components/general/ErrorBoundary"; - function Libraries() { const [data, setData] = useState(); const [metadata, setMetaData] = useState(); @@ -26,8 +25,7 @@ function Libraries() { }; const fetchLibraries = () => { - if(config) - { + if (config) { const url = `/stats/getLibraryCardStats`; axios .get(url, { @@ -43,9 +41,9 @@ function Libraries() { console.log(error); }); - const metadataurl = `/stats/getLibraryMetadata`; + const metadataurl = `/stats/getLibraryMetadata`; - axios + axios .get(metadataurl, { headers: { Authorization: `Bearer ${config.token}`, @@ -61,7 +59,6 @@ function Libraries() { } }; - if (!config) { fetchConfig(); } @@ -69,7 +66,7 @@ function Libraries() { fetchLibraries(); const intervalId = setInterval(fetchLibraries, 60000 * 60); return () => clearInterval(intervalId); - }, [ config]); + }, [config]); if (!data || !metadata) { return ; @@ -80,15 +77,19 @@ function Libraries() {

Libraries

- {data && - data.sort((a,b) => a.Name-b.Name).map((item) => ( - - data.Id === item.Id)} base_url={config.hostUrl}/> - - + {data && + data + .sort((a, b) => a.Name - b.Name) + .map((item) => ( + + data.Id === item.Id)} + base_url={config.hostUrl} + /> + ))}
-
); } diff --git a/src/pages/library_selector.js b/src/pages/library_selector.js index 2b21f2e9..1c3e48a0 100644 --- a/src/pages/library_selector.js +++ b/src/pages/library_selector.js @@ -10,7 +10,6 @@ import InformationLineIcon from "remixicon-react/InformationLineIcon"; import { Tooltip } from "@mui/material"; - function LibrarySelector() { const [data, setData] = useState(); const [config, setConfig] = useState(null); @@ -28,8 +27,7 @@ function LibrarySelector() { }; const fetchLibraries = () => { - if(config) - { + if (config) { const url = `/api/TrackedLibraries`; axios .get(url, { @@ -44,11 +42,9 @@ function LibrarySelector() { .catch((error) => { console.log(error); }); - } }; - if (!config) { fetchConfig(); } @@ -56,7 +52,7 @@ function LibrarySelector() { fetchLibraries(); const intervalId = setInterval(fetchLibraries, 60000 * 60); return () => clearInterval(intervalId); - }, [ config]); + }, [config]); if (!data) { return ; @@ -64,18 +60,28 @@ function LibrarySelector() { return (
-

Select Libraries to Import

+

+ Select Libraries to Import{" "} + + + {" "} + + + +

- {data && + {data && data.map((item) => ( - - - - - ))} + + + + ))}
-
); } diff --git a/src/pages/login.js b/src/pages/login.js index 27d073fd..4131875e 100644 --- a/src/pages/login.js +++ b/src/pages/login.js @@ -1,20 +1,19 @@ import React, { useState, useEffect } from "react"; import axios from "axios"; import Config from "../lib/config"; -import CryptoJS from 'crypto-js'; +import CryptoJS from "crypto-js"; import "./css/setup.css"; -import Form from 'react-bootstrap/Form'; -import Button from 'react-bootstrap/Button'; -import { InputGroup,Row } from "react-bootstrap"; +import Form from "react-bootstrap/Form"; +import Button from "react-bootstrap/Button"; +import { InputGroup, Row } from "react-bootstrap"; -import EyeFillIcon from 'remixicon-react/EyeFillIcon'; -import EyeOffFillIcon from 'remixicon-react/EyeOffFillIcon'; -import logo_dark from './images/icon-b-512.png'; +import EyeFillIcon from "remixicon-react/EyeFillIcon"; +import EyeOffFillIcon from "remixicon-react/EyeOffFillIcon"; +import logo_dark from "./images/icon-b-512.png"; // import LibrarySync from "./components/settings/librarySync"; -import Loading from './components/general/loading'; - +import Loading from "./components/general/loading"; function Login() { const [config, setConfig] = useState(null); @@ -27,46 +26,40 @@ function Login() { setFormValues({ ...formValues, [event.target.name]: event.target.value }); } - async function handleFormSubmit(event) { setProcessing(true); event.preventDefault(); - let hashedPassword= CryptoJS.SHA3(formValues.JS_PASSWORD).toString(); - - beginLogin(formValues.JS_USERNAME,hashedPassword); + let hashedPassword = CryptoJS.SHA3(formValues.JS_PASSWORD).toString(); + beginLogin(formValues.JS_USERNAME, hashedPassword); } - async function beginLogin(JS_USERNAME,hashedPassword) - { - + async function beginLogin(JS_USERNAME, hashedPassword) { axios - .post("/auth/login", { - username:JS_USERNAME, - password: hashedPassword - - }, { - headers: { - "Content-Type": "application/json", - }, - }) - .then(async (response) => { - - - localStorage.setItem('token',response.data.token); - setProcessing(false); - if(JS_USERNAME) - { - setsubmitButtonText("Success"); - window.location.reload(); - return; - } - - + .post( + "/auth/login", + { + username: JS_USERNAME, + password: hashedPassword, + }, + { + headers: { + "Content-Type": "application/json", + }, + }, + ) + .then(async (response) => { + localStorage.setItem("token", response.data.token); + setProcessing(false); + if (JS_USERNAME) { + setsubmitButtonText("Success"); + window.location.reload(); + return; + } }) .catch((error) => { - let errorMessage= `Error : ${error.response.status}`; + let errorMessage = `Error : ${error.response.status}`; if (error.code === "ERR_NETWORK") { errorMessage = `Unable to connect to Jellyfin Server`; } else if (error.response.status === 401) { @@ -74,18 +67,14 @@ function Login() { } else if (error.response.status === 404) { errorMessage = `Error ${error.response.status}: The requested URL was not found.`; } - if(JS_USERNAME) - { - setsubmitButtonText(errorMessage); - } + if (JS_USERNAME) { + setsubmitButtonText(errorMessage); + } - - setProcessing(false); }); } - useEffect(() => { const fetchConfig = async () => { try { @@ -96,62 +85,69 @@ function Login() { if (error.response.status !== 401 && error.response.status !== 403) { // console.log(error); } - - } } }; - if (!config) { fetchConfig(); beginLogin(); - } }, [config]); - if(!config || config.token) - { - return ; + if (!config || config.token) { + return ; } return (
- - - -

Jellystat

- + +

Jellystat

- - - - - - - Username - + + + + Username - - - - - - - Password - + + + + + Password - - -
diff --git a/src/pages/settings.js b/src/pages/settings.js index fad377b2..0ccbd554 100644 --- a/src/pages/settings.js +++ b/src/pages/settings.js @@ -1,5 +1,5 @@ import React from "react"; -import {Tabs, Tab } from 'react-bootstrap'; +import { Tabs, Tab } from "react-bootstrap"; import SettingsConfig from "./components/settings/settingsConfig"; import BackupFiles from "./components/settings/backupfiles"; @@ -9,46 +9,60 @@ import LibrarySelector from "./library_selector"; import Logs from "./components/settings/logs"; - - - import "./css/settings/settings.css"; export default function Settings() { - - return (
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - {/* */} - + + + + + + + + + + + + + + + + + + + + + + + + {/* */}
); } diff --git a/src/pages/setup.js b/src/pages/setup.js index 9cf3d731..73092ba6 100644 --- a/src/pages/setup.js +++ b/src/pages/setup.js @@ -1,33 +1,32 @@ import React, { useState, useEffect } from "react"; import axios from "axios"; import Config from "../lib/config"; -import Form from 'react-bootstrap/Form'; -import Button from 'react-bootstrap/Button'; -import { InputGroup,Row } from "react-bootstrap"; +import Form from "react-bootstrap/Form"; +import Button from "react-bootstrap/Button"; +import { InputGroup, Row } from "react-bootstrap"; -import EyeFillIcon from 'remixicon-react/EyeFillIcon'; -import EyeOffFillIcon from 'remixicon-react/EyeOffFillIcon'; -import logo_dark from './images/icon-b-512.png'; +import EyeFillIcon from "remixicon-react/EyeFillIcon"; +import EyeOffFillIcon from "remixicon-react/EyeOffFillIcon"; +import logo_dark from "./images/icon-b-512.png"; import "./css/setup.css"; -const token = localStorage.getItem('token'); - +const token = localStorage.getItem("token"); function Setup() { const [config, setConfig] = useState(null); const [formValues, setFormValues] = useState({}); const [processing, setProcessing] = useState(false); - const [submitButtonText, setsubmitButtonText] = useState("Save Jellyfin Details"); + const [submitButtonText, setsubmitButtonText] = useState( + "Save Jellyfin Details", + ); const [showPassword, setShowPassword] = useState(false); function handleFormChange(event) { setFormValues({ ...formValues, [event.target.name]: event.target.value }); } async function beginSync() { - - - setProcessing(true); - await axios + setProcessing(true); + await axios .get("/sync/beingSync", { headers: { Authorization: `Bearer ${config.token}`, @@ -40,29 +39,30 @@ function Setup() { } }) .catch((error) => { - console.log(error); + console.log(error); }); - setProcessing(false); + setProcessing(false); } async function validateSettings(_url, _apikey) { const result = await axios - .post("/api/validateSettings", { - url:_url, - apikey: _apikey - - }, { - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", + .post( + "/api/validateSettings", + { + url: _url, + apikey: _apikey, }, - }) - .catch((error) => { - - }); + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }, + ) + .catch((error) => {}); - let data=result.data; - return { isValid:data.isValid, errorMessage:data.errorMessage} ; + let data = result.data; + return { isValid: data.isValid, errorMessage: data.errorMessage }; } async function handleFormSubmit(event) { @@ -71,7 +71,7 @@ function Setup() { let validation = await validateSettings( formValues.JF_HOST, - formValues.JF_API_KEY + formValues.JF_API_KEY, ); if (!validation.isValid) { @@ -105,10 +105,8 @@ function Setup() { } else if (error.response.status === 401) { errorMessage = `Error ${error.response.status} Unauthorized`; } else if (error.response.status === 404) { - errorMessage = `Error ${error.response.status}: The requested URL was not found.`; } else { - errorMessage = `Error : ${error.response.status}`; } console.log(error); @@ -137,38 +135,53 @@ function Setup() { return (
- -

Jellystat

- First Time Setup Step 2 of 2 + +

Jellystat

+ First Time Setup Step 2 of 2
- - - - - - - URL - + + + + URL - - - - - - - API Key - + + + + + API Key - - -
diff --git a/src/pages/signup.js b/src/pages/signup.js index ca98a735..ce23264f 100644 --- a/src/pages/signup.js +++ b/src/pages/signup.js @@ -2,18 +2,16 @@ import React, { useState, useEffect } from "react"; import axios from "axios"; import Config from "../lib/config"; -import CryptoJS from 'crypto-js'; +import CryptoJS from "crypto-js"; import "./css/setup.css"; import Loading from "./components/general/loading"; -import Form from 'react-bootstrap/Form'; -import Button from 'react-bootstrap/Button'; -import { InputGroup,Row } from "react-bootstrap"; - -import EyeFillIcon from 'remixicon-react/EyeFillIcon'; -import EyeOffFillIcon from 'remixicon-react/EyeOffFillIcon'; -import logo_dark from './images/icon-b-512.png'; - +import Form from "react-bootstrap/Form"; +import Button from "react-bootstrap/Button"; +import { InputGroup, Row } from "react-bootstrap"; +import EyeFillIcon from "remixicon-react/EyeFillIcon"; +import EyeOffFillIcon from "remixicon-react/EyeOffFillIcon"; +import logo_dark from "./images/icon-b-512.png"; function Signup() { const [config, setConfig] = useState(null); @@ -26,35 +24,35 @@ function Signup() { setFormValues({ ...formValues, [event.target.name]: event.target.value }); } - async function handleFormSubmit(event) { setProcessing(true); event.preventDefault(); - let hashedPassword= CryptoJS.SHA3(formValues.JS_PASSWORD).toString(); + let hashedPassword = CryptoJS.SHA3(formValues.JS_PASSWORD).toString(); // Send a POST request to /api/setconfig/ with the updated configuration axios - .post("/auth/createuser", - { - username:formValues.JS_USERNAME, - password: hashedPassword - - }, { - headers: { - "Content-Type": "application/json", + .post( + "/auth/createuser", + { + username: formValues.JS_USERNAME, + password: hashedPassword, }, - }) + { + headers: { + "Content-Type": "application/json", + }, + }, + ) .then(async (response) => { - - localStorage.setItem('token',response.data.token); + localStorage.setItem("token", response.data.token); setsubmitButtonText("Settings Saved"); setProcessing(false); window.location.reload(); return; }) .catch((error) => { - let errorMessage= `Error : ${error.response.status}`; + let errorMessage = `Error : ${error.response.status}`; if (error.code === "ERR_NETWORK") { errorMessage = `Unable to connect to Jellyfin Server`; } else if (error.response.status === 401) { @@ -84,47 +82,61 @@ function Signup() { } }, [config]); - if(!config) - { - return ; + if (!config) { + return ; } return (
-
- -

Jellystat

- First Time Setup Step 1 of 2 - -
- - - - - - - Username - - - - - - - - - - Password - - - - - - - + Password + + + + - -
+ +
); diff --git a/src/pages/statistics.js b/src/pages/statistics.js index 29e298b9..7a784f43 100644 --- a/src/pages/statistics.js +++ b/src/pages/statistics.js @@ -9,7 +9,7 @@ import PlayStatsByHour from "./components/statistics/play-stats-by-hour"; function Statistics() { const [days, setDays] = useState(20); - const [input, setInput] = useState(20); + const [input, setInput] = useState(20); const handleKeyDown = (event) => { if (event.key === "Enter") { diff --git a/src/pages/testing.js b/src/pages/testing.js index 7c1a19b5..c62b779a 100644 --- a/src/pages/testing.js +++ b/src/pages/testing.js @@ -1,60 +1,45 @@ -import React from 'react'; - - -import './css/library/libraries.css'; - - +import React from "react"; +import "./css/library/libraries.css"; // import LibraryOverView from './components/libraryOverview'; // import HomeStatisticCards from './components/HomeStatisticCards'; // import Sessions from './components/sessions/sessions'; -import LibrarySelector from './library_selector'; - - +import LibrarySelector from "./library_selector"; function Testing() { - - - - -// async function getToken(username,password) { -// const response = await fetch('http://localhost:3003/login', { -// method: 'POST', -// headers: { -// 'Content-Type': 'application/json', -// }, -// body: JSON.stringify({ -// username: username, -// password: password, -// }), -// }); - -// const data = await response.json(); -// return data.token; -// } - -// // Make a GET request with JWT authentication -// async function getDataWithAuth() { -// try { -// const token = await getToken('test','pass'); // a function to get the JWT token -// // console.log(token); -// localStorage.setItem('token', token); -// } catch (error) { -// console.error(error); -// } -// } -// getDataWithAuth(); - - + // async function getToken(username,password) { + // const response = await fetch('http://localhost:3003/login', { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json', + // }, + // body: JSON.stringify({ + // username: username, + // password: password, + // }), + // }); + + // const data = await response.json(); + // return data.token; + // } + + // // Make a GET request with JWT authentication + // async function getDataWithAuth() { + // try { + // const token = await getToken('test','pass'); // a function to get the JWT token + // // console.log(token); + // localStorage.setItem('token', token); + // } catch (error) { + // console.error(error); + // } + // } + // getDataWithAuth(); return ( -
- - - +
+
- ); } diff --git a/src/pages/users.js b/src/pages/users.js index fc14bd6e..369a2dc4 100644 --- a/src/pages/users.js +++ b/src/pages/users.js @@ -1,95 +1,91 @@ import React, { useState, useEffect } from "react"; import axios from "axios"; import Config from "../lib/config"; -import { Link } from 'react-router-dom'; +import { Link } from "react-router-dom"; import AccountCircleFillIcon from "remixicon-react/AccountCircleFillIcon"; -import {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 TableSortLabel from '@mui/material/TableSortLabel'; -import Box from '@mui/material/Box'; -import { visuallyHidden } from '@mui/utils'; - +import { 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 TableSortLabel from "@mui/material/TableSortLabel"; +import Box from "@mui/material/Box"; +import { visuallyHidden } from "@mui/utils"; import "./css/users/users.css"; import Loading from "./components/general/loading"; -const token = localStorage.getItem('token'); - +const token = localStorage.getItem("token"); function EnhancedTableHead(props) { - const { order, orderBy, onRequestSort } = - props; + const { order, orderBy, onRequestSort } = props; const createSortHandler = (property) => (event) => { onRequestSort(event, property); }; const headCells = [ { - id: 'UserName', + id: "UserName", numeric: false, disablePadding: true, - label: 'User', + label: "User", }, { - id: 'LastWatched', + id: "LastWatched", numeric: false, disablePadding: false, - label: 'Last Watched', + label: "Last Watched", }, { - id: 'LastClient', + id: "LastClient", numeric: false, disablePadding: false, - label: 'Last Client', + label: "Last Client", }, { - id: 'TotalPlays', + id: "TotalPlays", numeric: false, disablePadding: false, - label: 'Plays', + label: "Plays", }, { - id: 'TotalWatchTime', + id: "TotalWatchTime", numeric: false, disablePadding: false, - label: 'Watch Time', - }, + label: "Watch Time", + }, { - id: 'LastSeen', + id: "LastSeen", numeric: false, disablePadding: false, - label: 'Last Seen', + label: "Last Seen", }, ]; - return ( - + {headCells.map((headCell) => ( {headCell.label} {orderBy === headCell.id ? ( - {order === 'desc' ? 'sorted descending' : 'sorted ascending'} + {order === "desc" ? "sorted descending" : "sorted ascending"} ) : null} @@ -100,72 +96,80 @@ function EnhancedTableHead(props) { ); } - function Row(row) { const { data } = row; function formatTotalWatchTime(seconds) { const hours = Math.floor(seconds / 3600); // 1 hour = 3600 seconds const minutes = Math.floor((seconds % 3600) / 60); // 1 minute = 60 seconds - let formattedTime=''; - if(hours) - { - formattedTime+=`${hours} hours`; + let formattedTime = ""; + if (hours) { + formattedTime += `${hours} hours`; } - if(minutes) - { - formattedTime+=` ${minutes} minutes`; + if (minutes) { + formattedTime += ` ${minutes} minutes`; } - - return formattedTime ; + + return formattedTime; } function formatLastSeenTime(time) { const units = { - days: ['Day', 'Days'], - hours: ['Hour', 'Hours'], - minutes: ['Minute', 'Minutes'], - seconds: ['Second', 'Seconds'] + days: ["Day", "Days"], + hours: ["Hour", "Hours"], + minutes: ["Minute", "Minutes"], + seconds: ["Second", "Seconds"], }; - - let formattedTime = ''; - + + let formattedTime = ""; + for (const unit in units) { if (time[unit]) { const unitName = units[unit][time[unit] > 1 ? 1 : 0]; formattedTime += `${time[unit]} ${unitName} `; } } - + return `${formattedTime}ago`; } - return ( - *': { borderBottom: 'unset' } }}> + *": { borderBottom: "unset" } }}> - {data.PrimaryImageTag ? ( - - ) : ( - - )} + {data.PrimaryImageTag ? ( + + ) : ( + + )} - {data.UserName} - {data.LastWatched || 'never'} - {data.LastClient || 'n/a'} + + + {data.UserName} + + + + + {data.LastWatched || "never"} + + + {data.LastClient || "n/a"} {data.TotalPlays} - {formatTotalWatchTime(data.TotalWatchTime) || '0 minutes'} - {data.LastSeen ? formatLastSeenTime(data.LastSeen) : 'never'} - + + {formatTotalWatchTime(data.TotalWatchTime) || "0 minutes"} + + + {data.LastSeen ? formatLastSeenTime(data.LastSeen) : "never"} + ); @@ -176,15 +180,10 @@ function Users() { const [config, setConfig] = useState(null); const [rowsPerPage, setRowsPerPage] = React.useState(10); const [page, setPage] = React.useState(0); - const [itemCount,setItemCount] = useState(10); - - const [order, setOrder] = React.useState('asc'); - const [orderBy, setOrderBy] = React.useState('LastSeen'); - - - - + const [itemCount, setItemCount] = useState(10); + const [order, setOrder] = React.useState("asc"); + const [orderBy, setOrderBy] = React.useState("LastSeen"); useEffect(() => { const fetchConfig = async () => { @@ -218,8 +217,6 @@ function Users() { } }; - - fetchData(); if (!config) { @@ -242,49 +239,46 @@ function Users() { setPage((prevPage) => prevPage - 1); }; - function formatLastSeenTime(time) { - if(!time) - { - return ' never'; - } + function formatLastSeenTime(time) { + if (!time) { + return " never"; + } const units = { - days: ['Day', 'Days'], - hours: ['Hour', 'Hours'], - minutes: ['Minute', 'Minutes'], - seconds: ['Second', 'Seconds'] + days: ["Day", "Days"], + hours: ["Hour", "Hours"], + minutes: ["Minute", "Minutes"], + seconds: ["Second", "Seconds"], }; - - let formattedTime = ''; - + + let formattedTime = ""; + for (const unit in units) { if (time[unit]) { const unitName = units[unit][time[unit] > 1 ? 1 : 0]; formattedTime += `${time[unit]} ${unitName} `; } } - + return `${formattedTime}ago`; } - - function descendingComparator(a, b, orderBy) { - if (orderBy==='LastSeen') { - let order_a=formatLastSeenTime(a[orderBy]); - let order_b=formatLastSeenTime(b[orderBy]); + if (orderBy === "LastSeen") { + let order_a = formatLastSeenTime(a[orderBy]); + let order_b = formatLastSeenTime(b[orderBy]); if (order_b > order_a) { return -1; } - if (order_a< order_b) { + if (order_a < order_b) { return 1; } return 0; } - if (orderBy === 'TotalPlays' || orderBy === 'TotalWatchTime') { + if (orderBy === "TotalPlays" || orderBy === "TotalWatchTime") { let order_a = parseInt(a[orderBy]); let order_b = parseInt(b[orderBy]); - + if (order_a < order_b) { return -1; } @@ -293,7 +287,7 @@ function Users() { } return 0; } - + if (b[orderBy] < a[orderBy]) { return -1; } @@ -302,25 +296,22 @@ function Users() { } return 0; } - + function getComparator(order, orderBy) { - return order === 'desc' + return order === "desc" ? (a, b) => descendingComparator(a, b, orderBy) : (a, b) => -descendingComparator(a, b, orderBy); } - function stableSort(array, comparator) { const stabilizedThis = array.map((el, index) => [el, index]); stabilizedThis.sort((a, b) => { - const order = comparator(a[0], b[0]); if (order !== 0) { return order; } return a[1] - b[1]; - }); return stabilizedThis.map((el) => el[0]); @@ -332,71 +323,107 @@ function Users() { ); const handleRequestSort = (event, property) => { - const isAsc = orderBy === property && order === 'asc'; - setOrder(isAsc ? 'desc' : 'asc'); + const isAsc = orderBy === property && order === "asc"; + setOrder(isAsc ? "desc" : "asc"); setOrderBy(property); }; - - - return (
-

All Users

-
+

All Users

+
Items
- +
- - - - - {visibleRows.map((row) => ( - - ))} - {data.length===0 ? :''} - - -
No Users Found
-
- -
- - - - - -
{`${page *rowsPerPage + 1}-${Math.min((page * rowsPerPage+ 1 ) + (rowsPerPage - 1),data.length)} of ${data.length}`}
- - - - -
-
- + + + + + {visibleRows.map((row) => ( + + ))} + {data.length === 0 ? ( + + + + ) : ( + "" + )} + +
+ No Users Found +
+
+ +
+ + + + +
{`${ + page * rowsPerPage + 1 + }-${Math.min( + page * rowsPerPage + 1 + (rowsPerPage - 1), + data.length, + )} of ${data.length}`}
+ + + +
+
); } diff --git a/src/setupProxy.js b/src/setupProxy.js index 009325b0..b142e687 100644 --- a/src/setupProxy.js +++ b/src/setupProxy.js @@ -1,63 +1,55 @@ -const { createProxyMiddleware } = require('http-proxy-middleware'); +const { createProxyMiddleware } = require("http-proxy-middleware"); -module.exports = function(app) { +module.exports = function (app) { app.use( `/api`, createProxyMiddleware({ target: `http://127.0.0.1:${process.env.PORT || 3003}`, changeOrigin: true, - }) + }), ); app.use( `/proxy`, createProxyMiddleware({ target: `http://127.0.0.1:${process.env.PORT || 3003}`, changeOrigin: true, - }) + }), ); app.use( `/stats`, createProxyMiddleware({ target: `http://127.0.0.1:${process.env.PORT || 3003}`, changeOrigin: true, - }) + }), ); app.use( `/sync`, createProxyMiddleware({ target: `http://127.0.0.1:${process.env.PORT || 3003}`, changeOrigin: true, - }) + }), ); app.use( `/auth`, createProxyMiddleware({ target: `http://127.0.0.1:${process.env.PORT || 3003}`, changeOrigin: true, - }) + }), ); app.use( `/backup`, createProxyMiddleware({ target: `http://127.0.0.1:${process.env.PORT || 3003}`, changeOrigin: true, - }) + }), ); app.use( `/logs`, createProxyMiddleware({ target: `http://127.0.0.1:${process.env.PORT || 3003}`, changeOrigin: true, - }) - ); - app.use( - '/socket.io', - createProxyMiddleware({ - target: `http://127.0.0.1:${process.env.PORT || 3003}`, - changeOrigin: true, - }) + }), ); - console.log(`Proxy middleware applied`); };