This commit is contained in:
@@ -49,32 +49,10 @@ body {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Header Logo and Text Layout */
|
||||
.header-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.header-logo .logo {
|
||||
height: 60px;
|
||||
width: auto;
|
||||
/* Clean display for new logo */
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.header-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.header-content h1 {
|
||||
font-size: 2rem;
|
||||
font-weight: 300;
|
||||
margin-bottom: 0.25rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.header-content h1 i {
|
||||
@@ -84,28 +62,6 @@ body {
|
||||
.header-content p {
|
||||
opacity: 0.9;
|
||||
font-size: 1rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Responsive logo layout */
|
||||
@media (max-width: 768px) {
|
||||
.header-logo {
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.header-text {
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header-logo .logo {
|
||||
height: 45px;
|
||||
}
|
||||
|
||||
.header-content h1 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Authentication Status */
|
||||
@@ -888,454 +844,59 @@ body {
|
||||
.justify-content-between { justify-content: space-between; }
|
||||
.align-items-center { align-items: center; }
|
||||
|
||||
/* Database Admin Tools Styles */
|
||||
.query-results {
|
||||
/* Manual Scan Styles */
|
||||
.scan-status {
|
||||
margin-top: 1rem;
|
||||
padding: 1rem;
|
||||
background: #f8f9fa;
|
||||
background-color: var(--light-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
|
||||
.query-info {
|
||||
padding: 1rem;
|
||||
border-radius: 0.375rem;
|
||||
.scan-progress {
|
||||
margin-bottom: 1rem;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.query-info.success {
|
||||
background-color: #d4edda;
|
||||
border: 1px solid #c3e6cb;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.query-info.error {
|
||||
background-color: #f8d7da;
|
||||
border: 1px solid #f5c6cb;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.results-table {
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.875rem;
|
||||
background: white;
|
||||
height: 1.5rem;
|
||||
background-color: #e9ecef;
|
||||
border-radius: 0.375rem;
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.results-table th {
|
||||
background-color: var(--dark-color);
|
||||
color: white;
|
||||
padding: 0.75rem;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.results-table td {
|
||||
padding: 0.75rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.results-table tr:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.results-table td em {
|
||||
color: var(--text-muted);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.query-results .table-container,
|
||||
.admin-results .table-container {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
|
||||
/* NFO Repair Table Styling */
|
||||
.nfo-repair-table-container {
|
||||
background: white;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: var(--shadow);
|
||||
overflow-x: auto;
|
||||
overflow-y: auto;
|
||||
max-height: 600px;
|
||||
margin-bottom: 1rem;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.nfo-repair-table {
|
||||
width: 100%;
|
||||
min-width: 800px;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.875rem;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.nfo-repair-table th {
|
||||
background-color: var(--dark-color);
|
||||
color: white;
|
||||
padding: 0.75rem 0.5rem;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.nfo-repair-table td {
|
||||
padding: 0.75rem 0.5rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.nfo-repair-table tr:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.media-type-badge {
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.media-type-badge.episode {
|
||||
background-color: #e3f2fd;
|
||||
color: #1976d2;
|
||||
}
|
||||
|
||||
.media-type-badge.movie {
|
||||
background-color: #f3e5f5;
|
||||
color: #7b1fa2;
|
||||
}
|
||||
|
||||
.status-ready {
|
||||
color: #2e7d32;
|
||||
font-weight: 600;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.status-missing {
|
||||
color: #d32f2f;
|
||||
font-weight: 600;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.nfo-fix-actions {
|
||||
margin-top: 1rem;
|
||||
padding: 1rem;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 0.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nfo-fix-actions .btn {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.admin-tools {
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--primary-color), var(--success-color));
|
||||
transition: width 0.3s ease;
|
||||
width: 0%;
|
||||
}
|
||||
|
||||
.scan-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.admin-tools .btn {
|
||||
text-align: left;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.admin-results {
|
||||
margin-top: 1rem;
|
||||
padding: 1rem;
|
||||
background: #f8f9fa;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
|
||||
.scan-summary,
|
||||
.fix-summary {
|
||||
padding: 1rem;
|
||||
border-radius: 0.375rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.scan-summary {
|
||||
background-color: #e7f3ff;
|
||||
border: 1px solid #b3d9ff;
|
||||
}
|
||||
|
||||
.fix-summary.success {
|
||||
background-color: #d4edda;
|
||||
border: 1px solid #c3e6cb;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.fix-errors {
|
||||
background-color: #fff3cd;
|
||||
border: 1px solid #ffeaa7;
|
||||
padding: 1rem;
|
||||
border-radius: 0.375rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.fix-errors ul {
|
||||
margin: 0.5rem 0 0 1rem;
|
||||
}
|
||||
|
||||
.missing-episodes {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
.scan-info span:first-child {
|
||||
color: var(--text-muted);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.loading::before {
|
||||
content: "⏳ ";
|
||||
}
|
||||
|
||||
.success {
|
||||
background-color: #d4edda;
|
||||
border: 1px solid #c3e6cb;
|
||||
color: #155724;
|
||||
padding: 1rem;
|
||||
border-radius: 0.375rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.error {
|
||||
background-color: #f8d7da;
|
||||
border: 1px solid #f5c6cb;
|
||||
color: #721c24;
|
||||
padding: 1rem;
|
||||
border-radius: 0.375rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
/* Make text areas more distinctive */
|
||||
textarea {
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 0.875rem;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
/* Button variations for admin tools */
|
||||
.btn.btn-info {
|
||||
background-color: #17a2b8;
|
||||
border-color: #17a2b8;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn.btn-info:hover {
|
||||
background-color: #138496;
|
||||
border-color: #117a8b;
|
||||
}
|
||||
|
||||
/* Missing IMDb Items Styles */
|
||||
.missing-imdb-table-container {
|
||||
overflow-x: auto;
|
||||
max-height: 600px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.missing-imdb-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
min-width: 800px;
|
||||
}
|
||||
|
||||
.missing-imdb-table thead {
|
||||
background-color: var(--bg-secondary);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.missing-imdb-table th,
|
||||
.missing-imdb-table td {
|
||||
padding: 0.75rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.missing-imdb-table th {
|
||||
.scan-info span:last-child {
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.missing-imdb-table td {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.missing-imdb-table .file-path {
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 0.85rem;
|
||||
max-width: 300px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.media-type-badge {
|
||||
display: inline-block;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.media-type-badge.tv-series {
|
||||
background-color: #e3f2fd;
|
||||
color: #1976d2;
|
||||
}
|
||||
|
||||
.media-type-badge.movie {
|
||||
background-color: #fff3e0;
|
||||
color: #f57c00;
|
||||
}
|
||||
|
||||
.missing-imdb-actions {
|
||||
background-color: var(--bg-secondary);
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.missing-imdb-actions ul {
|
||||
margin: 0.5rem 0 0 1rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.missing-imdb-actions li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.missing-imdb-actions code {
|
||||
background-color: var(--bg-primary);
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 0.85rem;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.btn-small {
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
/* NFO Statistics Styling */
|
||||
.nfo-stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.stat-box {
|
||||
background-color: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
color: var(--primary-color);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
.form-group small {
|
||||
display: block;
|
||||
margin-top: 0.25rem;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.recommendation-box {
|
||||
background-color: #fff3cd;
|
||||
border: 1px solid #ffeaa7;
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.recommendation-box ul {
|
||||
margin: 0.5rem 0 0 1rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.recommendation-box li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.success-box {
|
||||
background-color: #d4edda;
|
||||
border: 1px solid #c3e6cb;
|
||||
color: #155724;
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Responsive adjustments for admin tools */
|
||||
@media (max-width: 768px) {
|
||||
.table-container {
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.results-table {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.results-table th,
|
||||
.results-table td {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.admin-tools {
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.missing-imdb-table {
|
||||
min-width: 600px;
|
||||
}
|
||||
|
||||
.missing-imdb-table th,
|
||||
.missing-imdb-table td {
|
||||
padding: 0.5rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.missing-imdb-table .file-path {
|
||||
max-width: 200px;
|
||||
}
|
||||
.btn-sm {
|
||||
padding: 0.375rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
+59
-146
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>NFOGuard - Database Management</title>
|
||||
<link rel="stylesheet" href="/static/css/styles.css?v=2.8.2-20241026-nfo-table-redesign">
|
||||
<link rel="stylesheet" href="/static/css/styles.css?v=manual-scan-ui">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
@@ -12,12 +12,8 @@
|
||||
<!-- Header -->
|
||||
<header class="app-header">
|
||||
<div class="header-content">
|
||||
<div class="header-logo">
|
||||
<div class="header-text">
|
||||
<h1>NFOGuard</h1>
|
||||
<p>Database Management & Reporting</p>
|
||||
</div>
|
||||
</div>
|
||||
<h1><i class="fas fa-shield-alt"></i> NFOGuard</h1>
|
||||
<p>Database Management & Reporting</p>
|
||||
</div>
|
||||
<div class="auth-status" id="auth-status" style="display: none;">
|
||||
<span class="auth-user">
|
||||
@@ -50,24 +46,6 @@
|
||||
<main class="main-content">
|
||||
<!-- Dashboard Tab -->
|
||||
<div class="tab-content active" id="dashboard">
|
||||
<!-- Scan Status Banner -->
|
||||
<div class="scan-status-banner" id="dashboard-scan-status" style="display: none;">
|
||||
<div class="scan-status-content">
|
||||
<div class="scan-status-icon">
|
||||
<i class="fas fa-sync fa-spin"></i>
|
||||
</div>
|
||||
<div class="scan-status-info">
|
||||
<h4>Scan in Progress</h4>
|
||||
<p id="dashboard-scan-text">Processing media files...</p>
|
||||
<div class="scan-progress-mini">
|
||||
<div class="progress-bar-mini">
|
||||
<div class="progress-fill-mini" id="dashboard-scan-progress"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon movies">
|
||||
@@ -315,31 +293,50 @@
|
||||
<h2><i class="fas fa-tools"></i> Database Tools</h2>
|
||||
</div>
|
||||
|
||||
<!-- Scan Status Display -->
|
||||
<div class="scan-status-card" id="scan-status" style="display: none;">
|
||||
<div class="scan-status-header">
|
||||
<h3><i class="fas fa-sync fa-spin"></i> Scan in Progress</h3>
|
||||
<div class="scan-progress">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" id="scan-progress"></div>
|
||||
</div>
|
||||
<span class="progress-text" id="scan-progress-text">Initializing...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tools-grid">
|
||||
<!-- Manual Scan Tools -->
|
||||
<div class="tool-card">
|
||||
<h3><i class="fas fa-exchange-alt"></i> Bulk Source Update</h3>
|
||||
<p>Change source for multiple items at once</p>
|
||||
<form id="bulk-update-form">
|
||||
<div class="form-group">
|
||||
<label>Media Type:</label>
|
||||
<select id="bulk-media-type" required>
|
||||
<option value="">Select type...</option>
|
||||
<option value="movies">Movies</option>
|
||||
<option value="episodes">Episodes</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>From Source:</label>
|
||||
<input type="text" id="bulk-old-source" placeholder="e.g., no_valid_date_source" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>To Source:</label>
|
||||
<select id="bulk-new-source" required>
|
||||
<option value="">Select new source...</option>
|
||||
<option value="airdate">Air Date</option>
|
||||
<option value="digital_release">Digital Release</option>
|
||||
<option value="manual">Manual</option>
|
||||
<option value="radarr:db.history.import">Radarr Import</option>
|
||||
<option value="sonarr:history.import">Sonarr Import</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-warning">
|
||||
<i class="fas fa-exchange-alt"></i> Update Sources
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="tool-card">
|
||||
<h3><i class="fas fa-search"></i> Manual Scan</h3>
|
||||
<p>Run manual scans on your media library</p>
|
||||
<p>Scan specific folders or perform full library scans</p>
|
||||
<form id="manual-scan-form">
|
||||
<div class="form-group">
|
||||
<label>Scan Type:</label>
|
||||
<select id="scan-type" required>
|
||||
<option value="both">Both (Movies & TV)</option>
|
||||
<option value="both">TV Shows & Movies</option>
|
||||
<option value="tv">TV Shows Only</option>
|
||||
<option value="movies">Movies Only</option>
|
||||
<option value="tv">TV Series Only</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@@ -347,39 +344,34 @@
|
||||
<select id="scan-mode" required>
|
||||
<option value="smart">Smart (Recommended)</option>
|
||||
<option value="full">Full Scan</option>
|
||||
<option value="incomplete">Incomplete Items Only</option>
|
||||
<option value="incomplete">Incomplete Only</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn-primary">
|
||||
<div class="form-group">
|
||||
<label>Specific Path (Optional):</label>
|
||||
<input type="text" id="scan-path" placeholder="e.g., /mnt/unionfs/Media/TV/Series Name" title="Leave empty for full library scan">
|
||||
<small>Leave empty to scan entire library</small>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-play"></i> Start Scan
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="tool-card">
|
||||
<h3><i class="fas fa-folder"></i> Custom Directory Scan</h3>
|
||||
<p>Scan a specific directory or path</p>
|
||||
<form id="custom-scan-form">
|
||||
<div class="form-group">
|
||||
<label>Directory Path:</label>
|
||||
<input type="text" id="scan-path" placeholder="/media/Movies/specific-folder" />
|
||||
<small>Enter the full path to scan (will be auto-formatted)</small>
|
||||
<div id="scan-status" class="scan-status" style="display: none;">
|
||||
<div class="scan-progress">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" id="scan-progress-bar"></div>
|
||||
</div>
|
||||
<div class="scan-info">
|
||||
<span id="scan-current-operation">Initializing...</span>
|
||||
<span id="scan-progress-text">0%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Scan Type:</label>
|
||||
<select id="custom-scan-type" required>
|
||||
<option value="both">Auto-detect</option>
|
||||
<option value="movies">Movies</option>
|
||||
<option value="tv">TV Series</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn-primary">
|
||||
<i class="fas fa-search"></i> Scan Directory
|
||||
<button class="btn btn-secondary btn-sm" onclick="stopScanPolling()">
|
||||
<i class="fas fa-times"></i> Hide Status
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="tool-card">
|
||||
<h3><i class="fas fa-database"></i> Database Statistics</h3>
|
||||
<p>View detailed database information</p>
|
||||
@@ -390,85 +382,6 @@
|
||||
<i class="fas fa-sync"></i> Refresh Stats
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Database Query Tool -->
|
||||
<div class="tool-card">
|
||||
<h3><i class="fas fa-terminal"></i> Database Query Tool</h3>
|
||||
<p>Execute custom SQL queries on the NFOGuard database</p>
|
||||
<form id="database-query-form">
|
||||
<div class="form-group">
|
||||
<label>Quick Queries:</label>
|
||||
<select id="quick-query-select" onchange="loadQuickQuery()">
|
||||
<option value="">Select a predefined query...</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>SQL Query:</label>
|
||||
<textarea id="sql-query" rows="4" placeholder="SELECT * FROM episodes WHERE dateadded IS NULL LIMIT 10"></textarea>
|
||||
<small>Query will be automatically limited to 100 results for safety</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Result Limit:</label>
|
||||
<select id="query-limit">
|
||||
<option value="10">10 rows</option>
|
||||
<option value="50">50 rows</option>
|
||||
<option value="100" selected>100 rows</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-info">
|
||||
<i class="fas fa-play"></i> Execute Query
|
||||
</button>
|
||||
</form>
|
||||
<div id="query-results" class="query-results" style="display: none;">
|
||||
<h4>Query Results:</h4>
|
||||
<div id="query-results-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- NFO File Repair Tool -->
|
||||
<div class="tool-card">
|
||||
<h3><i class="fas fa-file-medical"></i> NFO File Repair</h3>
|
||||
<p>Fix missing <dateadded> elements in NFO files</p>
|
||||
<div class="form-group">
|
||||
<button class="btn btn-warning" onclick="checkMissingNFODates()">
|
||||
<i class="fas fa-search"></i> Scan for Missing dateadded in NFO Files
|
||||
</button>
|
||||
</div>
|
||||
<div id="nfo-scan-results" style="display: none;">
|
||||
<div id="nfo-scan-content"></div>
|
||||
<div class="form-group">
|
||||
<button class="btn btn-success" onclick="fixAllMissingNFODates()">
|
||||
<i class="fas fa-wrench"></i> Fix All Missing dateadded Elements
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="fixSpecificSeries()">
|
||||
<i class="fas fa-cog"></i> Fix Specific Series
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Database Admin Tools -->
|
||||
<div class="tool-card">
|
||||
<h3><i class="fas fa-user-shield"></i> Database Admin</h3>
|
||||
<p>Advanced database management tools</p>
|
||||
<div class="admin-tools">
|
||||
<button class="btn btn-info" onclick="showEpisodesMissingNFODateadded()">
|
||||
<i class="fas fa-exclamation-triangle"></i> Episodes Missing NFO dateadded
|
||||
</button>
|
||||
<button class="btn btn-warning" onclick="showMissingImdbItems()">
|
||||
<i class="fas fa-search"></i> Items Missing IMDb IDs
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="exportDatabaseReport()">
|
||||
<i class="fas fa-download"></i> Export Database Report
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="showRecentWebhookActivity()">
|
||||
<i class="fas fa-clock"></i> Recent Webhook Activity
|
||||
</button>
|
||||
</div>
|
||||
<div id="admin-results" class="admin-results" style="display: none;">
|
||||
<div id="admin-results-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
@@ -536,6 +449,6 @@
|
||||
<!-- Toast Notifications -->
|
||||
<div class="toast-container" id="toast-container"></div>
|
||||
|
||||
<script src="/static/js/app.js?v=3.0.1-nfo-table-redesign"></script>
|
||||
<script src="/static/js/app.js?v=manual-scan-ui"></script>
|
||||
</body>
|
||||
</html>
|
||||
+254
-853
File diff suppressed because it is too large
Load Diff
+58
-1
@@ -842,4 +842,61 @@ body {
|
||||
.d-block { display: block; }
|
||||
.d-flex { display: flex; }
|
||||
.justify-content-between { justify-content: space-between; }
|
||||
.align-items-center { align-items: center; }
|
||||
.align-items-center { align-items: center; }
|
||||
|
||||
/* Manual Scan Styles */
|
||||
.scan-status {
|
||||
margin-top: 1rem;
|
||||
padding: 1rem;
|
||||
background-color: var(--light-color);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
|
||||
.scan-progress {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 1.5rem;
|
||||
background-color: #e9ecef;
|
||||
border-radius: 0.375rem;
|
||||
overflow: hidden;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--primary-color), var(--success-color));
|
||||
transition: width 0.3s ease;
|
||||
width: 0%;
|
||||
}
|
||||
|
||||
.scan-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.scan-info span:first-child {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.scan-info span:last-child {
|
||||
font-weight: 600;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.form-group small {
|
||||
display: block;
|
||||
margin-top: 0.25rem;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 0.375rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
+47
-2
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>NFOGuard - Database Management</title>
|
||||
<link rel="stylesheet" href="/static/css/styles.css">
|
||||
<link rel="stylesheet" href="/static/css/styles.css?v=manual-scan-ui">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
@@ -327,6 +327,51 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="tool-card">
|
||||
<h3><i class="fas fa-search"></i> Manual Scan</h3>
|
||||
<p>Scan specific folders or perform full library scans</p>
|
||||
<form id="manual-scan-form">
|
||||
<div class="form-group">
|
||||
<label>Scan Type:</label>
|
||||
<select id="scan-type" required>
|
||||
<option value="both">TV Shows & Movies</option>
|
||||
<option value="tv">TV Shows Only</option>
|
||||
<option value="movies">Movies Only</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Scan Mode:</label>
|
||||
<select id="scan-mode" required>
|
||||
<option value="smart">Smart (Recommended)</option>
|
||||
<option value="full">Full Scan</option>
|
||||
<option value="incomplete">Incomplete Only</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Specific Path (Optional):</label>
|
||||
<input type="text" id="scan-path" placeholder="e.g., /mnt/unionfs/Media/TV/Series Name" title="Leave empty for full library scan">
|
||||
<small>Leave empty to scan entire library</small>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-play"></i> Start Scan
|
||||
</button>
|
||||
</form>
|
||||
<div id="scan-status" class="scan-status" style="display: none;">
|
||||
<div class="scan-progress">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" id="scan-progress-bar"></div>
|
||||
</div>
|
||||
<div class="scan-info">
|
||||
<span id="scan-current-operation">Initializing...</span>
|
||||
<span id="scan-progress-text">0%</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-secondary btn-sm" onclick="stopScanPolling()">
|
||||
<i class="fas fa-times"></i> Hide Status
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tool-card">
|
||||
<h3><i class="fas fa-database"></i> Database Statistics</h3>
|
||||
<p>View detailed database information</p>
|
||||
@@ -404,6 +449,6 @@
|
||||
<!-- Toast Notifications -->
|
||||
<div class="toast-container" id="toast-container"></div>
|
||||
|
||||
<script src="/static/js/app.js"></script>
|
||||
<script src="/static/js/app.js?v=manual-scan-ui"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -76,6 +76,7 @@ function initializeEventListeners() {
|
||||
// Forms
|
||||
document.getElementById('edit-form').addEventListener('submit', handleEditSubmit);
|
||||
document.getElementById('bulk-update-form').addEventListener('submit', handleBulkUpdate);
|
||||
document.getElementById('manual-scan-form').addEventListener('submit', handleManualScan);
|
||||
}
|
||||
|
||||
// API calls
|
||||
@@ -1436,6 +1437,169 @@ async function checkAuthStatus() {
|
||||
}
|
||||
}
|
||||
|
||||
// Manual Scan Functions
|
||||
async function handleManualScan(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const scanType = document.getElementById('scan-type').value;
|
||||
const scanMode = document.getElementById('scan-mode').value;
|
||||
const scanPath = document.getElementById('scan-path').value.trim();
|
||||
|
||||
// Validate inputs
|
||||
if (!scanType || !scanMode) {
|
||||
showToast('❌ Please select scan type and mode', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Build query parameters
|
||||
const params = new URLSearchParams({
|
||||
scan_type: scanType,
|
||||
scan_mode: scanMode
|
||||
});
|
||||
|
||||
if (scanPath) {
|
||||
params.append('path', scanPath);
|
||||
}
|
||||
|
||||
try {
|
||||
// Show scan status
|
||||
showScanStatus();
|
||||
|
||||
// Start the scan
|
||||
showToast('🚀 Starting manual scan...', 'info');
|
||||
const response = await fetch(`/manual/scan?${params}`, {
|
||||
method: 'POST',
|
||||
credentials: 'include'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.status === 'started') {
|
||||
showToast('✅ Scan started successfully', 'success');
|
||||
// Start polling for status
|
||||
startScanPolling();
|
||||
} else {
|
||||
showToast(`ℹ️ ${result.message || 'Scan completed'}`, 'info');
|
||||
hideScanStatus();
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Manual scan failed:', error);
|
||||
showToast(`❌ Scan failed: ${error.message}`, 'error');
|
||||
hideScanStatus();
|
||||
}
|
||||
}
|
||||
|
||||
function showScanStatus() {
|
||||
const scanStatus = document.getElementById('scan-status');
|
||||
const progressBar = document.getElementById('scan-progress-bar');
|
||||
const operationText = document.getElementById('scan-current-operation');
|
||||
const progressText = document.getElementById('scan-progress-text');
|
||||
|
||||
// Reset and show
|
||||
progressBar.style.width = '0%';
|
||||
operationText.textContent = 'Initializing scan...';
|
||||
progressText.textContent = '0%';
|
||||
scanStatus.style.display = 'block';
|
||||
}
|
||||
|
||||
function hideScanStatus() {
|
||||
document.getElementById('scan-status').style.display = 'none';
|
||||
if (window.scanPollingInterval) {
|
||||
clearInterval(window.scanPollingInterval);
|
||||
window.scanPollingInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
function stopScanPolling() {
|
||||
hideScanStatus();
|
||||
}
|
||||
|
||||
function startScanPolling() {
|
||||
// Poll every 2 seconds for scan status
|
||||
window.scanPollingInterval = setInterval(async () => {
|
||||
try {
|
||||
const response = await fetch('/api/scan/status');
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to get scan status');
|
||||
}
|
||||
|
||||
const status = await response.json();
|
||||
updateScanProgress(status);
|
||||
|
||||
// Stop polling if scan is complete
|
||||
if (!status.scanning) {
|
||||
stopScanPolling();
|
||||
showToast('✅ Scan completed!', 'success');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to poll scan status:', error);
|
||||
// Don't show error toast repeatedly, just stop polling
|
||||
stopScanPolling();
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function updateScanProgress(status) {
|
||||
const progressBar = document.getElementById('scan-progress-bar');
|
||||
const operationText = document.getElementById('scan-current-operation');
|
||||
const progressText = document.getElementById('scan-progress-text');
|
||||
|
||||
if (!status.scanning) {
|
||||
progressBar.style.width = '100%';
|
||||
operationText.textContent = 'Scan completed';
|
||||
progressText.textContent = '100%';
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate overall progress
|
||||
let totalProgress = 0;
|
||||
let progressDetails = '';
|
||||
|
||||
if (status.scan_type === 'both' || status.scan_type === 'tv') {
|
||||
const tvProgress = status.tv_series_total > 0 ?
|
||||
((status.tv_series_processed + status.tv_series_skipped) / status.tv_series_total) * 50 : 0;
|
||||
totalProgress += tvProgress;
|
||||
|
||||
if (status.tv_series_total > 0) {
|
||||
progressDetails += `TV: ${status.tv_series_processed + status.tv_series_skipped}/${status.tv_series_total} `;
|
||||
}
|
||||
}
|
||||
|
||||
if (status.scan_type === 'both' || status.scan_type === 'movies') {
|
||||
const movieProgress = status.movies_total > 0 ?
|
||||
((status.movies_processed + status.movies_skipped) / status.movies_total) * 50 : 0;
|
||||
totalProgress += movieProgress;
|
||||
|
||||
if (status.movies_total > 0) {
|
||||
progressDetails += `Movies: ${status.movies_processed + status.movies_skipped}/${status.movies_total}`;
|
||||
}
|
||||
}
|
||||
|
||||
// For single type scans, use full 100%
|
||||
if (status.scan_type !== 'both') {
|
||||
totalProgress *= 2;
|
||||
}
|
||||
|
||||
// Update progress bar
|
||||
progressBar.style.width = `${Math.min(totalProgress, 100)}%`;
|
||||
progressText.textContent = `${Math.round(totalProgress)}%`;
|
||||
|
||||
// Update operation text
|
||||
if (status.current_operation) {
|
||||
operationText.textContent = status.current_operation;
|
||||
} else if (status.current_item) {
|
||||
operationText.textContent = `Processing: ${status.current_item}`;
|
||||
} else {
|
||||
operationText.textContent = progressDetails || 'Scanning...';
|
||||
}
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
if (!confirm('Are you sure you want to logout?')) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user