From 5e238fdad2fd169b808d099e0878bb712cc319b9 Mon Sep 17 00:00:00 2001
From: fuzz <>
Date: Wed, 25 Oct 2023 16:23:25 +0100
Subject: [PATCH 1/8] initial commit
needs work - currently just an api endpoint that will return geolocation data for a given IP.
ToDo:
- Display IP in session-card
- Clicking IP opens modal with geolocation info
---
backend/routes/utils.js | 50 +++++++++++++++++++++++++++++++++++++++++
backend/server.js | 2 ++
2 files changed, 52 insertions(+)
create mode 100644 backend/routes/utils.js
diff --git a/backend/routes/utils.js b/backend/routes/utils.js
new file mode 100644
index 0000000..12d5bf8
--- /dev/null
+++ b/backend/routes/utils.js
@@ -0,0 +1,50 @@
+// api.js
+const https = require("https");
+const axios = require("axios");
+const express = require("express");
+
+const router = express.Router();
+
+const geoliteUrlBase = 'https://geolite.info/geoip/v2.1/city';
+
+const geoliteAccountId = process.env.GEOLITE_ACCOUNT_ID;
+const geoliteLicenseKey = process.env.GEOLITE_LICENSE_KEY;
+
+//https://stackoverflow.com/questions/5284147/validating-ipv4-addresses-with-regexp
+const ipRegex = new RegExp(`^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\\b){4}$`);
+
+const agent = new https.Agent({
+ rejectUnauthorized: (process.env.REJECT_SELF_SIGNED_CERTIFICATES || 'true').toLowerCase() ==='true'
+});
+
+const axios_instance = axios.create({
+ httpsAgent: agent
+});
+
+router.get("/geolocateIp", async (req, res) => {
+ try {
+ if(!(geoliteAccountId && geoliteLicenseKey)) {
+ return res.status(501).send('GeoLite information missing!');
+ }
+
+ const { ipAddress } = req.body;
+ ipRegex.lastIndex = 0;
+
+ if(!ipAddress || !ipRegex.test(ipAddress)) {
+ return res.status(400).send('Invalid IP address sent!');
+ }
+
+ const response = await axios_instance.get(`${geoliteUrlBase}/${ipAddress}`, {
+ auth: {
+ username: geoliteAccountId,
+ password: geoliteLicenseKey
+ }
+ });
+ return res.send(response.data);
+ } catch (error) {
+ res.status(503);
+ res.send(error);
+ }
+});
+
+module.exports = router;
\ No newline at end of file
diff --git a/backend/server.js b/backend/server.js
index 435635a..1e2a357 100644
--- a/backend/server.js
+++ b/backend/server.js
@@ -15,6 +15,7 @@ const ActivityMonitor = require('./tasks/ActivityMonitor');
const SyncTask = require('./tasks/SyncTask');
const BackupTask = require('./tasks/BackupTask');
const logRouter = require('./routes/logging');
+const utilsRouter = require('./routes/utils');
const dbInstance = require("./db");
@@ -109,6 +110,7 @@ app.use('/sync', authenticate , syncRouter.router,()=>{/* #swagger.tags = ['Syn
app.use('/stats', authenticate , statsRouter,()=>{/* #swagger.tags = ['Stats']*/}); // mount the API router at /stats, with JWT middleware
app.use('/backup', authenticate , backupRouter.router,()=>{/* #swagger.tags = ['Backup']*/}); // mount the API router at /backup, with JWT middleware
app.use('/logs', authenticate , logRouter.router,()=>{/* #swagger.tags = ['Logs']*/}); // mount the API router at /logs, with JWT middleware
+app.use('/utils', authenticate, utilsRouter, ()=>{/* #swagger.tags = ['Utils']*/}); // mount the API router at /utils, with JWT middleware
const swaggerUi = require('swagger-ui-express');
const swaggerDocument = require('./swagger.json');
From c1d3dec173199eba1ee6428a8c933462ff19ca20 Mon Sep 17 00:00:00 2001
From: fuzz <>
Date: Wed, 25 Oct 2023 18:07:47 +0100
Subject: [PATCH 2/8] use proxy, set up modal in frontend
Add row to session card with IP Address. Clickable, opens modal.
Modal WIP - displays general info about IP location (maybe can include a map within this modal?)
---
backend/routes/utils.js | 2 +-
src/pages/components/ip-info.js | 30 ++++++++++++
src/pages/components/sessions/session-card.js | 48 +++++++++++++++++--
src/setupProxy.js | 7 +++
4 files changed, 83 insertions(+), 4 deletions(-)
create mode 100644 src/pages/components/ip-info.js
diff --git a/backend/routes/utils.js b/backend/routes/utils.js
index 12d5bf8..4a1443c 100644
--- a/backend/routes/utils.js
+++ b/backend/routes/utils.js
@@ -21,7 +21,7 @@ const axios_instance = axios.create({
httpsAgent: agent
});
-router.get("/geolocateIp", async (req, res) => {
+router.post("/geolocateIp", async (req, res) => {
try {
if(!(geoliteAccountId && geoliteLicenseKey)) {
return res.status(501).send('GeoLite information missing!');
diff --git a/src/pages/components/ip-info.js b/src/pages/components/ip-info.js
new file mode 100644
index 0000000..a3c1b76
--- /dev/null
+++ b/src/pages/components/ip-info.js
@@ -0,0 +1,30 @@
+import React from "react";
+import Loading from "./general/loading";
+import Modal from 'react-bootstrap/Modal';
+
+export default function IpInfoModal(props) {
+ let modalBody = ;
+
+ if(props.geodata) {
+ modalBody =
+ City: {props.geodata.city.names['en']}
+ Country: {props.geodata.country.names['en']}
+ Postcode: {props.geodata.postal.code}
+ ISP: {props.geodata.traits.autonomous_system_organization}
+
+ }
+
+ return (
+
+ props.onHide()}>
+
+
+ Geolocation info for {props.ipAddress}
+
+
+
+ {modalBody}
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/pages/components/sessions/session-card.js b/src/pages/components/sessions/session-card.js
index d597b9c..327b281 100644
--- a/src/pages/components/sessions/session-card.js
+++ b/src/pages/components/sessions/session-card.js
@@ -1,4 +1,4 @@
-import React from "react";
+import React, {useState} from "react";
import { Link } from 'react-router-dom';
import Card from 'react-bootstrap/Card';
import Row from 'react-bootstrap/Row';
@@ -11,7 +11,9 @@ import PauseFillIcon from "remixicon-react/PauseFillIcon";
import { clientData } from "../../../lib/devices";
import Tooltip from "@mui/material/Tooltip";
+import IpInfoModal from '../ip-info';
+import axios from 'axios';
function ticksToTimeString(ticks) {
// Convert ticks to seconds
@@ -56,7 +58,7 @@ function convertBitrate(bitrate) {
}
}
-function sessionCard(props) {
+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%',
@@ -69,9 +71,42 @@ function sessionCard(props) {
height:'100%',
};
+ const token = localStorage.getItem('token');
+
+ const [modalVisible, setModalVisible] = useState(false);
+ const [modalData, setModalData] = useState();
+
+ function showModal() {
+ const fetchData = async () => {
+ const result = await axios.post(`/utils/geolocateIp`, {
+ ipAddress: props.data.session.RemoteEndPoint
+ }, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ "Content-Type": "application/json",
+ }
+ });
+ setModalData(result.data);
+ };
+
+ if(!modalData) {
+ fetchData();
+ }
+
+ setModalVisible(true);
+ }
+
+ function hideModal() {
+ setModalVisible(false);
+ }
return (
+
@@ -99,6 +134,13 @@ function sessionCard(props) {
{props.data.session.NowPlayingItem.SubtitleStream}
+
+
+
+ IP Address: {props.data.session.RemoteEndPoint}
+
+
+
@@ -223,4 +265,4 @@ function sessionCard(props) {
);
}
-export default sessionCard;
+export default SessionCard;
diff --git a/src/setupProxy.js b/src/setupProxy.js
index efff95e..2467e96 100644
--- a/src/setupProxy.js
+++ b/src/setupProxy.js
@@ -64,6 +64,13 @@ module.exports = function(app) {
changeOrigin: true,
})
);
+ app.use(
+ '/utils',
+ createProxyMiddleware({
+ target: `http://127.0.0.1:${process.env.PORT || 3003}`,
+ changeOrigin: true
+ })
+ );
console.log(`Proxy middleware applied`);
From e1c628900ed5aa36d46065e7796f6c14606cef23 Mon Sep 17 00:00:00 2001
From: fuzz <>
Date: Fri, 3 Nov 2023 12:40:32 +0000
Subject: [PATCH 3/8] increase stat card size
add padding to image
---
src/pages/css/statCard.css | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/src/pages/css/statCard.css b/src/pages/css/statCard.css
index ccd4e52..1e8d2b6 100644
--- a/src/pages/css/statCard.css
+++ b/src/pages/css/statCard.css
@@ -14,12 +14,12 @@
background-color: var(--background-color)!important;
color: white;
max-width: 500px;
- max-height: 180px;
+ max-height: 190px;
}
.stat-card-banner
{
- max-width: 120px !important;
+ max-width: 127px !important;
}
@@ -27,8 +27,9 @@
.stat-card-image {
- width: 120px !important;
- height: 180px;
+ width: 127px !important;
+ height: 190px;
+ padding: 5px;
}
.stat-card-icon
From ba6a885e40271d6c38c991d867899a8d64a78bfb Mon Sep 17 00:00:00 2001
From: fuzz <>
Date: Fri, 3 Nov 2023 12:51:57 +0000
Subject: [PATCH 4/8] merge fix
---
src/pages/components/sessions/session-card.js | 4 +++-
src/pages/css/statCard.css | 9 ++++-----
2 files changed, 7 insertions(+), 6 deletions(-)
diff --git a/src/pages/components/sessions/session-card.js b/src/pages/components/sessions/session-card.js
index 41d2cef..836a3a5 100644
--- a/src/pages/components/sessions/session-card.js
+++ b/src/pages/components/sessions/session-card.js
@@ -137,7 +137,9 @@ function SessionCard(props) {
- IP Address: {props.data.session.RemoteEndPoint}
+
+ IP Address: {props.data.session.RemoteEndPoint}
+
diff --git a/src/pages/css/statCard.css b/src/pages/css/statCard.css
index 1e8d2b6..ccd4e52 100644
--- a/src/pages/css/statCard.css
+++ b/src/pages/css/statCard.css
@@ -14,12 +14,12 @@
background-color: var(--background-color)!important;
color: white;
max-width: 500px;
- max-height: 190px;
+ max-height: 180px;
}
.stat-card-banner
{
- max-width: 127px !important;
+ max-width: 120px !important;
}
@@ -27,9 +27,8 @@
.stat-card-image {
- width: 127px !important;
- height: 190px;
- padding: 5px;
+ width: 120px !important;
+ height: 180px;
}
.stat-card-icon
From 3126d636f8e622031c7c9b0a58f45efbac356436 Mon Sep 17 00:00:00 2001
From: fuzz <>
Date: Fri, 3 Nov 2023 13:03:42 +0000
Subject: [PATCH 5/8] explicitly move to new row
otherwise ends up on same line as subtitle info
---
src/pages/components/sessions/session-card.js | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/pages/components/sessions/session-card.js b/src/pages/components/sessions/session-card.js
index 836a3a5..bde79f2 100644
--- a/src/pages/components/sessions/session-card.js
+++ b/src/pages/components/sessions/session-card.js
@@ -129,14 +129,16 @@ function SessionCard(props) {
{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.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.NowPlayingItem.SubtitleStream}
-
+
+
+
IP Address: {props.data.session.RemoteEndPoint}
From 300c6d5dfdebdfd77c4630e2cf10c29c64262037 Mon Sep 17 00:00:00 2001
From: fuzz <>
Date: Fri, 3 Nov 2023 17:03:50 +0000
Subject: [PATCH 6/8] update ipv4 regex
- filter out local ip addresses
- do not show modal for local ip addresses, only remote
- tooltip to show local/remote
---
backend/routes/utils.js | 4 +--
src/pages/components/sessions/session-card.js | 28 +++++++++++++++++--
2 files changed, 28 insertions(+), 4 deletions(-)
diff --git a/backend/routes/utils.js b/backend/routes/utils.js
index 4a1443c..60f055c 100644
--- a/backend/routes/utils.js
+++ b/backend/routes/utils.js
@@ -10,8 +10,8 @@ const geoliteUrlBase = 'https://geolite.info/geoip/v2.1/city';
const geoliteAccountId = process.env.GEOLITE_ACCOUNT_ID;
const geoliteLicenseKey = process.env.GEOLITE_LICENSE_KEY;
-//https://stackoverflow.com/questions/5284147/validating-ipv4-addresses-with-regexp
-const ipRegex = new RegExp(`^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\\b){4}$`);
+//https://stackoverflow.com/a/29268025
+const ipRegex = new RegExp(/\b(?!(10\.|172\.(1[6-9]|2[0-9]|3[0-1])\.|192\.168))(?:(?:2(?:[0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9])\.){3}(?:(?:2([0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9]))\b/);
const agent = new https.Agent({
rejectUnauthorized: (process.env.REJECT_SELF_SIGNED_CERTIFICATES || 'true').toLowerCase() ==='true'
diff --git a/src/pages/components/sessions/session-card.js b/src/pages/components/sessions/session-card.js
index bde79f2..552c0b4 100644
--- a/src/pages/components/sessions/session-card.js
+++ b/src/pages/components/sessions/session-card.js
@@ -1,4 +1,4 @@
-import React, {useState} from "react";
+import React, {useState,useEffect} from "react";
import { Link } from 'react-router-dom';
import Card from 'react-bootstrap/Card';
import Row from 'react-bootstrap/Row';
@@ -73,10 +73,24 @@ function SessionCard(props) {
const token = localStorage.getItem('token');
+ const ipv4Regex = new RegExp(/\b(?!(10\.|172\.(1[6-9]|2[0-9]|3[0-1])\.|192\.168))(?:(?:2(?:[0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9])\.){3}(?:(?:2([0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9]))\b/);
+
const [modalVisible, setModalVisible] = useState(false);
const [modalData, setModalData] = useState();
+ const [isRemoteSession, setIsRemoteSession] = useState();
+
+ useEffect(() => {
+ ipv4Regex.lastIndex = 0;
+ if(ipv4Regex.test(props.data.session.RemoteEndPoint)) {
+ setIsRemoteSession(true)
+ }
+ }, []);
function showModal() {
+ if(!isRemoteSession) {
+ return
+ }
+
const fetchData = async () => {
const result = await axios.post(`/utils/geolocateIp`, {
ipAddress: props.data.session.RemoteEndPoint
@@ -140,7 +154,17 @@ function SessionCard(props) {
- IP Address: {props.data.session.RemoteEndPoint}
+
+ {isRemoteSession ?
+
+ IP Address: {props.data.session.RemoteEndPoint}
+
+ :
+
+ IP Address: {props.data.session.RemoteEndPoint}
+
+ }
+
From 3089cb2c3f384b1a323e373badb7560d3b8bcdd3 Mon Sep 17 00:00:00 2001
From: fuzz <>
Date: Fri, 3 Nov 2023 18:01:28 +0000
Subject: [PATCH 7/8] use table-style for modal
more consistent with stream info modal from activity table
---
src/pages/components/ip-info.js | 53 ++++++++++++++++++++++++++++-----
1 file changed, 45 insertions(+), 8 deletions(-)
diff --git a/src/pages/components/ip-info.js b/src/pages/components/ip-info.js
index a3c1b76..5d3d040 100644
--- a/src/pages/components/ip-info.js
+++ b/src/pages/components/ip-info.js
@@ -1,16 +1,49 @@
import React from "react";
import Loading from "./general/loading";
-import Modal from 'react-bootstrap/Modal';
+import { Button, 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';
export default function IpInfoModal(props) {
let modalBody =
;
if(props.geodata) {
- modalBody =
- City: {props.geodata.city.names['en']}
- Country: {props.geodata.country.names['en']}
- Postcode: {props.geodata.postal.code}
- ISP: {props.geodata.traits.autonomous_system_organization}
+ modalBody =
+
+
+
+
+
+
+
+
+
+
+
+ City
+ {props.geodata.city.names['en']}
+
+
+ Country
+ {props.geodata.country.names['en']}
+
+
+ Postcode
+ {props.geodata.postal.code}
+
+
+ ISP
+ {props.geodata.traits.autonomous_system_organization}
+
+
+
+
+
}
@@ -21,9 +54,13 @@ export default function IpInfoModal(props) {
Geolocation info for {props.ipAddress}
-
-
+
{modalBody}
+
+ props.onHide()}>
+ Close
+
+
);
From c9b64aea1ebae3a306fc858b22d2b5c68803c943 Mon Sep 17 00:00:00 2001
From: Thegan Govender
Date: Sat, 4 Nov 2023 23:34:29 +0200
Subject: [PATCH 8/8] Update session-card.js
Removed Tooltip as it was unnecessary
Also added a check in the frontend. if the env variables where not set for the GEOLITE API, it would disable the option entirely
---
src/pages/components/sessions/session-card.js | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/pages/components/sessions/session-card.js b/src/pages/components/sessions/session-card.js
index 552c0b4..674fe3f 100644
--- a/src/pages/components/sessions/session-card.js
+++ b/src/pages/components/sessions/session-card.js
@@ -154,8 +154,8 @@ function SessionCard(props) {
-
- {isRemoteSession ?
+
+ {isRemoteSession && (process.env.GEOLITE_ACCOUNT_ID && process.env.GEOLITE_LICENSE_KEY) ?
IP Address: {props.data.session.RemoteEndPoint}
@@ -164,7 +164,7 @@ function SessionCard(props) {
IP Address: {props.data.session.RemoteEndPoint}
}
-
+