Skip to content

Commit 4253883

Browse files
authored
Merge pull request #17 from pir8radio/dev
Dev update - added drive status info, cleaned up a bunch of code
2 parents 88eb647 + 32904d5 commit 4253883

File tree

6 files changed

+296
-23
lines changed

6 files changed

+296
-23
lines changed

DownloadViewarr.js

Lines changed: 135 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,47 @@
11
const express = require('express');
22
const axios = require('axios');
3+
const path = require('path');
34
const app = express();
45

6+
7+
8+
/*==================================== SETTINGS =====================================*/
9+
10+
// RADARR SERVER SETTINGS
11+
const DEFAULT_RADARR_SERVER_IP = '127.0.0.1' // Radarr server IP/URL
12+
const DEFAULT_RADARR_SERVER_PORT = 7878 // Radarr server port
13+
const DEFAULT_RADARR_API_KEY = '1234abcdxxxxxxxxxxxxxxxxxxxxxxxx' // Radarr API Key
14+
15+
// SONARR SERVER SETTINGS
16+
const DEFAULT_SONARR_SERVER_IP = '127.0.0.1' // Sonarr server IP/URL
17+
const DEFAULT_SONARR_SERVER_PORT = 8989 // Sonarr server port
18+
const DEFAULT_SONARR_API_KEY = '1234abcdxxxxxxxxxxxxxxxxxxxxxxxx' // Sonarr API Key
19+
20+
// DOWNLOAD VIEWARR SETTINGS
21+
const DEFAULT_SERVER_PORT = 8888 // Port to view download status page
22+
const DEFAULT_ENABLE_DRIVE_STATUS = "FALSE" // True or False - Enable media drive(s) status.
23+
524
/*==================================== SETTINGS =====================================*/
25+
26+
27+
28+
29+
// Don't edit below this line
30+
631
const settings = {
732
movies: {
8-
apiServerIP: process.env.RADARR_SERVER_IP || '127.0.0.1', // Radarr server IP (Docker: from env var, Native: default to localhost)
9-
apiServerPort: process.env.RADARR_SERVER_PORT || 7878, // Radarr server port
10-
apiKey: process.env.RADARR_API_KEY || '1234abcdxxxxxxxxxxxxxxxxxxxxxxxx', // Radarr API Key
33+
apiServerIP: process.env.RADARR_SERVER_IP || DEFAULT_RADARR_SERVER_IP,
34+
apiServerPort: process.env.RADARR_SERVER_PORT || DEFAULT_RADARR_SERVER_PORT,
35+
apiKey: process.env.RADARR_API_KEY || DEFAULT_RADARR_API_KEY,
1136
},
1237
tvshows: {
13-
apiServerIP: process.env.SONARR_SERVER_IP || '127.0.0.1', // Sonarr server IP (Docker: from env var, Native: default to localhost)
14-
apiServerPort: process.env.SONARR_SERVER_PORT || 8989, // Sonarr server port
15-
apiKey: process.env.SONARR_API_KEY || '1234abcdxxxxxxxxxxxxxxxxxxxxxxxx', // Sonarr API Key
38+
apiServerIP: process.env.SONARR_SERVER_IP || DEFAULT_SONARR_SERVER_IP,
39+
apiServerPort: process.env.SONARR_SERVER_PORT || DEFAULT_SONARR_SERVER_PORT,
40+
apiKey: process.env.SONARR_API_KEY || DEFAULT_SONARR_API_KEY,
1641
},
17-
nodeServerPort: process.env.NODE_SERVER_PORT || 8888, // Port to view download page
42+
nodeServerPort: process.env.NODE_SERVER_PORT || DEFAULT_SERVER_PORT,
43+
enableDriveStatus: (process.env.ENABLE_DRIVE_STATUS || DEFAULT_ENABLE_DRIVE_STATUS).toLowerCase(),
1844
};
19-
/*==================================== SETTINGS =====================================*/
20-
2145

