Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
56039bd
Add webhook management and scheduling functionality
BreizhHardware Apr 25, 2025
e3e3a16
Add webhooks settings component and integrate into settings page
BreizhHardware Apr 25, 2025
165927f
Update webhook component for improved user experience and error handling
BreizhHardware Apr 25, 2025
193b47c
Improve error messages and logging in webhook manager and scheduler a…
BreizhHardware Apr 26, 2025
20d100c
make loading show on initial page load to prevent flicker when data i…
CyferShepard Apr 26, 2025
51e710e
Merge pull request #389 from CyferShepard/main
CyferShepard May 2, 2025
2de6616
bump version to 1.1.7 in package.json and package-lock.json
CyferShepard May 2, 2025
f05d9fb
Initial changes for total time view
Sanidhya30 May 5, 2025
c180033
Daily watch time changes
Sanidhya30 May 5, 2025
579a7a3
Changes for the daily & weekly playback duration charts
Sanidhya30 May 5, 2025
07fbfdc
Updating exports.down for migrations and css change
Sanidhya30 May 6, 2025
e668f71
Removing debug changes
Sanidhya30 May 6, 2025
e0aa9de
Added github container registry to pipeline
nath1416 May 22, 2025
84aa0f0
Merge pull request #395 from nath1416/feature/added-ghcr-pipeline
CyferShepard May 22, 2025
27062b2
Merge branch 'CyferShepard:main' into main
BreizhHardware May 23, 2025
280fa89
feat(webhooks): add support for playback and media notification events
BreizhHardware May 23, 2025
41e9a6e
Fix failed backup bug due to not awaiting log insert #393
CyferShepard May 24, 2025
eeada4f
feat(webhooks): add Discord webhook support and event notifications f…
BreizhHardware May 26, 2025
d9aba8a
Update src/pages/components/settings/webhooks.jsx
BreizhHardware May 26, 2025
247df5f
Update backend/routes/webhooks.js
BreizhHardware May 26, 2025
1f1a51f
Update backend/routes/webhooks.js
BreizhHardware May 26, 2025
b2e6a44
Update backend/routes/sync.js
BreizhHardware May 26, 2025
c3c7c68
Merge pull request #1 from BreizhHardware/dev
BreizhHardware May 26, 2025
2feef53
refactor(webhooks): improve Discord webhook handling and update notif…
BreizhHardware Jun 2, 2025
db8d0a3
feat: add language german
chrisvgt Jun 12, 2025
0957ec1
Merge pull request #404 from chrisvgt/feature/add_language_german
CyferShepard Jun 14, 2025
f11dfc1
fix: update base image to node:lts-slim for improved stability
CyferShepard Jun 14, 2025
48b5d37
Merge pull request #391 from Sanidhya30/main
CyferShepard Jun 14, 2025
24517c4
fix: update base image to node:lts-slim for improved stability
CyferShepard Jun 14, 2025
67bdb8d
refactor: Replace `moment` with `dayjs`
Zlendy Jun 11, 2025
5b3b039
fix: sanitize data before db insert
jon4hz Jun 29, 2025
03e8ced
Update iOS PWA Icon Sizing and Background
simoncaron Jul 3, 2025
2240a68
Merge pull request #415 from simoncaron/feature/update-ios-pwa-icon
CyferShepard Jul 3, 2025
f315825
fix: Backup job saves files with a literal "yyyy" instead of the curr…
Zlendy Jul 4, 2025
26173e5
fix: Custom parser is not enabled
Zlendy Jul 4, 2025
0f75598
Merge pull request #414 from jon4hz/main
CyferShepard Jul 7, 2025
7f03e7d
fix for #417, sets duration to 0 if negative int detected
CyferShepard Jul 8, 2025
8292ede
Update docker-compose.yml
JordyEGNL Aug 17, 2025
5f7e89c
Adaptive Polling Implementation
tppadn Aug 28, 2025
1ba1c2d
Merge pull request #410 from Zlendy/dayjs
CyferShepard Sep 11, 2025
e940ffe
Fixes for #441 and possibly #385
CyferShepard Sep 14, 2025
bc7571d
user configurable validation of psql ssl
gunanr Sep 20, 2025
b4e43c5
readme update
gunanr Sep 20, 2025
d2dfa41
POSTGRES_SSL_ENABLED environment variable
gunanr Sep 20, 2025
9181b95
update readme
gunanr Sep 20, 2025
dfe0e30
update readme
gunanr Sep 20, 2025
5b293d5
update readme
gunanr Sep 20, 2025
e978a2d
Merge pull request #444 from gunanr/postgres-ssl-verify-toggle
CyferShepard Sep 24, 2025
2f8d82e
Merge branch 'unstable' into main
tppadn Sep 29, 2025
e258682
Merge pull request #429 from tppadn/main
CyferShepard Oct 5, 2025
d79223b
Merge branch 'unstable' into main
CyferShepard Oct 5, 2025
6c15312
updated migrations to account for migrations from unstable branch
CyferShepard Oct 5, 2025
051acf5
Merge branch 'main' of https://github.com/BreizhHardware/Jellystat in…
CyferShepard Oct 5, 2025
8177cfb
Merge pull request #384 from BreizhHardware/main
CyferShepard Oct 5, 2025
f778191
Remove authMiddleware from event-status route in webhooks as it doesn…
CyferShepard Oct 5, 2025
70f6c98
Merge pull request #428 from JordyEGNL/patch-1
CyferShepard Oct 5, 2025
6831555
Convert UTC time to local timezone for play by hours stats
brikim Oct 13, 2025
a435057
fix: Items in "Recently Added" are out of order
Zlendy Oct 14, 2025
b81237a
Merge pull request #451 from Zlendy/fix/dayjs-recently-added
CyferShepard Oct 15, 2025
490c722
Re-Organize the session card to provide more information
brikim Oct 19, 2025
baf25a6
css adjustments to play/pause icons
CyferShepard Oct 19, 2025
b140db9
Check if AudioBitrate exists in the audio bitrate stream
brikim Oct 19, 2025
96d7268
Merge pull request #452 from brikim/feature/SessionCardUpdates
CyferShepard Oct 20, 2025
a81e863
Update to support 12/24 hour time in the session card ... Also a mino…
brikim Oct 20, 2025
bc51234
Merge pull request #453 from brikim/feature/SessionCardTimeAndFix
CyferShepard Oct 21, 2025
1acb8c2
Add support for using Websocket for session data in Jellyfin 10.11.0 …
CyferShepard Oct 21, 2025
b3d4e03
Merge branch 'unstable' of https://github.com/CyferShepard/Jellystat …
CyferShepard Oct 21, 2025
28f9bba
Fix socket URL in getSessions method for correct WebSocket connection
CyferShepard Oct 21, 2025
e92d3c4
Enhance WebSocket connection handling and configuration retrieval
CyferShepard Oct 21, 2025
23ec2fa
Limit the session card title to col-7 to restrict the name from overf…
brikim Oct 22, 2025
1826f16
Merge pull request #456 from brikim/fix/SessionCardTitleOverflow
CyferShepard Oct 22, 2025
f64cc12
Updated Jellyfin web-socket function to add reconnection timeout afte…
CyferShepard Oct 22, 2025
a2d8c18
Merge branch 'unstable' of https://github.com/CyferShepard/Jellystat …
CyferShepard Oct 22, 2025
d60d302
Disabled Webhooks do to secondary review finding incomplete code
CyferShepard Oct 23, 2025
99c5efe
Refactor session card device image source logic for iOS client
CyferShepard Nov 8, 2025
8d0f70b
Update Dockerfile to use specific Node.js version 22.21.1 for armv7 c…
CyferShepard Nov 8, 2025
4e1faf1
Update French translations in translation.json
MATAR0U Nov 21, 2025
2b6a8b7
Use VideoBitrate if available for transcoding stream. If no audio bit…
brikim Nov 22, 2025
edec2b9
Merge pull request #467 from brikim/feature/UseCorrectVideoBitrateAud…
CyferShepard Nov 23, 2025
996c5db
Merge pull request #466 from MATAR0U/patch-1
CyferShepard Nov 23, 2025
5606a58
Fixes for the stream info popup on the Activity tab. Transcoded audio…
brikim Nov 28, 2025
4c0b319
Fixed constant name
brikim Nov 28, 2025
bdc72fe
More fixes. Live TV does not have run time ticks. Added cases that if…
brikim Dec 6, 2025
65fee01
refactor/jellyfin-authentication
Dec 11, 2025
a99b4c9
Merge pull request #472 from SlayerTO/unstable
CyferShepard Dec 11, 2025
313edf2
Merge pull request #469 from brikim/fix/StreamInfoFixes
CyferShepard Dec 14, 2025
bc986e4
Enhance filter functionality across activity components
CyferShepard Dec 14, 2025
5b2fad4
refactor buildFilterList function
CyferShepard Dec 14, 2025
2e50daf
Created materialized view for library items to reduce response time
CyferShepard Dec 14, 2025
cf8637f
Refresh materialized views after purging library items
CyferShepard Dec 15, 2025
93f1b15
Implement JWT authentication for WebSocket connections
CyferShepard Dec 15, 2025
f1a214c
decoupled isNumber check from @mui/x-data-grid/internals
CyferShepard Dec 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .github/workflows/docker-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,21 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}

- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
tags: |
${{ steps.meta.outputs.tags }}
ghcr.io/${{ steps.meta.outputs.tags }}
platforms: linux/amd64,linux/arm64,linux/arm/v7
cache-from: type=gha
cache-to: type=gha,mode=max
9 changes: 9 additions & 0 deletions .github/workflows/docker-latest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ jobs:
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}

- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build Docker image
uses: docker/build-push-action@v5
Expand All @@ -53,4 +60,6 @@ jobs:
tags: |
cyfershepard/jellystat:latest
cyfershepard/jellystat:${{ env.VERSION }}
ghcr.io/cyfershepard/jellystat:latest
ghcr.io/cyfershepard/jellystat:${{ env.VERSION }}
platforms: linux/amd64,linux/arm64,linux/arm/v7
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Stage 1: Build the application
FROM node:slim AS builder
FROM node:22.21.1-bookworm-slim AS builder

WORKDIR /app

Expand All @@ -14,7 +14,7 @@ COPY entry.sh ./
RUN npm run build

# Stage 2: Create the production image
FROM node:slim
FROM node:22.21.1-bookworm-slim

RUN apt-get update && \
apt-get install -yqq --no-install-recommends wget && \
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
| POSTGRES_PASSWORD `REQUIRED` | `null` | `postgres` | Password that will be used in postgres database |
| POSTGRES_IP `REQUIRED` | `null` | `jellystat-db` or `192.168.0.5` | Hostname/IP of postgres instance |
| POSTGRES_PORT `REQUIRED` | `null` | `5432` | Port Postgres is running on |
| POSTGRES_SSL_ENABLED | `null` | `true` | Enable SSL connections to Postgres
| POSTGRES_SSL_REJECT_UNAUTHORIZED | `null` | `false` | Verify Postgres SSL certificates when POSTGRES_SSL_ENABLED=true
| JS_LISTEN_IP | `0.0.0.0`| `0.0.0.0` or `::` | Enable listening on specific IP or `::` for IPv6 |
| JWT_SECRET `REQUIRED` | `null` | `my-secret-jwt-key` | JWT Key to be used to encrypt JWT tokens for authentication |
| TZ `REQUIRED` | `null` | `Etc/UTC` | Server timezone (Can be found at https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List) |
Expand Down
20 changes: 10 additions & 10 deletions backend/classes/backup.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const fs = require("fs");
const path = require("path");
const configClass = require("./config");

