Auto-commit local changes before build (2026-03-20 12:13:27)
This commit is contained in:
parent
fb841fb4e6
commit
0cbd3e59b1
@ -92,6 +92,7 @@ def cove_accounts():
|
||||
)
|
||||
|
||||
customers = Customer.query.filter_by(active=True).order_by(Customer.name.asc()).all()
|
||||
customer_rows = [{"id": c.id, "name": c.name} for c in customers]
|
||||
jobs = Job.query.filter_by(archived=False).order_by(Job.job_name.asc()).all()
|
||||
|
||||
for acc in unmatched + matched:
|
||||
@ -104,7 +105,7 @@ def cove_accounts():
|
||||
"main/cove_accounts.html",
|
||||
unmatched=unmatched,
|
||||
matched=matched,
|
||||
customers=customers,
|
||||
customers=customer_rows,
|
||||
jobs=jobs,
|
||||
settings=settings,
|
||||
STATUS_LABELS={
|
||||
@ -150,9 +151,8 @@ def cove_account_link(cove_account_db_id: int):
|
||||
flash("Customer not found.", "danger")
|
||||
return redirect(url_for("main.cove_accounts"))
|
||||
|
||||
default_job_name = (cove_acc.account_name or cove_acc.computer_name or f"Cove account {cove_acc.account_id}").strip()
|
||||
job_name = (request.form.get("job_name") or default_job_name).strip()
|
||||
backup_type = (request.form.get("backup_type") or _derive_backup_type_for_account(cove_acc)).strip()
|
||||
job_name = (cove_acc.account_name or cove_acc.computer_name or f"Cove account {cove_acc.account_id}").strip()
|
||||
backup_type = _derive_backup_type_for_account(cove_acc)
|
||||
|
||||
job = Job(
|
||||
customer_id=customer.id,
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
{# ── Unmatched accounts (need a job) ─────────────────────────────────────── #}
|
||||
{% if unmatched %}
|
||||
<h4 class="mb-2">Unmatched <span class="badge bg-warning text-dark">{{ unmatched|length }}</span></h4>
|
||||
<p class="text-muted small mb-3">These accounts have no linked job yet. Create a new job or link to an existing one.</p>
|
||||
<p class="text-muted small mb-3">Click a row to create a new job or link to an existing one.</p>
|
||||
|
||||
<div class="table-responsive mb-4">
|
||||
<table class="table table-sm table-hover align-middle">
|
||||
@ -36,12 +36,17 @@
|
||||
<th>Last status</th>
|
||||
<th>Last run</th>
|
||||
<th>First seen</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for acc in unmatched %}
|
||||
<tr>
|
||||
<tr class="cove-unmatched-row"
|
||||
style="cursor: pointer;"
|
||||
data-id="{{ acc.id }}"
|
||||
data-job-name="{{ acc.derived_job_name | e }}"
|
||||
data-backup-software="{{ acc.derived_backup_software | e }}"
|
||||
data-backup-type="{{ acc.derived_backup_type | e }}"
|
||||
data-cove-customer="{{ acc.customer_name | e if acc.customer_name else '' }}">
|
||||
<td>{{ acc.derived_backup_software }}</td>
|
||||
<td>{{ acc.derived_backup_type }}</td>
|
||||
<td>{{ acc.derived_job_name }}</td>
|
||||
@ -57,108 +62,7 @@
|
||||
</td>
|
||||
<td class="text-muted small">{{ acc.last_run_at|local_datetime if acc.last_run_at else '—' }}</td>
|
||||
<td class="text-muted small">{{ acc.first_seen_at|local_datetime }}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#link-modal-{{ acc.id }}">
|
||||
Link / Create job
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{# Link modal #}
|
||||
<div class="modal fade" id="link-modal-{{ acc.id }}" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Link: {{ acc.account_name or acc.account_id }}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="text-muted small mb-3">
|
||||
Cove account <strong>{{ acc.account_id }}</strong> –
|
||||
customer: <strong>{{ acc.customer_name or '?' }}</strong>
|
||||
</p>
|
||||
|
||||
<ul class="nav nav-tabs mb-3" id="tab-{{ acc.id }}" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" data-bs-toggle="tab"
|
||||
data-bs-target="#create-{{ acc.id }}" type="button">
|
||||
Create new job
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" data-bs-toggle="tab"
|
||||
data-bs-target="#existing-{{ acc.id }}" type="button">
|
||||
Link to existing job
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
|
||||
{# Tab 1: Create new job #}
|
||||
<div class="tab-pane fade show active" id="create-{{ acc.id }}">
|
||||
<form method="post" action="{{ url_for('main.cove_account_link', cove_account_db_id=acc.id) }}">
|
||||
<input type="hidden" name="action" value="create" />
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Customer <span class="text-danger">*</span></label>
|
||||
<select class="form-select" name="customer_id" required>
|
||||
<option value="">Select customer…</option>
|
||||
{% for c in customers %}
|
||||
<option value="{{ c.id }}"
|
||||
{% if acc.customer_name and acc.customer_name.lower() == c.name.lower() %}selected{% endif %}>
|
||||
{{ c.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Job name</label>
|
||||
<input type="text" class="form-control" name="job_name"
|
||||
value="{{ acc.derived_job_name }}" />
|
||||
<div class="form-text">Defaults to the Cove account name.</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Backup type</label>
|
||||
<input type="text" class="form-control" name="backup_type"
|
||||
value="{{ acc.derived_backup_type }}" />
|
||||
<div class="form-text">Derived from Cove datasource profile.</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Create job & link</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{# Tab 2: Link to existing job #}
|
||||
<div class="tab-pane fade" id="existing-{{ acc.id }}">
|
||||
<form method="post" action="{{ url_for('main.cove_account_link', cove_account_db_id=acc.id) }}">
|
||||
<input type="hidden" name="action" value="link" />
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Job <span class="text-danger">*</span></label>
|
||||
<select class="form-select" name="job_id" required>
|
||||
<option value="">Select job…</option>
|
||||
{% for j in jobs %}
|
||||
<option value="{{ j.id }}">
|
||||
{{ j.customer.name ~ ' – ' if j.customer else '' }}{{ j.backup_software }} / {{ j.job_name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Link to job</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>{# /tab-content #}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>{# /modal #}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
@ -238,4 +142,158 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# ── Shared link/create modal ─────────────────────────────────────────────── #}
|
||||
<div class="modal fade" id="coveLinkModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="coveLinkModalTitle">Link Cove account</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<ul class="nav nav-tabs mb-3" id="coveLinkTabs" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" data-bs-toggle="tab"
|
||||
data-bs-target="#coveCreateTab" type="button">Create new job</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" data-bs-toggle="tab"
|
||||
data-bs-target="#coveExistingTab" type="button">Link to existing</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
|
||||
{# Tab 1: Create new job #}
|
||||
<div class="tab-pane fade show active" id="coveCreateTab">
|
||||
<form method="post" id="coveCreateForm">
|
||||
<input type="hidden" name="action" value="create" />
|
||||
<input type="hidden" name="customer_id" id="coveCustomerId" />
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Customer <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="coveCustomerInput"
|
||||
list="coveCustomerList" placeholder="Type customer name…" autocomplete="off" />
|
||||
<datalist id="coveCustomerList">
|
||||
{% for c in customers %}
|
||||
<option value="{{ c.name | e }}"></option>
|
||||
{% endfor %}
|
||||
</datalist>
|
||||
</div>
|
||||
|
||||
<dl class="row mb-3 dl-compact">
|
||||
<dt class="col-5">Job name</dt>
|
||||
<dd class="col-7" id="coveDisplayJobName"></dd>
|
||||
<dt class="col-5">Backup software</dt>
|
||||
<dd class="col-7" id="coveDisplayBackupSoftware"></dd>
|
||||
<dt class="col-5">Backup type</dt>
|
||||
<dd class="col-7" id="coveDisplayBackupType"></dd>
|
||||
</dl>
|
||||
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Create job & link</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{# Tab 2: Link to existing job #}
|
||||
<div class="tab-pane fade" id="coveExistingTab">
|
||||
<form method="post" id="coveLinkExistingForm">
|
||||
<input type="hidden" name="action" value="link" />
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Job <span class="text-danger">*</span></label>
|
||||
<select class="form-select" name="job_id" required>
|
||||
<option value="">Select job…</option>
|
||||
{% for j in jobs %}
|
||||
<option value="{{ j.id }}">
|
||||
{{ j.customer.name ~ ' – ' if j.customer else '' }}{{ j.backup_software }} / {{ j.job_name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Link to job</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>{# /tab-content #}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var modalEl = document.getElementById('coveLinkModal');
|
||||
if (!modalEl) return;
|
||||
var modal = new bootstrap.Modal(modalEl);
|
||||
|
||||
var customers = {{ customers | tojson | safe }};
|
||||
|
||||
function findCustomerIdByName(name) {
|
||||
var n = (name || '').trim().toLowerCase();
|
||||
for (var i = 0; i < customers.length; i++) {
|
||||
if (customers[i].name.toLowerCase() === n) return customers[i].id;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
var linkUrlTpl = "{{ url_for('main.cove_account_link', cove_account_db_id=0) }}";
|
||||
|
||||
document.querySelectorAll('tr.cove-unmatched-row').forEach(function (row) {
|
||||
row.addEventListener('click', function () {
|
||||
var id = row.getAttribute('data-id');
|
||||
var jobName = row.getAttribute('data-job-name') || '';
|
||||
var backupSoftware = row.getAttribute('data-backup-software') || '';
|
||||
var backupType = row.getAttribute('data-backup-type') || '';
|
||||
var coveCustomer = row.getAttribute('data-cove-customer') || '';
|
||||
var linkUrl = linkUrlTpl.replace('0', id);
|
||||
|
||||
document.getElementById('coveLinkModalTitle').textContent = jobName;
|
||||
document.getElementById('coveDisplayJobName').textContent = jobName;
|
||||
document.getElementById('coveDisplayBackupSoftware').textContent = backupSoftware;
|
||||
document.getElementById('coveDisplayBackupType').textContent = backupType;
|
||||
|
||||
// Pre-fill customer if Cove's customer name matches an existing customer
|
||||
var customerInput = document.getElementById('coveCustomerInput');
|
||||
var customerIdField = document.getElementById('coveCustomerId');
|
||||
if (customerInput) customerInput.value = '';
|
||||
if (customerIdField) customerIdField.value = '';
|
||||
if (coveCustomer && customerInput) {
|
||||
var matchedId = findCustomerIdByName(coveCustomer);
|
||||
if (matchedId) {
|
||||
customerInput.value = coveCustomer;
|
||||
customerIdField.value = String(matchedId);
|
||||
} else {
|
||||
customerInput.value = coveCustomer;
|
||||
}
|
||||
}
|
||||
|
||||
var createForm = document.getElementById('coveCreateForm');
|
||||
var linkForm = document.getElementById('coveLinkExistingForm');
|
||||
|
||||
if (createForm) {
|
||||
createForm.action = linkUrl;
|
||||
createForm.onsubmit = function (ev) {
|
||||
var cid = findCustomerIdByName(customerInput ? customerInput.value : '');
|
||||
if (!cid) {
|
||||
ev.preventDefault();
|
||||
alert('Please select an existing customer name from the list.');
|
||||
return false;
|
||||
}
|
||||
if (customerIdField) customerIdField.value = String(cid);
|
||||
};
|
||||
}
|
||||
if (linkForm) linkForm.action = linkUrl;
|
||||
|
||||
modal.show();
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@ -2,6 +2,14 @@
|
||||
|
||||
This file documents all changes made to this project via Claude Code.
|
||||
|
||||
## [2026-03-20] (2)
|
||||
|
||||
### Changed
|
||||
- Cove Accounts page: same clickable-row UX as Cloud Connect — removed per-row "Link / Create Job" button and inline modals (N modals → 1 shared modal):
|
||||
- Unmatched rows are clickable; modal pre-fills job name, backup software, backup type as read-only
|
||||
- Customer via datalist auto-complete; Cove's own customer name is used as pre-fill suggestion when it matches an existing customer
|
||||
- `routes_cove.py`: `customers` passed as dicts for `tojson`; `job_name` and `backup_type` now derived server-side (no longer read from form fields)
|
||||
|
||||
## [2026-03-20]
|
||||
|
||||
### Fixed
|
||||
|
||||
Loading…
Reference in New Issue
Block a user