Compare commits
3 Commits
v202603011
...
v202603011
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36deaba00e | ||
|
|
9608b7f601 | ||
|
|
0811b30cbd |
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **Admin: styled confirm dialog for all destructive actions** — replaced all native `window.confirm()` calls with the existing `#confirm-dialog` styled `<dialog>`. A new global `htmx:confirm` handler intercepts HTMX confirmation prompts and shows the dialog; form-submit buttons on affiliate pages were updated to use `confirmAction()`. Affected: pipeline Transform tab (Run Transform, Run Export, Run Full Pipeline), pipeline Overview tab (Run extractor), affiliate product delete, affiliate program delete (both form and list variants).
|
||||||
|
- **Pipeline tabs: no scrollbar** — added `scrollbar-width: none` and `::-webkit-scrollbar { display: none }` to `.pipeline-tabs` to suppress the spurious horizontal scrollbar on narrow viewports.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- **Stale-tier failures no longer exhaust the next proxy tier** — with parallel workers, threads that fetched a proxy just before tier escalation reported failures after the tier changed, immediately blowing through the new tier's circuit breaker before it ever got tried (Rayobyte was skipped entirely). `record_failure(proxy_url)` now checks which tier the proxy belongs to and ignores the circuit breaker when the proxy is from an already-escalated tier.
|
- **Stale-tier failures no longer exhaust the next proxy tier** — with parallel workers, threads that fetched a proxy just before tier escalation reported failures after the tier changed, immediately blowing through the new tier's circuit breaker before it ever got tried (Rayobyte was skipped entirely). `record_failure(proxy_url)` now checks which tier the proxy belongs to and ignores the circuit breaker when the proxy is from an already-escalated tier.
|
||||||
|
|
||||||
|
|||||||
@@ -434,8 +434,10 @@ def _find_venues_with_upcoming_slots(
|
|||||||
if not start_time_str:
|
if not start_time_str:
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
# Parse "2026-02-24T17:00:00" format
|
# start_time is "HH:MM:SS"; combine with resource's start_date
|
||||||
slot_start = datetime.fromisoformat(start_time_str).replace(tzinfo=UTC)
|
start_date = resource.get("start_date", "")
|
||||||
|
full_dt = f"{start_date}T{start_time_str}" if start_date else start_time_str
|
||||||
|
slot_start = datetime.fromisoformat(full_dt).replace(tzinfo=UTC)
|
||||||
if window_start <= slot_start < window_end:
|
if window_start <= slot_start < window_end:
|
||||||
tenant_ids.add(tid)
|
tenant_ids.add(tid)
|
||||||
break # found one upcoming slot, no need to check more
|
break # found one upcoming slot, no need to check more
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
<form method="post" action="{{ url_for('admin.affiliate_delete', product_id=product_id) }}" style="margin:0">
|
<form method="post" action="{{ url_for('admin.affiliate_delete', product_id=product_id) }}" style="margin:0">
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||||
<button type="submit" class="btn-outline"
|
<button type="submit" class="btn-outline"
|
||||||
onclick="return confirm('Delete this product? This cannot be undone.')">Delete</button>
|
onclick="event.preventDefault(); confirmAction('Delete this product? This cannot be undone.', this.closest('form'))">Delete</button>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
<form method="post" action="{{ url_for('admin.affiliate_program_delete', program_id=program_id) }}" style="margin:0">
|
<form method="post" action="{{ url_for('admin.affiliate_program_delete', program_id=program_id) }}" style="margin:0">
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||||
<button type="submit" class="btn-outline"
|
<button type="submit" class="btn-outline"
|
||||||
onclick="return confirm('Delete this program? Blocked if products reference it.')">Delete</button>
|
onclick="event.preventDefault(); confirmAction('Delete this program? Blocked if products reference it.', this.closest('form'))">Delete</button>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -244,5 +244,19 @@ function confirmAction(message, form) {
|
|||||||
document.getElementById('confirm-cancel').addEventListener('click', function() { dialog.close(); }, { once: true });
|
document.getElementById('confirm-cancel').addEventListener('click', function() { dialog.close(); }, { once: true });
|
||||||
dialog.showModal();
|
dialog.showModal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Intercept hx-confirm to use the styled dialog instead of window.confirm()
|
||||||
|
document.body.addEventListener('htmx:confirm', function(evt) {
|
||||||
|
var dialog = document.getElementById('confirm-dialog');
|
||||||
|
if (!dialog) return; // fallback: let HTMX use native confirm
|
||||||
|
evt.preventDefault();
|
||||||
|
document.getElementById('confirm-msg').textContent = evt.detail.question;
|
||||||
|
var ok = document.getElementById('confirm-ok');
|
||||||
|
var newOk = ok.cloneNode(true);
|
||||||
|
ok.replaceWith(newOk);
|
||||||
|
newOk.addEventListener('click', function() { dialog.close(); evt.detail.issueRequest(true); }, { once: true });
|
||||||
|
document.getElementById('confirm-cancel').addEventListener('click', function() { dialog.close(); }, { once: true });
|
||||||
|
dialog.showModal();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
<form method="post" action="{{ url_for('admin.affiliate_program_delete', program_id=prog.id) }}" style="display:inline">
|
<form method="post" action="{{ url_for('admin.affiliate_program_delete', program_id=prog.id) }}" style="display:inline">
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||||
<button type="submit" class="btn-outline btn-sm"
|
<button type="submit" class="btn-outline btn-sm"
|
||||||
onclick="return confirm('Delete {{ prog.name }}? This is blocked if products reference it.')">Delete</button>
|
onclick="event.preventDefault(); confirmAction('Delete {{ prog.name }}? This is blocked if products reference it.', this.closest('form'))">Delete</button>
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
<form method="post" action="{{ url_for('admin.affiliate_delete', product_id=product.id) }}" style="display:inline">
|
<form method="post" action="{{ url_for('admin.affiliate_delete', product_id=product.id) }}" style="display:inline">
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||||
<button type="submit" class="btn-outline btn-sm"
|
<button type="submit" class="btn-outline btn-sm"
|
||||||
onclick="return confirm('Delete {{ product.name }}?')">Delete</button>
|
onclick="event.preventDefault(); confirmAction('Delete {{ product.name }}?', this.closest('form'))">Delete</button>
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
hx-target="#pipeline-overview-content"
|
hx-target="#pipeline-overview-content"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
hx-vals='{"extractor": "{{ wf.name }}", "csrf_token": "{{ csrf_token() }}"}'
|
hx-vals='{"extractor": "{{ wf.name }}", "csrf_token": "{{ csrf_token() }}"}'
|
||||||
onclick="if (!confirm('Run {{ wf.name }} extractor?')) return false;">Run</button>
|
hx-confirm="Run {{ wf.name }} extractor?">Run</button>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-xs text-slate">{{ wf.schedule_label }}</p>
|
<p class="text-xs text-slate">{{ wf.schedule_label }}</p>
|
||||||
{% if run %}
|
{% if run %}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
hx-target="#pipeline-transform-content"
|
hx-target="#pipeline-transform-content"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
hx-vals='{"step": "transform", "csrf_token": "{{ csrf_token() }}"}'
|
hx-vals='{"step": "transform", "csrf_token": "{{ csrf_token() }}"}'
|
||||||
onclick="if (!confirm('Run SQLMesh transform (prod --auto-apply)?')) return false;">
|
hx-confirm="Run SQLMesh transform (prod --auto-apply)?">
|
||||||
Run Transform
|
Run Transform
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
hx-target="#pipeline-transform-content"
|
hx-target="#pipeline-transform-content"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
hx-vals='{"step": "export", "csrf_token": "{{ csrf_token() }}"}'
|
hx-vals='{"step": "export", "csrf_token": "{{ csrf_token() }}"}'
|
||||||
onclick="if (!confirm('Export serving tables (lakehouse → analytics.duckdb)?')) return false;">
|
hx-confirm="Export serving tables (lakehouse → analytics.duckdb)?">
|
||||||
Run Export
|
Run Export
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -138,7 +138,7 @@
|
|||||||
hx-target="#pipeline-transform-content"
|
hx-target="#pipeline-transform-content"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
hx-vals='{"step": "pipeline", "csrf_token": "{{ csrf_token() }}"}'
|
hx-vals='{"step": "pipeline", "csrf_token": "{{ csrf_token() }}"}'
|
||||||
onclick="if (!confirm('Run full ELT pipeline (extract → transform → export)?')) return false;">
|
hx-confirm="Run full ELT pipeline (extract → transform → export)?">
|
||||||
Run Full Pipeline
|
Run Full Pipeline
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,8 +15,9 @@
|
|||||||
|
|
||||||
.pipeline-tabs {
|
.pipeline-tabs {
|
||||||
display: flex; gap: 0; border-bottom: 2px solid #E2E8F0; margin-bottom: 1.5rem;
|
display: flex; gap: 0; border-bottom: 2px solid #E2E8F0; margin-bottom: 1.5rem;
|
||||||
overflow-x: auto; -webkit-overflow-scrolling: touch;
|
overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none;
|
||||||
}
|
}
|
||||||
|
.pipeline-tabs::-webkit-scrollbar { display: none; }
|
||||||
.pipeline-tabs button {
|
.pipeline-tabs button {
|
||||||
padding: 0.625rem 1.25rem; font-size: 0.8125rem; font-weight: 600;
|
padding: 0.625rem 1.25rem; font-size: 0.8125rem; font-weight: 600;
|
||||||
color: #64748B; background: none; border: none; cursor: pointer;
|
color: #64748B; background: none; border: none; cursor: pointer;
|
||||||
|
|||||||
Reference in New Issue
Block a user