feat: Replace database operations with file-based configuration actions
- Removed database-related code from the UI JavaScript - Added configuration backup functionality to export config as JSON - Added configuration reset functionality to restore defaults - Updated ConfigController with backup and reset endpoints - Enhanced file-based configuration persistence 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
c61f308de7
commit
63b33c5fc0
@ -1,3 +1,8 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@ -33,18 +38,80 @@ namespace TransmissionRssManager.Api.Controllers
|
||||
host = config.Transmission.Host,
|
||||
port = config.Transmission.Port,
|
||||
useHttps = config.Transmission.UseHttps,
|
||||
hasCredentials = !string.IsNullOrEmpty(config.Transmission.Username)
|
||||
hasCredentials = !string.IsNullOrEmpty(config.Transmission.Username),
|
||||
username = config.Transmission.Username
|
||||
},
|
||||
transmissionInstances = config.TransmissionInstances?.Select(i => new
|
||||
{
|
||||
id = i.Key,
|
||||
name = i.Value.Host,
|
||||
host = i.Value.Host,
|
||||
port = i.Value.Port,
|
||||
useHttps = i.Value.UseHttps,
|
||||
hasCredentials = !string.IsNullOrEmpty(i.Value.Username),
|
||||
username = i.Value.Username
|
||||
}),
|
||||
autoDownloadEnabled = config.AutoDownloadEnabled,
|
||||
checkIntervalMinutes = config.CheckIntervalMinutes,
|
||||
downloadDirectory = config.DownloadDirectory,
|
||||
mediaLibraryPath = config.MediaLibraryPath,
|
||||
postProcessing = config.PostProcessing
|
||||
postProcessing = config.PostProcessing,
|
||||
enableDetailedLogging = config.EnableDetailedLogging,
|
||||
userPreferences = config.UserPreferences
|
||||
};
|
||||
|
||||
return Ok(sanitizedConfig);
|
||||
}
|
||||
|
||||
[HttpGet("defaults")]
|
||||
public IActionResult GetDefaultConfig()
|
||||
{
|
||||
// Return default configuration settings
|
||||
var defaultConfig = new
|
||||
{
|
||||
transmission = new
|
||||
{
|
||||
host = "localhost",
|
||||
port = 9091,
|
||||
username = "",
|
||||
useHttps = false
|
||||
},
|
||||
autoDownloadEnabled = true,
|
||||
checkIntervalMinutes = 30,
|
||||
downloadDirectory = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Downloads"),
|
||||
mediaLibraryPath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Media"),
|
||||
postProcessing = new
|
||||
{
|
||||
enabled = false,
|
||||
extractArchives = true,
|
||||
organizeMedia = true,
|
||||
minimumSeedRatio = 1,
|
||||
mediaExtensions = new[] { ".mp4", ".mkv", ".avi" },
|
||||
autoOrganizeByMediaType = true,
|
||||
renameFiles = false,
|
||||
compressCompletedFiles = false,
|
||||
deleteCompletedAfterDays = 0
|
||||
},
|
||||
enableDetailedLogging = false,
|
||||
userPreferences = new
|
||||
{
|
||||
enableDarkMode = false,
|
||||
autoRefreshUIEnabled = true,
|
||||
autoRefreshIntervalSeconds = 30,
|
||||
notificationsEnabled = true,
|
||||
notificationEvents = new[] { "torrent-added", "torrent-completed", "torrent-error" },
|
||||
defaultView = "dashboard",
|
||||
confirmBeforeDelete = true,
|
||||
maxItemsPerPage = 25,
|
||||
dateTimeFormat = "yyyy-MM-dd HH:mm:ss",
|
||||
showCompletedTorrents = true,
|
||||
keepHistoryDays = 30
|
||||
}
|
||||
};
|
||||
|
||||
return Ok(defaultConfig);
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> UpdateConfig([FromBody] AppConfig config)
|
||||
{
|
||||
@ -59,5 +126,93 @@ namespace TransmissionRssManager.Api.Controllers
|
||||
await _configService.SaveConfigurationAsync(config);
|
||||
return Ok(new { success = true });
|
||||
}
|
||||
|
||||
[HttpPost("backup")]
|
||||
public IActionResult BackupConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get the current config
|
||||
var config = _configService.GetConfiguration();
|
||||
|
||||
// Serialize to JSON with indentation
|
||||
var options = new JsonSerializerOptions { WriteIndented = true };
|
||||
var json = JsonSerializer.Serialize(config, options);
|
||||
|
||||
// Create a memory stream from the JSON
|
||||
var stream = new MemoryStream(Encoding.UTF8.GetBytes(json));
|
||||
|
||||
// Set the content disposition and type
|
||||
var fileName = $"transmission-rss-config-backup-{DateTime.Now:yyyy-MM-dd}.json";
|
||||
return File(stream, "application/json", fileName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error creating configuration backup");
|
||||
return StatusCode(500, "Error creating configuration backup");
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("reset")]
|
||||
public async Task<IActionResult> ResetConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Create a default config
|
||||
var defaultConfig = new AppConfig
|
||||
{
|
||||
Transmission = new TransmissionConfig
|
||||
{
|
||||
Host = "localhost",
|
||||
Port = 9091,
|
||||
Username = "",
|
||||
Password = "",
|
||||
UseHttps = false
|
||||
},
|
||||
AutoDownloadEnabled = true,
|
||||
CheckIntervalMinutes = 30,
|
||||
DownloadDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Downloads"),
|
||||
MediaLibraryPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Media"),
|
||||
PostProcessing = new PostProcessingConfig
|
||||
{
|
||||
Enabled = false,
|
||||
ExtractArchives = true,
|
||||
OrganizeMedia = true,
|
||||
MinimumSeedRatio = 1.0f,
|
||||
MediaExtensions = new[] { ".mp4", ".mkv", ".avi", ".mov", ".wmv", ".m4v", ".mpg", ".mpeg", ".flv", ".webm" },
|
||||
AutoOrganizeByMediaType = true,
|
||||
RenameFiles = false,
|
||||
CompressCompletedFiles = false,
|
||||
DeleteCompletedAfterDays = 0
|
||||
},
|
||||
UserPreferences = new UserPreferencesConfig
|
||||
{
|
||||
EnableDarkMode = true,
|
||||
AutoRefreshUIEnabled = true,
|
||||
AutoRefreshIntervalSeconds = 30,
|
||||
NotificationsEnabled = true,
|
||||
NotificationEvents = new[] { "torrent-added", "torrent-completed", "torrent-error" },
|
||||
DefaultView = "dashboard",
|
||||
ConfirmBeforeDelete = true,
|
||||
MaxItemsPerPage = 25,
|
||||
DateTimeFormat = "yyyy-MM-dd HH:mm:ss",
|
||||
ShowCompletedTorrents = true,
|
||||
KeepHistoryDays = 30
|
||||
},
|
||||
Feeds = new List<RssFeed>(),
|
||||
EnableDetailedLogging = false
|
||||
};
|
||||
|
||||
// Save the default config
|
||||
await _configService.SaveConfigurationAsync(defaultConfig);
|
||||
|
||||
return Ok(new { success = true });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error resetting configuration");
|
||||
return StatusCode(500, "Error resetting configuration");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -8,6 +8,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// Load initial dashboard data
|
||||
loadDashboardData();
|
||||
|
||||
// Set up dark mode based on user preference
|
||||
initDarkMode();
|
||||
|
||||
// Set up auto refresh if enabled
|
||||
initAutoRefresh();
|
||||
|
||||
// Initialize Bootstrap tooltips
|
||||
const tooltips = document.querySelectorAll('[data-bs-toggle="tooltip"]');
|
||||
tooltips.forEach(tooltip => new bootstrap.Tooltip(tooltip));
|
||||
@ -86,15 +92,65 @@ function initEventListeners() {
|
||||
document.getElementById('btn-refresh-torrents').addEventListener('click', loadTorrents);
|
||||
document.getElementById('save-torrent-btn').addEventListener('click', saveTorrent);
|
||||
|
||||
// Logs page
|
||||
document.getElementById('btn-refresh-logs').addEventListener('click', refreshLogs);
|
||||
document.getElementById('btn-clear-logs').addEventListener('click', clearLogs);
|
||||
document.getElementById('btn-apply-log-filters').addEventListener('click', applyLogFilters);
|
||||
document.getElementById('btn-reset-log-filters').addEventListener('click', resetLogFilters);
|
||||
document.getElementById('btn-export-logs').addEventListener('click', exportLogs);
|
||||
|
||||
// Settings page
|
||||
document.getElementById('settings-form').addEventListener('submit', saveSettings);
|
||||
document.getElementById('dark-mode-toggle').addEventListener('click', toggleDarkMode);
|
||||
document.getElementById('btn-reset-settings').addEventListener('click', resetSettings);
|
||||
|
||||
// Configuration operations
|
||||
document.getElementById('btn-backup-config').addEventListener('click', backupConfig);
|
||||
document.getElementById('btn-reset-config').addEventListener('click', resetConfig);
|
||||
|
||||
// Additional Transmission Instances
|
||||
document.getElementById('add-transmission-instance').addEventListener('click', addTransmissionInstance);
|
||||
}
|
||||
|
||||
// Dashboard
|
||||
function loadDashboardData() {
|
||||
loadSystemStatus();
|
||||
loadRecentMatches();
|
||||
// Fetch dashboard statistics
|
||||
fetch('/api/dashboard/stats')
|
||||
.then(response => response.json())
|
||||
.then(stats => {
|
||||
document.getElementById('active-downloads').textContent = stats.activeDownloads;
|
||||
document.getElementById('seeding-torrents').textContent = stats.seedingTorrents;
|
||||
document.getElementById('active-feeds').textContent = stats.activeFeeds;
|
||||
document.getElementById('completed-today').textContent = stats.completedToday;
|
||||
|
||||
document.getElementById('added-today').textContent = stats.addedToday;
|
||||
document.getElementById('feeds-count').textContent = stats.feedsCount;
|
||||
document.getElementById('matched-count').textContent = stats.matchedCount;
|
||||
|
||||
// Format download/upload speeds
|
||||
const downloadSpeed = formatBytes(stats.downloadSpeed) + '/s';
|
||||
const uploadSpeed = formatBytes(stats.uploadSpeed) + '/s';
|
||||
document.getElementById('download-speed').textContent = downloadSpeed;
|
||||
document.getElementById('upload-speed').textContent = uploadSpeed;
|
||||
document.getElementById('current-speed').textContent = `↓${downloadSpeed} ↑${uploadSpeed}`;
|
||||
|
||||
// Set progress bars (max 100%)
|
||||
const maxSpeed = Math.max(stats.downloadSpeed, stats.uploadSpeed, 1);
|
||||
const dlPercent = Math.min(Math.round((stats.downloadSpeed / maxSpeed) * 100), 100);
|
||||
const ulPercent = Math.min(Math.round((stats.uploadSpeed / maxSpeed) * 100), 100);
|
||||
document.getElementById('download-speed-bar').style.width = `${dlPercent}%`;
|
||||
document.getElementById('upload-speed-bar').style.width = `${ulPercent}%`;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading dashboard stats:', error);
|
||||
});
|
||||
|
||||
// Load chart data
|
||||
loadDownloadHistoryChart();
|
||||
|
||||
// Load other dashboard components
|
||||
loadActiveTorrents();
|
||||
loadRecentMatches();
|
||||
}
|
||||
|
||||
function loadSystemStatus() {
|
||||
@ -914,3 +970,542 @@ function formatDate(date) {
|
||||
function padZero(num) {
|
||||
return num.toString().padStart(2, '0');
|
||||
}
|
||||
|
||||
// Dark Mode Functions
|
||||
function initDarkMode() {
|
||||
// Check local storage preference or system preference
|
||||
const darkModePreference = localStorage.getItem('darkMode');
|
||||
|
||||
if (darkModePreference === 'true' ||
|
||||
(darkModePreference === null && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
enableDarkMode();
|
||||
} else {
|
||||
disableDarkMode();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleDarkMode() {
|
||||
if (document.body.classList.contains('dark-mode')) {
|
||||
disableDarkMode();
|
||||
} else {
|
||||
enableDarkMode();
|
||||
}
|
||||
}
|
||||
|
||||
function enableDarkMode() {
|
||||
document.body.classList.add('dark-mode');
|
||||
document.getElementById('dark-mode-toggle').innerHTML = '<i class="bi bi-sun-fill"></i>';
|
||||
localStorage.setItem('darkMode', 'true');
|
||||
|
||||
// Also update user preferences if on settings page
|
||||
const darkModeCheckbox = document.getElementById('enable-dark-mode');
|
||||
if (darkModeCheckbox) {
|
||||
darkModeCheckbox.checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
function disableDarkMode() {
|
||||
document.body.classList.remove('dark-mode');
|
||||
document.getElementById('dark-mode-toggle').innerHTML = '<i class="bi bi-moon-fill"></i>';
|
||||
localStorage.setItem('darkMode', 'false');
|
||||
|
||||
// Also update user preferences if on settings page
|
||||
const darkModeCheckbox = document.getElementById('enable-dark-mode');
|
||||
if (darkModeCheckbox) {
|
||||
darkModeCheckbox.checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-refresh
|
||||
function initAutoRefresh() {
|
||||
// Get auto-refresh settings from local storage or use defaults
|
||||
const autoRefresh = localStorage.getItem('autoRefresh') !== 'false';
|
||||
const refreshInterval = parseInt(localStorage.getItem('refreshInterval')) || 30;
|
||||
|
||||
if (autoRefresh) {
|
||||
startAutoRefresh(refreshInterval);
|
||||
}
|
||||
}
|
||||
|
||||
function startAutoRefresh(intervalSeconds) {
|
||||
// Clear any existing interval
|
||||
if (window.refreshTimer) {
|
||||
clearInterval(window.refreshTimer);
|
||||
}
|
||||
|
||||
// Set new interval
|
||||
window.refreshTimer = setInterval(() => {
|
||||
const currentPage = window.location.hash.substring(1) || 'dashboard';
|
||||
loadPageData(currentPage);
|
||||
}, intervalSeconds * 1000);
|
||||
|
||||
localStorage.setItem('autoRefresh', 'true');
|
||||
localStorage.setItem('refreshInterval', intervalSeconds.toString());
|
||||
}
|
||||
|
||||
function stopAutoRefresh() {
|
||||
if (window.refreshTimer) {
|
||||
clearInterval(window.refreshTimer);
|
||||
window.refreshTimer = null;
|
||||
}
|
||||
|
||||
localStorage.setItem('autoRefresh', 'false');
|
||||
}
|
||||
|
||||
// Chart functions
|
||||
function loadDownloadHistoryChart() {
|
||||
fetch('/api/dashboard/history')
|
||||
.then(response => response.json())
|
||||
.then(history => {
|
||||
const ctx = document.getElementById('download-history-chart').getContext('2d');
|
||||
|
||||
// Extract dates and count values
|
||||
const labels = history.map(point => {
|
||||
const date = new Date(point.date);
|
||||
return `${date.getMonth() + 1}/${date.getDate()}`;
|
||||
});
|
||||
|
||||
const countData = history.map(point => point.count);
|
||||
const sizeData = history.map(point => point.totalSize / (1024 * 1024 * 1024)); // Convert to GB
|
||||
|
||||
// Create or update chart
|
||||
if (window.downloadHistoryChart) {
|
||||
window.downloadHistoryChart.data.labels = labels;
|
||||
window.downloadHistoryChart.data.datasets[0].data = countData;
|
||||
window.downloadHistoryChart.data.datasets[1].data = sizeData;
|
||||
window.downloadHistoryChart.update();
|
||||
} else {
|
||||
window.downloadHistoryChart = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [
|
||||
{
|
||||
label: 'Number of Downloads',
|
||||
data: countData,
|
||||
backgroundColor: 'rgba(13, 110, 253, 0.5)',
|
||||
borderColor: 'rgba(13, 110, 253, 1)',
|
||||
borderWidth: 1,
|
||||
yAxisID: 'y'
|
||||
},
|
||||
{
|
||||
label: 'Total Size (GB)',
|
||||
data: sizeData,
|
||||
type: 'line',
|
||||
borderColor: 'rgba(25, 135, 84, 1)',
|
||||
backgroundColor: 'rgba(25, 135, 84, 0.1)',
|
||||
borderWidth: 2,
|
||||
fill: true,
|
||||
tension: 0.4,
|
||||
yAxisID: 'y1'
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top',
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
let label = context.dataset.label || '';
|
||||
if (label) {
|
||||
label += ': ';
|
||||
}
|
||||
if (context.datasetIndex === 0) {
|
||||
label += context.parsed.y;
|
||||
} else {
|
||||
label += context.parsed.y.toFixed(2) + ' GB';
|
||||
}
|
||||
return label;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
type: 'linear',
|
||||
display: true,
|
||||
position: 'left',
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Number of Downloads'
|
||||
},
|
||||
beginAtZero: true
|
||||
},
|
||||
y1: {
|
||||
type: 'linear',
|
||||
display: true,
|
||||
position: 'right',
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Total Size (GB)'
|
||||
},
|
||||
beginAtZero: true,
|
||||
grid: {
|
||||
drawOnChartArea: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading download history chart:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// Logs Management
|
||||
function refreshLogs() {
|
||||
const logFilters = getLogFilters();
|
||||
loadLogs(logFilters);
|
||||
}
|
||||
|
||||
function getLogFilters() {
|
||||
return {
|
||||
level: document.getElementById('log-level').value,
|
||||
search: document.getElementById('log-search').value,
|
||||
dateRange: document.getElementById('log-date-range').value,
|
||||
skip: 0,
|
||||
take: parseInt(document.getElementById('items-per-page')?.value || 25)
|
||||
};
|
||||
}
|
||||
|
||||
function loadLogs(filters) {
|
||||
const tbody = document.getElementById('logs-table-body');
|
||||
tbody.innerHTML = '<tr><td colspan="4" class="text-center py-4">Loading logs...</td></tr>';
|
||||
|
||||
// Build query string
|
||||
const query = new URLSearchParams();
|
||||
if (filters.level && filters.level !== 'All') {
|
||||
query.append('Level', filters.level);
|
||||
}
|
||||
if (filters.search) {
|
||||
query.append('Search', filters.search);
|
||||
}
|
||||
|
||||
// Handle date range
|
||||
const now = new Date();
|
||||
let startDate = null;
|
||||
|
||||
switch (filters.dateRange) {
|
||||
case 'today':
|
||||
startDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0);
|
||||
break;
|
||||
case 'yesterday':
|
||||
startDate = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1, 0, 0, 0);
|
||||
break;
|
||||
case 'week':
|
||||
startDate = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 7, 0, 0, 0);
|
||||
break;
|
||||
case 'month':
|
||||
startDate = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 30, 0, 0, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
if (startDate) {
|
||||
query.append('StartDate', startDate.toISOString());
|
||||
}
|
||||
|
||||
query.append('Skip', filters.skip.toString());
|
||||
query.append('Take', filters.take.toString());
|
||||
|
||||
fetch(`/api/logs?${query.toString()}`)
|
||||
.then(response => response.json())
|
||||
.then(logs => {
|
||||
if (logs.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="4" class="text-center py-4">No logs found</td></tr>';
|
||||
document.getElementById('log-count').textContent = '0 entries';
|
||||
document.getElementById('logs-pagination-info').textContent = 'Showing 0 of 0 entries';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
logs.forEach(log => {
|
||||
const timestamp = new Date(log.timestamp);
|
||||
const levelClass = getLevelClass(log.level);
|
||||
|
||||
html += `<tr>
|
||||
<td class="text-nowrap">${formatDate(timestamp)}</td>
|
||||
<td><span class="badge ${levelClass}">${log.level}</span></td>
|
||||
<td>${log.message}</td>
|
||||
<td>${log.context || ''}</td>
|
||||
</tr>`;
|
||||
});
|
||||
|
||||
tbody.innerHTML = html;
|
||||
document.getElementById('log-count').textContent = `${logs.length} entries`;
|
||||
document.getElementById('logs-pagination-info').textContent = `Showing ${logs.length} entries`;
|
||||
|
||||
// Update pagination (simplified for now)
|
||||
updateLogPagination(filters, logs.length);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading logs:', error);
|
||||
tbody.innerHTML = '<tr><td colspan="4" class="text-center py-4">Error loading logs</td></tr>';
|
||||
});
|
||||
}
|
||||
|
||||
function updateLogPagination(filters, count) {
|
||||
const pagination = document.getElementById('logs-pagination');
|
||||
|
||||
// Simplified pagination - just first page for now
|
||||
pagination.innerHTML = `
|
||||
<li class="page-item active">
|
||||
<a class="page-link" href="#">1</a>
|
||||
</li>
|
||||
`;
|
||||
}
|
||||
|
||||
function getLevelClass(level) {
|
||||
switch (level.toLowerCase()) {
|
||||
case 'debug':
|
||||
return 'bg-secondary';
|
||||
case 'information':
|
||||
return 'bg-info';
|
||||
case 'warning':
|
||||
return 'bg-warning';
|
||||
case 'error':
|
||||
return 'bg-danger';
|
||||
case 'critical':
|
||||
return 'bg-dark';
|
||||
default:
|
||||
return 'bg-secondary';
|
||||
}
|
||||
}
|
||||
|
||||
function applyLogFilters() {
|
||||
const filters = getLogFilters();
|
||||
loadLogs(filters);
|
||||
}
|
||||
|
||||
function resetLogFilters() {
|
||||
document.getElementById('log-level').value = 'All';
|
||||
document.getElementById('log-search').value = '';
|
||||
document.getElementById('log-date-range').value = 'week';
|
||||
loadLogs(getLogFilters());
|
||||
}
|
||||
|
||||
function clearLogs() {
|
||||
if (!confirm('Are you sure you want to clear all logs? This action cannot be undone.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('/api/logs/clear', {
|
||||
method: 'POST'
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to clear logs');
|
||||
}
|
||||
|
||||
// Refresh logs
|
||||
loadLogs(getLogFilters());
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error clearing logs:', error);
|
||||
alert('Error clearing logs');
|
||||
});
|
||||
}
|
||||
|
||||
function exportLogs() {
|
||||
const filters = getLogFilters();
|
||||
const query = new URLSearchParams();
|
||||
|
||||
if (filters.level && filters.level !== 'All') {
|
||||
query.append('Level', filters.level);
|
||||
}
|
||||
if (filters.search) {
|
||||
query.append('Search', filters.search);
|
||||
}
|
||||
|
||||
// Create download link
|
||||
const link = document.createElement('a');
|
||||
link.href = `/api/logs/export?${query.toString()}`;
|
||||
link.download = `transmission-rss-logs-${new Date().toISOString().slice(0, 10)}.csv`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
|
||||
// Helper function to format file sizes
|
||||
function formatBytes(bytes, decimals = 2) {
|
||||
if (bytes === 0) return '0 B';
|
||||
|
||||
const k = 1024;
|
||||
const dm = decimals < 0 ? 0 : decimals;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
// Configuration Operations
|
||||
function backupConfig() {
|
||||
if (!confirm('This will create a backup of your configuration files. Do you want to continue?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('/api/config/backup', {
|
||||
method: 'POST'
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to backup configuration');
|
||||
}
|
||||
return response.blob();
|
||||
})
|
||||
.then(blob => {
|
||||
// Create download link for the backup file
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = url;
|
||||
a.download = `transmission-rss-config-backup-${new Date().toISOString().slice(0, 10)}.json`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
alert('Configuration backup created successfully.');
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error backing up configuration:', error);
|
||||
alert('Error creating configuration backup.');
|
||||
});
|
||||
}
|
||||
|
||||
function resetConfig() {
|
||||
if (!confirm('WARNING: This will reset your configuration to default settings. All your feeds, rules, and user preferences will be lost. This cannot be undone. Are you absolutely sure?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm('FINAL WARNING: All feeds, rules, and settings will be permanently deleted. Type "RESET" to confirm.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmation = prompt('Type "RESET" to confirm configuration reset:');
|
||||
if (confirmation !== 'RESET') {
|
||||
alert('Configuration reset cancelled.');
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('/api/config/reset', {
|
||||
method: 'POST'
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to reset configuration');
|
||||
}
|
||||
|
||||
alert('Configuration has been reset to defaults. The application will now reload.');
|
||||
window.location.reload();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error resetting configuration:', error);
|
||||
alert('Error resetting configuration.');
|
||||
});
|
||||
}
|
||||
|
||||
// Transmission Instance Management
|
||||
function addTransmissionInstance() {
|
||||
const instancesList = document.getElementById('transmission-instances-list');
|
||||
const instanceCount = document.querySelectorAll('.transmission-instance').length;
|
||||
const newInstanceIndex = instanceCount + 1;
|
||||
|
||||
const instanceHtml = `
|
||||
<div class="transmission-instance card mb-3" id="transmission-instance-${newInstanceIndex}">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between mb-3">
|
||||
<h5 class="card-title">Instance #${newInstanceIndex}</h5>
|
||||
<button type="button" class="btn btn-sm btn-danger" onclick="removeTransmissionInstance(${newInstanceIndex})">
|
||||
<i class="bi bi-trash me-1"></i>Remove
|
||||
</button>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Name</label>
|
||||
<input type="text" class="form-control" name="transmissionInstances[${newInstanceIndex}].name" placeholder="Secondary Server">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Host</label>
|
||||
<input type="text" class="form-control" name="transmissionInstances[${newInstanceIndex}].host">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Port</label>
|
||||
<input type="number" class="form-control" name="transmissionInstances[${newInstanceIndex}].port" value="9091">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Username</label>
|
||||
<input type="text" class="form-control" name="transmissionInstances[${newInstanceIndex}].username">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Password</label>
|
||||
<input type="password" class="form-control" name="transmissionInstances[${newInstanceIndex}].password">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-check form-switch mt-4">
|
||||
<input class="form-check-input" type="checkbox" name="transmissionInstances[${newInstanceIndex}].useHttps">
|
||||
<label class="form-check-label">Use HTTPS</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
// If the "no instances" message is showing, remove it
|
||||
if (instancesList.querySelector('.text-center.text-muted')) {
|
||||
instancesList.innerHTML = '';
|
||||
}
|
||||
|
||||
// Add the new instance
|
||||
instancesList.insertAdjacentHTML('beforeend', instanceHtml);
|
||||
}
|
||||
|
||||
function removeTransmissionInstance(index) {
|
||||
const instance = document.getElementById(`transmission-instance-${index}`);
|
||||
if (instance) {
|
||||
instance.remove();
|
||||
|
||||
// If there are no instances left, show the "no instances" message
|
||||
const instancesList = document.getElementById('transmission-instances-list');
|
||||
if (instancesList.children.length === 0) {
|
||||
instancesList.innerHTML = '<div class="text-center text-muted py-3">No additional instances configured</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resetSettings() {
|
||||
if (!confirm('This will reset all settings to their default values. Are you sure?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load default settings
|
||||
fetch('/api/config/defaults')
|
||||
.then(response => response.json())
|
||||
.then(defaults => {
|
||||
// Apply defaults to form
|
||||
loadSettingsIntoForm(defaults);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading default settings:', error);
|
||||
alert('Error loading default settings');
|
||||
});
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user