30 KiB
Technical Notes (Internal)
Last updated: 2026-02-23 (late)
Purpose
Internal technical snapshot of the backupchecks repository for faster onboarding, troubleshooting, and change impact analysis.
Repository Overview
- Application: Flask web app with SQLAlchemy and Flask-Migrate.
- Runtime: Containerized (Docker), deployed via Docker Compose stack.
- Primary source code location:
containers/backupchecks/src. - The project also contains extensive functional documentation in
docs/and multiple roadmap TODO files at repository root.
Main Structure
containers/backupchecks/Dockerfile: Python 3.12-slim image, startsgunicornwithbackend.app:create_app().containers/backupchecks/requirements.txt: Flask stack + PostgreSQL driver + reporting libraries (reportlab,Markdown).containers/backupchecks/src/backend/app: backend domain logic, routes, parsers, models, migrations.containers/backupchecks/src/templates: Jinja templates for auth/main/documentation pages.containers/backupchecks/src/static: CSS, images, favicon.deploy/backupchecks-stack.yml: compose stack withbackupchecks,postgres,adminer.build-and-push.sh: release/test build script with version bumping, tags, and image push.docs/: functional design, changelogs, migration notes, API notes.
Application Architecture (Current Observation)
- Factory pattern:
create_app()incontainers/backupchecks/src/backend/app/__init__.py. - Blueprints:
auth_bpfor authentication.main_bpfor core functionality.doc_bpfor internal documentation pages.
- Database initialization at startup:
db.create_all()run_migrations()
- Background tasks:
start_auto_importer(app)starts the automatic mail importer thread.start_cove_importer(app)starts the Cove Data Protection polling thread (started only whencove_import_enabledis set).
- Health endpoint:
GET /healthreturns{ "status": "ok" }.
Functional Processing Flow
- Import:
- Email is fetched via Microsoft Graph API.
- Parse:
- Parser selection through registry + software-specific parser implementations.
- Approve:
- New jobs first appear in Inbox for initial customer assignment.
- Auto-process:
- Subsequent emails for known jobs automatically create
JobRunrecords.
- Subsequent emails for known jobs automatically create
- Monitor:
- Runs appear in Daily Jobs and Run Checks.
- Review:
- Manual review removes items from the unreviewed operational queue.
Configuration and Runtime
- Config is built from environment variables in
containers/backupchecks/src/backend/app/config.py. - Important variables:
APP_SECRET_KEYAPP_ENVAPP_PORTPOSTGRES_DBPOSTGRES_USERPOSTGRES_PASSWORDDB_HOSTDB_PORT
- Database URI pattern:
postgresql+psycopg2://<user>:<pass>@<host>:<port>/<db>
- Default timezone in config:
Europe/Amsterdam.
Data Model (High-level)
File: containers/backupchecks/src/backend/app/models.py
- Auth/users:
Userwith role(s), active role in session.
- System settings:
SystemSettingswith Graph/mail settings, import settings, UI timezone, dashboard policy, sandbox flag.- Autotask configuration and cache fields are present.
- Cove Data Protection fields:
cove_enabled,cove_api_url,cove_api_username,cove_api_password,cove_import_enabled,cove_import_interval_minutes,cove_partner_id,cove_last_import_at. - Microsoft Entra SSO fields:
entra_sso_enabled,entra_tenant_id,entra_client_id,entra_client_secret,entra_redirect_uri,entra_allowed_domain,entra_allowed_group_ids,entra_auto_provision_users.
- Logging:
AuditLog(legacy aliasAdminLog).
- Domain:
Customer,Job,JobRun,OverrideMailMessage,MailObjectCoveAccount(Cove staging table — see Cove integration section)Ticket,TicketScope,TicketJobRunRemark,RemarkScope,RemarkJobRunFeedbackItem,FeedbackVote,FeedbackReply,FeedbackAttachment
Foreign Key Relationships & Deletion Order
Critical deletion order to avoid constraint violations:
- Clean auxiliary tables (ticket_job_runs, remark_job_runs, scopes, overrides)
- Unlink mails from jobs (UPDATE mail_messages SET job_id = NULL)
- Delete mail_objects
- Delete jobs (cascades to job_runs)
- Delete mails
Key Model Fields
MailMessage model:
from_address(NOTsender!) - sender emailsubject- email subjecttext_body- plain text contenthtml_body- HTML contentreceived_at- timestamplocation- inbox/processed/deletedjob_id- link to Job (nullable)
Job model:
customer_id- FK to Customerjob_name- parsed from emailbackup_software- e.g., "Veeam", "Synology", "Cove Data Protection"backup_type- e.g., "Backup Job", "Active Backup"cove_account_id- (nullable int) links this job to a Cove AccountId
JobRun model:
source_type- NULL = email (backwards compat),"cove_api"for Cove-imported runsexternal_id- deduplication key for Cove runs:"cove-{account_id}-{run_ts}"
Parser Architecture
- Folder:
containers/backupchecks/src/backend/app/parsers/ - Two layers:
registry.py:- matching/documentation/visibility on
/parsers. - examples must stay generic (no customer names).
- matching/documentation/visibility on
- parser files (
veeam.py,synology.py, etc.):- actual detection and parsing logic.
- return structured output: software, type, job name, status, objects.
- Practical rule:
- extend patterns by adding, not replacing (backward compatibility).
Parser Types
Informational Parsers:
- DSM Updates, Account Protection, Firmware Updates
- Set appropriate backup_type (e.g., "Updates", "Firmware Update")
- Do NOT participate in schedule learning
- Usually still visible in Run Checks for awareness
- Exception: non-backup 3CX informational types (
Update,SSL Certificate) are hidden from Run Checks
Regular Parsers:
- Backup jobs (Veeam, Synology Active Backup, NAKIVO, etc.)
- Participate in schedule learning (daily/weekly/monthly detection)
- Generate missed runs when expected runs don't occur
Example: Synology Updates Parser (synology.py)
- Handles multiple update notification types under same job:
- DSM automatic update cancelled
- Packages out-of-date
- Combined notifications (DSM + packages)
- Detection patterns:
- DSM: "Automatische DSM-update", "DSM-update op", "automatic DSM update"
- Packages: "Packages on", "out-of-date", "Package Center"
- Hostname extraction from multiple patterns
- Returns: backup_type "Updates", job_name "Synology Automatic Update"
Cove Data Protection Integration
Overview
Cove (N-able) Data Protection is a cloud backup platform. Backupchecks integrates with it via the Cove JSON-RPC API, following the same inbox-style staging flow as email imports.
Files
containers/backupchecks/src/backend/app/cove_importer.py– API client, account processing, JobRun creationcontainers/backupchecks/src/backend/app/cove_importer_service.py– background polling threadcontainers/backupchecks/src/backend/app/main/routes_cove.py–/cove/accountsroutescontainers/backupchecks/src/templates/main/cove_accounts.html– inbox-style accounts page
API Details
- Endpoint:
https://api.backup.management/jsonapi(JSON-RPC 2.0) - Login:
POSTwith{"jsonrpc":"2.0","id":"jsonrpc","method":"Login","params":{"username":"...","password":"..."}}- Returns
visaat top level (data["visa"]), not insideresult - Returns
PartnerIdinsideresult
- Returns
- EnumerateAccountStatistics:
POSTwith visa in payload,query(lowercase) withPartnerId,StartRecordNumber,RecordsCount,Columns - Settings format per account:
[{"D09F00": "5"}, {"I1": "device name"}, ...]— list of single-key dicts, flatten withdict.update(item)
Column Codes
| Code | Meaning |
|---|---|
I1 |
Account/device name |
I18 |
Computer name |
I8 |
Customer/partner name |
I78 |
Active datasource label |
D09F00 |
Overall last session status code |
D09F09 |
Last successful session timestamp (Unix) |
D09F15 |
Last session end timestamp (Unix) |
D09F08 |
28-day colorbar string |
D1F00/F15 |
Files & Folders status/timestamp |
D10F00/F15 |
VssMsSql |
D11F00/F15 |
VssSharePoint |
D19F00/F15 |
M365 Exchange |
D20F00/F15 |
M365 OneDrive |
D5F00/F15 |
M365 SharePoint |
D23F00/F15 |
M365 Teams |
Status Code Mapping
| Cove code | Meaning | Backupchecks status |
|---|---|---|
| 1 | In process | Warning |
| 2 | Failed | Error |
| 3 | Aborted | Error |
| 5 | Completed | Success |
| 6 | Interrupted | Error |
| 7 | Not started | Warning |
| 8 | Completed with errors | Warning |
| 9 | In progress with faults | Warning |
| 10 | Over quota | Error |
| 11 | No selection | Warning |
| 12 | Restarted | Warning |
Inbox-Style Flow (mirrors email import)
- Cove importer fetches all accounts via paginated
EnumerateAccountStatistics(250/page). - Every account is upserted into the
cove_accountsstaging table (always, regardless of job link). - Accounts without a
job_idappear on/cove/accounts("Cove Accounts" page) for admin action. - Admin can:
- Create new job – creates a
Jobwithbackup_software="Cove Data Protection"and links it. - Link to existing job – sets
job.cove_account_idandcove_acc.job_id.
- Create new job – creates a
- Linking an account triggers an immediate import attempt; linked accounts then generate
JobRunrecords (deduplicated per job viajob_id + external_id). - Per-datasource objects are persisted to
customer_objects,job_object_links,run_object_links.
CoveAccount Model
class CoveAccount(db.Model):
__tablename__ = "cove_accounts"
id # PK
account_id # Cove AccountId (unique)
account_name # I1
computer_name # I18
customer_name # I8
datasource_types # I78
last_status_code # D09F00 (int)
last_run_at # D09F15 (datetime)
colorbar_28d # D09F08
job_id # FK → jobs.id (nullable — None = unmatched)
first_seen_at
last_seen_at
job # relationship → Job
Deduplication
external_id = f"cove-{account_id}-{run_ts}" where run_ts is Unix timestamp from D09F15 (fallback to D09F09 when needed).
Deduplication is enforced per linked job:
- check
JobRun.query.filter_by(job_id=job.id, external_id=external_id).first() - this prevents cross-job collisions when accounts are relinked.
Run Enrichment
- Cove-created
JobRun.remarkcontains account/computer/customer and last status/timestamp summary. - Per-datasource run object records include:
- mapped Backupchecks status
- readable status details in
error_message - datasource-level session timestamp in
observed_at
Cove Accounts UI Notes
/cove/accountsderives display fields to align with existing job logic:backup_software:Cove Data Protectionbackup_type:Server,Workstation, orMicrosoft 365job_name: based on Cove account/computer fallback- readable datasource labels instead of raw
I78code stream
computer_nameis shown in both unmatched and matched account tables.
Background Thread
cove_importer_service.py — same pattern as auto_importer_service.py:
- Thread name:
"cove_importer" - Checks
settings.cove_import_enabled - Interval:
settings.cove_import_interval_minutes(default 30) - Calls
run_cove_import(settings)which returns(total, created, skipped, errors)
Settings UI
Settings → Integrations → Cove section:
- Enable toggle, API URL, username, password (masked, only overwritten if non-empty)
- Import enabled + interval
- "Test Connection" button (AJAX →
POST /settings/cove/test-connection) returns{ok, partner_id, message} - "Run import now" button (→
POST /settings/cove/run-now) triggers manual import
Routes
| Route | Method | Description |
|---|---|---|
/cove/accounts |
GET | Inbox-style page: unmatched + matched accounts |
/cove/accounts/<id>/link |
POST | action=create or action=link |
/cove/accounts/<id>/unlink |
POST | Removes job link, puts account back in unmatched |
/settings/cove/test-connection |
POST | AJAX: verify credentials, save partner_id |
/settings/cove/run-now |
POST | Manual import trigger |
Migrations
migrate_cove_integration()— adds 8 columns tosystem_settings,cove_account_idtojobs,source_type+external_idtojob_runs, dedup index onjob_runs.external_idmigrate_cove_accounts_table()— createscove_accountstable with indexes
Ticketing and Autotask (Critical Rules)
Two Ticket Types
-
Internal Tickets (tickets table)
- Created manually or via Autotask integration
- Stored in
ticketstable withticket_code(e.g., "T20250123.0001") - Linked to runs via
ticket_job_runsmany-to-many table - Scoped to jobs via
ticket_scopestable - Have
resolved_atfield for resolution tracking - Auto-propagation: Automatically linked to new runs via
link_open_internal_tickets_to_run
-
Autotask Tickets (job_runs columns)
- Created via Run Checks modal → "Create Autotask Ticket"
- Stored directly in JobRun columns:
autotask_ticket_id,autotask_ticket_number, etc.
- When created, also creates matching internal ticket for legacy UI compatibility
Microsoft Entra SSO (Current State)
Status
- Implemented but marked Untested in Backupchecks.
Routes
GET /auth/entra/login– starts Entra auth code flow.GET /auth/entra/callback– exchanges code, maps/provisions local user, logs in session./auth/logout– Entra-aware logout redirect when user authenticated via Entra.
Access Controls
- Optional tenant/domain restriction (
entra_allowed_domain). - Optional Entra security-group allowlist (
entra_allowed_group_ids) based on group object IDs. - Group overage / missing groups claim blocks login intentionally when group gate is enabled.
Local User Mapping
- Primary mapping by
preferred_username/UPN/email. - Optional auto-provision (
entra_auto_provision_users) creates local Viewer users for unknown identities.
Documentation
- Built-in docs page:
/documentation/settings/entra-sso - Includes configuration steps and explicit untested warning.
Navbar Notes (Latest)
- To reduce split-screen overflow, nav is compacted by grouping:
- admin-only links under
Admindropdown - secondary non-admin links under
Moredropdown
- admin-only links under
- Primary operational links remain visible (notably
Run Checks). - Viewer role now exposes
CustomersandJobsdirectly in navbar.- Have
autotask_ticket_deleted_atfield for deletion tracking - Resolution tracked via matching internal ticket's
resolved_atfield - Auto-propagation: Linked to new runs via two-strategy approach
- Have
Ticket Propagation to New Runs
When a new JobRun is created (via email import OR missed run generation), link_open_internal_tickets_to_run ensures:
Strategy 1: Internal ticket linking
- Query finds tickets where:
COALESCE(ts.resolved_at, t.resolved_at) IS NULL - Creates
ticket_job_runslinks automatically - Tickets remain visible until explicitly resolved
- NO date-based logic - resolved = immediately hidden from new runs
Strategy 2: Autotask ticket propagation (independent)
- Check if internal ticket code exists → find matching Autotask run → copy ticket info
- If no match, directly search for most recent Autotask ticket on job where:
autotask_ticket_deleted_at IS NULL(not deleted in PSA)- Internal ticket
resolved_at IS NULL(not resolved in PSA)
- Copy
autotask_ticket_id,autotask_ticket_number,created_at,created_by_user_idto new run
Where Ticket Linking is Called
link_open_internal_tickets_to_run is invoked in three locations:
- Email-based runs:
routes_inbox.pyandmail_importer.py- after creating JobRun from parsed email - Missed runs:
routes_run_checks.pyin_ensure_missed_runs_for_job- after creating missed JobRun records- Weekly schedule: After creating weekly missed run (with flush to get run.id)
- Monthly schedule: After creating monthly missed run (with flush to get run.id)
- Critical: Without this call, missed runs don't get ticket propagation!
Display Logic - Link-Based System
All pages use explicit link-based queries (no date-based logic):
Job Details Page:
- Two sources for ticket display:
- Direct links (
ticket_job_runs WHERE job_run_id = X) → always show (audit trail) - Active window (
ticket_scopes WHERE job_id = Y AND resolved_at IS NULL) → only unresolved
- Direct links (
- Result: Old runs keep their ticket references, new runs don't get resolved tickets
Run Checks Main Page (Indicators 🎫):
- Query:
ticket_scopes JOIN tickets WHERE job_id = X AND resolved_at IS NULL - Only shows indicator if unresolved tickets exist for the job
Run Checks Popup Modal:
- API:
/api/job-runs/<run_id>/alerts - Two-source ticket display:
- Direct links:
tickets JOIN ticket_job_runs WHERE job_run_id = X - Job-level scope:
tickets JOIN ticket_scopes WHERE job_id = Y AND resolved_at IS NULL AND active_from_date <= run_date
- Direct links:
- Prevents duplicates by tracking seen ticket IDs
- Shows newly created tickets immediately (via scope) without waiting for resolve action
- Two-source remark display:
- Direct links:
remarks JOIN remark_job_runs WHERE job_run_id = X - Job-level scope:
remarks JOIN remark_scopes WHERE job_id = Y AND resolved_at IS NULL AND active_from_date <= run_date(with timezone-safe fallback fromstart_date)
- Direct links:
- Prevents duplicates by tracking seen remark IDs
Debug Logging for Ticket Linking (Reference)
If you need to debug ticket linking issues, add this to link_open_internal_tickets_to_run in ticketing_utils.py after the rows query:
try:
from .models import AuditLog
details = []
if rows:
for tid, code, t_resolved, ts_resolved in rows:
details.append(f"ticket_id={tid}, code={code}, t.resolved_at={t_resolved}, ts.resolved_at={ts_resolved}")
else:
details.append("No open tickets found for this job")
audit = AuditLog(
user="system", event_type="ticket_link_debug",
message=f"link_open_internal_tickets_to_run called: run_id={run.id}, job_id={job.id}, found={len(rows)} ticket(s)",
details="\n".join(details)
)
db.session.add(audit)
db.session.commit()
except Exception:
pass
Visible on Logging page under event_type = "ticket_link_debug". Remove after debugging.
Resolved vs Deleted
- Resolved: Ticket completed in Autotask (tracked in internal
tickets.resolved_at)- Stops propagating to new runs
- Ticket still exists in PSA
- Synced via PSA polling
- Deleted: Ticket removed from Autotask (tracked in
job_runs.autotask_ticket_deleted_at)- Also stops propagating
- Ticket no longer exists in PSA
- Rare operation
Critical Rules
- ❌ NEVER use date-based resolved logic:
resolved_at >= run_dateORactive_from_date <= run_date - ✅ Only show tickets that are ACTUALLY LINKED via
ticket_job_runstable - ✅ Resolved tickets stop linking immediately when resolved
- ✅ Old links preserved for audit trail (visible on old runs)
- ✅ All queries must use explicit JOIN to link tables
- ✅ Consistency: All pages use same "resolved = NULL" logic
- ✅ CRITICAL: Preserve description field during Autotask updates - must include "description" in optional_fields list
UI and UX Notes
Navbar
- Fixed-top positioning
- Collapses on mobile (hamburger menu)
- Dynamic padding adjustment via JavaScript (measures navbar height, adjusts main content padding-top)
- Role-based menu items (Admin sees more than Operator/Viewer)
Status Badges
- Success: Green
- Warning: Yellow/Orange
- Failed/Error: Red
- Override applied: Blue badge
- Reviewed: Checkmark indicator
Ticket Copy Functionality
- Copy button (⧉) available on both Run Checks and Job Details pages
- Allows quick copying of ticket numbers to clipboard
- Cross-browser compatible with three-tier fallback mechanism:
- Modern Clipboard API:
navigator.clipboard.writeText()- works in modern browsers with HTTPS - Legacy execCommand:
document.execCommand('copy')- fallback for older browsers and Edge - Prompt fallback:
window.prompt()- last resort if clipboard access fails
- Modern Clipboard API:
- Visual feedback: button changes to ✓ checkmark for 800ms after successful copy
- Implementation uses hidden textarea for execCommand method to ensure compatibility
- No user interaction required in modern browsers (direct copy)
Checkbox Behavior
- All checkboxes on Inbox and Run Checks pages use
autocomplete="off" - Prevents browser from auto-selecting checkboxes after page reload
- Fixes issue where deleting items would cause same number of new items to be selected
Customers to Jobs Navigation (2026-02-16)
- Customers page links each customer name to filtered Jobs view:
GET /jobs?customer_id=<customer_id>
- Jobs route behavior:
- Accepts optional
customer_idquery parameter inroutes_jobs.py. - If set: returns jobs for that customer only.
- If not set: keeps default filter that hides jobs linked to inactive customers.
- Accepts optional
- Jobs UI behavior:
- Shows active filter banner with selected customer name.
- Provides "Clear filter" action back to unfiltered
/jobs.
- Templates touched:
templates/main/customers.htmltemplates/main/jobs.html
Global Grouped Search (2026-02-16)
- New route:
GET /searchinmain/routes_search.py
- New UI:
- Navbar search form in
templates/layout/base.html - Grouped result page in
templates/main/search.html
- Navbar search form in
- Search behavior:
- Case-insensitive matching (
ILIKE). *wildcard is supported and translated to SQL%.- Automatic contains behavior is applied per term (
*term*) when wildcard not explicitly set. - Multi-term queries use AND across terms and OR across configured columns within each section.
- Per-section pagination is supported via query params:
p_inbox,p_customers,p_jobs,p_daily_jobs,p_run_checks,p_tickets,p_remarks,p_overrides,p_reports. - Pagination keeps search state for all sections while browsing one section.
- "Open
" links pass qto destination overview pages so page-level filtering matches the search term.
- Case-insensitive matching (
- Grouped sections:
- Inbox, Customers, Jobs, Daily Jobs, Run Checks, Tickets, Remarks, Existing overrides, Reports.
- Daily Jobs search result details:
- Meta now includes expected run time, success indicator, and run count for the selected day.
- Link now opens Daily Jobs with modal auto-open using
open_job_idquery parameter (same modal flow as clicking a row in Daily Jobs).
- Access control:
- Search results are role-aware and only show sections/data the active role can access.
run_checksresults are restricted toadmin/operator.reportssupportsadmin/operator/viewer/reporter.
- Current performance strategy:
- Per-section limit (
SEARCH_LIMIT_PER_SECTION = 10), with total count per section. - No schema migration required for V1.
- Per-section limit (
Feedback Module with Screenshots
- Models:
FeedbackItem,FeedbackVote,FeedbackReply,FeedbackAttachment. - Attachments:
- multiple uploads, type validation, per-file size limits, storage in database (BYTEA).
Validation Snapshot
- 2026-02-16: Test build + push succeeded via
update-and-build.sh t. - Pushed image:
gitea.oskamp.info/ivooskamp/backupchecks:dev. - 2026-02-16: Test build + push succeeded on branch
v20260216-02-global-search. - Pushed image digest:
sha256:6996675b9529426fe2ad58b5f353479623f3ebe24b34552c17ad0421d8a7ee0f. - 2026-02-16: Additional test build + push cycles succeeded on
v20260216-02-global-search. - Latest pushed image digest:
sha256:8ec8bfcbb928e282182fa223ce8bf7f92112d20e79f4a8602d015991700df5d7. - 2026-02-16: Additional test build + push cycles succeeded after search enhancements.
- Latest pushed image digest:
sha256:b36b5cdd4bc7c4dadedca0534f1904a6e12b5b97abc4f12bc51e42921976f061. - Delete strategy:
- soft delete by default,
- permanent delete only for admins and only after soft delete.
Deployment and Operations
- Stack exposes:
- app on
8080 - adminer on
8081
- app on
- PostgreSQL persistent volume:
/docker/appdata/backupchecks/backupchecks-postgres:/var/lib/postgresql/data
deploy/backupchecks-stack.ymlalso contains example.envvariables at the bottom.
Build/Release Flow
File: build-and-push.sh
- Bump options:
1patch,2minor,3major,ttest.
- Release build:
- update
version.txt - commit + tag + push
- docker push of
:<version>,:dev,:latest
- update
- Test build:
- only
:dev - no commit/tag.
- only
- Services are discovered under
containers/*with Dockerfile-per-service.
Technical Observations / Attention Points
README.mdis currently empty; quick-start entry context is missing.LICENSEis currently empty.docs/architecture.mdis currently empty.deploy/backupchecks-stack.ymlcontains hardcoded example values (Changeme), with risk if used without proper secrets management.- The app performs DB initialization + migrations at startup; for larger schema changes this can impact startup time/robustness.
- There is significant parser and ticketing complexity; route changes carry regression risk without targeted testing.
- For Autotask update calls, the
descriptionfield must be explicitly preserved to prevent unintended NULL overwrite. - Security hygiene remains important:
- no customer names in parser examples/source,
- no hardcoded credentials.
Quick References
- App entrypoint:
containers/backupchecks/src/backend/app/main.py - App factory:
containers/backupchecks/src/backend/app/__init__.py - Config:
containers/backupchecks/src/backend/app/config.py - Models:
containers/backupchecks/src/backend/app/models.py - Parsers:
containers/backupchecks/src/backend/app/parsers/registry.py - Ticketing utilities:
containers/backupchecks/src/backend/app/ticketing_utils.py - Run Checks routes:
containers/backupchecks/src/backend/app/main/routes_run_checks.py - Cove importer:
containers/backupchecks/src/backend/app/cove_importer.py - Cove routes:
containers/backupchecks/src/backend/app/main/routes_cove.py - Compose stack:
deploy/backupchecks-stack.yml - Build script:
build-and-push.sh
Recent Changes
2026-02-23
- Cove Data Protection full integration:
cove_importer.py– Cove API client (login, paginated enumeration, status mapping, deduplication, per-datasource object persistence)cove_importer_service.py– background polling thread (same pattern asauto_importer_service.py)CoveAccountstaging model +migrate_cove_accounts_table()migrationSystemSettings– 8 new Cove fields,Job–cove_account_id,JobRun–source_type+external_idroutes_cove.py– inbox-style/cove/accountswith link/unlink routescove_accounts.html– unmatched accounts shown first with Bootstrap modals (create job / link to existing), matched accounts with Unlink- Settings > Integrations: Cove section with test connection (AJAX) and manual import trigger
- Navbar: "Cove Accounts" link for admin/operator when
cove_enabled
- Cove API key findings (from test script + N-able support):
- Visa is returned at top level of login response, not inside
result - Settings per account are a list of single-key dicts
[{"D09F00":"5"}, ...]— flatten withflat.update(item) - EnumerateAccountStatistics params must use lowercase
querykey andRecordsCount(notRecordCount) - Login params must use lowercase
username/password - D02/D03 are legacy; use D10/D11 or D09 (Total) instead
- Visa is returned at top level of login response, not inside
2026-02-19
- Added 3CX Update parser support:
threecx.pynow recognizes subject3CX Notification: Update Successful - <host>and stores it as informational with:backup_software = 3CXbackup_type = Updateoverall_status = Success
- 3CX informational schedule behavior:
3CX / Updateand3CX / SSL Certificateare excluded from schedule inference inroutes_shared.py(no Expected/Missed generation).
- Run Checks visibility scope (3CX-only):
- Run Checks now hides only non-backup 3CX informational jobs (
Update,SSL Certificate). - Other backup software/types remain visible and unchanged.
- Run Checks now hides only non-backup 3CX informational jobs (
- Fixed remark visibility mismatch:
/api/job-runs/<run_id>/alertsnow loads remarks from both:remark_job_runs(explicit run links),remark_scopes(active job-scoped remarks),
- with duplicate prevention by remark ID.
- This resolves cases where the remark indicator appeared but remarks were not shown in Run Checks modal or Job Details modal.
2026-02-13
- Fixed missed runs ticket propagation: Added
link_open_internal_tickets_to_runcalls in_ensure_missed_runs_for_job(routes_run_checks.py) after creating both weekly and monthly missed JobRun records. Previously only email-based runs got ticket linking, causing missed runs to not show internal tickets or Autotask tickets. Requireddb.session.flush()before linking to ensure run.id is available. - Fixed checkbox auto-selection: Added
autocomplete="off"to all checkboxes on Inbox and Run Checks pages. Prevents browser from automatically re-selecting checkboxes after page reload following delete actions.
2026-02-12
- Fixed Run Checks modal ticket display: Implemented two-source display logic (ticket_job_runs + ticket_scopes). Previously only showed tickets after they were resolved (when ticket_job_runs entry was created). Now shows tickets immediately upon creation via scope query.
- Fixed copy button in Edge: Moved clipboard functions inside IIFE scope for proper closure access (Edge is stricter than Firefox about scope resolution).
2026-02-10
- Added screenshot support to Feedback system: Multiple file upload, inline display, two-stage delete (soft delete for audit trail, permanent delete for cleanup).
- Completed transition to link-based ticket system: All pages now use JOIN queries, no date-based logic. Added cross-browser copy ticket functionality with three-tier fallback mechanism to both Run Checks and Job Details pages.