Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## v0.12.1

- Dashboard layout is now responsive on small screens: header, metrics, filters, table, and detail panel adapt instead of overflowing.

---

## v0.12.0

Adds one-off scheduled tasks: run a task once at a timestamp computed at runtime, rather than on a cadence fixed at import time.
Expand Down
44 changes: 42 additions & 2 deletions fastapi_taskflow/dashboard/css.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,8 @@
[data-theme="dark"] .mc-avg .metric-value { color: #f0abfc; }

/* ── Table ──────────────────────────────────────────────── */
.table-wrap { background: var(--db-surface); border: 1px solid var(--db-border); border-radius: 10px; overflow: hidden; }
table { width: 100%; border-collapse: collapse; }
.table-wrap { background: var(--db-surface); border: 1px solid var(--db-border); border-radius: 10px; overflow-x: auto; -webkit-overflow-scrolling: touch; }
table { width: 100%; min-width: 640px; border-collapse: collapse; }
.th { padding: 9px 14px; text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--db-text-muted); font-weight: 600; background: var(--db-surface-2); border-bottom: 1px solid var(--db-border); cursor: pointer; user-select: none; white-space: nowrap; }
.th:hover { color: var(--db-text-2); background: var(--db-surface-3); }
.th--active { color: #009688; }
Expand Down Expand Up @@ -427,4 +427,44 @@
.q-btn-cancel { display: none; padding: 5px 14px; background: var(--db-surface); color: var(--db-text-2); border: 1px solid var(--db-border); border-radius: 5px; font-size: 0.82rem; cursor: pointer; height: 30px; margin-bottom: 1px; }
.q-btn-cancel:hover { background: var(--db-surface-3); }
.q-empty { color: var(--db-text-faint); padding: 40px; text-align: center; font-size: 0.9rem; }

/* ── Responsive ─────────────────────────────────────────── */
@media (max-width: 1100px) {
.metrics { grid-template-columns: repeat(5, minmax(0, 1fr)); row-gap: 8px; }
.filters-right { width: 240px; }
}

@media (max-width: 900px) {
.top-row { flex-direction: column; }
.filters-right { width: 100%; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); align-content: start; }
.metrics { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.q-grid { grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
}

@media (max-width: 680px) {
.main { padding: 16px 14px 48px; }
.header { height: auto; min-height: 52px; flex-wrap: wrap; padding: 8px 14px; row-gap: 8px; }
.header-title { font-size: 13px; }
.header-badge { display: none; }
.header > div[style*="margin-left:auto"] { margin-left: 0; width: 100%; justify-content: space-between; }
.metrics { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.metric-value { font-size: 17px; }
.metrics-col .search { height: 40px; font-size: 15px; }
.tab-bar { max-width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }
.d-row2, .d-row3 { grid-template-columns: 1fr; gap: 12px 0; }
.analytics-grid { grid-template-columns: 1fr; }
.q-stats { grid-template-columns: repeat(2, 1fr); }
.q-grid { grid-template-columns: 1fr; }
.rtask-grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
.panel { width: 100%; }
.popup { width: 92vw; padding: 20px; }
.bulk-bar, .dlq-toolbar { flex-wrap: wrap; }
.pagination { flex-wrap: wrap; justify-content: center; }
}

@media (max-width: 420px) {
.metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.filters-right { grid-template-columns: 1fr; }
.metrics-col .search { height: 44px; font-size: 16px; }
}
"""
23 changes: 17 additions & 6 deletions fastapi_taskflow/dashboard/js.py
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,8 @@

// ── Build header tabs ────────────────────────────────────────
const tabsEl = document.getElementById('detail-tabs');
var prevActiveBtn = tabsEl.querySelector('.panel-tab--active');
var activePanelId = prevActiveBtn ? prevActiveBtn.dataset.panel : 'panel-details';
tabsEl.innerHTML = '';

function makeTab(label, panelId, extraClass) {
Expand All @@ -631,9 +633,17 @@
return btn;
}

makeTab('Details', 'panel-details').classList.add('panel-tab--active');
if (hasLogs) makeTab('Logs (' + task.logs.length + ')', 'panel-logs');
if (hasError) makeTab('Error', 'panel-error', 'panel-tab--error');
var detailsBtn = makeTab('Details', 'panel-details');
var logsBtn = hasLogs ? makeTab('Logs (' + task.logs.length + ')', 'panel-logs') : null;
var errorBtn = hasError ? makeTab('Error', 'panel-error', 'panel-tab--error') : null;

// Keep whichever tab was active before this re-render (e.g. a live SSE
// update refreshing the open task) instead of always snapping back to
// Details.
var tabButtons = { 'panel-details': detailsBtn, 'panel-logs': logsBtn, 'panel-error': errorBtn };
var activeBtn = tabButtons[activePanelId] || detailsBtn;
activeBtn.classList.add('panel-tab--active');
activePanelId = activeBtn.dataset.panel;

// ── Details panel ────────────────────────────────────────────
var detailsHtml =
Expand Down Expand Up @@ -776,10 +786,11 @@
: '';

// ── Render all three panels into detail-content ──────────────
function panelCls(id) { return 'd-tab-panel' + (id === activePanelId ? ' d-tab-panel--active' : ''); }
document.getElementById('detail-content').innerHTML =
'<div id="panel-details" class="d-tab-panel d-tab-panel--active">' + detailsHtml + '</div>'
+ (hasLogs ? '<div id="panel-logs" class="d-tab-panel">' + logsHtml + '</div>' : '')
+ (hasError ? '<div id="panel-error" class="d-tab-panel">' + errorHtml + '</div>' : '');
'<div id="panel-details" class="' + panelCls('panel-details') + '">' + detailsHtml + '</div>'
+ (hasLogs ? '<div id="panel-logs" class="' + panelCls('panel-logs') + '">' + logsHtml + '</div>' : '')
+ (hasError ? '<div id="panel-error" class="' + panelCls('panel-error') + '">' + errorHtml + '</div>' : '');
}

function switchTab(btn) {
Expand Down
8 changes: 4 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "fastapi-taskflow"
version = "0.12.0"
version = "0.12.1"
description = "Retries, persistence, and visibility for FastAPI's BackgroundTasks. Production-grade background tasks. No workers, no brokers, drop-in."
readme = "README.md"
requires-python = ">=3.10"
Expand Down Expand Up @@ -35,7 +35,7 @@ scheduler = [
"croniter>=1.4",
]
encryption = [
"cryptography>=41.0",
"cryptography>=50.0",
]
process = [
"cloudpickle>=3.0",
Expand All @@ -45,7 +45,7 @@ all = [
"psycopg2-binary>=2.9",
"PyMySQL>=1.1",
"croniter>=1.4",
"cryptography>=41.0",
"cryptography>=50.0",
"cloudpickle>=3.0",
]
dev = [
Expand All @@ -63,7 +63,7 @@ docs = [
dev = [
"cloudpickle>=3.0",
"croniter>=1.4",
"cryptography>=41.0",
"cryptography>=50.0",
"httpx>=0.28.1",
"mkdocs-material>=9.5",
"mypy>=1.20.0",
Expand Down
Loading
Loading