const moment = require("moment");
const dayjs = require("dayjs");
const Logging = require("./logging");

const taskstate = require("../logging/taskstate");
Expand Down Expand Up @@ -34,7 +34,7 @@ async function backup(refLog) {
if (config.error) {
refLog.logData.push({ color: "red", Message: "Backup Failed: Failed to get config" });
refLog.logData.push({ color: "red", Message: "Backup Failed with errors" });
Logging.updateLog(refLog.uuid, refLog.logData, taskstate.FAILED);
await Logging.updateLog(refLog.uuid, refLog.logData, taskstate.FAILED);
return;
}

Expand All @@ -50,7 +50,7 @@ async function backup(refLog) {
// Get data from each table and append it to the backup file

try {
let now = moment();
let now = dayjs();
const backuppath = "./" + backupfolder;

if (!fs.existsSync(backuppath)) {
Expand All @@ -61,7 +61,7 @@ async function backup(refLog) {
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.logData, taskstate.FAILED);
await Logging.updateLog(refLog.uuid, refLog.logData, taskstate.FAILED);
await pool.end();
return;
}
Expand All @@ -73,18 +73,18 @@ async function backup(refLog) {
if (filteredTables.length === 0) {
refLog.logData.push({ color: "red", Message: "Backup Failed: No tables to backup" });
refLog.logData.push({ color: "red", Message: "Backup Failed with errors" });
Logging.updateLog(refLog.uuid, refLog.logData, taskstate.FAILED);
await Logging.updateLog(refLog.uuid, refLog.logData, 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 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`);
refLog.logData.push({ color: "yellow", Message: "Begin Backup " + directoryPath });
const stream = fs.createWriteStream(directoryPath, { flags: "a" });
stream.on("error", (error) => {
stream.on("error", async (error) => {
refLog.logData.push({ color: "red", Message: "Backup Failed: " + error });
Logging.updateLog(refLog.uuid, refLog.logData, taskstate.FAILED);
await Logging.updateLog(refLog.uuid, refLog.logData, taskstate.FAILED);
return;
});
const backup_data = [];
Expand Down Expand Up @@ -152,7 +152,7 @@ async function backup(refLog) {
} catch (error) {
console.log(error);
refLog.logData.push({ color: "red", Message: "Backup Failed: " + error });
Logging.updateLog(refLog.uuid, refLog.logData, taskstate.FAILED);
await Logging.updateLog(refLog.uuid, refLog.logData, taskstate.FAILED);
}

await pool.end();
Expand Down
17 changes: 10 additions & 7 deletions backend/classes/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@ class Config {
return { state: 0, error: "Config Details Not Found" };
}

const _config = config[0];

return {
JF_HOST: process.env.JF_HOST ?? config[0].JF_HOST,
JF_API_KEY: process.env.JF_API_KEY ?? config[0].JF_API_KEY,
APP_USER: config[0].APP_USER,
APP_PASSWORD: config[0].APP_PASSWORD,
REQUIRE_LOGIN: config[0].REQUIRE_LOGIN,
settings: config[0].settings,
api_keys: config[0].api_keys,
JF_HOST: process.env.JF_HOST ?? _config.JF_HOST,
JF_EXTERNAL_HOST: _config.settings?.EXTERNAL_URL,
JF_API_KEY: process.env.JF_API_KEY ?? _config.JF_API_KEY,
APP_USER: _config.APP_USER,
APP_PASSWORD: _config.APP_PASSWORD,
REQUIRE_LOGIN: _config.REQUIRE_LOGIN,
settings: _config.settings,
api_keys: _config.api_keys,
state: state,
IS_JELLYFIN: (process.env.IS_EMBY_API || "false").toLowerCase() === "false",
};
Expand Down
121 changes: 120 additions & 1 deletion backend/classes/db-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ function wrapField(field) {
field.includes("AVG") ||
field.includes("DISTINCT") ||
field.includes("json_agg") ||
field.includes("CASE")
field.includes("CASE") ||
field.includes("REGEXP_REPLACE")
) {
return field;
}
Expand Down Expand Up @@ -203,6 +204,124 @@ async function query({
client.release();
}
}

function buildFilterList(query, filtersArray, filterFields) {
if (filtersArray.length > 0) {
query.where = query.where || [];
filtersArray.forEach((filter) => {
const findField = filterFields.find((item) => item.field === filter.field);
const column = findField?.column || "a.ActivityDateInserted";
const isColumn = findField?.isColumn || false;
const applyToCTE = findField?.applyToCTE || false;
if (filter.min) {
query.where.push({
column: column,
operator: ">=",
value: `$${query.values.length + 1}`,
});

query.values.push(filter.min);

if (applyToCTE) {
if (query.cte) {
if (!query.cte.where) {
query.cte.where = [];
}
query.cte.where.push({
column: column,
operator: ">=",
value: `$${query.values.length + 1}`,
});

query.values.push(filter.min);
}
}
}

if (filter.in) {
const values = filter.in.split(",");
const valuesPlaceholders = values.map((_, i) => `$${query.values.length + i + 1}`).join(", ");
query.where.push({
column: column,
operator: "in",
value: `(${valuesPlaceholders})`,
});

if (applyToCTE) {
if (query.cte) {
if (!query.cte.where) {
query.cte.where = [];
}
const valuesPlaceholdersCTE = values.map((_, i) => `$${query.values.length + values.length + i + 1}`).join(", ");
query.cte.where.push({
column: column,
operator: "in",
value: `(${valuesPlaceholdersCTE})`,
});
}
}
query.values.push(...values);
if (applyToCTE && query.cte) {
query.values.push(...values);
}
}

if (filter.max) {
query.where.push({
column: column,
operator: "<=",
value: `$${query.values.length + 1}`,
});

query.values.push(filter.max);

if (applyToCTE) {
if (query.cte) {
if (!query.cte.where) {
query.cte.where = [];
}
query.cte.where.push({
column: column,
operator: "<=",
value: `$${query.values.length + 1}`,
});

query.values.push(filter.max);
}
}
}

if (filter.value) {
const whereClause = {
operator: "LIKE",
value: `$${query.values.length + 1}`,
};

query.values.push(`%${filter.value.toLowerCase()}%`);

if (isColumn) {
whereClause.column = column;
} else {
whereClause.field = column;
}
query.where.push(whereClause);

if (applyToCTE) {
if (query.cte) {
if (!query.cte.where) {
query.cte.where = [];
}
whereClause.value = `$${query.values.length + 1}`;
query.cte.where.push(whereClause);

query.values.push(`%${filter.value.toLowerCase()}%`);
}
}
}
});
}
}
module.exports = {
query,
buildFilterList,
};
17 changes: 17 additions & 0 deletions backend/classes/emby-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,23 @@ class EmbyAPI {
}
}

async getUserById(userid) {
if (!this.configReady) {
const success = await this.#fetchConfig();
if (!success) {
return null;
}
}

try {
const users = await this.getUsers();
return users.find((user) => user.Id === userid) || null;
} catch (error) {
this.#errorHandler(error);
return null;
}
}

async getItemsByID({ ids, params }) {
if (!this.configReady) {
return [];
Expand Down
Loading