mirror of
https://github.com/plexguide/Huntarr.git
synced 2026-01-25 12:48:59 -06:00
Updates
This commit is contained in:
@@ -1065,3 +1065,101 @@ input:checked + .slider:before {
|
||||
.connection-status.testing {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
/* Swaparr specific styles */
|
||||
.swaparr-panel {
|
||||
margin-bottom: 15px;
|
||||
border-radius: 8px;
|
||||
background-color: var(--bg-secondary);
|
||||
border-left: 4px solid var(--accent-color);
|
||||
}
|
||||
|
||||
.swaparr-config {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.swaparr-config h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.swaparr-config-content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.swaparr-config-content span {
|
||||
background-color: var(--bg-tertiary);
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.swaparr-table {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.swaparr-table table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background-color: var(--bg-secondary);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.swaparr-table th {
|
||||
background-color: var(--bg-tertiary);
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.swaparr-table td {
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid var(--bg-tertiary);
|
||||
}
|
||||
|
||||
.swaparr-status-striked {
|
||||
background-color: rgba(255, 193, 7, 0.1);
|
||||
}
|
||||
|
||||
.swaparr-status-pending {
|
||||
background-color: rgba(13, 110, 253, 0.1);
|
||||
}
|
||||
|
||||
.swaparr-status-ignored {
|
||||
background-color: rgba(108, 117, 125, 0.1);
|
||||
}
|
||||
|
||||
.swaparr-status-normal {
|
||||
background-color: rgba(25, 135, 84, 0.1);
|
||||
}
|
||||
|
||||
.swaparr-status-removed {
|
||||
background-color: rgba(220, 53, 69, 0.1);
|
||||
}
|
||||
|
||||
/* When in dark mode */
|
||||
.dark-theme .swaparr-status-striked {
|
||||
background-color: rgba(255, 193, 7, 0.2);
|
||||
}
|
||||
|
||||
.dark-theme .swaparr-status-pending {
|
||||
background-color: rgba(13, 110, 253, 0.2);
|
||||
}
|
||||
|
||||
.dark-theme .swaparr-status-ignored {
|
||||
background-color: rgba(108, 117, 125, 0.2);
|
||||
}
|
||||
|
||||
.dark-theme .swaparr-status-normal {
|
||||
background-color: rgba(25, 135, 84, 0.2);
|
||||
}
|
||||
|
||||
.dark-theme .swaparr-status-removed {
|
||||
background-color: rgba(220, 53, 69, 0.2);
|
||||
}
|
||||
|
||||
381
frontend/static/js/apps/swaparr.js
Normal file
381
frontend/static/js/apps/swaparr.js
Normal file
@@ -0,0 +1,381 @@
|
||||
// Swaparr-specific functionality
|
||||
|
||||
(function(app) {
|
||||
if (!app) {
|
||||
console.error("Huntarr App core is not loaded!");
|
||||
return;
|
||||
}
|
||||
|
||||
const swaparrModule = {
|
||||
elements: {},
|
||||
isTableView: true, // Default to table view for Swaparr logs
|
||||
hasRenderedAnyContent: false, // Track if we've rendered any content
|
||||
|
||||
// Store data for display
|
||||
logData: {
|
||||
config: {
|
||||
platform: '',
|
||||
maxStrikes: 3,
|
||||
scanInterval: '10m',
|
||||
maxDownloadTime: '2h',
|
||||
ignoreAboveSize: '25 GB'
|
||||
},
|
||||
downloads: [], // Will store download status records
|
||||
rawLogs: [] // Store raw logs for backup display
|
||||
},
|
||||
|
||||
init: function() {
|
||||
console.log('[Swaparr Module] Initializing...');
|
||||
this.setupLogProcessor();
|
||||
|
||||
// Add a listener for when the log tab changes to Swaparr
|
||||
const swaparrTab = document.querySelector('.log-tab[data-app="swaparr"]');
|
||||
if (swaparrTab) {
|
||||
swaparrTab.addEventListener('click', () => {
|
||||
console.log('[Swaparr Module] Swaparr tab clicked');
|
||||
// Small delay to ensure everything is ready
|
||||
setTimeout(() => {
|
||||
this.ensureContentRendered();
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
setupLogProcessor: function() {
|
||||
// Setup a listener for custom event from huntarrUI's log processing
|
||||
document.addEventListener('swaparrLogReceived', (event) => {
|
||||
console.log('[Swaparr Module] Received log event:', event.detail.logData.substring(0, 100) + '...');
|
||||
this.processLogLine(event.detail.logData);
|
||||
});
|
||||
},
|
||||
|
||||
processLogLine: function(logLine) {
|
||||
// Always store raw logs for backup display
|
||||
this.logData.rawLogs.push(logLine);
|
||||
|
||||
// Limit raw logs storage to prevent memory issues
|
||||
if (this.logData.rawLogs.length > 500) {
|
||||
this.logData.rawLogs.shift();
|
||||
}
|
||||
|
||||
// Process log lines specific to Swaparr
|
||||
if (!logLine) return;
|
||||
|
||||
// Check if this looks like a Swaparr config line and extract information
|
||||
if (logLine.includes('Platform:') && logLine.includes('Max strikes:')) {
|
||||
this.extractConfigInfo(logLine);
|
||||
this.renderConfigPanel();
|
||||
return;
|
||||
}
|
||||
|
||||
// Look for strike-related logs from system
|
||||
if (logLine.includes('Added strike') ||
|
||||
logLine.includes('Max strikes reached') ||
|
||||
logLine.includes('removing download') ||
|
||||
logLine.includes('Would have removed')) {
|
||||
|
||||
this.processStrikeLog(logLine);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is a table header/separator line
|
||||
if (logLine.includes('strikes') && logLine.includes('status') && logLine.includes('name') && logLine.includes('size') && logLine.includes('eta')) {
|
||||
// This is the header line, we can ignore it or use it to confirm table format
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to match download info line
|
||||
// Format: [strikes/max] status name size eta
|
||||
// Example: 2/3 Striked MyDownload.mkv 1.5 GB 2h 15m
|
||||
const downloadLinePattern = /(\d+\/\d+)\s+(\w+)\s+(.+?)\s+(\d+(?:\.\d+)?)\s*(\w+)\s+([\ddhms\s]+|Infinite)/;
|
||||
const match = logLine.match(downloadLinePattern);
|
||||
|
||||
if (match) {
|
||||
// Extract download information
|
||||
const downloadInfo = {
|
||||
strikes: match[1],
|
||||
status: match[2],
|
||||
name: match[3],
|
||||
size: match[4] + ' ' + match[5],
|
||||
eta: match[6]
|
||||
};
|
||||
|
||||
// Update or add to our list of downloads
|
||||
this.updateDownloadsList(downloadInfo);
|
||||
this.renderTableView();
|
||||
}
|
||||
|
||||
// If we're viewing the Swaparr tab, always ensure content is rendered
|
||||
if (app.currentLogApp === 'swaparr') {
|
||||
this.ensureContentRendered();
|
||||
}
|
||||
},
|
||||
|
||||
// Process strike-related logs from system logs
|
||||
processStrikeLog: function(logLine) {
|
||||
// Try to extract download name and strike info
|
||||
let downloadName = '';
|
||||
let strikes = '1/3'; // Default value
|
||||
let status = 'Striked';
|
||||
|
||||
// Extract download name
|
||||
if (logLine.includes('Added strike')) {
|
||||
const match = logLine.match(/Added strike \((\d+)\/(\d+)\) to (.+?) - Reason:/);
|
||||
if (match) {
|
||||
strikes = `${match[1]}/${match[2]}`;
|
||||
downloadName = match[3];
|
||||
status = 'Striked';
|
||||
}
|
||||
} else if (logLine.includes('Max strikes reached')) {
|
||||
const match = logLine.match(/Max strikes reached for (.+?), removing download/);
|
||||
if (match) {
|
||||
downloadName = match[1];
|
||||
status = 'Removed';
|
||||
}
|
||||
} else if (logLine.includes('Would have removed')) {
|
||||
const match = logLine.match(/Would have removed (.+?) after (\d+) strikes/);
|
||||
if (match) {
|
||||
downloadName = match[1];
|
||||
status = 'Pending Removal';
|
||||
strikes = `${match[2]}/3`;
|
||||
}
|
||||
}
|
||||
|
||||
if (downloadName) {
|
||||
// Create a download info object with partial information
|
||||
const downloadInfo = {
|
||||
strikes: strikes,
|
||||
status: status,
|
||||
name: downloadName,
|
||||
size: 'Unknown',
|
||||
eta: 'Unknown'
|
||||
};
|
||||
|
||||
// Update downloads list
|
||||
this.updateDownloadsList(downloadInfo);
|
||||
this.renderTableView();
|
||||
}
|
||||
},
|
||||
|
||||
extractConfigInfo: function(logLine) {
|
||||
// Extract the config data from the log line
|
||||
const platformMatch = logLine.match(/Platform:\s+(\w+)/);
|
||||
const maxStrikesMatch = logLine.match(/Max strikes:\s+(\d+)/);
|
||||
const scanIntervalMatch = logLine.match(/Scan interval:\s+(\d+\w+)/);
|
||||
const maxDownloadTimeMatch = logLine.match(/Max download time:\s+(\d+\w+)/);
|
||||
const ignoreSizeMatch = logLine.match(/Ignore above size:\s+(\d+\s*\w+)/);
|
||||
|
||||
if (platformMatch) this.logData.config.platform = platformMatch[1];
|
||||
if (maxStrikesMatch) this.logData.config.maxStrikes = maxStrikesMatch[1];
|
||||
if (scanIntervalMatch) this.logData.config.scanInterval = scanIntervalMatch[1];
|
||||
if (maxDownloadTimeMatch) this.logData.config.maxDownloadTime = maxDownloadTimeMatch[1];
|
||||
if (ignoreSizeMatch) this.logData.config.ignoreAboveSize = ignoreSizeMatch[1];
|
||||
},
|
||||
|
||||
updateDownloadsList: function(downloadInfo) {
|
||||
// Find if this download already exists in our list
|
||||
const existingIndex = this.logData.downloads.findIndex(item =>
|
||||
item.name.trim() === downloadInfo.name.trim()
|
||||
);
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
// Update existing entry
|
||||
this.logData.downloads[existingIndex] = downloadInfo;
|
||||
} else {
|
||||
// Add new entry
|
||||
this.logData.downloads.push(downloadInfo);
|
||||
}
|
||||
},
|
||||
|
||||
renderConfigPanel: function() {
|
||||
// Find the logs container
|
||||
const logsContainer = document.getElementById('logsContainer');
|
||||
if (!logsContainer) return;
|
||||
|
||||
// If the user has selected swaparr logs, show the config panel at the top
|
||||
if (app.currentLogApp === 'swaparr') {
|
||||
// Check if config panel already exists
|
||||
let configPanel = document.getElementById('swaparr-config-panel');
|
||||
if (!configPanel) {
|
||||
// Create the panel
|
||||
configPanel = document.createElement('div');
|
||||
configPanel.id = 'swaparr-config-panel';
|
||||
configPanel.classList.add('swaparr-panel');
|
||||
logsContainer.appendChild(configPanel);
|
||||
}
|
||||
|
||||
// Update the panel content
|
||||
configPanel.innerHTML = `
|
||||
<div class="swaparr-config">
|
||||
<h3>Swaparr${this.logData.config.platform ? ' — ' + this.logData.config.platform : ''}</h3>
|
||||
<div class="swaparr-config-content">
|
||||
<span>Max strikes: ${this.logData.config.maxStrikes}</span>
|
||||
<span>Scan interval: ${this.logData.config.scanInterval}</span>
|
||||
<span>Max download time: ${this.logData.config.maxDownloadTime}</span>
|
||||
<span>Ignore above size: ${this.logData.config.ignoreAboveSize}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.hasRenderedAnyContent = true;
|
||||
}
|
||||
},
|
||||
|
||||
renderTableView: function() {
|
||||
// Find the logs container
|
||||
const logsContainer = document.getElementById('logsContainer');
|
||||
if (!logsContainer || app.currentLogApp !== 'swaparr') return;
|
||||
|
||||
// Check if table already exists
|
||||
let tableView = document.getElementById('swaparr-table-view');
|
||||
if (!tableView) {
|
||||
// Create the table
|
||||
tableView = document.createElement('div');
|
||||
tableView.id = 'swaparr-table-view';
|
||||
tableView.classList.add('swaparr-table');
|
||||
logsContainer.appendChild(tableView);
|
||||
}
|
||||
|
||||
// Only render table if we have downloads to show
|
||||
if (this.logData.downloads.length > 0) {
|
||||
// Generate table HTML
|
||||
let tableHTML = `
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Strikes</th>
|
||||
<th>Status</th>
|
||||
<th>Name</th>
|
||||
<th>Size</th>
|
||||
<th>ETA</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
`;
|
||||
|
||||
// Add each download as a row
|
||||
this.logData.downloads.forEach(download => {
|
||||
// Apply status-specific CSS class
|
||||
let statusClass = download.status.toLowerCase();
|
||||
|
||||
// Normalize some status values
|
||||
if (statusClass === 'pending removal') statusClass = 'pending';
|
||||
if (statusClass === 'removed') statusClass = 'removed';
|
||||
if (statusClass === 'striked') statusClass = 'striked';
|
||||
if (statusClass === 'normal') statusClass = 'normal';
|
||||
if (statusClass === 'ignored') statusClass = 'ignored';
|
||||
|
||||
tableHTML += `
|
||||
<tr class="swaparr-status-${statusClass}">
|
||||
<td>${download.strikes}</td>
|
||||
<td>${download.status}</td>
|
||||
<td>${download.name}</td>
|
||||
<td>${download.size}</td>
|
||||
<td>${download.eta}</td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
|
||||
tableHTML += `
|
||||
</tbody>
|
||||
</table>
|
||||
`;
|
||||
|
||||
tableView.innerHTML = tableHTML;
|
||||
this.hasRenderedAnyContent = true;
|
||||
}
|
||||
},
|
||||
|
||||
// Render raw logs if we don't have structured content
|
||||
renderRawLogs: function() {
|
||||
// Only show raw logs if we have no other content
|
||||
if (this.hasRenderedAnyContent) return;
|
||||
|
||||
const logsContainer = document.getElementById('logsContainer');
|
||||
if (!logsContainer || app.currentLogApp !== 'swaparr') return;
|
||||
|
||||
// Start with a message
|
||||
const noDataMessage = document.createElement('div');
|
||||
noDataMessage.classList.add('swaparr-panel');
|
||||
noDataMessage.innerHTML = `
|
||||
<div class="swaparr-config">
|
||||
<h3>Swaparr Logs</h3>
|
||||
<p>Waiting for structured Swaparr data. Showing raw logs below:</p>
|
||||
</div>
|
||||
`;
|
||||
logsContainer.appendChild(noDataMessage);
|
||||
|
||||
// Add raw logs
|
||||
for (const logLine of this.logData.rawLogs) {
|
||||
const logEntry = document.createElement('div');
|
||||
logEntry.className = 'log-entry';
|
||||
logEntry.innerHTML = `<span class="log-message">${logLine}</span>`;
|
||||
|
||||
// Basic level detection
|
||||
if (logLine.includes('ERROR')) logEntry.classList.add('log-error');
|
||||
else if (logLine.includes('WARN') || logLine.includes('WARNING')) logEntry.classList.add('log-warning');
|
||||
else if (logLine.includes('DEBUG')) logEntry.classList.add('log-debug');
|
||||
else logEntry.classList.add('log-info');
|
||||
|
||||
logsContainer.appendChild(logEntry);
|
||||
}
|
||||
|
||||
this.hasRenderedAnyContent = true;
|
||||
},
|
||||
|
||||
// Make sure we display something in the Swaparr tab
|
||||
ensureContentRendered: function() {
|
||||
console.log('[Swaparr Module] Ensuring content is rendered, has content:', this.hasRenderedAnyContent);
|
||||
|
||||
// Reset rendered flag
|
||||
this.hasRenderedAnyContent = false;
|
||||
|
||||
// Check if we're viewing Swaparr tab
|
||||
if (app.currentLogApp !== 'swaparr') return;
|
||||
|
||||
// First try to render structured content
|
||||
this.renderConfigPanel();
|
||||
this.renderTableView();
|
||||
|
||||
// If no structured content, show raw logs
|
||||
if (!this.hasRenderedAnyContent) {
|
||||
this.renderRawLogs();
|
||||
}
|
||||
},
|
||||
|
||||
// Clear the data when switching log views
|
||||
clearData: function() {
|
||||
this.logData.downloads = [];
|
||||
// Keep raw logs for now
|
||||
this.hasRenderedAnyContent = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize the module
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
swaparrModule.init();
|
||||
|
||||
if (app) {
|
||||
app.swaparrModule = swaparrModule;
|
||||
|
||||
// Setup a handler for when log tabs are changed
|
||||
document.querySelectorAll('.log-tab').forEach(tab => {
|
||||
tab.addEventListener('click', (e) => {
|
||||
// If switching to swaparr tab, make sure we render the view
|
||||
if (e.target.getAttribute('data-app') === 'swaparr') {
|
||||
console.log('[Swaparr Module] Swaparr tab clicked via delegation');
|
||||
// Small delay to allow logs to load
|
||||
setTimeout(() => {
|
||||
swaparrModule.ensureContentRendered();
|
||||
}, 200);
|
||||
}
|
||||
// If switching away from swaparr tab, clear the data
|
||||
else if (app.currentLogApp === 'swaparr') {
|
||||
swaparrModule.clearData();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
})(window.huntarrUI); // Pass the global UI object
|
||||
@@ -487,34 +487,105 @@ let huntarrUI = {
|
||||
const logRegex = /^(?:\\[(\\w+)\\]\\s)?([\\d\\-]+\\s[\\d:]+)\\s-\\s([\\w\\.]+)\\s-\\s(\\w+)\\s-\\s(.*)$/;
|
||||
const match = logString.match(logRegex);
|
||||
|
||||
// First determine the app type for this log message
|
||||
let logAppType = 'system'; // Default to system
|
||||
|
||||
if (match && match[1]) {
|
||||
// If we have a match with app tag like [SONARR], use that
|
||||
logAppType = match[1].toLowerCase();
|
||||
} else if (match && match[3]) {
|
||||
// Otherwise try to determine from the logger name (e.g., huntarr.sonarr)
|
||||
const loggerParts = match[3].split('.');
|
||||
if (loggerParts.length > 1) {
|
||||
const possibleApp = loggerParts[1].toLowerCase();
|
||||
if (['sonarr', 'radarr', 'lidarr', 'readarr', 'whisparr', 'swaparr'].includes(possibleApp)) {
|
||||
logAppType = possibleApp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Special case for Swaparr-related system logs (added strikes, etc.)
|
||||
if (logAppType === 'system' &&
|
||||
(logString.includes('Added strike') ||
|
||||
logString.includes('Max strikes reached') ||
|
||||
logString.includes('Would have removed') ||
|
||||
logString.includes('strikes, removing download') ||
|
||||
logString.includes('processing stalled downloads'))) {
|
||||
logAppType = 'swaparr';
|
||||
}
|
||||
|
||||
// Determine if this log should be displayed based on the selected app tab
|
||||
const shouldDisplay =
|
||||
this.currentLogApp === 'all' ||
|
||||
this.currentLogApp === logAppType;
|
||||
|
||||
if (!shouldDisplay) return;
|
||||
|
||||
const logEntry = document.createElement('div');
|
||||
logEntry.className = 'log-entry';
|
||||
|
||||
if (match) {
|
||||
const [, appName, timestamp, loggerName, level, message] = match;
|
||||
// Special handling for Swaparr logs to enable table view
|
||||
if (logAppType === 'swaparr' && this.currentLogApp === 'swaparr') {
|
||||
if (match) {
|
||||
const [, appName, timestamp, loggerName, level, message] = match;
|
||||
|
||||
logEntry.innerHTML = `
|
||||
<span class="log-timestamp" title="${timestamp}">${timestamp.split(' ')[1]}</span>
|
||||
${appName ? `<span class="log-app" title="Source: ${appName}">[${appName}]</span>` : ''}
|
||||
<span class="log-level log-level-${level.toLowerCase()}" title="Level: ${level}">${level}</span>
|
||||
<span class="log-logger" title="Logger: ${loggerName}">(${loggerName.replace('huntarr.', '')})</span>
|
||||
<span class="log-message">${message}</span>
|
||||
`;
|
||||
logEntry.classList.add(`log-${level.toLowerCase()}`);
|
||||
} else {
|
||||
// Fallback for lines that don't match the expected format
|
||||
logEntry.innerHTML = `<span class="log-message">${logString}</span>`;
|
||||
|
||||
// Basic level detection for fallback
|
||||
if (logString.includes('ERROR')) logEntry.classList.add('log-error');
|
||||
else if (logString.includes('WARN') || logString.includes('WARNING')) logEntry.classList.add('log-warning');
|
||||
else if (logString.includes('DEBUG')) logEntry.classList.add('log-debug');
|
||||
else logEntry.classList.add('log-info');
|
||||
}
|
||||
|
||||
// Use backticks for template literal
|
||||
logEntry.innerHTML = `
|
||||
<span class="log-timestamp" title="${timestamp}">${timestamp.split(' ')[1]}</span>
|
||||
${appName ? `<span class="log-app" title="Source: ${appName}">[${appName}]</span>` : ''}
|
||||
<span class="log-level log-level-${level.toLowerCase()}" title="Level: ${level}">${level}</span>
|
||||
<span class="log-logger" title="Logger: ${loggerName}">(${loggerName.replace('huntarr.', '')})</span>
|
||||
<span class="log-message">${message}</span>
|
||||
`; // End template literal with backtick
|
||||
logEntry.classList.add(`log-${level.toLowerCase()}`);
|
||||
|
||||
} else {
|
||||
// Fallback for lines that don't match the expected format
|
||||
logEntry.innerHTML = `<span class="log-message">${logString}</span>`;
|
||||
// Basic level detection for fallback
|
||||
if (logString.includes('ERROR')) logEntry.classList.add('log-error');
|
||||
else if (logString.includes('WARN') || logString.includes('WARNING')) logEntry.classList.add('log-warning'); // Added WARN check
|
||||
else if (logString.includes('DEBUG')) logEntry.classList.add('log-debug');
|
||||
else logEntry.classList.add('log-info');
|
||||
// Add to logs container
|
||||
this.elements.logsContainer.appendChild(logEntry);
|
||||
|
||||
// Dispatch a custom event for swaparr.js to process
|
||||
const swaparrEvent = new CustomEvent('swaparrLogReceived', {
|
||||
detail: {
|
||||
logData: match && match[5] ? match[5] : logString
|
||||
}
|
||||
});
|
||||
document.dispatchEvent(swaparrEvent);
|
||||
}
|
||||
// Standard log handling for other apps or all logs
|
||||
else {
|
||||
if (match) {
|
||||
const [, appName, timestamp, loggerName, level, message] = match;
|
||||
|
||||
logEntry.innerHTML = `
|
||||
<span class="log-timestamp" title="${timestamp}">${timestamp.split(' ')[1]}</span>
|
||||
${appName ? `<span class="log-app" title="Source: ${appName}">[${appName}]</span>` : ''}
|
||||
<span class="log-level log-level-${level.toLowerCase()}" title="Level: ${level}">${level}</span>
|
||||
<span class="log-logger" title="Logger: ${loggerName}">(${loggerName.replace('huntarr.', '')})</span>
|
||||
<span class="log-message">${message}</span>
|
||||
`;
|
||||
logEntry.classList.add(`log-${level.toLowerCase()}`);
|
||||
} else {
|
||||
// Fallback for lines that don't match the expected format
|
||||
logEntry.innerHTML = `<span class="log-message">${logString}</span>`;
|
||||
|
||||
// Basic level detection for fallback
|
||||
if (logString.includes('ERROR')) logEntry.classList.add('log-error');
|
||||
else if (logString.includes('WARN') || logString.includes('WARNING')) logEntry.classList.add('log-warning');
|
||||
else if (logString.includes('DEBUG')) logEntry.classList.add('log-debug');
|
||||
else logEntry.classList.add('log-info');
|
||||
}
|
||||
|
||||
// Add to logs container
|
||||
this.elements.logsContainer.appendChild(logEntry);
|
||||
}
|
||||
|
||||
// Add to logs container
|
||||
this.elements.logsContainer.appendChild(logEntry);
|
||||
|
||||
// Auto-scroll to bottom if enabled
|
||||
if (this.autoScroll) {
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
<script src="/static/js/apps/radarr.js"></script>
|
||||
<script src="/static/js/apps/lidarr.js"></script>
|
||||
<script src="/static/js/apps/readarr.js"></script>
|
||||
<script src="/static/js/apps/swaparr.js"></script>
|
||||
<!-- ...existing code... -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -8,6 +8,7 @@
|
||||
<button class="log-tab" data-app="lidarr">Lidarr</button>
|
||||
<button class="log-tab" data-app="readarr">Readarr</button>
|
||||
<button class="log-tab" data-app="whisparr">Whisparr</button>
|
||||
<button class="log-tab" data-app="swaparr">Swaparr</button>
|
||||
<button class="log-tab" data-app="system">System</button>
|
||||
</div>
|
||||
<div class="log-controls">
|
||||
|
||||
@@ -23,7 +23,8 @@ APP_LOG_FILES = {
|
||||
"radarr": LOG_DIR / "radarr.log", # Updated filename
|
||||
"lidarr": LOG_DIR / "lidarr.log", # Updated filename
|
||||
"readarr": LOG_DIR / "readarr.log", # Updated filename
|
||||
"whisparr": LOG_DIR / "whisparr.log" # Added Whisparr
|
||||
"whisparr": LOG_DIR / "whisparr.log", # Added Whisparr
|
||||
"swaparr": LOG_DIR / "swaparr.log" # Added Swaparr
|
||||
}
|
||||
|
||||
# Global logger instances
|
||||
|
||||
@@ -74,6 +74,7 @@ KNOWN_LOG_FILES = {
|
||||
"lidarr": APP_LOG_FILES.get("lidarr"),
|
||||
"readarr": APP_LOG_FILES.get("readarr"),
|
||||
"whisparr": APP_LOG_FILES.get("whisparr"),
|
||||
"swaparr": APP_LOG_FILES.get("swaparr"), # Added Swaparr to known log files
|
||||
"system": MAIN_LOG_FILE, # Map 'system' to the main huntarr log
|
||||
}
|
||||
# Filter out None values if an app log file doesn't exist
|
||||
|
||||
Reference in New Issue
Block a user