Compare commits
No commits in common. "58f0e27dd95b9d9e145f0d230106001c9e122668" and "97f3a7f9dcd4ba3b7f83730127ca2f36b45cbea4" have entirely different histories.
58f0e27dd9
...
97f3a7f9dc
@ -1 +1 @@
|
|||||||
v20260108-33-runchecks-success-override
|
v20260108-32-runchecks-ticket-copy-button
|
||||||
|
|||||||
@ -30,7 +30,6 @@ from ..models import (
|
|||||||
JobRunReviewEvent,
|
JobRunReviewEvent,
|
||||||
MailMessage,
|
MailMessage,
|
||||||
MailObject,
|
MailObject,
|
||||||
Override,
|
|
||||||
User,
|
User,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -39,15 +38,6 @@ from ..models import (
|
|||||||
MISSED_GRACE_WINDOW = timedelta(hours=1)
|
MISSED_GRACE_WINDOW = timedelta(hours=1)
|
||||||
|
|
||||||
|
|
||||||
def _status_is_success(status: str | None) -> bool:
|
|
||||||
s = (status or "").strip().lower()
|
|
||||||
if not s:
|
|
||||||
return False
|
|
||||||
if "override" in s:
|
|
||||||
return True
|
|
||||||
return "success" in s
|
|
||||||
|
|
||||||
|
|
||||||
def _utc_naive_from_local(dt_local: datetime) -> datetime:
|
def _utc_naive_from_local(dt_local: datetime) -> datetime:
|
||||||
"""Convert a timezone-aware local datetime to UTC naive, matching DB convention."""
|
"""Convert a timezone-aware local datetime to UTC naive, matching DB convention."""
|
||||||
if dt_local.tzinfo is None:
|
if dt_local.tzinfo is None:
|
||||||
@ -832,146 +822,3 @@ def api_run_checks_unmark_reviewed():
|
|||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return jsonify({"status": "ok", "updated": updated, "skipped": skipped})
|
return jsonify({"status": "ok", "updated": updated, "skipped": skipped})
|
||||||
|
|
||||||
|
|
||||||
@main_bp.post("/api/run-checks/mark-success-override")
|
|
||||||
@login_required
|
|
||||||
@roles_required("admin", "operator")
|
|
||||||
def api_run_checks_mark_success_override():
|
|
||||||
"""Create a time-bounded override so the selected run is treated as Success (override)."""
|
|
||||||
data = request.get_json(silent=True) or {}
|
|
||||||
try:
|
|
||||||
run_id = int(data.get("run_id") or 0)
|
|
||||||
except Exception:
|
|
||||||
run_id = 0
|
|
||||||
|
|
||||||
if run_id <= 0:
|
|
||||||
return jsonify({"status": "error", "message": "Invalid run_id."}), 400
|
|
||||||
|
|
||||||
run = JobRun.query.get_or_404(run_id)
|
|
||||||
job = Job.query.get_or_404(run.job_id)
|
|
||||||
|
|
||||||
# Do not allow overriding a missed placeholder run.
|
|
||||||
if bool(getattr(run, "missed", False)):
|
|
||||||
return jsonify({"status": "error", "message": "Missed runs cannot be marked as success."}), 400
|
|
||||||
|
|
||||||
# If it is already a success or already overridden, do nothing.
|
|
||||||
if bool(getattr(run, "override_applied", False)):
|
|
||||||
return jsonify({"status": "ok", "message": "Already overridden."})
|
|
||||||
|
|
||||||
if _status_is_success(getattr(run, "status", None)):
|
|
||||||
return jsonify({"status": "ok", "message": "Already successful."})
|
|
||||||
|
|
||||||
# Build a tight validity window around this run.
|
|
||||||
run_ts = getattr(run, "run_at", None) or getattr(run, "created_at", None) or datetime.utcnow()
|
|
||||||
start_at = run_ts - timedelta(minutes=1)
|
|
||||||
end_at = run_ts + timedelta(minutes=1)
|
|
||||||
|
|
||||||
comment = (data.get("comment") or "").strip()
|
|
||||||
if not comment:
|
|
||||||
# Keep it short and consistent; Operators will typically include a ticket number separately.
|
|
||||||
comment = "Marked as success from Run Checks"
|
|
||||||
comment = comment[:2000]
|
|
||||||
|
|
||||||
created_any = False
|
|
||||||
|
|
||||||
# Prefer object-level overrides (scoped to this job) to avoid impacting other jobs.
|
|
||||||
obj_rows = []
|
|
||||||
try:
|
|
||||||
obj_rows = (
|
|
||||||
db.session.execute(
|
|
||||||
text(
|
|
||||||
"""
|
|
||||||
SELECT
|
|
||||||
co.object_name AS object_name,
|
|
||||||
rol.status AS status,
|
|
||||||
rol.error_message AS error_message
|
|
||||||
FROM run_object_links rol
|
|
||||||
JOIN customer_objects co ON co.id = rol.customer_object_id
|
|
||||||
WHERE rol.run_id = :run_id
|
|
||||||
ORDER BY co.object_name ASC
|
|
||||||
"""
|
|
||||||
),
|
|
||||||
{"run_id": run.id},
|
|
||||||
)
|
|
||||||
.mappings()
|
|
||||||
.all()
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
obj_rows = []
|
|
||||||
|
|
||||||
def _obj_is_problem(status: str | None) -> bool:
|
|
||||||
s = (status or "").strip().lower()
|
|
||||||
if not s:
|
|
||||||
return False
|
|
||||||
if "success" in s:
|
|
||||||
return False
|
|
||||||
if "override" in s:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
for rr in obj_rows or []:
|
|
||||||
obj_name = (rr.get("object_name") or "").strip()
|
|
||||||
obj_status = (rr.get("status") or "").strip()
|
|
||||||
if (not obj_name) or (not _obj_is_problem(obj_status)):
|
|
||||||
continue
|
|
||||||
|
|
||||||
err = (rr.get("error_message") or "").strip()
|
|
||||||
ov = Override(
|
|
||||||
level="object",
|
|
||||||
job_id=job.id,
|
|
||||||
object_name=obj_name,
|
|
||||||
match_status=(obj_status or None),
|
|
||||||
match_error_contains=(err[:255] if err else None),
|
|
||||||
treat_as_success=True,
|
|
||||||
active=True,
|
|
||||||
comment=comment,
|
|
||||||
created_by=current_user.username,
|
|
||||||
start_at=start_at,
|
|
||||||
end_at=end_at,
|
|
||||||
)
|
|
||||||
db.session.add(ov)
|
|
||||||
created_any = True
|
|
||||||
|
|
||||||
# If we couldn't build a safe object-scoped override, fall back to a very tight global override.
|
|
||||||
if not created_any:
|
|
||||||
match_error_contains = (getattr(run, "remark", None) or "").strip()
|
|
||||||
if not match_error_contains:
|
|
||||||
# As a last resort, try to match any error message from legacy objects.
|
|
||||||
try:
|
|
||||||
objs = list(run.objects) if hasattr(run, "objects") else []
|
|
||||||
except Exception:
|
|
||||||
objs = []
|
|
||||||
for obj in objs or []:
|
|
||||||
em = (getattr(obj, "error_message", None) or "").strip()
|
|
||||||
if em:
|
|
||||||
match_error_contains = em
|
|
||||||
break
|
|
||||||
|
|
||||||
ov = Override(
|
|
||||||
level="global",
|
|
||||||
backup_software=job.backup_software or None,
|
|
||||||
backup_type=job.backup_type or None,
|
|
||||||
match_status=(getattr(run, "status", None) or None),
|
|
||||||
match_error_contains=(match_error_contains[:255] if match_error_contains else None),
|
|
||||||
treat_as_success=True,
|
|
||||||
active=True,
|
|
||||||
comment=comment,
|
|
||||||
created_by=current_user.username,
|
|
||||||
start_at=start_at,
|
|
||||||
end_at=end_at,
|
|
||||||
)
|
|
||||||
db.session.add(ov)
|
|
||||||
created_any = True
|
|
||||||
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
# Recompute flags so the overview and modal reflect the override immediately.
|
|
||||||
try:
|
|
||||||
from .routes_shared import _recompute_override_flags_for_runs
|
|
||||||
|
|
||||||
_recompute_override_flags_for_runs(job_ids=[job.id], start_at=start_at, end_at=end_at, only_unreviewed=False)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return jsonify({"status": "ok", "message": "Override created."})
|
|
||||||
|
|||||||
@ -263,7 +263,6 @@
|
|||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<a id="rcm_eml_btn" class="btn btn-outline-primary" href="#" style="display:none;" rel="nofollow">Download EML</a>
|
<a id="rcm_eml_btn" class="btn btn-outline-primary" href="#" style="display:none;" rel="nofollow">Download EML</a>
|
||||||
<a id="rcm_job_btn" class="btn btn-outline-secondary" href="#">Open job page</a>
|
<a id="rcm_job_btn" class="btn btn-outline-secondary" href="#">Open job page</a>
|
||||||
<button type="button" class="btn btn-outline-primary" id="rcm_mark_success_override" disabled>Mark success (override)</button>
|
|
||||||
<button type="button" class="btn btn-primary" id="rcm_mark_all_reviewed" disabled>Mark as Reviewed</button>
|
<button type="button" class="btn btn-primary" id="rcm_mark_all_reviewed" disabled>Mark as Reviewed</button>
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
</div>
|
</div>
|
||||||
@ -286,7 +285,6 @@
|
|||||||
var currentPayload = null;
|
var currentPayload = null;
|
||||||
|
|
||||||
var btnMarkAllReviewed = document.getElementById('rcm_mark_all_reviewed');
|
var btnMarkAllReviewed = document.getElementById('rcm_mark_all_reviewed');
|
||||||
var btnMarkSuccessOverride = document.getElementById('rcm_mark_success_override');
|
|
||||||
|
|
||||||
// Shift-click range selection for checkbox rows
|
// Shift-click range selection for checkbox rows
|
||||||
var lastCheckedCb = null;
|
var lastCheckedCb = null;
|
||||||
@ -647,25 +645,6 @@ table.addEventListener('change', function (e) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (btnMarkSuccessOverride) {
|
|
||||||
btnMarkSuccessOverride.addEventListener('click', function () {
|
|
||||||
if (!currentRunId) return;
|
|
||||||
btnMarkSuccessOverride.disabled = true;
|
|
||||||
apiJson('/api/run-checks/mark-success-override', {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({ run_id: currentRunId })
|
|
||||||
})
|
|
||||||
.then(function (j) {
|
|
||||||
if (!j || j.status !== 'ok') throw new Error((j && j.message) || 'Failed');
|
|
||||||
window.location.reload();
|
|
||||||
})
|
|
||||||
.catch(function (e) {
|
|
||||||
alert((e && e.message) ? e.message : 'Failed to mark as success (override).');
|
|
||||||
btnMarkSuccessOverride.disabled = false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderAlerts(payload) {
|
function renderAlerts(payload) {
|
||||||
var box = document.getElementById('rcm_alerts');
|
var box = document.getElementById('rcm_alerts');
|
||||||
if (!box) return;
|
if (!box) return;
|
||||||
@ -903,11 +882,6 @@ if (tStatus) tStatus.textContent = '';
|
|||||||
currentRunId = run.id || null;
|
currentRunId = run.id || null;
|
||||||
if (window.__rcmClearCreateStatus) window.__rcmClearCreateStatus();
|
if (window.__rcmClearCreateStatus) window.__rcmClearCreateStatus();
|
||||||
if (window.__rcmSetCreateDisabled) window.__rcmSetCreateDisabled(!currentRunId);
|
if (window.__rcmSetCreateDisabled) window.__rcmSetCreateDisabled(!currentRunId);
|
||||||
if (btnMarkSuccessOverride) {
|
|
||||||
var _rs = (run.status || '').toString().toLowerCase();
|
|
||||||
var _canOverride = !!currentRunId && !run.missed && (_rs.indexOf('override') === -1) && (_rs.indexOf('success') === -1);
|
|
||||||
btnMarkSuccessOverride.disabled = !_canOverride;
|
|
||||||
}
|
|
||||||
loadAlerts(currentRunId);
|
loadAlerts(currentRunId);
|
||||||
|
|
||||||
var mail = run.mail || null;
|
var mail = run.mail || null;
|
||||||
@ -971,7 +945,6 @@ if (tStatus) tStatus.textContent = '';
|
|||||||
currentJobId = jobId;
|
currentJobId = jobId;
|
||||||
|
|
||||||
if (btnMarkAllReviewed) btnMarkAllReviewed.disabled = true;
|
if (btnMarkAllReviewed) btnMarkAllReviewed.disabled = true;
|
||||||
if (btnMarkSuccessOverride) btnMarkSuccessOverride.disabled = true;
|
|
||||||
|
|
||||||
var modalEl = document.getElementById('runChecksModal');
|
var modalEl = document.getElementById('runChecksModal');
|
||||||
var modal = bootstrap.Modal.getOrCreateInstance(modalEl);
|
var modal = bootstrap.Modal.getOrCreateInstance(modalEl);
|
||||||
|
|||||||
@ -53,16 +53,6 @@
|
|||||||
- Implemented copy-to-clipboard functionality to copy only the ticket number.
|
- Implemented copy-to-clipboard functionality to copy only the ticket number.
|
||||||
- Prevented accidental selection of the appended status text (e.g. “Active”) when copying tickets.
|
- Prevented accidental selection of the appended status text (e.g. “Active”) when copying tickets.
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## v20260108-33-runchecks-success-override
|
|
||||||
|
|
||||||
- Added a manual “Success (override)” action in the Run Checks popup.
|
|
||||||
- Operators and Admins can mark a backup run as successful even if it originally failed or warned.
|
|
||||||
- Implemented backend support to store the override state.
|
|
||||||
- Updated UI logic so overridden runs are displayed with the blue success indicator.
|
|
||||||
- Ensured the override only affects the selected run and does not modify original run data.
|
|
||||||
|
|
||||||
================================================================================================================================================
|
================================================================================================================================================
|
||||||
## v0.1.18
|
## v0.1.18
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user