| <!DOCTYPE html> |
| <html lang="fa" dir="rtl"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>داشبورد هوشمند مدیریت گیتهاب اکشنز</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@400;600;800&display=swap" rel="stylesheet"> |
| <style> |
| body { |
| font-family: 'Vazirmatn', system-ui, -apple-system, sans-serif; |
| background-color: #0f172a; |
| color: #f1f5f9; |
| } |
| </style> |
| </head> |
| <body class="min-h-screen py-10 px-4"> |
| <div class="max-w-6xl w-full mx-auto bg-slate-900 border border-slate-800 rounded-2xl shadow-2xl p-6 md:p-8"> |
| |
| |
| <header class="text-center mb-8 border-b border-slate-800 pb-6 flex flex-col md:flex-row md:justify-between md:items-center"> |
| <div class="text-right"> |
| <h1 class="text-3xl font-extrabold text-blue-500 mb-2">داشبورد مدیریت گیتهاب اکشنز</h1> |
| <p class="text-slate-400 text-sm">مانیتورینگ، پاکسازی و مدیریت فرآیندها به صورت آنی</p> |
| </div> |
| <div class="mt-4 md:mt-0 flex gap-3 flex-wrap"> |
| <a href="/fluxpro" class="bg-indigo-600 hover:bg-indigo-700 text-white font-semibold py-2 px-4 rounded-lg text-sm transition-colors">ابزار Flux Pro</a> |
| <a href="/flux2" class="bg-teal-600 hover:bg-teal-700 text-white font-semibold py-2 px-4 rounded-lg text-sm transition-colors">ابزار FLUX.2</a> |
| </div> |
| </header> |
|
|
| |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8"> |
| <div class="bg-slate-800/40 p-6 rounded-xl border border-slate-800 flex flex-col items-center shadow-lg"> |
| <span class="text-xs font-semibold text-slate-400 mb-1">اکشنهای در حال کار</span> |
| <span id="activeCount" class="text-3xl font-bold text-yellow-500">۰</span> |
| </div> |
| <div class="bg-slate-800/40 p-6 rounded-xl border border-slate-800 flex flex-col items-center shadow-lg"> |
| <span class="text-xs font-semibold text-slate-400 mb-1">اکشنهای غیرفعال امروز</span> |
| <span id="inactiveCount" class="text-3xl font-bold text-blue-500">۰</span> |
| </div> |
| <div class="bg-slate-800/40 p-6 rounded-xl border border-slate-800 flex flex-col items-center shadow-lg"> |
| <span class="text-xs font-semibold text-slate-400 mb-1">کارهای موفق امروز</span> |
| <span id="completedCount" class="text-3xl font-bold text-green-500">۰</span> |
| </div> |
| </div> |
|
|
| |
| <div class="bg-slate-800/20 border border-slate-800/80 p-6 rounded-xl mb-8"> |
| <h3 class="text-lg font-bold text-slate-300 mb-4 text-right">عملیات کنترلی</h3> |
| <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4"> |
| <button onclick="loadActiveActions()" class="bg-yellow-600/20 border border-yellow-500/30 text-yellow-400 hover:bg-yellow-600/30 font-semibold py-2.5 px-4 rounded-lg text-sm transition-all"> |
| دیدن اکشنهای در حال کار |
| </button> |
| <button onclick="loadInactiveToday()" class="bg-blue-600/20 border border-blue-500/30 text-blue-400 hover:bg-blue-600/30 font-semibold py-2.5 px-4 rounded-lg text-sm transition-all"> |
| دیدن اکشنهای غیر فعال امروز |
| </button> |
| <button onclick="loadCompletedTodayCount()" class="bg-green-600/20 border border-green-500/30 text-green-400 hover:bg-green-600/30 font-semibold py-2.5 px-4 rounded-lg text-sm transition-all"> |
| تعداد کارهای انجام شده امروز |
| </button> |
| <button onclick="deleteInactive()" class="bg-red-600/20 border border-red-500/30 text-red-400 hover:bg-red-600/30 font-semibold py-2.5 px-4 rounded-lg text-sm transition-all"> |
| حذف تمام اکشنهای غیرفعال |
| </button> |
| <button onclick="deleteFailedCompleted()" class="bg-rose-600/20 border border-rose-500/30 text-rose-400 hover:bg-rose-600/30 font-semibold py-2.5 px-4 rounded-lg text-sm transition-all"> |
| حذف تمام تکمیل شده و خطا زده |
| </button> |
| <button onclick="cancelDeleteActive()" class="bg-orange-600/20 border border-orange-500/30 text-orange-400 hover:bg-orange-600/30 font-semibold py-2.5 px-4 rounded-lg text-sm transition-all"> |
| غیرفعال کردن اکشنهای فعال و حذف آنها |
| </button> |
| </div> |
| </div> |
|
|
| |
| <div id="displayContainer" class="hidden border border-slate-800 bg-slate-950/40 rounded-2xl p-6"> |
| <div class="flex justify-between items-center mb-4"> |
| <h3 id="tableTitle" class="text-lg font-bold text-slate-300">لیست فرآیندها</h3> |
| <span id="tableLoading" class="hidden text-sm text-blue-400 animate-pulse">در حال بروزرسانی...</span> |
| </div> |
| <div class="overflow-x-auto"> |
| <table class="w-full text-right text-sm text-slate-300"> |
| <thead class="bg-slate-800 text-xs text-slate-400"> |
| <tr> |
| <th class="p-3 rounded-r-lg">شناسه اکشن</th> |
| <th class="p-3">نام اکشن</th> |
| <th class="p-3">وضعیت</th> |
| <th class="p-3">نتیجه</th> |
| <th class="p-3">زمان شروع</th> |
| <th class="p-3 rounded-l-lg text-left">لینک گیتهاب</th> |
| </tr> |
| </thead> |
| <tbody id="runsListTable"> |
| |
| </tbody> |
| </table> |
| </div> |
| </div> |
|
|
| |
| <div id="statusAlert" class="hidden mt-6 bg-slate-800 border border-slate-700 text-slate-300 text-sm rounded-xl p-4 text-right shadow-md"></div> |
| </div> |
|
|
| <script> |
| window.addEventListener('load', () => { |
| updateStats(); |
| }); |
| |
| async function updateStats() { |
| try { |
| const resActive = await fetch('/api/actions/active'); |
| const dataActive = await resActive.json(); |
| if (dataActive.status === 'success') { |
| document.getElementById('activeCount').textContent = dataActive.actions.length; |
| } |
| |
| const resInactive = await fetch('/api/actions/inactive-today'); |
| const dataInactive = await resInactive.json(); |
| if (dataInactive.status === 'success') { |
| document.getElementById('inactiveCount').textContent = dataInactive.actions.length; |
| } |
| |
| const resCompleted = await fetch('/api/actions/completed-today-count'); |
| const dataCompleted = await resCompleted.json(); |
| if (dataCompleted.status === 'success') { |
| document.getElementById('completedCount').textContent = dataCompleted.count; |
| } |
| } catch (e) { |
| console.error('Error updating dashboard stats:', e); |
| } |
| } |
| |
| function showAlert(msg, isSuccess = true) { |
| const alert = document.getElementById('statusAlert'); |
| alert.textContent = msg; |
| alert.className = isSuccess |
| ? 'mt-6 bg-emerald-950/40 border border-emerald-900 text-emerald-300 text-sm rounded-xl p-4 text-right shadow-md' |
| : 'mt-6 bg-rose-950/40 border border-rose-900 text-rose-300 text-sm rounded-xl p-4 text-right shadow-md'; |
| alert.classList.remove('hidden'); |
| setTimeout(() => { alert.classList.add('hidden'); }, 5000); |
| } |
| |
| async function loadActiveActions() { |
| const container = document.getElementById('displayContainer'); |
| const tbody = document.getElementById('runsListTable'); |
| const title = document.getElementById('tableTitle'); |
| const loading = document.getElementById('tableLoading'); |
| |
| container.classList.remove('hidden'); |
| loading.classList.remove('hidden'); |
| tbody.innerHTML = ''; |
| title.textContent = 'اکشنهای در حال کار (Queued / In Progress)'; |
| |
| try { |
| const res = await fetch('/api/actions/active'); |
| const data = await res.json(); |
| loading.classList.add('hidden'); |
| |
| if (data.status === 'success' && data.actions.length > 0) { |
| data.actions.forEach(action => { |
| const row = ` |
| <tr class="border-b border-slate-800/50 hover:bg-slate-800/30"> |
| <td class="p-3 font-mono">${action.id}</td> |
| <td class="p-3 font-semibold text-slate-200">${action.name}</td> |
| <td class="p-3"><span class="px-2 py-1 text-xs font-semibold rounded bg-yellow-950/40 text-yellow-400 border border-yellow-900">${action.status}</span></td> |
| <td class="p-3 text-slate-400">نامعلوم</td> |
| <td class="p-3 text-xs text-slate-400">${new Date(action.created_at).toLocaleString('fa-IR')}</td> |
| <td class="p-3 text-left"><a href="${action.html_url}" target="_blank" class="text-blue-400 hover:underline">دیدن در گیتهاب</a></td> |
| </tr> |
| `; |
| tbody.insertAdjacentHTML('beforeend', row); |
| }); |
| } else { |
| tbody.innerHTML = '<tr><td colspan="6" class="p-4 text-center text-slate-500">هیچ اکشن فعال در حال کارهای شما نیست.</td></tr>'; |
| } |
| } catch (e) { |
| loading.classList.add('hidden'); |
| showAlert('خطا در دریافت اطلاعات!', false); |
| } |
| } |
| |
| async function loadInactiveToday() { |
| const container = document.getElementById('displayContainer'); |
| const tbody = document.getElementById('runsListTable'); |
| const title = document.getElementById('tableTitle'); |
| const loading = document.getElementById('tableLoading'); |
| |
| container.classList.remove('hidden'); |
| loading.classList.remove('hidden'); |
| tbody.innerHTML = ''; |
| title.textContent = 'اکشنهای غیر فعال امروز (Completed)'; |
| |
| try { |
| const res = await fetch('/api/actions/inactive-today'); |
| const data = await res.json(); |
| loading.classList.add('hidden'); |
| |
| if (data.status === 'success' && data.actions.length > 0) { |
| data.actions.forEach(action => { |
| let badgeColor = action.conclusion === 'success' ? 'bg-emerald-950/40 text-emerald-400 border-emerald-900' : 'bg-rose-950/40 text-rose-400 border-rose-900'; |
| const row = ` |
| <tr class="border-b border-slate-800/50 hover:bg-slate-800/30"> |
| <td class="p-3 font-mono">${action.id}</td> |
| <td class="p-3 font-semibold text-slate-200">${action.name}</td> |
| <td class="p-3"><span class="px-2 py-1 text-xs font-semibold rounded bg-blue-950/40 text-blue-400 border border-blue-900">${action.status}</span></td> |
| <td class="p-3"><span class="px-2 py-1 text-xs font-semibold rounded border ${badgeColor}">${action.conclusion || 'نامعلوم'}</span></td> |
| <td class="p-3 text-xs text-slate-400">${new Date(action.created_at).toLocaleString('fa-IR')}</td> |
| <td class="p-3 text-left"><a href="${action.html_url}" target="_blank" class="text-blue-400 hover:underline">دیدن در گیتهاب</a></td> |
| </tr> |
| `; |
| tbody.insertAdjacentHTML('beforeend', row); |
| }); |
| } else { |
| tbody.innerHTML = '<tr><td colspan="6" class="p-4 text-center text-slate-500">هیچ اکشن غیرفعالشدهای برای امروز ثبت نشده است.</td></tr>'; |
| } |
| } catch (e) { |
| loading.classList.add('hidden'); |
| showAlert('خطا در دریافت اطلاعات!', false); |
| } |
| } |
| |
| async function loadCompletedTodayCount() { |
| try { |
| const res = await fetch('/api/actions/completed-today-count'); |
| const data = await res.json(); |
| if (data.status === 'success') { |
| showAlert(`تعداد کارهای با موفقیت انجام شده امروز: ${data.count} عدد`, true); |
| updateStats(); |
| } |
| } catch (e) { |
| showAlert('خطا در دریافت اطلاعات آمار امروز!', false); |
| } |
| } |
| |
| async function deleteInactive() { |
| if (!confirm('آیا مطمئن هستید که میخواهید تمام اکشنهای غیرفعال را حذف کنید؟ این عمل غیر قابل بازگشت است.')) return; |
| showAlert('در حال ارسال درخواست پاکسازی اکشنهای غیرفعال از گیتهاب...', true); |
| try { |
| const res = await fetch('/api/actions/delete-inactive', { method: 'POST' }); |
| const data = await res.json(); |
| if (data.status === 'success') { |
| showAlert(`عملیات موفقیتآمیز بود! تعداد ${data.deleted_count} اکشن غیرفعال با موفقیت حذف شد.`, true); |
| updateStats(); |
| document.getElementById('displayContainer').classList.add('hidden'); |
| } else { |
| showAlert('خطایی در انجام فرآیند حذف رخ داد.', false); |
| } |
| } catch (e) { |
| showAlert('خطای ارتباط با سرور بکاند!', false); |
| } |
| } |
| |
| async function deleteFailedCompleted() { |
| if (!confirm('آیا مطمئن هستید که میخواهید تمام اکشنهای تکمیلشده، خطا زده و کنسل شده را به صورت کامل حذف کنید؟')) return; |
| showAlert('در حال ارسال درخواست پاکسازی اکشنهای تکمیل شده و خطا زده از گیتهاب...', true); |
| try { |
| const res = await fetch('/api/actions/delete-failed-completed', { method: 'POST' }); |
| const data = await res.json(); |
| if (data.status === 'success') { |
| showAlert(`عملیات موفقیتآمیز بود! تعداد ${data.deleted_count} اکشن تکمیل شده یا خطا زده با موفقیت حذف گردید.`, true); |
| updateStats(); |
| document.getElementById('displayContainer').classList.add('hidden'); |
| } else { |
| showAlert('خطایی در انجام فرآیند حذف رخ داد.', false); |
| } |
| } catch (e) { |
| showAlert('خطای ارتباط با سرور بکاند!', false); |
| } |
| } |
| |
| async function cancelDeleteActive() { |
| if (!confirm('آیا مطمئن هستید که میخواهید تمام اکشنهای فعال در حال اجرا را لغو (متوقف) و به طور کامل حذف کنید؟')) return; |
| showAlert('در حال ارسال درخواست لغو و حذف اکشنهای فعال به گیتهاب...', true); |
| try { |
| const res = await fetch('/api/actions/cancel-delete-active', { method: 'POST' }); |
| const data = await res.json(); |
| if (data.status === 'success') { |
| showAlert(`عملیات با موفقیت انجام شد! تعداد ${data.cancelled_count} اکشن فعال لغو شد و ${data.deleted_count} اکشن متوقف شده با موفقیت حذف گردید.`, true); |
| updateStats(); |
| document.getElementById('displayContainer').classList.add('hidden'); |
| } else { |
| showAlert('خطایی در انجام فرآیند لغو و حذف رخ داد.', false); |
| } |
| } catch (e) { |
| showAlert('خطای ارتباط با سرور بکاند!', false); |
| } |
| } |
| </script> |
| </body> |
| </html> |