Auto-commit local changes before build (2026-03-20 11:31:49)
This commit is contained in:
parent
44214cc2c6
commit
c7021d393d
@ -224,20 +224,22 @@ def upsert_cloud_connect_report(mail_message_id: int, html_body: str) -> dict:
|
||||
counters = {"total": len(rows), "linked": 0, "unlinked": 0, "created": 0, "skipped": 0}
|
||||
|
||||
for row in rows:
|
||||
user = row["user"]
|
||||
section = row["section"]
|
||||
user = row["user"]
|
||||
section = row["section"]
|
||||
repo_name = row["repo_name"] or "" # never None — part of unique key
|
||||
|
||||
# Upsert the staging record — keyed on (user, section).
|
||||
acc = CloudConnectAccount.query.filter_by(user=user, section=section).first()
|
||||
# Upsert the staging record — keyed on (user, section, repo_name).
|
||||
acc = CloudConnectAccount.query.filter_by(user=user, section=section, repo_name=repo_name).first()
|
||||
if acc is None:
|
||||
acc = CloudConnectAccount(
|
||||
user=user,
|
||||
section=section,
|
||||
repo_name=repo_name,
|
||||
first_seen_at=now,
|
||||
)
|
||||
db.session.add(acc)
|
||||
|
||||
acc.repo_name = row["repo_name"]
|
||||
acc.repo_name = repo_name
|
||||
acc.repo_type = row["repo_type"]
|
||||
acc.num_items = row["num_items"]
|
||||
acc.total_quota = row["total_quota"]
|
||||
@ -262,7 +264,8 @@ def upsert_cloud_connect_report(mail_message_id: int, html_body: str) -> dict:
|
||||
continue
|
||||
|
||||
# Deduplicate: one run per job per report date.
|
||||
external_id = f"vcc-{user}-{section}-{report_date}".lower().replace(" ", "_")
|
||||
repo_slug = repo_name.lower().replace(" ", "_")
|
||||
external_id = f"vcc-{user}-{section}-{repo_slug}-{report_date}".lower().replace(" ", "_")
|
||||
|
||||
existing = JobRun.query.filter_by(job_id=job.id, external_id=external_id).first()
|
||||
if existing:
|
||||
|
||||
@ -40,7 +40,9 @@ def cloud_connect_accounts():
|
||||
acc.derived_backup_type = (
|
||||
"Cloud Connect Agent" if acc.section == "Agent" else "Cloud Connect Backup"
|
||||
)
|
||||
acc.derived_job_name = acc.user
|
||||
# Use repo_name as the suggested job name if set (distinguishes multiple repos
|
||||
# per user); fall back to user name when repo_name is absent.
|
||||
acc.derived_job_name = acc.repo_name.strip() if acc.repo_name and acc.repo_name.strip() else acc.user
|
||||
|
||||
return render_template(
|
||||
"main/cloud_connect_accounts.html",
|
||||
@ -149,7 +151,7 @@ def cloud_connect_account_link(cc_account_db_id: int):
|
||||
|
||||
customer = Customer.query.get_or_404(customer_id)
|
||||
|
||||
job_name = acc.user.strip()
|
||||
job_name = (acc.repo_name.strip() if acc.repo_name and acc.repo_name.strip() else acc.user.strip())
|
||||
backup_type = "Cloud Connect Agent" if acc.section == "Agent" else "Cloud Connect Backup"
|
||||
|
||||
job = Job(
|
||||
|
||||
@ -1312,6 +1312,60 @@ def migrate_cloud_connect_accounts_table() -> None:
|
||||
print(f"[migrations] Failed to migrate cloud_connect_accounts table: {exc}")
|
||||
|
||||
|
||||
def migrate_cc_accounts_repo_unique_key() -> None:
|
||||
"""Extend the cloud_connect_accounts unique key to include repo_name.
|
||||
|
||||
Old key: (user, section)
|
||||
New key: (user, section, repo_name)
|
||||
|
||||
This allows a single user to have multiple repository entries in the Cloud Connect
|
||||
daily report (e.g. both a Veeam Cloud Connect Repository and an Immutable repository),
|
||||
each linked to a separate Backupchecks job.
|
||||
"""
|
||||
try:
|
||||
engine = db.get_engine()
|
||||
except Exception as exc:
|
||||
print(f"[migrations] Could not get engine for cc repo key migration: {exc}")
|
||||
return
|
||||
|
||||
try:
|
||||
with engine.begin() as conn:
|
||||
# Make repo_name NOT NULL with default '' (required for unique constraint).
|
||||
conn.execute(text(
|
||||
"UPDATE cloud_connect_accounts SET repo_name = '' WHERE repo_name IS NULL"
|
||||
))
|
||||
conn.execute(text(
|
||||
"ALTER TABLE cloud_connect_accounts ALTER COLUMN repo_name SET NOT NULL"
|
||||
))
|
||||
conn.execute(text(
|
||||
"ALTER TABLE cloud_connect_accounts ALTER COLUMN repo_name SET DEFAULT ''"
|
||||
))
|
||||
|
||||
# Drop old (user, section) constraint if it still exists.
|
||||
conn.execute(text(
|
||||
"ALTER TABLE cloud_connect_accounts "
|
||||
"DROP CONSTRAINT IF EXISTS uq_cloud_connect_accounts_user_section"
|
||||
))
|
||||
|
||||
# Add new (user, section, repo_name) constraint if not already present.
|
||||
conn.execute(text("""
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint
|
||||
WHERE conname = 'uq_cloud_connect_accounts_user_section_repo'
|
||||
) THEN
|
||||
ALTER TABLE cloud_connect_accounts
|
||||
ADD CONSTRAINT uq_cloud_connect_accounts_user_section_repo
|
||||
UNIQUE ("user", section, repo_name);
|
||||
END IF;
|
||||
END $$;
|
||||
"""))
|
||||
print("[migrations] migrate_cc_accounts_repo_unique_key completed.")
|
||||
except Exception as exc:
|
||||
print(f"[migrations] Failed migrate_cc_accounts_repo_unique_key: {exc}")
|
||||
|
||||
|
||||
def run_migrations() -> None:
|
||||
print("[migrations] Starting migrations...")
|
||||
migrate_add_username_to_users()
|
||||
@ -1358,6 +1412,7 @@ def run_migrations() -> None:
|
||||
migrate_cove_integration()
|
||||
migrate_cove_accounts_table()
|
||||
migrate_cloud_connect_accounts_table()
|
||||
migrate_cc_accounts_repo_unique_key()
|
||||
migrate_entra_sso_settings()
|
||||
print("[migrations] All migrations completed.")
|
||||
|
||||
|
||||
@ -399,7 +399,7 @@ class CloudConnectAccount(db.Model):
|
||||
user = db.Column(db.String(255), nullable=False)
|
||||
section = db.Column(db.String(32), nullable=False)
|
||||
|
||||
repo_name = db.Column(db.String(512), nullable=True)
|
||||
repo_name = db.Column(db.String(512), nullable=False, default="")
|
||||
repo_type = db.Column(db.String(255), nullable=True)
|
||||
num_items = db.Column(db.String(64), nullable=True)
|
||||
total_quota = db.Column(db.String(32), nullable=True)
|
||||
@ -418,7 +418,7 @@ class CloudConnectAccount(db.Model):
|
||||
job = db.relationship("Job", backref=db.backref("cloud_connect_account", uselist=False))
|
||||
|
||||
__table_args__ = (
|
||||
db.UniqueConstraint("user", "section", name="uq_cloud_connect_accounts_user_section"),
|
||||
db.UniqueConstraint("user", "section", "repo_name", name="uq_cloud_connect_accounts_user_section_repo"),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -33,7 +33,8 @@
|
||||
data-id="{{ acc.id }}"
|
||||
data-user="{{ acc.user | e }}"
|
||||
data-section="{{ acc.section | e }}"
|
||||
data-backup-type="{{ acc.derived_backup_type | e }}">
|
||||
data-backup-type="{{ acc.derived_backup_type | e }}"
|
||||
data-job-name="{{ acc.derived_job_name | e }}">
|
||||
<td class="fw-semibold">{{ acc.user }}</td>
|
||||
<td><span class="badge bg-secondary">{{ acc.section }}</span></td>
|
||||
<td class="text-muted small">{{ acc.repo_name or '—' }}<br><span class="text-muted" style="font-size:11px;">{{ acc.repo_type or '' }}</span></td>
|
||||
@ -236,11 +237,12 @@
|
||||
var user = row.getAttribute('data-user');
|
||||
var section = row.getAttribute('data-section');
|
||||
var backupType = row.getAttribute('data-backup-type');
|
||||
var jobName = row.getAttribute('data-job-name') || user;
|
||||
var linkUrl = linkUrlTpl.replace('0', id);
|
||||
|
||||
document.getElementById('ccLinkModalTitle').textContent = user + ' (' + section + ')';
|
||||
document.getElementById('ccDisplayBackupType').textContent = backupType;
|
||||
document.getElementById('ccDisplayJobName').textContent = user;
|
||||
document.getElementById('ccDisplayJobName').textContent = jobName;
|
||||
|
||||
var customerInput = document.getElementById('ccCustomerInput');
|
||||
var customerIdField = document.getElementById('ccCustomerId');
|
||||
|
||||
@ -5,6 +5,13 @@ This file documents all changes made to this project via Claude Code.
|
||||
## [2026-03-20]
|
||||
|
||||
### Fixed
|
||||
- Cloud Connect accounts: users with multiple repositories (e.g. Veeam Cloud Connect Repository + Cloud Connect Immutable) now get a separate staging account entry per repository instead of overwriting each other:
|
||||
- `CloudConnectAccount` unique key changed from `(user, section)` to `(user, section, repo_name)`
|
||||
- Migration `migrate_cc_accounts_repo_unique_key`: drops old constraint, makes `repo_name` NOT NULL (default `''`), adds new constraint
|
||||
- Importer: upserts on `(user, section, repo_name)`; `external_id` now includes repo slug so each repo gets its own `JobRun`
|
||||
- Job creation suggestion uses `repo_name` as the default job name (falls back to user when repo_name is empty)
|
||||
- Cloud Connect accounts page: `data-job-name` attribute on row, modal reads it correctly
|
||||
|
||||
- Cloud Connect runs in job detail page popup now show a structured CC summary instead of the raw report email with all tenants:
|
||||
- `routes_inbox.py` (`inbox_message_detail`): accepts optional `?run_id=` parameter; when the run has `source_type = "cloud_connect"`, returns `cloud_connect_summary` dict and per-run objects from `run_object_links` instead of MailObjects
|
||||
- `job_detail.html`: passes `run_id` to the detail API; if `cloud_connect_summary` is returned, shows the CC summary panel, collapses the raw email (accessible via "show" toggle), and shows only the single per-run repository object
|
||||
|
||||
Loading…
Reference in New Issue
Block a user