feat: implement jobs dashboard template with status overview and management actions
This commit is contained in:
parent
03f4814bcc
commit
b125799129
@ -77,6 +77,58 @@
|
||||
.jobs-stat.active .jobs-stat-lbl {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.text-right { text-align: right !important; }
|
||||
|
||||
/* Actions Dropdown */
|
||||
.dropdown {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
.dropdown-menu {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 100%;
|
||||
margin-top: 6px;
|
||||
background: #111422;
|
||||
border: 1px solid var(--border-bright);
|
||||
border-radius: var(--radius-sm);
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6);
|
||||
z-index: 1000;
|
||||
min-width: 175px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.dropdown-menu.show {
|
||||
display: block;
|
||||
}
|
||||
.dropdown-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 16px;
|
||||
color: var(--text-secondary);
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
background: none;
|
||||
border: none;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
.dropdown-item:hover {
|
||||
background: rgba(99, 102, 241, 0.15);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.dropdown-item.text-danger {
|
||||
color: #fca5a5;
|
||||
}
|
||||
.dropdown-item.text-danger:hover {
|
||||
background: rgba(239, 68, 68, 0.15);
|
||||
color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
@ -133,7 +185,7 @@
|
||||
<th>Status</th>
|
||||
<th>Schedule</th>
|
||||
<th>Started</th>
|
||||
<th>Actions</th>
|
||||
<th class="text-right" style="width: 140px; padding-right: 20px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -153,12 +205,14 @@
|
||||
<span class="badge badge-purple running-pulse">Running</span>
|
||||
{% elif job.status == 'finished' %}
|
||||
<span class="badge badge-green">Finished</span>
|
||||
{% elif 'finished with errors' in job.status %}
|
||||
<span class="badge badge-yellow" title="{{ job.status }}" style="cursor: help;">Finished *</span>
|
||||
{% elif job.status == 'queued' %}
|
||||
<span class="badge badge-yellow">Queued</span>
|
||||
{% elif job.status.startswith('failed') %}
|
||||
<span class="badge badge-red" title="{{ job.status }}">Failed</span>
|
||||
{% else %}
|
||||
<span class="badge badge-gray">{{ job.status }}</span>
|
||||
<span class="badge badge-gray" title="{{ job.status }}">{{ job.status }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
@ -205,44 +259,63 @@
|
||||
<td class="text-small text-muted">
|
||||
{{ job.started_fmt }}
|
||||
</td>
|
||||
<td>
|
||||
<div class="job-actions" style="display: flex; gap: 8px; align-items: center;">
|
||||
<a href="/job/{{ job.id }}" class="btn btn-ghost btn-sm">
|
||||
<span style="margin-right: 4px; font-size: 12px; line-height: 1;">👁︎</span>View
|
||||
<td class="text-right" style="padding-right: 20px;">
|
||||
<div style="display: flex; gap: 6px; align-items: center; justify-content: flex-end;">
|
||||
<!-- View Button -->
|
||||
<a href="/job/{{ job.id }}" class="btn btn-ghost btn-sm" style="padding: 6px 12px; font-size: 12.5px;" title="View Details">
|
||||
<span style="font-size: 12px; line-height: 1;">👁︎</span> View
|
||||
</a>
|
||||
{% if job.status != 'running' and job.status != 'queued' %}
|
||||
<form method="post" action="/job/{{ job.id }}/run" style="margin: 0;">
|
||||
<button class="btn btn-primary btn-sm" type="submit">
|
||||
<span style="margin-right: 4px; font-size: 11px; line-height: 1;">▶︎</span>Run Now
|
||||
</button>
|
||||
</form>
|
||||
<a href="/job/{{ job.id }}/edit" class="btn btn-secondary btn-sm">
|
||||
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 4px;"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 1 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>Edit
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<!-- Quick Run/Stop Button -->
|
||||
{% if job.status == 'running' or job.status == 'queued' %}
|
||||
<form method="post" action="/job/{{ job.id }}/stop"
|
||||
style="margin: 0;"
|
||||
onsubmit="return confirm('Are you sure you want to stop this running backup?')">
|
||||
<button class="btn btn-danger btn-sm" type="submit">
|
||||
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 4px;"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/></svg>
|
||||
Force Stop
|
||||
<form method="post" action="/job/{{ job.id }}/stop" style="margin: 0;" onsubmit="return confirm('Are you sure you want to stop this running backup?')">
|
||||
<button class="btn btn-danger btn-sm" type="submit" style="width: 28px; height: 28px; padding: 0; min-width: 28px;" title="Force Stop">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="display:block; margin:auto;"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/></svg>
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<form method="post" action="/job/{{ job.id }}/run" style="margin: 0;">
|
||||
<button class="btn btn-primary btn-sm" type="submit" style="width: 28px; height: 28px; padding: 0; min-width: 28px;" title="Run Backup Now">
|
||||
<span style="font-size: 10px; line-height: 1; display:block; margin:auto;">▶︎</span>
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
<!-- Actions Dropdown -->
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-secondary btn-sm" onclick="toggleDropdown(event, 'drop-{{ job.id }}')" style="width: 28px; height: 28px; padding: 0; min-width: 28px; display: flex; align-items: center; justify-content: center;" title="More Actions">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="display:block; margin:auto;"><circle cx="12" cy="12" r="1.5"/><circle cx="12" cy="5" r="1.5"/><circle cx="12" cy="19" r="1.5"/></svg>
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if job.schedule_id %}
|
||||
<form method="post" action="/job/{{ job.id }}/cancel-schedule"
|
||||
style="margin: 0;"
|
||||
onsubmit="return confirm('Cancel this schedule?')">
|
||||
<button class="btn btn-secondary btn-sm" type="submit">Cancel Schedule</button>
|
||||
</form>
|
||||
{% elif job.schedule_type and job.schedule_type != 'now' %}
|
||||
<form method="post" action="/job/{{ job.id }}/reactivate-schedule"
|
||||
style="margin: 0;"
|
||||
onsubmit="return confirm('Reactivate this schedule?')">
|
||||
<button class="btn btn-success btn-sm" type="submit">Reactivate Schedule</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
<div class="dropdown-menu" id="drop-{{ job.id }}">
|
||||
<a href="/job/{{ job.id }}/edit" class="dropdown-item">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="margin-right: 4px;"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 1 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
|
||||
Edit Config
|
||||
</a>
|
||||
|
||||
{% if job.schedule_id %}
|
||||
<form method="post" action="/job/{{ job.id }}/cancel-schedule" style="margin: 0;">
|
||||
<button class="dropdown-item text-danger" type="submit" onclick="return confirm('Cancel this schedule?')">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="margin-right: 4px; color: var(--danger);"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/></svg>
|
||||
Cancel Schedule
|
||||
</button>
|
||||
</form>
|
||||
{% elif job.schedule_type and job.schedule_type != 'now' %}
|
||||
<form method="post" action="/job/{{ job.id }}/reactivate-schedule" style="margin: 0;">
|
||||
<button class="dropdown-item" type="submit" onclick="return confirm('Reactivate this schedule?')">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="margin-right: 4px; color: var(--success);"><circle cx="12" cy="12" r="10"/><polyline points="12 8 12 12 14 14"/></svg>
|
||||
Reactivate Schedule
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="/job/{{ job.id }}/delete" style="margin: 0;">
|
||||
<button class="dropdown-item text-danger" type="submit" onclick="return confirm('Are you sure you want to delete this job? This will cancel any active schedule and delete the job logs.')">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="margin-right: 4px; color: var(--danger);"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
|
||||
Delete Job
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@ -268,6 +341,27 @@
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// Dropdown Manager
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!e.target.closest('.dropdown')) {
|
||||
document.querySelectorAll('.dropdown-menu.show').forEach(function(menu) {
|
||||
menu.classList.remove('show');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
window.toggleDropdown = function(e, menuId) {
|
||||
e.stopPropagation();
|
||||
const menu = document.getElementById(menuId);
|
||||
const wasShown = menu.classList.contains('show');
|
||||
document.querySelectorAll('.dropdown-menu.show').forEach(function(m) {
|
||||
m.classList.remove('show');
|
||||
});
|
||||
if (!wasShown) {
|
||||
menu.classList.add('show');
|
||||
}
|
||||
};
|
||||
|
||||
// Auto-refresh jobs page every 8 seconds if any jobs are running
|
||||
const hasRunning = {{ 'true' if jobs|selectattr('status','equalto','running')|list|length > 0 else 'false' }};
|
||||
if (hasRunning) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user