mirror of
https://github.com/BreizhHardware/Jellystat.git
synced 2026-01-18 16:27:20 +01:00
Merge pull request #123 from DaftFuzz/feature/geolocate-ip
Feature/geolocate ip
This commit is contained in:
50
backend/routes/utils.js
Normal file
50
backend/routes/utils.js
Normal file
@@ -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/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'
|
||||
});
|
||||
|
||||
const axios_instance = axios.create({
|
||||
httpsAgent: agent
|
||||
});
|
||||
|
||||
router.post("/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;
|
||||
@@ -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');
|
||||
|
||||
67
src/pages/components/ip-info.js
Normal file
67
src/pages/components/ip-info.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import React from "react";
|
||||
import Loading from "./general/loading";
|
||||
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 = <Loading/>;
|
||||
|
||||
if(props.geodata) {
|
||||
modalBody = <Modal.Body>
|
||||
<div className="StreamInfo">
|
||||
<TableContainer className="overflow-hidden">
|
||||
<Table aria-label="collapsible table" >
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell/>
|
||||
<TableCell></TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell className="py-0 pb-1">City</TableCell>
|
||||
<TableCell>{props.geodata.city.names['en']}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell className="py-0 pb-1">Country</TableCell>
|
||||
<TableCell>{props.geodata.country.names['en']}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell className="py-0 pb-1">Postcode</TableCell>
|
||||
<TableCell>{props.geodata.postal.code}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell className="py-0 pb-1">ISP</TableCell>
|
||||
<TableCell>{props.geodata.traits.autonomous_system_organization}</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</div>
|
||||
</Modal.Body>
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Modal show={props.show} onHide={() => props.onHide()}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
Geolocation info for {props.ipAddress}
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
{modalBody}
|
||||
<Modal.Footer>
|
||||
<Button variant="outline-primary" onClick={()=>props.onHide()}>
|
||||
Close
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import React 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';
|
||||
@@ -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,56 @@ function sessionCard(props) {
|
||||
height:'100%',
|
||||
};
|
||||
|
||||
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
|
||||
}, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
});
|
||||
setModalData(result.data);
|
||||
};
|
||||
|
||||
if(!modalData) {
|
||||
fetchData();
|
||||
}
|
||||
|
||||
setModalVisible(true);
|
||||
}
|
||||
|
||||
function hideModal() {
|
||||
setModalVisible(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="stat-card" style={cardStyle}>
|
||||
<IpInfoModal
|
||||
show={modalVisible}
|
||||
onHide={hideModal}
|
||||
ipAddress={props.data.session.RemoteEndPoint}
|
||||
geodata={modalData}/>
|
||||
<div style={cardBgStyle} className="rounded-top">
|
||||
<Row className="h-100">
|
||||
<Col className="d-none d-lg-block stat-card-banner">
|
||||
@@ -94,13 +143,27 @@ function sessionCard(props) {
|
||||
<Row className="ellipse card-client-version"> {props.data.session.Client + " " + props.data.session.ApplicationVersion}</Row>
|
||||
<Row className="d-flex flex-column flex-md-row">
|
||||
<Col className="px-0 col-auto ellipse">{props.data.session.PlayState.PlayMethod}</Col>
|
||||
<Col className="px-0 px-md-2 col-auto ellipse">{(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)+' )':'')}</Col>
|
||||
<Col className="px-0 px-md-2 col-auto ellipse">{(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)+' )':'')}</Col>
|
||||
<Col className="px-0 col-auto ellipse">
|
||||
<Tooltip title={props.data.session.NowPlayingItem.SubtitleStream}>
|
||||
<span>
|
||||
{props.data.session.NowPlayingItem.SubtitleStream}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col className="px-0 col-auto ellipse">
|
||||
|
||||
{isRemoteSession && (process.env.GEOLITE_ACCOUNT_ID && process.env.GEOLITE_LICENSE_KEY) ?
|
||||
<Card.Text>
|
||||
IP Address: <Link onClick={showModal}>{props.data.session.RemoteEndPoint}</Link>
|
||||
</Card.Text>
|
||||
:
|
||||
<span>
|
||||
IP Address: {props.data.session.RemoteEndPoint}
|
||||
</span>
|
||||
}
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
@@ -232,4 +295,4 @@ function sessionCard(props) {
|
||||
);
|
||||
}
|
||||
|
||||
export default sessionCard;
|
||||
export default SessionCard;
|
||||
|
||||
@@ -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`);
|
||||
|
||||
Reference in New Issue
Block a user