Spaces:
Paused
Paused
| <html lang="fa" dir="rtl"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>سامانه اسکراپینگ حقوقی | داشبورد هوشمند</title> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
| <link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@200;300;400;500;600;700;800;900&display=swap" rel="stylesheet"> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"> | |
| <!-- Load API Client and Core System --> | |
| <script src="js/api-client.js"></script> | |
| <script src="js/core.js"></script> | |
| <script src="js/notifications.js"></script> | |
| <script src="js/scraping-control.js"></script> | |
| <style> | |
| :root { | |
| /* رنگبندی مدرن و هارمونیک - منطبق با index */ | |
| --text-primary: #0f172a; | |
| --text-secondary: #475569; | |
| --text-muted: #64748b; | |
| --text-light: #ffffff; | |
| /* پسزمینههای بهبود یافته */ | |
| --body-bg: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 50%, #cbd5e1 100%); | |
| --card-bg: rgba(255, 255, 255, 0.95); | |
| --glass-bg: rgba(255, 255, 255, 0.9); | |
| --glass-border: rgba(148, 163, 184, 0.2); | |
| /* گرادیانهای مدرن */ | |
| --primary-gradient: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%); | |
| --secondary-gradient: linear-gradient(135deg, #06b6d4 0%, #0891b2 100%); | |
| --success-gradient: linear-gradient(135deg, #10b981 0%, #047857 100%); | |
| --warning-gradient: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); | |
| --danger-gradient: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); | |
| --purple-gradient: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%); | |
| /* سایههای ملایم */ | |
| --shadow-xs: 0 1px 3px rgba(0, 0, 0, 0.05); | |
| --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.08); | |
| --shadow-md: 0 4px 15px rgba(0, 0, 0, 0.1); | |
| --shadow-lg: 0 8px 25px rgba(0, 0, 0, 0.12); | |
| --shadow-glow-primary: 0 0 20px rgba(59, 130, 246, 0.15); | |
| --shadow-glow-success: 0 0 20px rgba(16, 185, 129, 0.15); | |
| --shadow-glow-warning: 0 0 20px rgba(245, 158, 11, 0.15); | |
| --shadow-glow-purple: 0 0 20px rgba(139, 92, 246, 0.15); | |
| /* متغیرهای کامپکت */ | |
| --sidebar-width: 260px; | |
| --border-radius: 12px; | |
| --border-radius-sm: 8px; | |
| --transition-smooth: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); | |
| --transition-fast: all 0.15s ease-in-out; | |
| /* فونتهای کامپکت */ | |
| --font-size-xs: 0.7rem; | |
| --font-size-sm: 0.8rem; | |
| --font-size-base: 0.9rem; | |
| --font-size-lg: 1.1rem; | |
| --font-size-xl: 1.25rem; | |
| --font-size-2xl: 1.5rem; | |
| } | |
| /* ریست و تنظیمات پایه */ | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Vazirmatn', -apple-system, BlinkMacSystemFont, sans-serif; | |
| background: var(--body-bg); | |
| color: var(--text-primary); | |
| line-height: 1.6; | |
| overflow-x: hidden; | |
| font-size: var(--font-size-base); | |
| } | |
| /* اسکرولبار مدرن */ | |
| ::-webkit-scrollbar { | |
| inline-size: 6px; | |
| block-size: 6px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: rgba(0, 0, 0, 0.02); | |
| border-radius: 10px; | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: var(--primary-gradient); | |
| border-radius: 10px; | |
| } | |
| /* کانتینر اصلی */ | |
| .dashboard-container { | |
| display: flex; | |
| min-height: 100vh; | |
| width: 100%; | |
| } | |
| /* سایدبار کامپکت - منطبق با index */ | |
| .sidebar { | |
| width: var(--sidebar-width); | |
| background: linear-gradient(135deg, | |
| rgba(248, 250, 252, 0.98) 0%, | |
| rgba(241, 245, 249, 0.95) 25%, | |
| rgba(226, 232, 240, 0.98) 50%, | |
| rgba(203, 213, 225, 0.95) 75%, | |
| rgba(148, 163, 184, 0.1) 100%); | |
| backdrop-filter: blur(25px); | |
| padding: 1rem 0; | |
| position: fixed; | |
| height: 100vh; | |
| right: 0; | |
| top: 0; | |
| z-index: 1000; | |
| overflow-y: auto; | |
| box-shadow: -8px 0 32px rgba(59, 130, 246, 0.12); | |
| border-left: 1px solid rgba(59, 130, 246, 0.15); | |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| } | |
| .sidebar-header { | |
| padding: 0 1rem 1rem; | |
| border-bottom: 1px solid rgba(59, 130, 246, 0.12); | |
| margin-bottom: 1rem; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| background: linear-gradient(135deg, | |
| rgba(255, 255, 255, 0.4) 0%, | |
| rgba(248, 250, 252, 0.2) 100%); | |
| margin: 0 0.5rem 1rem; | |
| border-radius: var(--border-radius); | |
| backdrop-filter: blur(10px); | |
| } | |
| .logo { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.6rem; | |
| color: var(--text-primary); | |
| text-decoration: none; | |
| } | |
| .logo-icon { | |
| width: 2rem; | |
| height: 2rem; | |
| background: var(--primary-gradient); | |
| border-radius: var(--border-radius-sm); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 1rem; | |
| color: white; | |
| box-shadow: var(--shadow-glow-primary); | |
| } | |
| .logo-text { | |
| font-size: var(--font-size-lg); | |
| font-weight: 700; | |
| background: var(--primary-gradient); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| } | |
| .nav-section { | |
| margin-bottom: 1rem; | |
| } | |
| .nav-title { | |
| padding: 0 1rem 0.4rem; | |
| font-size: var(--font-size-xs); | |
| font-weight: 600; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| color: var(--text-secondary); | |
| } | |
| .nav-menu { | |
| list-style: none; | |
| } | |
| .nav-item { | |
| margin: 0.15rem 0.5rem; | |
| } | |
| .nav-link { | |
| display: flex; | |
| align-items: center; | |
| padding: 0.6rem 0.8rem; | |
| color: var(--text-primary); | |
| text-decoration: none; | |
| border-radius: var(--border-radius-sm); | |
| transition: var(--transition-smooth); | |
| font-weight: 500; | |
| font-size: var(--font-size-sm); | |
| cursor: pointer; | |
| border: 1px solid transparent; | |
| } | |
| .nav-link:hover { | |
| color: var(--text-primary); | |
| transform: translateX(-2px); | |
| border-color: rgba(59, 130, 246, 0.15); | |
| background: rgba(59, 130, 246, 0.05); | |
| } | |
| .nav-link.active { | |
| background: var(--primary-gradient); | |
| color: var(--text-light); | |
| box-shadow: var(--shadow-md); | |
| } | |
| .nav-icon { | |
| margin-left: 0.6rem; | |
| width: 1rem; | |
| text-align: center; | |
| font-size: 0.9rem; | |
| } | |
| .nav-badge { | |
| background: var(--danger-gradient); | |
| color: white; | |
| padding: 0.15rem 0.4rem; | |
| border-radius: 10px; | |
| font-size: var(--font-size-xs); | |
| font-weight: 600; | |
| margin-right: auto; | |
| min-width: 1.2rem; | |
| text-align: center; | |
| } | |
| /* محتوای اصلی */ | |
| .main-content { | |
| flex: 1; | |
| margin-right: var(--sidebar-width); | |
| padding: 1rem; | |
| min-height: 100vh; | |
| width: calc(100% - var(--sidebar-width)); | |
| } | |
| /* هدر کامپکت - منطبق با index */ | |
| .dashboard-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 1.2rem; | |
| padding: 0.8rem 0; | |
| } | |
| .dashboard-title { | |
| font-size: var(--font-size-2xl); | |
| font-weight: 800; | |
| background: var(--primary-gradient); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.6rem; | |
| } | |
| .header-actions { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.8rem; | |
| } | |
| /* آمار کلی - منطبق با index */ | |
| .stats-grid { | |
| display: grid; | |
| grid-template-columns: repeat(4, 1fr); | |
| gap: 1rem; | |
| margin-bottom: 1.2rem; | |
| } | |
| .stat-card { | |
| background: var(--card-bg); | |
| backdrop-filter: blur(10px); | |
| border-radius: var(--border-radius); | |
| padding: 1.2rem; | |
| box-shadow: var(--shadow-md); | |
| border: 1px solid rgba(255, 255, 255, 0.3); | |
| position: relative; | |
| overflow: hidden; | |
| transition: var(--transition-smooth); | |
| min-height: 130px; | |
| } | |
| .stat-card::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| height: 4px; | |
| background: var(--primary-gradient); | |
| } | |
| .stat-card.primary::before { background: var(--primary-gradient); } | |
| .stat-card.success::before { background: var(--success-gradient); } | |
| .stat-card.danger::before { background: var(--danger-gradient); } | |
| .stat-card.warning::before { background: var(--warning-gradient); } | |
| .stat-card.purple::before { background: var(--purple-gradient); } | |
| .stat-card:hover { | |
| transform: translateY(-6px) scale(1.02); | |
| box-shadow: var(--shadow-lg); | |
| } | |
| .stat-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: flex-start; | |
| margin-bottom: 0.8rem; | |
| } | |
| .stat-icon { | |
| width: 2.2rem; | |
| height: 2.2rem; | |
| border-radius: var(--border-radius-sm); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 1.1rem; | |
| box-shadow: var(--shadow-sm); | |
| transition: var(--transition-smooth); | |
| } | |
| .stat-icon.primary { background: var(--primary-gradient); color: white; } | |
| .stat-icon.success { background: var(--success-gradient); color: white; } | |
| .stat-icon.danger { background: var(--danger-gradient); color: white; } | |
| .stat-icon.warning { background: var(--warning-gradient); color: white; } | |
| .stat-icon.purple { background: var(--purple-gradient); color: white; } | |
| .stat-content { | |
| flex: 1; | |
| } | |
| .stat-title { | |
| font-size: var(--font-size-xs); | |
| color: var(--text-secondary); | |
| font-weight: 600; | |
| margin-bottom: 0.3rem; | |
| } | |
| .stat-value { | |
| font-size: var(--font-size-xl); | |
| font-weight: 800; | |
| color: var(--text-primary); | |
| line-height: 1; | |
| margin-bottom: 0.3rem; | |
| } | |
| .stat-value.small { | |
| font-size: var(--font-size-base); | |
| } | |
| .stat-extra { | |
| font-size: var(--font-size-xs); | |
| color: var(--text-muted); | |
| margin-bottom: 0.3rem; | |
| } | |
| .stat-change { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.25rem; | |
| font-size: var(--font-size-xs); | |
| font-weight: 700; | |
| } | |
| .stat-change.positive { color: #059669; } | |
| .stat-change.negative { color: #dc2626; } | |
| /* کارتهای عملیات اصلی */ | |
| .control-section { | |
| display: grid; | |
| grid-template-columns: 2fr 1fr; | |
| gap: 1.5rem; | |
| margin-bottom: 1.5rem; | |
| } | |
| .control-card { | |
| background: var(--card-bg); | |
| backdrop-filter: blur(10px); | |
| border-radius: var(--border-radius); | |
| padding: 1.5rem; | |
| box-shadow: var(--shadow-md); | |
| border: 1px solid rgba(255, 255, 255, 0.3); | |
| transition: var(--transition-smooth); | |
| } | |
| .control-card:hover { | |
| transform: translateY(-4px); | |
| box-shadow: var(--shadow-lg); | |
| } | |
| .control-header { | |
| display: flex; | |
| justify-content: between; | |
| align-items: center; | |
| margin-bottom: 1.2rem; | |
| } | |
| .control-title { | |
| font-size: var(--font-size-lg); | |
| font-weight: 700; | |
| color: var(--text-primary); | |
| display: flex; | |
| align-items: center; | |
| gap: 0.6rem; | |
| } | |
| /* مراجع آماده */ | |
| .sources-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); | |
| gap: 1rem; | |
| max-height: 350px; | |
| overflow-y: auto; | |
| padding-left: 0.5rem; | |
| } | |
| .source-item { | |
| background: var(--glass-bg); | |
| border: 2px solid var(--glass-border); | |
| border-radius: var(--border-radius-sm); | |
| padding: 1rem; | |
| cursor: pointer; | |
| transition: var(--transition-smooth); | |
| position: relative; | |
| } | |
| .source-item:hover { | |
| border-color: rgba(59, 130, 246, 0.3); | |
| background: rgba(59, 130, 246, 0.03); | |
| transform: translateY(-2px); | |
| box-shadow: var(--shadow-sm); | |
| } | |
| .source-item.selected { | |
| border-color: #3b82f6; | |
| background: rgba(59, 130, 246, 0.05); | |
| box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); | |
| } | |
| .source-checkbox { | |
| position: absolute; | |
| top: 0.8rem; | |
| left: 0.8rem; | |
| width: 1.2rem; | |
| height: 1.2rem; | |
| cursor: pointer; | |
| } | |
| .source-info { | |
| margin-right: 2rem; | |
| } | |
| .source-name { | |
| font-weight: 600; | |
| color: var(--text-primary); | |
| margin-bottom: 0.3rem; | |
| font-size: var(--font-size-sm); | |
| } | |
| .source-url { | |
| font-size: var(--font-size-xs); | |
| color: var(--text-muted); | |
| margin-bottom: 0.5rem; | |
| direction: ltr; | |
| text-align: left; | |
| } | |
| .source-meta { | |
| display: flex; | |
| gap: 0.5rem; | |
| flex-wrap: wrap; | |
| } | |
| .source-tag { | |
| background: rgba(59, 130, 246, 0.1); | |
| color: #3b82f6; | |
| padding: 0.15rem 0.5rem; | |
| border-radius: 10px; | |
| font-size: var(--font-size-xs); | |
| font-weight: 500; | |
| } | |
| .source-status { | |
| width: 8px; | |
| height: 8px; | |
| border-radius: 50%; | |
| margin-top: 0.4rem; | |
| position: absolute; | |
| top: 0.8rem; | |
| right: 0.8rem; | |
| } | |
| .source-status.online { | |
| background: #10b981; | |
| box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.2); | |
| } | |
| .source-status.offline { | |
| background: #ef4444; | |
| box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.2); | |
| } | |
| /* دکمههای عمل - منطبق با index */ | |
| .action-buttons { | |
| display: flex; | |
| gap: 0.8rem; | |
| margin-bottom: 1.5rem; | |
| flex-wrap: wrap; | |
| } | |
| .btn { | |
| padding: 0.6rem 1.2rem; | |
| border: none; | |
| border-radius: var(--border-radius-sm); | |
| font-family: inherit; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: var(--transition-smooth); | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| text-decoration: none; | |
| font-size: var(--font-size-sm); | |
| min-width: 120px; | |
| justify-content: center; | |
| } | |
| .btn-primary { | |
| background: var(--primary-gradient); | |
| color: white; | |
| box-shadow: var(--shadow-sm); | |
| } | |
| .btn-primary:hover { | |
| box-shadow: var(--shadow-md), var(--shadow-glow-primary); | |
| transform: translateY(-2px); | |
| } | |
| .btn-success { | |
| background: var(--success-gradient); | |
| color: white; | |
| } | |
| .btn-success:hover { | |
| box-shadow: var(--shadow-md), var(--shadow-glow-success); | |
| transform: translateY(-2px); | |
| } | |
| .btn-warning { | |
| background: var(--warning-gradient); | |
| color: white; | |
| } | |
| .btn-warning:hover { | |
| box-shadow: var(--shadow-md), var(--shadow-glow-warning); | |
| transform: translateY(-2px); | |
| } | |
| .btn-secondary { | |
| background: var(--glass-bg); | |
| color: var(--text-primary); | |
| border: 1px solid var(--glass-border); | |
| } | |
| .btn-secondary:hover { | |
| background: rgba(59, 130, 246, 0.05); | |
| border-color: rgba(59, 130, 246, 0.3); | |
| transform: translateY(-2px); | |
| } | |
| .btn-auto { | |
| background: var(--purple-gradient); | |
| color: white; | |
| font-size: var(--font-size-base); | |
| padding: 0.8rem 1.5rem; | |
| position: relative; | |
| overflow: hidden; | |
| animation: pulse-auto 3s ease-in-out infinite; | |
| } | |
| @keyframes pulse-auto { | |
| 0%, 100% { box-shadow: var(--shadow-sm); } | |
| 50% { box-shadow: var(--shadow-lg), var(--shadow-glow-purple); } | |
| } | |
| .btn-auto:hover { | |
| box-shadow: var(--shadow-xl), var(--shadow-glow-purple); | |
| transform: translateY(-3px) scale(1.02); | |
| } | |
| .btn:disabled { | |
| opacity: 0.6; | |
| cursor: not-allowed; | |
| transform: none ; | |
| box-shadow: var(--shadow-sm) ; | |
| } | |
| .btn-sm { | |
| padding: 0.4rem 0.8rem; | |
| font-size: var(--font-size-xs); | |
| min-width: auto; | |
| } | |
| /* تنظیمات سریع */ | |
| .quick-settings { | |
| background: rgba(59, 130, 246, 0.03); | |
| border: 1px solid rgba(59, 130, 246, 0.1); | |
| border-radius: var(--border-radius-sm); | |
| padding: 1rem; | |
| margin-top: 1rem; | |
| } | |
| .settings-title { | |
| font-size: var(--font-size-sm); | |
| font-weight: 600; | |
| color: var(--text-primary); | |
| margin-bottom: 0.8rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .settings-grid { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 1rem; | |
| } | |
| .setting-item label { | |
| display: block; | |
| font-size: var(--font-size-xs); | |
| font-weight: 600; | |
| color: var(--text-secondary); | |
| margin-bottom: 0.4rem; | |
| } | |
| .setting-item select { | |
| width: 100%; | |
| padding: 0.5rem; | |
| border: 1px solid var(--glass-border); | |
| border-radius: var(--border-radius-sm); | |
| background: var(--glass-bg); | |
| color: var(--text-primary); | |
| font-family: inherit; | |
| font-size: var(--font-size-xs); | |
| } | |
| /* وضعیت فعالیت */ | |
| .activity-status { | |
| background: var(--card-bg); | |
| border-radius: var(--border-radius); | |
| padding: 1.5rem; | |
| margin-bottom: 1.5rem; | |
| box-shadow: var(--shadow-md); | |
| border: 1px solid rgba(255, 255, 255, 0.3); | |
| display: none; | |
| } | |
| .activity-status.show { | |
| display: block; | |
| } | |
| .status-content { | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| } | |
| .status-icon { | |
| width: 3rem; | |
| height: 3rem; | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 1.2rem; | |
| flex-shrink: 0; | |
| } | |
| .status-loading { | |
| background: rgba(59, 130, 246, 0.1); | |
| color: #3b82f6; | |
| border: 2px solid rgba(59, 130, 246, 0.2); | |
| } | |
| .status-success { | |
| background: rgba(16, 185, 129, 0.1); | |
| color: #10b981; | |
| border: 2px solid rgba(16, 185, 129, 0.2); | |
| } | |
| .status-error { | |
| background: rgba(239, 68, 68, 0.1); | |
| color: #ef4444; | |
| border: 2px solid rgba(239, 68, 68, 0.2); | |
| } | |
| .status-details { | |
| flex: 1; | |
| } | |
| .status-title { | |
| font-size: var(--font-size-lg); | |
| font-weight: 600; | |
| color: var(--text-primary); | |
| margin-bottom: 0.3rem; | |
| } | |
| .status-message { | |
| font-size: var(--font-size-sm); | |
| color: var(--text-secondary); | |
| margin-bottom: 0.8rem; | |
| } | |
| .status-progress { | |
| width: 100%; | |
| height: 6px; | |
| background: rgba(0, 0, 0, 0.08); | |
| border-radius: 3px; | |
| overflow: hidden; | |
| } | |
| .status-progress-bar { | |
| height: 100%; | |
| background: var(--primary-gradient); | |
| border-radius: 3px; | |
| transition: width 0.3s ease; | |
| position: relative; | |
| } | |
| .status-progress-bar::after { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent); | |
| animation: shimmer 2s infinite; | |
| } | |
| @keyframes shimmer { | |
| 0% { transform: translateX(-100%); } | |
| 100% { transform: translateX(100%); } | |
| } | |
| /* نتایج */ | |
| .results-section { | |
| background: var(--card-bg); | |
| border-radius: var(--border-radius); | |
| box-shadow: var(--shadow-md); | |
| margin-bottom: 1.5rem; | |
| border: 1px solid rgba(255, 255, 255, 0.3); | |
| overflow: hidden; | |
| display: none; | |
| } | |
| .results-section.show { | |
| display: block; | |
| } | |
| .results-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 1.5rem; | |
| border-bottom: 1px solid rgba(0, 0, 0, 0.05); | |
| background: linear-gradient(135deg, rgba(16, 185, 129, 0.02), rgba(255, 255, 255, 0.05)); | |
| } | |
| .results-title { | |
| font-size: var(--font-size-lg); | |
| font-weight: 700; | |
| color: var(--text-primary); | |
| display: flex; | |
| align-items: center; | |
| gap: 0.6rem; | |
| } | |
| .results-actions { | |
| display: flex; | |
| gap: 0.5rem; | |
| } | |
| .results-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); | |
| gap: 1rem; | |
| padding: 1.5rem; | |
| } | |
| .result-card { | |
| background: var(--glass-bg); | |
| border: 1px solid var(--glass-border); | |
| border-radius: var(--border-radius-sm); | |
| padding: 1.5rem; | |
| transition: var(--transition-smooth); | |
| } | |
| .result-card:hover { | |
| border-color: rgba(16, 185, 129, 0.3); | |
| transform: translateY(-2px); | |
| box-shadow: var(--shadow-sm); | |
| } | |
| .result-source { | |
| font-weight: 600; | |
| color: var(--text-primary); | |
| margin-bottom: 0.5rem; | |
| font-size: var(--font-size-sm); | |
| } | |
| .result-meta { | |
| display: flex; | |
| gap: 1rem; | |
| margin-bottom: 1rem; | |
| font-size: var(--font-size-xs); | |
| color: var(--text-muted); | |
| flex-wrap: wrap; | |
| } | |
| .result-meta span { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.3rem; | |
| } | |
| .result-content { | |
| font-size: var(--font-size-sm); | |
| color: var(--text-secondary); | |
| line-height: 1.5; | |
| max-height: 100px; | |
| overflow: hidden; | |
| margin-bottom: 1rem; | |
| } | |
| .result-actions { | |
| display: flex; | |
| gap: 0.5rem; | |
| padding-top: 1rem; | |
| border-top: 1px solid rgba(0, 0, 0, 0.05); | |
| } | |
| /* Toast Notifications - منطبق با index */ | |
| .toast-container { | |
| position: fixed; | |
| top: 1rem; | |
| left: 1rem; | |
| z-index: 10001; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 0.5rem; | |
| } | |
| .toast { | |
| background: var(--card-bg); | |
| border-radius: var(--border-radius-sm); | |
| padding: 1rem 1.5rem; | |
| box-shadow: var(--shadow-lg); | |
| border-left: 4px solid; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.8rem; | |
| min-width: 300px; | |
| transform: translateX(-100%); | |
| transition: all 0.3s ease; | |
| } | |
| .toast.show { | |
| transform: translateX(0); | |
| } | |
| .toast.success { border-left-color: #10b981; } | |
| .toast.error { border-left-color: #ef4444; } | |
| .toast.warning { border-left-color: #f59e0b; } | |
| .toast.info { border-left-color: #3b82f6; } | |
| .toast-icon { | |
| font-size: 1.2rem; | |
| } | |
| .toast.success .toast-icon { color: #10b981; } | |
| .toast.error .toast-icon { color: #ef4444; } | |
| .toast.warning .toast-icon { color: #f59e0b; } | |
| .toast.info .toast-icon { color: #3b82f6; } | |
| .toast-content { | |
| flex: 1; | |
| } | |
| .toast-title { | |
| font-weight: 600; | |
| font-size: var(--font-size-sm); | |
| margin-bottom: 0.2rem; | |
| } | |
| .toast-message { | |
| font-size: var(--font-size-xs); | |
| color: var(--text-secondary); | |
| } | |
| .toast-close { | |
| background: none; | |
| border: none; | |
| color: var(--text-secondary); | |
| cursor: pointer; | |
| font-size: 1rem; | |
| transition: var(--transition-fast); | |
| } | |
| .toast-close:hover { | |
| color: var(--text-primary); | |
| } | |
| /* دکمه منوی موبایل */ | |
| .mobile-menu-toggle { | |
| display: none; | |
| background: var(--glass-bg); | |
| border: 1px solid var(--glass-border); | |
| padding: 0.5rem; | |
| border-radius: var(--border-radius-sm); | |
| color: var(--text-primary); | |
| font-size: 1rem; | |
| cursor: pointer; | |
| transition: var(--transition-fast); | |
| } | |
| .mobile-menu-toggle:hover { | |
| background: var(--primary-gradient); | |
| color: white; | |
| } | |
| /* واکنشگرایی */ | |
| @media (max-width: 992px) { | |
| .mobile-menu-toggle { | |
| display: block; | |
| } | |
| .sidebar { | |
| transform: translateX(100%); | |
| position: fixed; | |
| z-index: 10000; | |
| } | |
| .sidebar.open { | |
| transform: translateX(0); | |
| } | |
| .main-content { | |
| margin-right: 0; | |
| width: 100%; | |
| padding: 1rem; | |
| } | |
| .dashboard-header { | |
| flex-direction: column; | |
| align-items: flex-start; | |
| gap: 0.8rem; | |
| } | |
| .header-actions { | |
| width: 100%; | |
| justify-content: space-between; | |
| } | |
| .stats-grid { | |
| grid-template-columns: repeat(2, 1fr); | |
| } | |
| .control-section { | |
| grid-template-columns: 1fr; | |
| } | |
| .sources-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| .settings-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| @media (max-width: 768px) { | |
| .main-content { | |
| padding: 0.8rem; | |
| } | |
| .stats-grid { | |
| grid-template-columns: 1fr; | |
| gap: 0.6rem; | |
| } | |
| .stat-card { | |
| min-height: 100px; | |
| padding: 0.8rem; | |
| } | |
| .stat-value { | |
| font-size: var(--font-size-lg); | |
| } | |
| .action-buttons { | |
| flex-direction: column; | |
| } | |
| .btn { | |
| justify-content: center; | |
| width: 100%; | |
| } | |
| .results-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="dashboard-container"> | |
| <!-- سایدبار --> | |
| <aside class="sidebar" id="sidebar"> | |
| <div class="sidebar-header"> | |
| <div class="logo"> | |
| <div class="logo-icon"> | |
| <i class="fas fa-spider"></i> | |
| </div> | |
| <div class="logo-text">سامانه اسکراپینگ</div> | |
| </div> | |
| </div> | |
| <nav> | |
| <div class="nav-section"> | |
| <h6 class="nav-title">داشبورد</h6> | |
| <ul class="nav-menu"> | |
| <li class="nav-item"> | |
| <a href="index.html" class="nav-link"> | |
| <i class="fas fa-chart-pie nav-icon"></i> | |
| <span>نمای کلی</span> | |
| </a> | |
| </li> | |
| <li class="nav-item"> | |
| <a href="scraping_dashboard.html" class="nav-link active"> | |
| <i class="fas fa-spider nav-icon"></i> | |
| <span>اسکراپینگ خودکار</span> | |
| </a> | |
| </li> | |
| </ul> | |
| </div> | |
| <div class="nav-section"> | |
| <h6 class="nav-title">مدیریت اسناد</h6> | |
| <ul class="nav-menu"> | |
| <li class="nav-item"> | |
| <a href="documents.html" class="nav-link"> | |
| <i class="fas fa-file-alt nav-icon"></i> | |
| <span>مدیریت اسناد</span> | |
| <span class="nav-badge">6</span> | |
| </a> | |
| </li> | |
| <li class="nav-item"> | |
| <a href="upload.html" class="nav-link"> | |
| <i class="fas fa-cloud-upload-alt nav-icon"></i> | |
| <span>آپلود فایل</span> | |
| </a> | |
| </li> | |
| <li class="nav-item"> | |
| <a href="search.html" class="nav-link"> | |
| <i class="fas fa-search nav-icon"></i> | |
| <span>جستجو</span> | |
| </a> | |
| </li> | |
| </ul> | |
| </div> | |
| <div class="nav-section"> | |
| <h6 class="nav-title">ابزارها</h6> | |
| <ul class="nav-menu"> | |
| <li class="nav-item"> | |
| <a href="scraping.html" class="nav-link"> | |
| <i class="fas fa-globe nav-icon"></i> | |
| <span>استخراج محتوا</span> | |
| </a> | |
| </li> | |
| <li class="nav-item"> | |
| <a href="analytics.html" class="nav-link"> | |
| <i class="fas fa-chart-line nav-icon"></i> | |
| <span>آمار و تحلیل</span> | |
| </a> | |
| </li> | |
| <li class="nav-item"> | |
| <a href="reports.html" class="nav-link"> | |
| <i class="fas fa-file-export nav-icon"></i> | |
| <span>گزارشها</span> | |
| </a> | |
| </li> | |
| </ul> | |
| </div> | |
| <div class="nav-section"> | |
| <h6 class="nav-title">تنظیمات</h6> | |
| <ul class="nav-menu"> | |
| <li class="nav-item"> | |
| <a href="settings.html" class="nav-link"> | |
| <i class="fas fa-cog nav-icon"></i> | |
| <span>تنظیمات</span> | |
| </a> | |
| </li> | |
| <li class="nav-item"> | |
| <a href="#" class="nav-link"> | |
| <i class="fas fa-sign-out-alt nav-icon"></i> | |
| <span>خروج</span> | |
| </a> | |
| </li> | |
| </ul> | |
| </div> | |
| </nav> | |
| </aside> | |
| <!-- محتوای اصلی --> | |
| <main class="main-content"> | |
| <!-- هدر صفحه --> | |
| <header class="dashboard-header"> | |
| <div> | |
| <h1 class="dashboard-title"> | |
| <i class="fas fa-spider"></i> | |
| <span>سامانه اسکراپینگ حقوقی</span> | |
| </h1> | |
| </div> | |
| <div class="header-actions"> | |
| <button type="button" class="mobile-menu-toggle" id="mobileMenuToggle" aria-label="منوی موبایل"> | |
| <i class="fas fa-bars"></i> | |
| </button> | |
| </div> | |
| </header> | |
| <!-- آمار کلی --> | |
| <section aria-labelledby="stats-section"> | |
| <h2 id="stats-section" class="sr-only">آمار اسکراپینگ</h2> | |
| <div class="stats-grid"> | |
| <div class="stat-card primary"> | |
| <div class="stat-header"> | |
| <div class="stat-content"> | |
| <div class="stat-title">مراجع حقوقی</div> | |
| <div class="stat-value" id="totalSources">10</div> | |
| <div class="stat-extra">مراجع آماده</div> | |
| <div class="stat-change positive"> | |
| <i class="fas fa-arrow-up"></i> | |
| <span>+100%</span> | |
| </div> | |
| </div> | |
| <div class="stat-icon primary"> | |
| <i class="fas fa-globe"></i> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="stat-card success"> | |
| <div class="stat-header"> | |
| <div class="stat-content"> | |
| <div class="stat-title">مراجع انتخاب شده</div> | |
| <div class="stat-value" id="selectedSources">0</div> | |
| <div class="stat-extra">آماده اسکراپینگ</div> | |
| <div class="stat-change positive"> | |
| <i class="fas fa-arrow-up"></i> | |
| <span>+0%</span> | |
| </div> | |
| </div> | |
| <div class="stat-icon success"> | |
| <i class="fas fa-check-circle"></i> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="stat-card warning"> | |
| <div class="stat-header"> | |
| <div class="stat-content"> | |
| <div class="stat-title">محتوای استخراج شده</div> | |
| <div class="stat-value" id="scrapedItems">0</div> | |
| <div class="stat-extra">آیتم استخراج شده</div> | |
| <div class="stat-change positive"> | |
| <i class="fas fa-arrow-up"></i> | |
| <span>+0%</span> | |
| </div> | |
| </div> | |
| <div class="stat-icon warning"> | |
| <i class="fas fa-download"></i> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="stat-card purple"> | |
| <div class="stat-header"> | |
| <div class="stat-content"> | |
| <div class="stat-title">آخرین بروزرسانی</div> | |
| <div class="stat-value small" id="lastUpdate">--</div> | |
| <div class="stat-extra">زمان آخرین فعالیت</div> | |
| <div class="stat-change positive"> | |
| <i class="fas fa-clock"></i> | |
| <span>جدید</span> | |
| </div> | |
| </div> | |
| <div class="stat-icon purple"> | |
| <i class="fas fa-clock"></i> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- کنترل عملیات --> | |
| <section class="control-section"> | |
| <!-- مراجع آماده --> | |
| <div class="control-card"> | |
| <div class="control-header"> | |
| <h2 class="control-title"> | |
| <i class="fas fa-list"></i> | |
| مراجع حقوقی آماده | |
| </h2> | |
| </div> | |
| <div class="sources-grid" id="sourcesGrid"> | |
| <!-- مراجع اینجا لود میشوند --> | |
| </div> | |
| </div> | |
| <!-- کنترل عملیات --> | |
| <div class="control-card"> | |
| <div class="control-header"> | |
| <h2 class="control-title"> | |
| <i class="fas fa-cogs"></i> | |
| کنترل عملیات | |
| </h2> | |
| </div> | |
| <div class="action-buttons"> | |
| <button type="button" class="btn btn-auto" onclick="startAutoScraping()" id="autoScrapingBtn"> | |
| <i class="fas fa-magic"></i> | |
| اسکراپینگ خودکار | |
| </button> | |
| <button type="button" class="btn btn-primary" onclick="startSelectedScraping()" id="selectedScrapingBtn"> | |
| <i class="fas fa-play"></i> | |
| اسکراپینگ انتخابی | |
| </button> | |
| <button type="button" class="btn btn-warning" onclick="stopScraping()" id="stopScrapingBtn" disabled> | |
| <i class="fas fa-stop"></i> | |
| توقف | |
| </button> | |
| <button type="button" class="btn btn-secondary" onclick="refreshData()"> | |
| <i class="fas fa-sync-alt"></i> | |
| بروزرسانی | |
| </button> | |
| </div> | |
| <div class="action-buttons"> | |
| <button type="button" class="btn btn-secondary" onclick="selectAllSources()"> | |
| <i class="fas fa-check-double"></i> | |
| انتخاب همه | |
| </button> | |
| <button type="button" class="btn btn-secondary" onclick="clearAllSources()"> | |
| <i class="fas fa-times"></i> | |
| پاک کردن انتخاب | |
| </button> | |
| <button type="button" class="btn btn-secondary" onclick="checkSourcesStatus()"> | |
| <i class="fas fa-wifi"></i> | |
| بررسی وضعیت | |
| </button> | |
| </div> | |
| <div class="quick-settings"> | |
| <h4 class="settings-title"> | |
| <i class="fas fa-sliders-h"></i> | |
| تنظیمات سریع | |
| </h4> | |
| <div class="settings-grid"> | |
| <div class="setting-item"> | |
| <label>عمق اسکراپینگ</label> | |
| <select id="scrapingDepth"> | |
| <option value="1">سطح ۱ (صفحه اصلی)</option> | |
| <option value="2" selected>سطح ۲ (یک سطح عمق)</option> | |
| <option value="3">سطح ۳ (دو سطح عمق)</option> | |
| </select> | |
| </div> | |
| <div class="setting-item"> | |
| <label>تاخیر بین درخواستها</label> | |
| <select id="scrapingDelay"> | |
| <option value="1">۱ ثانیه</option> | |
| <option value="2" selected>۲ ثانیه</option> | |
| <option value="3">۳ ثانیه</option> | |
| <option value="5">۵ ثانیه</option> | |
| </select> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- وضعیت فعالیت --> | |
| <div class="activity-status" id="activityStatus"> | |
| <div class="status-content"> | |
| <div class="status-icon status-loading" id="statusIcon"> | |
| <i class="fas fa-spinner fa-spin"></i> | |
| </div> | |
| <div class="status-details"> | |
| <div class="status-title" id="statusTitle">در حال اسکراپینگ...</div> | |
| <div class="status-message" id="statusMessage">در حال اتصال به مراجع...</div> | |
| <div class="status-progress"> | |
| <div class="status-progress-bar" id="statusProgressBar"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- نتایج --> | |
| <div class="results-section" id="resultsSection"> | |
| <div class="results-header"> | |
| <h3 class="results-title"> | |
| <i class="fas fa-check-circle"></i> | |
| نتایج اسکراپینگ | |
| </h3> | |
| <div class="results-actions"> | |
| <button type="button" class="btn btn-success btn-sm" onclick="exportResults()"> | |
| <i class="fas fa-download"></i> | |
| دانلود JSON | |
| </button> | |
| <button type="button" class="btn btn-primary btn-sm" onclick="saveToDatabase()"> | |
| <i class="fas fa-save"></i> | |
| ذخیره | |
| </button> | |
| </div> | |
| </div> | |
| <div class="results-grid" id="resultsGrid"> | |
| <!-- نتایج اینجا نمایش داده میشوند --> | |
| </div> | |
| </div> | |
| </main> | |
| </div> | |
| <!-- Toast Container --> | |
| <div class="toast-container" id="toastContainer"></div> | |
| <script> | |
| // دادههای مراجع حقوقی | |
| const legalSources = [ | |
| { | |
| id: 'divan', | |
| name: 'دیوان عدالت اداری', | |
| url: 'https://www.divan.gov.ir', | |
| category: 'قضایی', | |
| tags: ['دیوان', 'عدالت اداری', 'آرا'], | |
| status: 'online', | |
| priority: 1 | |
| }, | |
| { | |
| id: 'dadiran', | |
| name: 'پورتال ملی خدمات قضایی', | |
| url: 'https://www.dadiran.ir', | |
| category: 'خدمات قضایی', | |
| tags: ['دادگستری', 'خدمات', 'پورتال ملی'], | |
| status: 'online', | |
| priority: 1 | |
| }, | |
| { | |
| id: 'judiciary', | |
| name: 'قوه قضائیه', | |
| url: 'https://www.judiciary.ir', | |
| category: 'قضایی', | |
| tags: ['قوه قضائیه', 'اخبار', 'اطلاعیه'], | |
| status: 'online', | |
| priority: 1 | |
| }, | |
| { | |
| id: 'lawcenter', | |
| name: 'مرکز مطالعات و تحقیقات قوانین', | |
| url: 'https://www.lawcenter.gov.ir', | |
| category: 'تحقیقات', | |
| tags: ['قوانین', 'تحقیقات', 'مطالعات'], | |
| status: 'online', | |
| priority: 2 | |
| }, | |
| { | |
| id: 'mohaseabat', | |
| name: 'دیوان محاسبات کشور', | |
| url: 'https://www.mohaseabat.gov.ir', | |
| category: 'مالی', | |
| tags: ['محاسبات', 'مالی', 'گزارش'], | |
| status: 'online', | |
| priority: 2 | |
| }, | |
| { | |
| id: 'majlis', | |
| name: 'مجلس شورای اسلامی', | |
| url: 'https://www.majlis.ir', | |
| category: 'قانونگذاری', | |
| tags: ['مجلس', 'قوانین', 'لوایح'], | |
| status: 'online', | |
| priority: 1 | |
| }, | |
| { | |
| id: 'shora-negahban', | |
| name: 'شورای نگهبان', | |
| url: 'https://www.shora-rc.ir', | |
| category: 'نظارتی', | |
| tags: ['شورای نگهبان', 'نظارت', 'تایید'], | |
| status: 'online', | |
| priority: 2 | |
| }, | |
| { | |
| id: 'maslahat', | |
| name: 'مجمع تشخیص مصلحت نظام', | |
| url: 'https://www.maslahat.ir', | |
| category: 'مشاورهای', | |
| tags: ['مجمع', 'مصلحت', 'مشاوره'], | |
| status: 'online', | |
| priority: 2 | |
| }, | |
| { | |
| id: 'dadgostary-tehran', | |
| name: 'دادگستری استان تهران', | |
| url: 'https://www.tehran-justice.ir', | |
| category: 'قضایی محلی', | |
| tags: ['دادگستری', 'تهران', 'محلی'], | |
| status: 'online', | |
| priority: 3 | |
| }, | |
| { | |
| id: 'kanoon-vokala', | |
| name: 'کانون وکلای دادگستری', | |
| url: 'https://www.iranbar.org', | |
| category: 'صنفی', | |
| tags: ['وکلا', 'کانون', 'صنفی'], | |
| status: 'online', | |
| priority: 3 | |
| } | |
| ]; | |
| // متغیرهای عمومی | |
| let selectedSources = []; | |
| let scrapingActive = false; | |
| let scrapingResults = []; | |
| let scrapingProgress = 0; | |
| // شروع برنامه | |
| document.addEventListener('DOMContentLoaded', function() { | |
| initializeDashboard(); | |
| loadSources(); | |
| updateStatistics(); | |
| setupEventListeners(); | |
| showToast('سامانه اسکراپینگ حقوقی آماده است', 'success', 'آماده'); | |
| }); | |
| // مقداردهی اولیه | |
| function initializeDashboard() { | |
| console.log('🕷️ Legal Scraping Dashboard Initializing...'); | |
| // شبیهسازی دادههای موجود | |
| const mockData = { | |
| scrapedItems: Math.floor(Math.random() * 1000) + 500, | |
| lastUpdate: new Date().toLocaleDateString('fa-IR') | |
| }; | |
| document.getElementById('scrapedItems').textContent = mockData.scrapedItems; | |
| document.getElementById('lastUpdate').textContent = mockData.lastUpdate; | |
| } | |
| // Setup event listeners | |
| function setupEventListeners() { | |
| // Mobile menu toggle | |
| const mobileMenuToggle = document.getElementById('mobileMenuToggle'); | |
| const sidebar = document.getElementById('sidebar'); | |
| if (mobileMenuToggle && sidebar) { | |
| mobileMenuToggle.addEventListener('click', () => { | |
| sidebar.classList.toggle('open'); | |
| }); | |
| // Close sidebar when clicking outside on mobile | |
| document.addEventListener('click', (e) => { | |
| if (window.innerWidth <= 992 && | |
| sidebar.classList.contains('open') && | |
| !sidebar.contains(e.target) && | |
| !mobileMenuToggle.contains(e.target)) { | |
| sidebar.classList.remove('open'); | |
| } | |
| }); | |
| } | |
| } | |
| // بارگذاری مراجع | |
| function loadSources() { | |
| const sourcesGrid = document.getElementById('sourcesGrid'); | |
| let html = ''; | |
| legalSources.forEach(source => { | |
| html += ` | |
| <div class="source-item" onclick="toggleSource('${source.id}')" id="source-${source.id}"> | |
| <input type="checkbox" class="source-checkbox" id="checkbox-${source.id}" onchange="toggleSource('${source.id}')"> | |
| <div class="source-status ${source.status}"></div> | |
| <div class="source-info"> | |
| <div class="source-name">${source.name}</div> | |
| <div class="source-url">${source.url}</div> | |
| <div class="source-meta"> | |
| <span class="source-tag">${source.category}</span> | |
| ${source.tags.slice(0, 2).map(tag => `<span class="source-tag">${tag}</span>`).join('')} | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| }); | |
| sourcesGrid.innerHTML = html; | |
| } | |
| // تغییر وضعیت انتخاب مرجع | |
| function toggleSource(sourceId) { | |
| const sourceElement = document.getElementById(`source-${sourceId}`); | |
| const checkbox = document.getElementById(`checkbox-${sourceId}`); | |
| if (selectedSources.includes(sourceId)) { | |
| selectedSources = selectedSources.filter(id => id !== sourceId); | |
| sourceElement.classList.remove('selected'); | |
| checkbox.checked = false; | |
| } else { | |
| selectedSources.push(sourceId); | |
| sourceElement.classList.add('selected'); | |
| checkbox.checked = true; | |
| } | |
| updateStatistics(); | |
| } | |
| // انتخاب همه مراجع | |
| function selectAllSources() { | |
| selectedSources = legalSources.map(source => source.id); | |
| legalSources.forEach(source => { | |
| const sourceElement = document.getElementById(`source-${source.id}`); | |
| const checkbox = document.getElementById(`checkbox-${source.id}`); | |
| sourceElement.classList.add('selected'); | |
| checkbox.checked = true; | |
| }); | |
| updateStatistics(); | |
| showToast('همه مراجع انتخاب شدند', 'success', 'انتخاب'); | |
| } | |
| // پاک کردن انتخاب همه مراجع | |
| function clearAllSources() { | |
| selectedSources = []; | |
| legalSources.forEach(source => { | |
| const sourceElement = document.getElementById(`source-${source.id}`); | |
| const checkbox = document.getElementById(`checkbox-${source.id}`); | |
| sourceElement.classList.remove('selected'); | |
| checkbox.checked = false; | |
| }); | |
| updateStatistics(); | |
| showToast('انتخاب همه مراجع پاک شد', 'info', 'پاک سازی'); | |
| } | |
| // بروزرسانی آمار | |
| function updateStatistics() { | |
| document.getElementById('selectedSources').textContent = selectedSources.length; | |
| } | |
| // شروع اسکراپینگ خودکار | |
| function startAutoScraping() { | |
| if (scrapingActive) { | |
| showToast('اسکراپینگ در حال انجام است', 'warning', 'هشدار'); | |
| return; | |
| } | |
| // انتخاب خودکار مراجع بر اساس اولویت | |
| const autoSelectedSources = legalSources | |
| .filter(source => source.priority <= 2) | |
| .map(source => source.id); | |
| if (autoSelectedSources.length === 0) { | |
| showToast('هیچ مرجع مناسبی برای اسکراپینگ خودکار یافت نشد', 'error', 'خطا'); | |
| return; | |
| } | |
| // بروزرسانی انتخابها | |
| clearAllSources(); | |
| autoSelectedSources.forEach(sourceId => { | |
| toggleSource(sourceId); | |
| }); | |
| showToast(`${autoSelectedSources.length} مرجع به صورت خودکار انتخاب شد`, 'info', 'انتخاب خودکار'); | |
| setTimeout(() => { | |
| startScraping(autoSelectedSources); | |
| }, 1000); | |
| } | |
| // شروع اسکراپینگ مراجع انتخابی | |
| function startSelectedScraping() { | |
| if (scrapingActive) { | |
| showToast('اسکراپینگ در حال انجام است', 'warning', 'هشدار'); | |
| return; | |
| } | |
| if (selectedSources.length === 0) { | |
| showToast('لطفاً حداقل یک مرجع انتخاب کنید', 'warning', 'هشدار'); | |
| return; | |
| } | |
| startScraping(selectedSources); | |
| } | |
| // شروع فرآیند اسکراپینگ | |
| function startScraping(sources) { | |
| scrapingActive = true; | |
| scrapingResults = []; | |
| scrapingProgress = 0; | |
| // بروزرسانی UI | |
| document.getElementById('autoScrapingBtn').disabled = true; | |
| document.getElementById('selectedScrapingBtn').disabled = true; | |
| document.getElementById('stopScrapingBtn').disabled = false; | |
| // نمایش وضعیت | |
| const activityStatus = document.getElementById('activityStatus'); | |
| activityStatus.classList.add('show'); | |
| updateScrapingStatus('loading', 'شروع اسکراپینگ...', 'در حال آمادهسازی...', 0); | |
| showToast(`شروع اسکراپینگ ${sources.length} مرجع`, 'info', 'شروع'); | |
| // شبیهسازی فرآیند اسکراپینگ | |
| performScraping(sources); | |
| } | |
| // انجام اسکراپینگ (شبیهسازی) | |
| async function performScraping(sources) { | |
| const totalSteps = sources.length * 5; // 5 مرحله برای هر مرجع | |
| let currentStep = 0; | |
| for (let i = 0; i < sources.length; i++) { | |
| const sourceId = sources[i]; | |
| const source = legalSources.find(s => s.id === sourceId); | |
| if (!source) continue; | |
| // مراحل اسکراپینگ برای هر مرجع | |
| const steps = [ | |
| { title: `اتصال به ${source.name}`, message: 'در حال برقراری ارتباط...' }, | |
| { title: `دریافت صفحه ${source.name}`, message: 'در حال دانلود HTML...' }, | |
| { title: `تجزیه محتوا ${source.name}`, message: 'در حال استخراج اطلاعات...' }, | |
| { title: `پردازش دادهها ${source.name}`, message: 'در حال تمیز کردن متن...' }, | |
| { title: `ذخیره نتایج ${source.name}`, message: 'در حال ذخیره در پایگاه داده...' } | |
| ]; | |
| for (let j = 0; j < steps.length; j++) { | |
| currentStep++; | |
| const progress = (currentStep / totalSteps) * 100; | |
| updateScrapingStatus('loading', steps[j].title, steps[j].message, progress); | |
| // شبیهسازی تاخیر | |
| await new Promise(resolve => setTimeout(resolve, 800)); | |
| if (!scrapingActive) { | |
| return; // متوقف شده | |
| } | |
| } | |
| // تولید نتیجه تصادفی برای این مرجع | |
| const result = generateMockResult(source); | |
| scrapingResults.push(result); | |
| } | |
| // تکمیل اسکراپینگ | |
| completeScrapingSuccess(); | |
| } | |
| // تولید نتیجه تصادفی | |
| function generateMockResult(source) { | |
| const sampleContents = [ | |
| 'قانون جدید در زمینه حقوق شهروندی تصویب شد...', | |
| 'دیوان عدالت اداری رای مهمی در خصوص اختیارات دولتی صادر کرد...', | |
| 'آخرین تغییرات در قوانین مدنی و تجارت اعلام شد...', | |
| 'گزارش جدید از عملکرد دستگاههای قضایی منتشر شد...', | |
| 'رویههای جدید برای تسریع در روند قضایی ابلاغ شد...' | |
| ]; | |
| const randomContent = sampleContents[Math.floor(Math.random() * sampleContents.length)]; | |
| return { | |
| source: source.name, | |
| url: source.url, | |
| title: `آخرین اطلاعات از ${source.name}`, | |
| content: randomContent, | |
| wordCount: Math.floor(Math.random() * 500) + 100, | |
| scrapedAt: new Date().toISOString(), | |
| links: Math.floor(Math.random() * 10) + 5, | |
| status: 'success' | |
| }; | |
| } | |
| // تکمیل موفق اسکراپینگ | |
| function completeScrapingSuccess() { | |
| updateScrapingStatus('success', 'اسکراپینگ تکمیل شد', `${scrapingResults.length} مرجع با موفقیت پردازش شد`, 100); | |
| setTimeout(() => { | |
| document.getElementById('activityStatus').classList.remove('show'); | |
| displayResults(); | |
| resetScrapingState(); | |
| showToast(`اسکراپینگ تکمیل شد - ${scrapingResults.length} نتیجه`, 'success', 'تکمیل'); | |
| // بروزرسانی آمار | |
| const currentScraped = parseInt(document.getElementById('scrapedItems').textContent); | |
| document.getElementById('scrapedItems').textContent = currentScraped + scrapingResults.length; | |
| document.getElementById('lastUpdate').textContent = new Date().toLocaleDateString('fa-IR'); | |
| }, 2000); | |
| } | |
| // نمایش نتایج | |
| function displayResults() { | |
| const resultsSection = document.getElementById('resultsSection'); | |
| const resultsGrid = document.getElementById('resultsGrid'); | |
| let html = ''; | |
| scrapingResults.forEach((result, index) => { | |
| html += ` | |
| <div class="result-card"> | |
| <div class="result-source">${result.source}</div> | |
| <div class="result-meta"> | |
| <span><i class="fas fa-file-text"></i> ${result.wordCount} کلمه</span> | |
| <span><i class="fas fa-link"></i> ${result.links} لینک</span> | |
| <span><i class="fas fa-clock"></i> ${new Date(result.scrapedAt).toLocaleTimeString('fa-IR')}</span> | |
| </div> | |
| <div class="result-content">${result.content}</div> | |
| <div class="result-actions"> | |
| <button class="btn btn-primary btn-sm" onclick="viewFullResult(${index})"> | |
| <i class="fas fa-eye"></i> | |
| مشاهده کامل | |
| </button> | |
| <button class="btn btn-success btn-sm" onclick="saveResult(${index})"> | |
| <i class="fas fa-save"></i> | |
| ذخیره | |
| </button> | |
| </div> | |
| </div> | |
| `; | |
| }); | |
| resultsGrid.innerHTML = html; | |
| resultsSection.classList.add('show'); | |
| } | |
| // بروزرسانی وضعیت اسکراپینگ | |
| function updateScrapingStatus(type, title, message, progress) { | |
| const statusIcon = document.getElementById('statusIcon'); | |
| const statusTitle = document.getElementById('statusTitle'); | |
| const statusMessage = document.getElementById('statusMessage'); | |
| const progressBar = document.getElementById('statusProgressBar'); | |
| statusIcon.className = `status-icon status-${type}`; | |
| statusTitle.textContent = title; | |
| statusMessage.textContent = message; | |
| progressBar.style.width = `${progress}%`; | |
| const icons = { | |
| loading: 'spinner fa-spin', | |
| success: 'check-circle', | |
| error: 'exclamation-triangle' | |
| }; | |
| statusIcon.innerHTML = `<i class="fas fa-${icons[type]}"></i>`; | |
| } | |
| // توقف اسکراپینگ | |
| function stopScraping() { | |
| if (!scrapingActive) return; | |
| scrapingActive = false; | |
| updateScrapingStatus('error', 'اسکراپینگ متوقف شد', 'فرآیند توسط کاربر متوقف شد', scrapingProgress); | |
| setTimeout(() => { | |
| document.getElementById('activityStatus').classList.remove('show'); | |
| resetScrapingState(); | |
| }, 2000); | |
| showToast('اسکراپینگ متوقف شد', 'warning', 'توقف'); | |
| } | |
| // بازنشانی وضعیت اسکراپینگ | |
| function resetScrapingState() { | |
| scrapingActive = false; | |
| scrapingProgress = 0; | |
| document.getElementById('autoScrapingBtn').disabled = false; | |
| document.getElementById('selectedScrapingBtn').disabled = false; | |
| document.getElementById('stopScrapingBtn').disabled = true; | |
| } | |
| // بررسی وضعیت مراجع | |
| function checkSourcesStatus() { | |
| showToast('در حال بررسی وضعیت مراجع...', 'info', 'بررسی'); | |
| // شبیهسازی بررسی | |
| setTimeout(() => { | |
| const onlineCount = legalSources.filter(s => s.status === 'online').length; | |
| showToast(`${onlineCount} از ${legalSources.length} مرجع آنلاین هستند`, 'success', 'وضعیت مراجع'); | |
| }, 1500); | |
| } | |
| // بروزرسانی دادهها | |
| function refreshData() { | |
| showToast('در حال بروزرسانی...', 'info', 'بروزرسانی'); | |
| setTimeout(() => { | |
| updateStatistics(); | |
| showToast('دادهها بروزرسانی شد', 'success', 'بروزرسانی'); | |
| }, 1000); | |
| } | |
| // صادرات نتایج | |
| function exportResults() { | |
| if (scrapingResults.length === 0) { | |
| showToast('نتیجهای برای صادرات وجود ندارد', 'warning', 'هشدار'); | |
| return; | |
| } | |
| const dataStr = JSON.stringify(scrapingResults, null, 2); | |
| const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr); | |
| const exportFileDefaultName = `legal-scraping-results-${new Date().toISOString().split('T')[0]}.json`; | |
| const linkElement = document.createElement('a'); | |
| linkElement.setAttribute('href', dataUri); | |
| linkElement.setAttribute('download', exportFileDefaultName); | |
| linkElement.click(); | |
| showToast('فایل JSON آماده دانلود است', 'success', 'صادرات'); | |
| } | |
| // ذخیره در پایگاه داده | |
| function saveToDatabase() { | |
| if (scrapingResults.length === 0) { | |
| showToast('نتیجهای برای ذخیره وجود ندارد', 'warning', 'هشدار'); | |
| return; | |
| } | |
| // شبیهسازی ذخیره | |
| showToast('در حال ذخیره در پایگاه داده...', 'info', 'ذخیره'); | |
| setTimeout(() => { | |
| showToast(`${scrapingResults.length} نتیجه در پایگاه داده ذخیره شد`, 'success', 'ذخیره موفق'); | |
| }, 2000); | |
| } | |
| // مشاهده نتیجه کامل | |
| function viewFullResult(index) { | |
| const result = scrapingResults[index]; | |
| if (!result) return; | |
| showToast(`مشاهده نتیجه کامل: ${result.source}`, 'info', 'نمایش'); | |
| } | |
| // ذخیره نتیجه منفرد | |
| function saveResult(index) { | |
| const result = scrapingResults[index]; | |
| if (!result) return; | |
| showToast(`نتیجه ${result.source} ذخیره شد`, 'success', 'ذخیره'); | |
| } | |
| // نمایش Toast | |
| function showToast(message, type = 'info', title = 'اعلان') { | |
| const toastContainer = document.getElementById('toastContainer'); | |
| if (!toastContainer) return; | |
| const toast = document.createElement('div'); | |
| toast.className = `toast ${type}`; | |
| const icons = { | |
| success: 'check-circle', | |
| error: 'exclamation-triangle', | |
| warning: 'exclamation-circle', | |
| info: 'info-circle' | |
| }; | |
| toast.innerHTML = ` | |
| <div class="toast-icon"> | |
| <i class="fas fa-${icons[type]}"></i> | |
| </div> | |
| <div class="toast-content"> | |
| <div class="toast-title">${title}</div> | |
| <div class="toast-message">${message}</div> | |
| </div> | |
| <button type="button" class="toast-close" onclick="this.parentElement.remove()"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| `; | |
| toastContainer.appendChild(toast); | |
| setTimeout(() => toast.classList.add('show'), 100); | |
| setTimeout(() => { | |
| if (toast.parentElement) { | |
| toast.classList.remove('show'); | |
| setTimeout(() => { | |
| if (toast.parentElement) { | |
| toast.remove(); | |
| } | |
| }, 300); | |
| } | |
| }, 5000); | |
| } | |
| console.log('🕷️ Legal Scraping Dashboard Ready!'); | |
| </script> | |
| </body> | |
| </html> |