2246
// Middleware to enable CORS
2347
app.use((req, res, next) => {
@@ -29,6 +53,107 @@ app.use((req, res, next) => {
2953
// Serve static files from the "public" directory
3054
app.use(express.static('public'));
3155

56+
// Function to fetch root folders from Radarr or Sonarr
57+
async function fetchRootFolders(apiBaseUrl, apiKey) {
58+
try {
59+
const response = await axios.get(`${apiBaseUrl}/rootfolder`, {
60+
headers: { 'X-Api-Key': apiKey },
61+
});
62+
return response.data.map(folder => folder.path); // Extract folder paths
63+
} catch (error) {
64+
console.error('Error fetching root folders:', error.message);
65+
return [];
66+
}
67+
}
68+
69+
// Function to fetch disk space information from Radarr or Sonarr APIs
70+
async function fetchDiskSpace(apiBaseUrl, apiKey) {
71+
try {
72+
const response = await axios.get(`${apiBaseUrl}/diskspace?apikey=${apiKey}`);
73+
return response.data; // Return the API response data
74+
} catch (error) {
75+
console.error(`Error fetching disk space from API: ${error.message}`);
76+
return null; // Return null on error
77+
}
78+
}
79+
80+
app.get('/api/drive-space', async (req, res) => {
81+
if (!["true", "yes"].includes(settings.enableDriveStatus)) {
82+
return res.status(403).send('Drive status is disabled.');
83+
}
84+
85+
try {
86+
// Fetch root folders for Movies and TV Shows
87+
const radarrRootFolders = await fetchRootFolders(
88+
`http://${settings.movies.apiServerIP}:${settings.movies.apiServerPort}/api/v3`,
89+
settings.movies.apiKey
90+
);
91+
const sonarrRootFolders = await fetchRootFolders(
92+
`http://${settings.tvshows.apiServerIP}:${settings.tvshows.apiServerPort}/api/v3`,
93+
settings.tvshows.apiKey
94+
);
95+
96+
const allFolders = [...new Set([...radarrRootFolders, ...sonarrRootFolders])];
97+
98+
// Fetch disk space data from Radarr API first, fallback to Sonarr API if Radarr fails
99+
let diskSpaces = await fetchDiskSpace(
100+
`http://${settings.movies.apiServerIP}:${settings.movies.apiServerPort}/api/v3`,
101+
settings.movies.apiKey
102+
);
103+
104+
if (!diskSpaces) {
105+
diskSpaces = await fetchDiskSpace(
106+
`http://${settings.tvshows.apiServerIP}:${settings.tvshows.apiServerPort}/api/v3`,
107+
settings.tvshows.apiKey
108+
);
109+
}
110+
111+
if (!diskSpaces) {
112+
return res.status(500).send('Failed to fetch disk space data');
113+
}
114+
115+
// Filter disk spaces to only include those with a matching root folder
116+
const filteredDiskSpaces = diskSpaces.filter(disk =>
117+
allFolders.some(folder => folder.startsWith(disk.path))
118+
);
119+
120+
// Deduplicate results based on the root drive (e.g., "F:\\")
121+
const uniqueDriveSpaces = Object.values(
122+
filteredDiskSpaces.reduce((acc, drive) => {
123+
const rootDrive = drive.path.split(':')[0] + ':\\'; // Extract root drive
124+
if (!acc[rootDrive]) {
125+
acc[rootDrive] = {
126+
path: rootDrive,
127+
label: drive.label,
128+
freeSpace: drive.freeSpace,
129+
totalSpace: drive.totalSpace,
130+
};
131+
} else {
132+
// If already exists, sum up the free and total space
133+
acc[rootDrive].freeSpace += drive.freeSpace;
134+
acc[rootDrive].totalSpace += drive.totalSpace;
135+
}
136+
return acc;
137+
}, {})
138+
);
139+
140+
// Format the response
141+
const formattedDriveSpaces = uniqueDriveSpaces.map(drive => ({
142+
path: drive.path,
143+
label: drive.label,
144+
freeSpace: `${(drive.freeSpace / 1e9).toFixed(2)} GB`,
145+
totalSpace: `${(drive.totalSpace / 1e9).toFixed(2)} GB`,
146+
usedSpace: `${((drive.totalSpace - drive.freeSpace) / 1e9).toFixed(2)} GB`,
147+
percentageUsed: Math.round(((drive.totalSpace - drive.freeSpace) / drive.totalSpace) * 100)
148+
}));
149+
150+
res.json(formattedDriveSpaces); // Return the formatted drive space data
151+
} catch (error) {
152+
console.error('Error in /api/drive-space:', error.message);
153+
res.status(500).send('Failed to fetch drive space data');
154+
}
155+
});
156+
32157
// Function to fetch data from the external API
33158
async function fetchData(type) {
34159
try {
@@ -91,5 +216,6 @@ app.get('/api/queue/downloading', async (req, res) => {
91216

92217
// Start the server
93218
app.listen(settings.nodeServerPort, () => {
219+
console.log(`Drive status enabled: ${settings.enableDriveStatus}`);
94220
console.log(`Server running at http://localhost:${settings.nodeServerPort}`);
95221
});

README.md

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ docker pull pir8radio/downloadviewarr
2222
## Features
2323

2424
- Fetches download queue data from Radarr and Sonarr using their APIs.
25+
- Fetches media drive space info from Radarr or Sonarr APIs.
2526
- Displays data in an organized format on a web page.
2627
- Easy to configure and run locally.
2728
- Can be used by itself, or embeded as an iframe within ombi or some other request app.
@@ -53,6 +54,12 @@ docker pull pir8radio/downloadviewarr
5354
<img src="https://github.com/user-attachments/assets/81261ff3-58c1-45d7-9686-ad0324aa8b4f" width="300">
5455
</div>
5556

57+
- Media Drive Status (optional)
58+
<div align="center">
59+
<img src="https://github.com/user-attachments/assets/36ce7efb-c1a6-4c3e-90b0-f1e29638b604">
60+
</div>
61+
62+
5663
## Installation
5764

5865
1. Clone the repository:
@@ -72,20 +79,23 @@ docker pull pir8radio/downloadviewarr
7279

7380
```javascript
7481
/*==================================== SETTINGS =====================================*/
75-
const settings = {
76-
movies: {
77-
apiServerIP: process.env.RADARR_SERVER_IP || '127.0.0.1', // Radarr server IP (Docker: from env var, Native: default to localhost)
78-
apiServerPort: process.env.RADARR_SERVER_PORT || 7878, // Radarr server port
79-
apiKey: process.env.RADARR_API_KEY || '1234abcdxxxxxxxxxxxxxxxxxxxxxxxx', // Radarr API Key
80-
},
81-
tvshows: {
82-
apiServerIP: process.env.SONARR_SERVER_IP || '127.0.0.1', // Sonarr server IP (Docker: from env var, Native: default to localhost)
83-
apiServerPort: process.env.SONARR_SERVER_PORT || 8989, // Sonarr server port
84-
apiKey: process.env.SONARR_API_KEY || '1234abcdxxxxxxxxxxxxxxxxxxxxxxxx', // Sonarr API Key
85-
},
86-
nodeServerPort: process.env.NODE_SERVER_PORT || 8888, // Port to view download page
87-
};
82+
83+
// RADARR SERVER SETTINGS
84+
const DEFAULT_RADARR_SERVER_IP = '127.0.0.1' // Radarr server IP/URL
85+
const DEFAULT_RADARR_SERVER_PORT = 7878 // Radarr server port
86+
const DEFAULT_RADARR_API_KEY = '1234abcdxxxxxxxxxxxxxxxxxxxxxxxx' // Radarr API Key
87+
88+
// SONARR SERVER SETTINGS
89+
const DEFAULT_SONARR_SERVER_IP = '127.0.0.1' // Sonarr server IP/URL
90+
const DEFAULT_SONARR_SERVER_PORT = 8989 // Sonarr server port
91+
const DEFAULT_SONARR_API_KEY = '1234abcdxxxxxxxxxxxxxxxxxxxxxxxx' // Sonarr API Key
92+
93+
// DOWNLOAD VIEWARR SETTINGS
94+
const DEFAULT_SERVER_PORT = 8888 // Port to view download status page
95+
const DEFAULT_ENABLE_DRIVE_STATUS = "TRUE" // True or False - Enable media drive(s) bar graphs.
96+
8897
/*==================================== SETTINGS =====================================*/
98+
8999
```
90100

91101
4. Start the application:
@@ -143,6 +153,11 @@ Developed by [pir8radio](https://github.com/pir8radio).
143153

144154

145155
# Update History
156+
## 05-04-2025
157+
- Cleaned up settings section, gave dedicated settings.
158+
- Added support for auto generated progress bar style drive information, % Used - Free - etc
159+
- Changed how we were getting drive capacity, now get it from the radarr or sonarr api, so no need to map drives or anything for docker users.
160+
146161
## 04-26-2025
147162
- This should be the last flash update, lol sorry guys, been pushing out features!
148163
- added tab download indicator to the floating button embed

docker-compose.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ services:
1212
RADARR_SERVER_PORT: 7878
1313
SONARR_SERVER_PORT: 8989
1414
NODE_SERVER_PORT: 8888
15-
restart: unless-stopped
15+
ENABLE_DRIVE_STATUS: "TRUE"
16+
restart: unless-stopped

public/index.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ <h1>Download Queue</h1>
5454
<div class="loader" id="loadingIcon"></div>
5555
<div class="live-text">Live</div>
5656
</div>
57+
58+
<div id="driveSpaceContainer" class="drive-space-container">
59+
<!-- Drive bars will be dynamically populated here -->
60+
</div>
5761
<script src="/scripts.js"></script>
5862
</body>
5963
</html>

public/scripts.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@ async function fetchAndPopulateTable() {
104104
// Call checkOverflow to handle progress bar overflows
105105
checkOverflow();
106106

107+
// Call fetchDriveSpace to update the drive space info
108+
fetchDriveSpace();
109+
107110
// Refresh loader animation
108111
resetLoaderAnimation();
109112
if (loaderTimeout) clearTimeout(loaderTimeout);
@@ -330,6 +333,72 @@ function checkOverflow() {
330333
});
331334
}
332335

336+
// Function to check media drive(s) status
337+
let driveStatusDisabled = false;
338+
let runCounter = 0;
339+
340+
async function fetchDriveSpace() {
341+
// If "Drive status is disabled." was returned previously, skip execution
342+
if (driveStatusDisabled) {
343+
return;
344+
}
345+
346+
// Execute only on first run and when runCounter reaches 0
347+
if (runCounter > 0) {
348+
runCounter--;
349+
return;
350+
}
351+
352+
try {
353+
const response = await fetch('/api/drive-space');
354+
355+
// First run check
356+
if (response.status === 403) { // Status 403 indicates drive status is disabled
357+
driveStatusDisabled = true; // Mark that future runs should be skipped
358+
console.log("Drive status is disabled. Will not fetch drive space in future runs.");
359+
return;
360+
}
361+
362+
const drives = await response.json();
363+
364+
const driveSpaceContainer = document.getElementById('driveSpaceContainer');
365+
if (driveSpaceContainer) {
366+
driveSpaceContainer.innerHTML = ''; // Clear any existing content
367+
368+
drives.forEach(drive => {
369+
// Convert values from GB to TB
370+
const totalTB = (parseFloat(drive.totalSpace) / 1024).toFixed(2);
371+
const usedTB = (parseFloat(drive.usedSpace) / 1024).toFixed(2);
372+
const availableTB = (parseFloat(drive.freeSpace) / 1024).toFixed(2);
373+
const percentage = drive.percentageUsed;
374+
375+
// Create bar graph section for each drive
376+
const driveElement = document.createElement('div');
377+
driveElement.className = 'drive-space';
378+
379+
driveElement.innerHTML = `
380+
<div class="drive-space-label">${drive.path}</div>
381+
<div class="drive-space-progress">
382+
<div class="drive-space-progress-bar"
383+
style="width: ${percentage}%"
384+
title="${usedTB} TB of ${totalTB} TB used (${availableTB} TB available)">
385+
<span class="drive-space-text">${percentage}%</span>
386+
</div>
387+
</div>
388+
`;
389+
390+
driveSpaceContainer.appendChild(driveElement);
391+
});
392+
}
393+
394+
// Reset runCounter after fetch execution
395+
runCounter = 10;
396+
397+
} catch (error) {
398+
console.error('Failed to fetch drive space data:', error);
399+
}
400+
}
401+
333402
// Restore settings on page load
334403
document.addEventListener('DOMContentLoaded', () => {
335404
monitorTabFromUrl(); // Handle URL parameter on page load

0 commit comments

Comments
 (0)