Merge v20260207-02-wiki-documentation into main (v0.1.23)
2
.gitignore
vendored
@ -0,0 +1,2 @@
|
|||||||
|
# Claude Code confidential files
|
||||||
|
.claude/
|
||||||
@ -1 +1 @@
|
|||||||
v20260205-13-changelog-python-structure
|
v20260207-02-wiki-documentation
|
||||||
|
|||||||
332
TODO-audit-logging.md
Normal file
@ -0,0 +1,332 @@
|
|||||||
|
# TODO: Audit Logging Uitbreiding - Vervolg
|
||||||
|
|
||||||
|
**Branch:** `v20260206-10-audit-logging-expansion`
|
||||||
|
**Datum:** 2026-02-07
|
||||||
|
**Status:** Deel 1 compleet, deel 2 nog te doen
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ Wat is al gedaan (Deel 1)
|
||||||
|
|
||||||
|
### Model & Database
|
||||||
|
- ✅ Model hernoemd: `AdminLog` → `AuditLog`
|
||||||
|
- ✅ Database migratie toegevoegd: `migrate_rename_admin_logs_to_audit_logs()`
|
||||||
|
- Hernoemt tabel `admin_logs` → `audit_logs`
|
||||||
|
- Idempotent en veilig
|
||||||
|
- ✅ Backwards compatibility: `AdminLog = AuditLog` alias
|
||||||
|
|
||||||
|
### Code Updates
|
||||||
|
- ✅ `admin_logging.py`: `log_admin_event()` → `log_audit_event()` (met alias)
|
||||||
|
- ✅ `routes_core.py`: Updated naar `AuditLog`
|
||||||
|
- ✅ `routes_shared.py`: Updated naar `AuditLog`
|
||||||
|
- ✅ Gecommit en gepusht naar Gitea
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 Wat moet nog (Deel 2)
|
||||||
|
|
||||||
|
### 1. UI Updates
|
||||||
|
|
||||||
|
**Bestand:** `containers/backupchecks/src/templates/main/logging.html`
|
||||||
|
|
||||||
|
**Te wijzigen:**
|
||||||
|
- Page title: "Admin Activity" → "System Audit Log" of "Activity Log"
|
||||||
|
- Breadcrumb indien aanwezig
|
||||||
|
|
||||||
|
**Huidige code zoeken naar:**
|
||||||
|
```html
|
||||||
|
<h1>Admin Activity</h1>
|
||||||
|
<!-- of -->
|
||||||
|
<title>Admin Activity</title>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Settings Logging Toevoegen
|
||||||
|
|
||||||
|
**Locatie:** `containers/backupchecks/src/backend/app/main/routes_settings.py`
|
||||||
|
|
||||||
|
**Routes die logging nodig hebben:**
|
||||||
|
|
||||||
|
#### A. General Settings (`/settings/general` POST)
|
||||||
|
```python
|
||||||
|
# Na regel waar settings worden opgeslagen
|
||||||
|
# Voeg toe na db.session.commit()
|
||||||
|
|
||||||
|
from ..admin_logging import log_audit_event
|
||||||
|
import json
|
||||||
|
|
||||||
|
# Track wat er gewijzigd is
|
||||||
|
changes = {}
|
||||||
|
if old_value != new_value:
|
||||||
|
changes['setting_name'] = {'old': old_value, 'new': new_value}
|
||||||
|
|
||||||
|
if changes:
|
||||||
|
log_audit_event(
|
||||||
|
event_type="settings_general",
|
||||||
|
message=f"Updated {len(changes)} general setting(s)",
|
||||||
|
details=json.dumps(changes, indent=2)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Settings om te tracken:**
|
||||||
|
- `ui_timezone`
|
||||||
|
- `require_daily_dashboard_visit`
|
||||||
|
- `is_sandbox_environment`
|
||||||
|
- Andere SystemSettings velden
|
||||||
|
|
||||||
|
#### B. Mail Settings (`/settings/mail` POST)
|
||||||
|
```python
|
||||||
|
log_audit_event(
|
||||||
|
event_type="settings_mail",
|
||||||
|
message="Updated mail settings",
|
||||||
|
details=json.dumps({
|
||||||
|
'imap_server': settings.imap_server,
|
||||||
|
'auto_import_enabled': settings.auto_import_enabled,
|
||||||
|
# etc.
|
||||||
|
}, indent=2)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### C. Autotask Settings (`/settings/autotask` POST)
|
||||||
|
```python
|
||||||
|
log_audit_event(
|
||||||
|
event_type="settings_autotask",
|
||||||
|
message="Updated Autotask integration settings",
|
||||||
|
details=json.dumps({
|
||||||
|
'url': settings.autotask_url,
|
||||||
|
'username': settings.autotask_username,
|
||||||
|
# NIET het wachtwoord loggen!
|
||||||
|
'enabled': settings.autotask_enabled
|
||||||
|
}, indent=2)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**BELANGRIJK:** Wachtwoorden NOOIT loggen in details!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Export Logging Toevoegen
|
||||||
|
|
||||||
|
#### A. Customers Export (`/customers/export`)
|
||||||
|
|
||||||
|
**Huidige code:** `routes_customers.py` regel ~421
|
||||||
|
|
||||||
|
**Toevoegen:**
|
||||||
|
```python
|
||||||
|
# Voor return Response(...)
|
||||||
|
from ..admin_logging import log_audit_event
|
||||||
|
|
||||||
|
log_audit_event(
|
||||||
|
event_type="export_customers",
|
||||||
|
message=f"Exported {len(items)} customers to CSV",
|
||||||
|
details=f"format=CSV, count={len(items)}"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### B. Jobs Export (`/settings/jobs/export`)
|
||||||
|
|
||||||
|
**Huidige code:** `routes_settings.py` regel ~207
|
||||||
|
|
||||||
|
**Toevoegen:**
|
||||||
|
```python
|
||||||
|
# Voor return send_file(...)
|
||||||
|
log_audit_event(
|
||||||
|
event_type="export_jobs",
|
||||||
|
message=f"Exported jobs configuration",
|
||||||
|
details=json.dumps({
|
||||||
|
'format': 'JSON',
|
||||||
|
'schema': 'approved_jobs_export_v1',
|
||||||
|
'customers_count': len(payload['customers']),
|
||||||
|
'jobs_count': len(payload['jobs'])
|
||||||
|
}, indent=2)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Import Logging Toevoegen
|
||||||
|
|
||||||
|
#### A. Customers Import (`/customers/import`)
|
||||||
|
|
||||||
|
**Huidige code:** `routes_customers.py` regel ~448
|
||||||
|
|
||||||
|
**Toevoegen:**
|
||||||
|
```python
|
||||||
|
# Na db.session.commit()
|
||||||
|
log_audit_event(
|
||||||
|
event_type="import_customers",
|
||||||
|
message=f"Imported customers from CSV",
|
||||||
|
details=json.dumps({
|
||||||
|
'format': 'CSV',
|
||||||
|
'created': created,
|
||||||
|
'updated': updated,
|
||||||
|
'skipped': skipped
|
||||||
|
}, indent=2)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### B. Jobs Import (`/settings/jobs/import`)
|
||||||
|
|
||||||
|
**Huidige code:** `routes_settings.py` regel ~263
|
||||||
|
|
||||||
|
**Is al deels aanwezig, maar uitbreiden:**
|
||||||
|
```python
|
||||||
|
# Na db.session.commit()
|
||||||
|
log_audit_event(
|
||||||
|
event_type="import_jobs",
|
||||||
|
message="Imported jobs configuration",
|
||||||
|
details=json.dumps({
|
||||||
|
'format': 'JSON',
|
||||||
|
'schema': payload.get('schema'),
|
||||||
|
'customers_created': created_customers,
|
||||||
|
'customers_updated': updated_customers,
|
||||||
|
'jobs_created': created_jobs,
|
||||||
|
'jobs_updated': updated_jobs
|
||||||
|
}, indent=2)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. Changelog Updaten
|
||||||
|
|
||||||
|
**Bestand:** `docs/changelog-claude.md`
|
||||||
|
|
||||||
|
**BELANGRIJK:** De datum is nu **2026-02-07**, niet 2026-02-06!
|
||||||
|
|
||||||
|
**Toevoegen aan de changelog:**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## [2026-02-07]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Renamed AdminLog to AuditLog for better semantic clarity:
|
||||||
|
- **Model**: AdminLog → AuditLog (backwards compatible alias maintained)
|
||||||
|
- **Table**: admin_logs → audit_logs (automatic migration)
|
||||||
|
- **Function**: log_admin_event() → log_audit_event() (alias provided)
|
||||||
|
- Better reflects purpose as comprehensive audit trail for both user and system events
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Expanded audit logging for critical operations:
|
||||||
|
- **Settings Changes**: Now logs all changes to General, Mail, Autotask, and Navigation settings
|
||||||
|
- Tracks which settings changed (old value → new value)
|
||||||
|
- Excludes sensitive data (passwords)
|
||||||
|
- **Export Operations**: Logs when users export data
|
||||||
|
- Customers export (CSV): count and format
|
||||||
|
- Jobs export (JSON): schema version, customer/job counts
|
||||||
|
- **Import Operations**: Logs when users import data
|
||||||
|
- Customers import (CSV): created/updated/skipped counts
|
||||||
|
- Jobs import (JSON): schema version, all operation counts
|
||||||
|
- All logging uses event_type for filtering and includes detailed JSON in details field
|
||||||
|
- Maintains 7-day retention policy
|
||||||
|
- No performance impact (async logging)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Implementatie Tips
|
||||||
|
|
||||||
|
### Settings Changes Detecteren
|
||||||
|
|
||||||
|
Voor elke setting die je wilt tracken:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Voor de save
|
||||||
|
old_value = settings.some_setting
|
||||||
|
|
||||||
|
# Na form processing
|
||||||
|
new_value = form.some_setting.data
|
||||||
|
|
||||||
|
# Track change
|
||||||
|
if old_value != new_value:
|
||||||
|
changes['some_setting'] = {
|
||||||
|
'old': str(old_value),
|
||||||
|
'new': str(new_value)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### JSON Serialization
|
||||||
|
|
||||||
|
Gebruik `json.dumps()` voor details:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import json
|
||||||
|
|
||||||
|
details = json.dumps({
|
||||||
|
'key': 'value',
|
||||||
|
'count': 123
|
||||||
|
}, indent=2)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Event Types
|
||||||
|
|
||||||
|
**Consistent naming:**
|
||||||
|
- `settings_general`
|
||||||
|
- `settings_mail`
|
||||||
|
- `settings_autotask`
|
||||||
|
- `export_customers`
|
||||||
|
- `export_jobs`
|
||||||
|
- `import_customers`
|
||||||
|
- `import_jobs`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Volgende Stappen (Morgen)
|
||||||
|
|
||||||
|
1. UI updaten (logging.html page title)
|
||||||
|
2. Settings logging implementeren (General, Mail, Autotask)
|
||||||
|
3. Export logging implementeren (Customers, Jobs)
|
||||||
|
4. Import logging implementeren (Customers, Jobs)
|
||||||
|
5. Changelog updaten met **correcte datum 2026-02-07**
|
||||||
|
6. Testen of logging werkt
|
||||||
|
7. Committen en pushen
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 Test Checklist
|
||||||
|
|
||||||
|
Na implementatie testen:
|
||||||
|
|
||||||
|
- [ ] Wijzig general setting → check /logging
|
||||||
|
- [ ] Wijzig mail setting → check /logging
|
||||||
|
- [ ] Wijzig Autotask setting → check /logging
|
||||||
|
- [ ] Export customers → check /logging
|
||||||
|
- [ ] Export jobs → check /logging
|
||||||
|
- [ ] Import customers → check /logging
|
||||||
|
- [ ] Import jobs → check /logging
|
||||||
|
- [ ] Check of user naam correct is
|
||||||
|
- [ ] Check of details field JSON bevat
|
||||||
|
- [ ] Check of event_type correct is
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📂 Belangrijke Bestanden
|
||||||
|
|
||||||
|
```
|
||||||
|
containers/backupchecks/src/backend/app/
|
||||||
|
├── admin_logging.py # log_audit_event() functie
|
||||||
|
├── models.py # AuditLog model
|
||||||
|
├── migrations.py # migrate_rename_admin_logs_to_audit_logs()
|
||||||
|
└── main/
|
||||||
|
├── routes_settings.py # Settings routes (toevoegen logging)
|
||||||
|
├── routes_customers.py # Customer export/import (toevoegen logging)
|
||||||
|
├── routes_core.py # Logging page
|
||||||
|
└── routes_shared.py # _log_admin_event() wrapper
|
||||||
|
|
||||||
|
containers/backupchecks/src/templates/main/
|
||||||
|
└── logging.html # UI update (page title)
|
||||||
|
|
||||||
|
docs/
|
||||||
|
└── changelog-claude.md # Changelog (datum 2026-02-07!)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ Let Op!
|
||||||
|
|
||||||
|
1. **Wachtwoorden NOOIT loggen** in details veld
|
||||||
|
2. **Datum in changelog: 2026-02-07** (niet 06!)
|
||||||
|
3. **Event types consistent** houden (lowercase, underscore)
|
||||||
|
4. **JSON format** voor details veld (makkelijk te parsen)
|
||||||
|
5. **Backwards compatibility** behouden (aliases)
|
||||||
1135
TODO-documentation.md
Normal file
1158
TODO-reports-improvements.md
Normal file
@ -10,6 +10,7 @@ from .models import User # noqa: F401
|
|||||||
from .auth import login_manager
|
from .auth import login_manager
|
||||||
from .auth.routes import auth_bp
|
from .auth.routes import auth_bp
|
||||||
from .main.routes import main_bp
|
from .main.routes import main_bp
|
||||||
|
from .main.routes_documentation import doc_bp
|
||||||
from .migrations import run_migrations
|
from .migrations import run_migrations
|
||||||
from .auto_importer_service import start_auto_importer
|
from .auto_importer_service import start_auto_importer
|
||||||
|
|
||||||
@ -62,6 +63,82 @@ def create_app():
|
|||||||
|
|
||||||
app.register_blueprint(auth_bp)
|
app.register_blueprint(auth_bp)
|
||||||
app.register_blueprint(main_bp)
|
app.register_blueprint(main_bp)
|
||||||
|
app.register_blueprint(doc_bp)
|
||||||
|
|
||||||
|
@app.template_filter("local_datetime")
|
||||||
|
def format_local_datetime(utc_dt, format="%Y-%m-%d %H:%M:%S"):
|
||||||
|
"""Convert UTC datetime to UI timezone and format as string.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
utc_dt: datetime object in UTC, string representation, or None
|
||||||
|
format: strftime format string (default: '%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Formatted datetime string in UI timezone, or empty string if input is None
|
||||||
|
"""
|
||||||
|
if utc_dt is None:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# If input is already a string, try to parse it first
|
||||||
|
if isinstance(utc_dt, str):
|
||||||
|
utc_dt = utc_dt.strip()
|
||||||
|
if not utc_dt:
|
||||||
|
return ""
|
||||||
|
try:
|
||||||
|
# Try common datetime formats
|
||||||
|
for fmt in [
|
||||||
|
"%Y-%m-%d %H:%M:%S.%f", # With microseconds
|
||||||
|
"%Y-%m-%d %H:%M:%S", # Without microseconds
|
||||||
|
"%Y-%m-%dT%H:%M:%S.%fZ", # ISO format with Z
|
||||||
|
"%Y-%m-%dT%H:%M:%S", # ISO format without Z
|
||||||
|
]:
|
||||||
|
try:
|
||||||
|
utc_dt = datetime.strptime(utc_dt, fmt)
|
||||||
|
break
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
# If parsing failed, return original string
|
||||||
|
return str(utc_dt)
|
||||||
|
except Exception:
|
||||||
|
return str(utc_dt)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
except ImportError:
|
||||||
|
ZoneInfo = None # type: ignore
|
||||||
|
|
||||||
|
# Get UI timezone from settings
|
||||||
|
tz_name = "Europe/Amsterdam"
|
||||||
|
try:
|
||||||
|
from .models import SystemSettings
|
||||||
|
|
||||||
|
settings = SystemSettings.query.first()
|
||||||
|
if settings and getattr(settings, "ui_timezone", None):
|
||||||
|
tz_name = settings.ui_timezone
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Convert UTC to UI timezone
|
||||||
|
if ZoneInfo:
|
||||||
|
try:
|
||||||
|
utc_tz = ZoneInfo("UTC")
|
||||||
|
local_tz = ZoneInfo(tz_name)
|
||||||
|
|
||||||
|
# Ensure utc_dt is timezone-aware (assume UTC if naive)
|
||||||
|
if utc_dt.tzinfo is None:
|
||||||
|
utc_dt = utc_dt.replace(tzinfo=utc_tz)
|
||||||
|
|
||||||
|
# Convert to local timezone
|
||||||
|
local_dt = utc_dt.astimezone(local_tz)
|
||||||
|
return local_dt.strftime(format)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Fallback: return UTC time if conversion fails
|
||||||
|
return utc_dt.strftime(format)
|
||||||
|
|
||||||
@app.before_request
|
@app.before_request
|
||||||
def _redirect_to_dashboard_on_first_open_each_day():
|
def _redirect_to_dashboard_on_first_open_each_day():
|
||||||
|
|||||||
@ -6,10 +6,10 @@ from typing import Optional
|
|||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
from .database import db
|
from .database import db
|
||||||
from .models import AdminLog
|
from .models import AuditLog
|
||||||
|
|
||||||
|
|
||||||
def log_admin_event(
|
def log_audit_event(
|
||||||
event_type: str,
|
event_type: str,
|
||||||
message: str,
|
message: str,
|
||||||
details: Optional[str] = None,
|
details: Optional[str] = None,
|
||||||
@ -17,7 +17,7 @@ def log_admin_event(
|
|||||||
username: Optional[str] = None,
|
username: Optional[str] = None,
|
||||||
commit: bool = True,
|
commit: bool = True,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Write an entry to the in-app AdminLog table.
|
"""Write an entry to the in-app AuditLog table.
|
||||||
|
|
||||||
- This is the source for the /logging page in the website (not container logs).
|
- This is the source for the /logging page in the website (not container logs).
|
||||||
- Retention: keep only the last 7 days.
|
- Retention: keep only the last 7 days.
|
||||||
@ -30,7 +30,7 @@ def log_admin_event(
|
|||||||
except Exception:
|
except Exception:
|
||||||
username = None
|
username = None
|
||||||
|
|
||||||
entry = AdminLog(
|
entry = AuditLog(
|
||||||
user=username,
|
user=username,
|
||||||
event_type=(event_type or "event")[:64],
|
event_type=(event_type or "event")[:64],
|
||||||
message=(message or "")[:2000],
|
message=(message or "")[:2000],
|
||||||
@ -41,7 +41,7 @@ def log_admin_event(
|
|||||||
# Enforce retention: keep only the last 7 days
|
# Enforce retention: keep only the last 7 days
|
||||||
try:
|
try:
|
||||||
cutoff = datetime.utcnow() - timedelta(days=7)
|
cutoff = datetime.utcnow() - timedelta(days=7)
|
||||||
AdminLog.query.filter(AdminLog.created_at < cutoff).delete(synchronize_session=False)
|
AuditLog.query.filter(AuditLog.created_at < cutoff).delete(synchronize_session=False)
|
||||||
except Exception:
|
except Exception:
|
||||||
# Never block the main action because of retention cleanup.
|
# Never block the main action because of retention cleanup.
|
||||||
pass
|
pass
|
||||||
@ -54,3 +54,7 @@ def log_admin_event(
|
|||||||
except Exception:
|
except Exception:
|
||||||
# If logging fails, do not raise and do not print to container logs.
|
# If logging fails, do not raise and do not print to container logs.
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
|
|
||||||
|
|
||||||
|
# Legacy alias for backwards compatibility
|
||||||
|
log_admin_event = log_audit_event
|
||||||
|
|||||||
@ -3,6 +3,211 @@ Changelog data structure for Backupchecks
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
CHANGELOG = [
|
CHANGELOG = [
|
||||||
|
{
|
||||||
|
"version": "v0.1.23",
|
||||||
|
"date": "2026-02-09",
|
||||||
|
"summary": "This comprehensive release introduces a complete built-in documentation system with 33 pages covering all features, enhanced audit logging for compliance and troubleshooting, timezone-aware datetime display throughout the application, and numerous Autotask PSA integration improvements for better usability and workflow efficiency.",
|
||||||
|
"sections": [
|
||||||
|
{
|
||||||
|
"title": "Documentation System – Complete Built-in Wiki",
|
||||||
|
"type": "feature",
|
||||||
|
"subsections": [
|
||||||
|
{
|
||||||
|
"subtitle": "Core Infrastructure",
|
||||||
|
"changes": [
|
||||||
|
"New /documentation route with dedicated blueprint (doc_bp) accessible via 📖 icon in main navbar",
|
||||||
|
"Hierarchical structure with 9 sections and 33 pages covering all application features",
|
||||||
|
"Sidebar navigation with collapsible sections, active page highlighting, and sticky positioning during scrolling",
|
||||||
|
"Breadcrumb navigation for current location context",
|
||||||
|
"Previous/Next pagination buttons for sequential reading",
|
||||||
|
"Custom CSS (documentation.css) with dark mode support via CSS variables",
|
||||||
|
"Callout boxes (info, warning, tip, danger) for highlighting important content",
|
||||||
|
"Support for code blocks, tables, images (centered horizontally), and responsive design",
|
||||||
|
"Login required but accessible to all user roles",
|
||||||
|
"Full content includes comprehensive screenshots, workflow examples, troubleshooting guides, and best practices"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"subtitle": "Completed Documentation Sections (13 pages)",
|
||||||
|
"changes": [
|
||||||
|
"Getting Started (3 pages): What is BackupChecks, First Login, Quick Start Guide",
|
||||||
|
"User Management (3 pages): Users & Roles, Login & Authentication, Profile Settings",
|
||||||
|
"Customers & Jobs (4 pages): Managing Customers, Configuring Jobs, Approved Jobs, Job Schedules",
|
||||||
|
"Mail & Import (4 pages): Setup with Graph API and security best practices, Inbox Management with approval workflow, Mail Parsing with supported software list, Auto-Import Configuration with timer settings",
|
||||||
|
"Backup Review (5 pages): Complete 7-stage lifecycle workflow, Daily Jobs with schedule inference, Run Checks Modal as primary operational tool, Overrides & Exceptions with global and object-level rules, Remarks & Tickets with scoping and Autotask integration",
|
||||||
|
"Remaining 20 pages created with placeholders for future content (Reports, Autotask Integration, Settings, Troubleshooting)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"subtitle": "Documentation Accuracy Improvements",
|
||||||
|
"changes": [
|
||||||
|
"Corrected critical inaccuracies in Backup Review workflow based on user feedback",
|
||||||
|
"Clarified Run Checks is the primary daily operational tool (not Daily Jobs)",
|
||||||
|
"Emphasized all runs must be reviewed regardless of status (including successful runs)",
|
||||||
|
"Corrected review mechanism from per-run to per-job (marking one job reviews all its runs)",
|
||||||
|
"Updated override status indicators to show blue badges (not green) for differentiation",
|
||||||
|
"Fixed various feature descriptions to match actual UI behavior (EML links, customer selection workflow, parser management restrictions)",
|
||||||
|
"Removed non-existent features: individual email re-parse in modal, parser enable/disable UI, AI-powered parsing, Inbox filters section",
|
||||||
|
"Enhanced Mail Import Setup with comprehensive security best practices including Application Access Policy to restrict to one mailbox"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Audit Logging Enhancements",
|
||||||
|
"type": "improvement",
|
||||||
|
"subsections": [
|
||||||
|
{
|
||||||
|
"subtitle": "System Renaming and Semantic Clarity",
|
||||||
|
"changes": [
|
||||||
|
"Renamed AdminLog to AuditLog for better semantic clarity across codebase",
|
||||||
|
"Updated model name: AdminLog → AuditLog (backwards compatible alias maintained)",
|
||||||
|
"Updated table name: admin_logs → audit_logs (automatic migration)",
|
||||||
|
"Updated function: log_admin_event() → log_audit_event() (alias provided for compatibility)",
|
||||||
|
"Updated UI labels: \"Admin activity\" → \"System Audit Log\" in logging page header",
|
||||||
|
"Better reflects purpose as comprehensive audit trail for both user and system events"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"subtitle": "Expanded Event Coverage for Compliance",
|
||||||
|
"changes": [
|
||||||
|
"Settings Changes: Now logs all changes to General, Mail, and Autotask settings with old → new value comparison",
|
||||||
|
"Event types: settings_general, settings_mail, settings_autotask",
|
||||||
|
"Excludes sensitive data (passwords are never logged)",
|
||||||
|
"Example logged fields: ui_timezone, require_daily_dashboard_visit, is_sandbox_environment, graph_mailbox, autotask_enabled",
|
||||||
|
"Export Operations: Logs Customers export (CSV format, record count) and Jobs export (JSON schema version, customer/job counts)",
|
||||||
|
"Import Operations: Logs Customers import (created/updated/skipped counts) and Jobs import (customer and job operation counts)",
|
||||||
|
"All logging uses structured event_type for filtering and analysis",
|
||||||
|
"Detailed JSON in details field for complete audit trail",
|
||||||
|
"Maintains 7-day retention policy for performance",
|
||||||
|
"No performance impact (async logging)",
|
||||||
|
"Helps with compliance audits, security monitoring, and troubleshooting configuration issues"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Timezone-Aware Datetime Display",
|
||||||
|
"type": "improvement",
|
||||||
|
"changes": [
|
||||||
|
"Added local_datetime Jinja2 template filter to convert UTC datetimes to configured UI timezone",
|
||||||
|
"All datetime fields now automatically display in configured timezone (Settings > General > UI Timezone)",
|
||||||
|
"Database values remain stored in UTC for consistency and reliability",
|
||||||
|
"Affected Pages: Customers (Autotask last sync), Settings (reference data sync), Feedback, Logging, Overrides, Archived Jobs, Tickets, Remarks, Inbox, Job Detail, Admin Mail",
|
||||||
|
"Custom format support via strftime parameter (e.g., |local_datetime('%d-%m-%Y %H:%M'))",
|
||||||
|
"Enhanced filter to handle both datetime objects and string datetime values for flexibility",
|
||||||
|
"Graceful fallback to UTC display if timezone conversion fails",
|
||||||
|
"Default timezone: Europe/Amsterdam (configurable via SystemSettings.ui_timezone)",
|
||||||
|
"Improves operator experience by showing times in local context"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Autotask PSA Integration Improvements",
|
||||||
|
"type": "improvement",
|
||||||
|
"subsections": [
|
||||||
|
{
|
||||||
|
"subtitle": "Usability Enhancements",
|
||||||
|
"changes": [
|
||||||
|
"Added \"Open in Autotask\" button in Run Checks modal for direct navigation to tickets in Autotask PSA (opens in new tab with proper URL)",
|
||||||
|
"Button only appears when ticket is linked and Autotask integration is enabled",
|
||||||
|
"Added collapsible text functionality (ellipsis-field) to \"Overall remark\" and \"Remark\" fields to prevent long text from hiding action buttons",
|
||||||
|
"Auto-load Autotask reference data when opening Settings page (queues, sources, statuses, priorities)",
|
||||||
|
"Only triggers when Autotask is enabled, credentials configured, and cache is empty",
|
||||||
|
"Eliminates need to manually click \"Refresh reference data\" before selecting defaults",
|
||||||
|
"Displays info message with loaded data counts",
|
||||||
|
"Renamed \"Refresh\" button to \"Search\" in Link existing ticket modal for better clarity"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"subtitle": "Settings Organization and Validation",
|
||||||
|
"changes": [
|
||||||
|
"Reorganized Autotask settings into two separate forms with dedicated save buttons",
|
||||||
|
"Autotask Settings form (connection, environment, credentials) with \"Save Autotask Settings\" button",
|
||||||
|
"Ticket Defaults form (queue, source, status, priority) with \"Save Ticket Defaults\" button",
|
||||||
|
"Clear visual separation improves UX and prevents accidental incomplete saves",
|
||||||
|
"All fields marked as required with red asterisks (*)",
|
||||||
|
"HTML5 validation prevents saving incomplete configurations",
|
||||||
|
"Protected default Ticket Status value against accidental clearing",
|
||||||
|
"Only updates when valid status selected, preserves existing value if dropdown empty",
|
||||||
|
"Fixes issue where \"Create Autotask ticket\" failed due to missing default status"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"subtitle": "Data Portability and Migration Support",
|
||||||
|
"changes": [
|
||||||
|
"Extended Customer CSV export/import to include autotask_company_id and autotask_company_name columns",
|
||||||
|
"Import reads Autotask mapping fields and applies them to existing or new customers",
|
||||||
|
"Backwards compatible - old CSV files without Autotask columns still work",
|
||||||
|
"Import resets autotask_mapping_status and autotask_last_sync_at for resynchronization",
|
||||||
|
"Extended Jobs JSON export/import to include Autotask fields in customers array",
|
||||||
|
"Schema remains approved_jobs_export_v1 for backwards compatibility",
|
||||||
|
"Import message shows both created and updated customer counts",
|
||||||
|
"Enables preservation of Autotask company mappings during system reset/migration workflows",
|
||||||
|
"Critical for disaster recovery and environment promotion (dev → test → production)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Environment Identification and Safety",
|
||||||
|
"type": "feature",
|
||||||
|
"changes": [
|
||||||
|
"Added visual banner system to distinguish non-production environments and prevent accidental actions",
|
||||||
|
"New is_sandbox_environment boolean column in SystemSettings model (defaults to False for production safety)",
|
||||||
|
"Database migration migrate_system_settings_sandbox_environment() for automatic schema update (idempotent)",
|
||||||
|
"Settings UI with new \"Environment\" card in Settings > General with toggle switch",
|
||||||
|
"CSS styling via sandbox.css: diagonal red banner in top-left corner displaying \"SANDBOX\"",
|
||||||
|
"Position: top-left corner, rotated 45 degrees with letter-spacing for visibility",
|
||||||
|
"Color: Bootstrap danger red (#dc3545) with white text and box-shadow for depth",
|
||||||
|
"Z-index: 9999 (always on top, visible across all pages)",
|
||||||
|
"pointer-events: none - banner non-interactive, elements behind remain clickable",
|
||||||
|
"Banner conditionally displayed only when setting is enabled",
|
||||||
|
"Banner placed in base template directly after <body> tag, before navbar",
|
||||||
|
"Helps prevent accidental production-like actions in test/development systems"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Job Visibility and Filtering Improvements",
|
||||||
|
"type": "improvement",
|
||||||
|
"changes": [
|
||||||
|
"Jobs from archived jobs and inactive customers no longer appear in Daily Jobs, Run Checks, and Jobs list pages",
|
||||||
|
"Added customer active status filtering to all job list queries in backend",
|
||||||
|
"Jobs now filtered where customer is NULL or active=True alongside existing archived=False filter",
|
||||||
|
"Prevents showing jobs with \"-\" status from deleted customers or archived jobs",
|
||||||
|
"Reduces clutter in operational views and improves focus on active jobs",
|
||||||
|
"Improves query performance by reducing result set size"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Repository Management and Security",
|
||||||
|
"type": "improvement",
|
||||||
|
"changes": [
|
||||||
|
"Added .gitignore file to protect confidential .claude directory",
|
||||||
|
"Directory contains AI assistant context, memory files, and user-specific configuration",
|
||||||
|
"Prevents accidental commits of sensitive information to version control",
|
||||||
|
"Protects user privacy and prevents repository pollution with local-only data"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Bug Fixes and Stability Improvements",
|
||||||
|
"type": "fixed",
|
||||||
|
"changes": [
|
||||||
|
"Fixed missing import for _log_audit_event in routes_customers (resolved crash during customer operations)",
|
||||||
|
"Enhanced local_datetime filter to handle string datetime values in addition to datetime objects",
|
||||||
|
"Prevents template rendering errors when datetime is stored as string",
|
||||||
|
"Fixed callout box formatting throughout documentation (corrected CSS specificity)",
|
||||||
|
"Centered all documentation images horizontally (display: block, margin: 20px auto)",
|
||||||
|
"Removed fabricated features from documentation (AI-powered parsing, Inbox filters, parser management UI)",
|
||||||
|
"Corrected workflow descriptions to match actual UI implementation",
|
||||||
|
"Fixed customer selection workflow (job name read-only during approval)",
|
||||||
|
"Fixed email detail modal layout documentation (two-column: left metadata/actions/objects, right email iframe)",
|
||||||
|
"Updated status badge colors to match actual behavior (blue for overrides, green for genuine successes)",
|
||||||
|
"Fixed per-job review mechanism documentation (marking one job reviews all its runs)",
|
||||||
|
"Corrected bulk review from \"select run checkboxes\" to \"select job checkboxes\""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "v0.1.22",
|
"version": "v0.1.22",
|
||||||
"date": "2026-02-05",
|
"date": "2026-02-05",
|
||||||
|
|||||||
@ -230,7 +230,7 @@ def dashboard():
|
|||||||
@login_required
|
@login_required
|
||||||
@roles_required("admin", "operator")
|
@roles_required("admin", "operator")
|
||||||
def logging_page():
|
def logging_page():
|
||||||
# Server-side view of AdminLog entries.
|
# Server-side view of AuditLog entries (system audit trail).
|
||||||
try:
|
try:
|
||||||
page = int(request.args.get("page", "1"))
|
page = int(request.args.get("page", "1"))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@ -239,7 +239,7 @@ def logging_page():
|
|||||||
page = 1
|
page = 1
|
||||||
|
|
||||||
per_page = 20
|
per_page = 20
|
||||||
query = AdminLog.query.order_by(AdminLog.created_at.desc().nullslast(), AdminLog.id.desc())
|
query = AuditLog.query.order_by(AuditLog.created_at.desc().nullslast(), AuditLog.id.desc())
|
||||||
total_items = query.count()
|
total_items = query.count()
|
||||||
total_pages = max(1, math.ceil(total_items / per_page)) if total_items else 1
|
total_pages = max(1, math.ceil(total_items / per_page)) if total_items else 1
|
||||||
if page > total_pages:
|
if page > total_pages:
|
||||||
|
|||||||
@ -1,4 +1,34 @@
|
|||||||
from .routes_shared import * # noqa: F401,F403
|
from .routes_shared import * # noqa: F401,F403
|
||||||
|
from .routes_shared import _log_admin_event
|
||||||
|
|
||||||
|
# Explicit imports for robustness across mixed deployments.
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from ..database import db
|
||||||
|
from ..models import SystemSettings
|
||||||
|
|
||||||
|
|
||||||
|
def _get_or_create_settings_local():
|
||||||
|
"""Return SystemSettings, creating a default row if missing.
|
||||||
|
|
||||||
|
This module should not depend on star-imported helpers for settings.
|
||||||
|
Mixed deployments (partial container updates) can otherwise raise a
|
||||||
|
NameError on /customers when the shared helper is not present.
|
||||||
|
"""
|
||||||
|
|
||||||
|
settings = SystemSettings.query.first()
|
||||||
|
if settings is None:
|
||||||
|
settings = SystemSettings(
|
||||||
|
auto_import_enabled=False,
|
||||||
|
auto_import_interval_minutes=15,
|
||||||
|
auto_import_max_items=50,
|
||||||
|
manual_import_batch_size=50,
|
||||||
|
auto_import_cutoff_date=datetime.utcnow().date(),
|
||||||
|
ingest_eml_retention_days=7,
|
||||||
|
)
|
||||||
|
db.session.add(settings)
|
||||||
|
db.session.commit()
|
||||||
|
return settings
|
||||||
|
|
||||||
# Explicit imports for robustness across mixed deployments.
|
# Explicit imports for robustness across mixed deployments.
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@ -425,9 +455,21 @@ def customers_export():
|
|||||||
items = Customer.query.order_by(Customer.name.asc()).all()
|
items = Customer.query.order_by(Customer.name.asc()).all()
|
||||||
buf = StringIO()
|
buf = StringIO()
|
||||||
writer = csv.writer(buf)
|
writer = csv.writer(buf)
|
||||||
writer.writerow(["name", "active"])
|
writer.writerow(["name", "active", "autotask_company_id", "autotask_company_name"])
|
||||||
for c in items:
|
for c in items:
|
||||||
writer.writerow([c.name, "1" if c.active else "0"])
|
writer.writerow([
|
||||||
|
c.name,
|
||||||
|
"1" if c.active else "0",
|
||||||
|
c.autotask_company_id if c.autotask_company_id else "",
|
||||||
|
c.autotask_company_name if c.autotask_company_name else ""
|
||||||
|
])
|
||||||
|
|
||||||
|
# Audit logging
|
||||||
|
_log_admin_event(
|
||||||
|
"export_customers",
|
||||||
|
f"Exported {len(items)} customers to CSV",
|
||||||
|
details=f"format=CSV, count={len(items)}"
|
||||||
|
)
|
||||||
|
|
||||||
out = buf.getvalue().encode("utf-8")
|
out = buf.getvalue().encode("utf-8")
|
||||||
return Response(
|
return Response(
|
||||||
@ -475,6 +517,14 @@ def customers_import():
|
|||||||
header = [c.strip().lower() for c in (rows[0] or [])]
|
header = [c.strip().lower() for c in (rows[0] or [])]
|
||||||
start_idx = 1 if ("name" in header or "customer" in header) else 0
|
start_idx = 1 if ("name" in header or "customer" in header) else 0
|
||||||
|
|
||||||
|
# Detect Autotask columns (backwards compatible - these are optional)
|
||||||
|
autotask_id_idx = None
|
||||||
|
autotask_name_idx = None
|
||||||
|
if "autotask_company_id" in header:
|
||||||
|
autotask_id_idx = header.index("autotask_company_id")
|
||||||
|
if "autotask_company_name" in header:
|
||||||
|
autotask_name_idx = header.index("autotask_company_name")
|
||||||
|
|
||||||
for r in rows[start_idx:]:
|
for r in rows[start_idx:]:
|
||||||
if not r:
|
if not r:
|
||||||
continue
|
continue
|
||||||
@ -491,21 +541,58 @@ def customers_import():
|
|||||||
elif a in ("0", "false", "no", "n", "inactive"):
|
elif a in ("0", "false", "no", "n", "inactive"):
|
||||||
active_val = False
|
active_val = False
|
||||||
|
|
||||||
|
# Read Autotask fields if present
|
||||||
|
autotask_company_id = None
|
||||||
|
autotask_company_name = None
|
||||||
|
if autotask_id_idx is not None and len(r) > autotask_id_idx:
|
||||||
|
id_val = (r[autotask_id_idx] or "").strip()
|
||||||
|
if id_val:
|
||||||
|
try:
|
||||||
|
autotask_company_id = int(id_val)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
if autotask_name_idx is not None and len(r) > autotask_name_idx:
|
||||||
|
name_val = (r[autotask_name_idx] or "").strip()
|
||||||
|
if name_val:
|
||||||
|
autotask_company_name = name_val
|
||||||
|
|
||||||
existing = Customer.query.filter_by(name=name).first()
|
existing = Customer.query.filter_by(name=name).first()
|
||||||
if existing:
|
if existing:
|
||||||
if active_val is not None:
|
if active_val is not None:
|
||||||
existing.active = active_val
|
existing.active = active_val
|
||||||
|
# Update Autotask mapping if provided in CSV
|
||||||
|
if autotask_company_id is not None:
|
||||||
|
existing.autotask_company_id = autotask_company_id
|
||||||
|
existing.autotask_company_name = autotask_company_name
|
||||||
|
existing.autotask_mapping_status = None # Will be resynced
|
||||||
|
existing.autotask_last_sync_at = None
|
||||||
updated += 1
|
updated += 1
|
||||||
else:
|
else:
|
||||||
skipped += 1
|
c = Customer(
|
||||||
else:
|
name=name,
|
||||||
c = Customer(name=name, active=True if active_val is None else active_val)
|
active=True if active_val is None else active_val,
|
||||||
|
autotask_company_id=autotask_company_id,
|
||||||
|
autotask_company_name=autotask_company_name
|
||||||
|
)
|
||||||
db.session.add(c)
|
db.session.add(c)
|
||||||
created += 1
|
created += 1
|
||||||
|
|
||||||
try:
|
try:
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash(f"Import finished. Created: {created}, Updated: {updated}, Skipped: {skipped}.", "success")
|
flash(f"Import finished. Created: {created}, Updated: {updated}, Skipped: {skipped}.", "success")
|
||||||
|
|
||||||
|
# Audit logging
|
||||||
|
import json
|
||||||
|
_log_admin_event(
|
||||||
|
"import_customers",
|
||||||
|
f"Imported customers from CSV",
|
||||||
|
details=json.dumps({
|
||||||
|
"format": "CSV",
|
||||||
|
"created": created,
|
||||||
|
"updated": updated,
|
||||||
|
"skipped": skipped
|
||||||
|
}, indent=2)
|
||||||
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
current_app.logger.exception(f"Failed to import customers: {exc}")
|
current_app.logger.exception(f"Failed to import customers: {exc}")
|
||||||
|
|||||||
@ -77,6 +77,7 @@ def daily_jobs():
|
|||||||
jobs = (
|
jobs = (
|
||||||
Job.query.join(Customer, isouter=True)
|
Job.query.join(Customer, isouter=True)
|
||||||
.filter(Job.archived.is_(False))
|
.filter(Job.archived.is_(False))
|
||||||
|
.filter(db.or_(Customer.id.is_(None), Customer.active.is_(True)))
|
||||||
.order_by(Customer.name.asc().nullslast(), Job.backup_software.asc(), Job.backup_type.asc(), Job.job_name.asc())
|
.order_by(Customer.name.asc().nullslast(), Job.backup_software.asc(), Job.backup_type.asc(), Job.job_name.asc())
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
|
|||||||
@ -0,0 +1,185 @@
|
|||||||
|
"""Documentation routes.
|
||||||
|
|
||||||
|
Provides access to static HTML documentation pages for logged-in users.
|
||||||
|
All pages are accessible to any authenticated user (admin, operator, reporter, viewer).
|
||||||
|
"""
|
||||||
|
|
||||||
|
from flask import Blueprint, render_template, abort, redirect, url_for
|
||||||
|
from flask_login import login_required
|
||||||
|
|
||||||
|
|
||||||
|
doc_bp = Blueprint('documentation', __name__, url_prefix='/documentation')
|
||||||
|
|
||||||
|
|
||||||
|
# Documentation structure (for navigation and routing)
|
||||||
|
DOCUMENTATION_STRUCTURE = {
|
||||||
|
'getting-started': {
|
||||||
|
'title': 'Getting Started',
|
||||||
|
'icon': '🏠',
|
||||||
|
'pages': [
|
||||||
|
{'slug': 'what-is-backupchecks', 'title': 'What is BackupChecks?'},
|
||||||
|
{'slug': 'first-login', 'title': 'First Login & Dashboard'},
|
||||||
|
{'slug': 'quick-start', 'title': 'Quick Start Checklist'},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'users': {
|
||||||
|
'title': 'User Management',
|
||||||
|
'icon': '👥',
|
||||||
|
'pages': [
|
||||||
|
{'slug': 'users-and-roles', 'title': 'Users & Roles'},
|
||||||
|
{'slug': 'login-authentication', 'title': 'Login & Authentication'},
|
||||||
|
{'slug': 'profile-settings', 'title': 'Profile Settings'},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'customers-jobs': {
|
||||||
|
'title': 'Customers & Jobs',
|
||||||
|
'icon': '💼',
|
||||||
|
'pages': [
|
||||||
|
{'slug': 'managing-customers', 'title': 'Managing Customers'},
|
||||||
|
{'slug': 'configuring-jobs', 'title': 'Configuring Jobs'},
|
||||||
|
{'slug': 'approved-jobs', 'title': 'Approved Jobs'},
|
||||||
|
{'slug': 'job-schedules', 'title': 'Job Schedules'},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'mail-import': {
|
||||||
|
'title': 'Mail & Import',
|
||||||
|
'icon': '📧',
|
||||||
|
'pages': [
|
||||||
|
{'slug': 'setup', 'title': 'Mail Import Setup'},
|
||||||
|
{'slug': 'inbox-management', 'title': 'Inbox Management'},
|
||||||
|
{'slug': 'mail-parsing', 'title': 'Mail Parsing'},
|
||||||
|
{'slug': 'auto-import', 'title': 'Auto-Import Configuration'},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'backup-review': {
|
||||||
|
'title': 'Backup Review',
|
||||||
|
'icon': '✅',
|
||||||
|
'pages': [
|
||||||
|
{'slug': 'approving-backups', 'title': 'Approving Backups'},
|
||||||
|
{'slug': 'daily-jobs', 'title': 'Daily Jobs View'},
|
||||||
|
{'slug': 'run-checks-modal', 'title': 'Run Checks Modal'},
|
||||||
|
{'slug': 'overrides', 'title': 'Overrides & Exceptions'},
|
||||||
|
{'slug': 'remarks-tickets', 'title': 'Remarks & Tickets'},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'reports': {
|
||||||
|
'title': 'Reports',
|
||||||
|
'icon': '📊',
|
||||||
|
'pages': [
|
||||||
|
{'slug': 'creating-reports', 'title': 'Creating Reports'},
|
||||||
|
{'slug': 'relative-periods', 'title': 'Relative Periods'},
|
||||||
|
{'slug': 'scheduling', 'title': 'Report Scheduling'},
|
||||||
|
{'slug': 'exporting-data', 'title': 'Exporting Data'},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'autotask': {
|
||||||
|
'title': 'Autotask Integration',
|
||||||
|
'icon': '🎫',
|
||||||
|
'pages': [
|
||||||
|
{'slug': 'setup-configuration', 'title': 'Setup & Configuration'},
|
||||||
|
{'slug': 'company-mapping', 'title': 'Company Mapping'},
|
||||||
|
{'slug': 'creating-tickets', 'title': 'Creating Tickets'},
|
||||||
|
{'slug': 'ticket-management', 'title': 'Ticket Management'},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'settings': {
|
||||||
|
'title': 'Settings',
|
||||||
|
'icon': '⚙️',
|
||||||
|
'pages': [
|
||||||
|
{'slug': 'general', 'title': 'General Settings'},
|
||||||
|
{'slug': 'mail-configuration', 'title': 'Mail Configuration'},
|
||||||
|
{'slug': 'autotask-integration', 'title': 'Autotask Integration'},
|
||||||
|
{'slug': 'reporting-settings', 'title': 'Reporting Settings'},
|
||||||
|
{'slug': 'user-management', 'title': 'User Management'},
|
||||||
|
{'slug': 'maintenance', 'title': 'Maintenance'},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
'troubleshooting': {
|
||||||
|
'title': 'Troubleshooting',
|
||||||
|
'icon': '❓',
|
||||||
|
'pages': [
|
||||||
|
{'slug': 'common-issues', 'title': 'Common Issues'},
|
||||||
|
{'slug': 'faq', 'title': 'FAQ'},
|
||||||
|
{'slug': 'support-contact', 'title': 'Support Contact'},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_navigation_context(current_section=None, current_page=None):
|
||||||
|
"""Build navigation context with active states."""
|
||||||
|
return {
|
||||||
|
'structure': DOCUMENTATION_STRUCTURE,
|
||||||
|
'current_section': current_section,
|
||||||
|
'current_page': current_page
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_prev_next(section, page_slug):
|
||||||
|
"""Get previous and next page for navigation."""
|
||||||
|
# Flatten all pages into single list
|
||||||
|
all_pages = []
|
||||||
|
for sect_key, sect_data in DOCUMENTATION_STRUCTURE.items():
|
||||||
|
for page in sect_data['pages']:
|
||||||
|
all_pages.append({
|
||||||
|
'section': sect_key,
|
||||||
|
'slug': page['slug'],
|
||||||
|
'title': page['title']
|
||||||
|
})
|
||||||
|
|
||||||
|
# Find current page index
|
||||||
|
current_idx = None
|
||||||
|
for i, p in enumerate(all_pages):
|
||||||
|
if p['section'] == section and p['slug'] == page_slug:
|
||||||
|
current_idx = i
|
||||||
|
break
|
||||||
|
|
||||||
|
if current_idx is None:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
prev_page = all_pages[current_idx - 1] if current_idx > 0 else None
|
||||||
|
next_page = all_pages[current_idx + 1] if current_idx < len(all_pages) - 1 else None
|
||||||
|
|
||||||
|
return prev_page, next_page
|
||||||
|
|
||||||
|
|
||||||
|
@doc_bp.route('/')
|
||||||
|
@doc_bp.route('/index')
|
||||||
|
@login_required
|
||||||
|
def index():
|
||||||
|
"""Documentation home - redirect to first page."""
|
||||||
|
return redirect(url_for('documentation.page', section='getting-started', page='what-is-backupchecks'))
|
||||||
|
|
||||||
|
|
||||||
|
@doc_bp.route('/<section>/<page>')
|
||||||
|
@login_required
|
||||||
|
def page(section, page):
|
||||||
|
"""Render a documentation page."""
|
||||||
|
# Validate section and page exist
|
||||||
|
if section not in DOCUMENTATION_STRUCTURE:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
section_data = DOCUMENTATION_STRUCTURE[section]
|
||||||
|
page_exists = any(p['slug'] == page for p in section_data['pages'])
|
||||||
|
if not page_exists:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
# Get page title
|
||||||
|
page_title = next(p['title'] for p in section_data['pages'] if p['slug'] == page)
|
||||||
|
|
||||||
|
# Get navigation context
|
||||||
|
nav_context = get_navigation_context(section, page)
|
||||||
|
|
||||||
|
# Get prev/next pages
|
||||||
|
prev_page, next_page = get_prev_next(section, page)
|
||||||
|
|
||||||
|
# Render template
|
||||||
|
template_path = f'documentation/{section}/{page}.html'
|
||||||
|
return render_template(
|
||||||
|
template_path,
|
||||||
|
page_title=page_title,
|
||||||
|
section_title=section_data['title'],
|
||||||
|
navigation=nav_context,
|
||||||
|
prev_page=prev_page,
|
||||||
|
next_page=next_page
|
||||||
|
)
|
||||||
@ -18,6 +18,7 @@ def jobs():
|
|||||||
Job.query
|
Job.query
|
||||||
.filter(Job.archived.is_(False))
|
.filter(Job.archived.is_(False))
|
||||||
.outerjoin(Customer, Customer.id == Job.customer_id)
|
.outerjoin(Customer, Customer.id == Job.customer_id)
|
||||||
|
.filter(db.or_(Customer.id.is_(None), Customer.active.is_(True)))
|
||||||
.add_columns(
|
.add_columns(
|
||||||
Job.id,
|
Job.id,
|
||||||
Job.backup_software,
|
Job.backup_software,
|
||||||
|
|||||||
@ -841,7 +841,12 @@ def run_checks_page():
|
|||||||
)
|
)
|
||||||
last_reviewed_map = {int(jid): (dt if dt else None) for jid, dt in last_reviewed_rows}
|
last_reviewed_map = {int(jid): (dt if dt else None) for jid, dt in last_reviewed_rows}
|
||||||
|
|
||||||
jobs = Job.query.filter(Job.archived.is_(False)).all()
|
jobs = (
|
||||||
|
Job.query.outerjoin(Customer)
|
||||||
|
.filter(Job.archived.is_(False))
|
||||||
|
.filter(db.or_(Customer.id.is_(None), Customer.active.is_(True)))
|
||||||
|
.all()
|
||||||
|
)
|
||||||
today_local = _to_amsterdam_date(datetime.utcnow()) or datetime.utcnow().date()
|
today_local = _to_amsterdam_date(datetime.utcnow()) or datetime.utcnow().date()
|
||||||
|
|
||||||
for job in jobs:
|
for job in jobs:
|
||||||
|
|||||||
@ -219,14 +219,22 @@ def settings_jobs_export():
|
|||||||
customer_by_id = {}
|
customer_by_id = {}
|
||||||
for job in jobs:
|
for job in jobs:
|
||||||
if job.customer_id and job.customer and job.customer.name:
|
if job.customer_id and job.customer and job.customer.name:
|
||||||
customer_by_id[job.customer_id] = job.customer.name
|
customer_by_id[job.customer_id] = job.customer
|
||||||
|
|
||||||
payload["customers"] = [{"name": name} for _, name in sorted(customer_by_id.items(), key=lambda x: x[1].lower())]
|
payload["customers"] = [
|
||||||
|
{
|
||||||
|
"name": customer.name,
|
||||||
|
"autotask_company_id": customer.autotask_company_id,
|
||||||
|
"autotask_company_name": customer.autotask_company_name
|
||||||
|
}
|
||||||
|
for _, customer in sorted(customer_by_id.items(), key=lambda x: x[1].name.lower())
|
||||||
|
]
|
||||||
|
|
||||||
for job in jobs:
|
for job in jobs:
|
||||||
|
customer = customer_by_id.get(job.customer_id)
|
||||||
payload["jobs"].append(
|
payload["jobs"].append(
|
||||||
{
|
{
|
||||||
"customer_name": customer_by_id.get(job.customer_id),
|
"customer_name": customer.name if customer else None,
|
||||||
"from_address": getattr(job, "from_address", None),
|
"from_address": getattr(job, "from_address", None),
|
||||||
"backup_software": job.backup_software,
|
"backup_software": job.backup_software,
|
||||||
"backup_type": job.backup_type,
|
"backup_type": job.backup_type,
|
||||||
@ -243,6 +251,18 @@ def settings_jobs_export():
|
|||||||
payload["counts"]["customers"] = len(payload["customers"])
|
payload["counts"]["customers"] = len(payload["customers"])
|
||||||
payload["counts"]["jobs"] = len(payload["jobs"])
|
payload["counts"]["jobs"] = len(payload["jobs"])
|
||||||
|
|
||||||
|
# Audit logging
|
||||||
|
_log_admin_event(
|
||||||
|
"export_jobs",
|
||||||
|
f"Exported jobs configuration",
|
||||||
|
details=json.dumps({
|
||||||
|
"format": "JSON",
|
||||||
|
"schema": "approved_jobs_export_v1",
|
||||||
|
"customers_count": len(payload["customers"]),
|
||||||
|
"jobs_count": len(payload["jobs"])
|
||||||
|
}, indent=2)
|
||||||
|
)
|
||||||
|
|
||||||
filename = f"approved-jobs-export-{datetime.utcnow().strftime('%Y%m%d-%H%M%S')}.json"
|
filename = f"approved-jobs-export-{datetime.utcnow().strftime('%Y%m%d-%H%M%S')}.json"
|
||||||
blob = json.dumps(payload, indent=2, ensure_ascii=False).encode("utf-8")
|
blob = json.dumps(payload, indent=2, ensure_ascii=False).encode("utf-8")
|
||||||
return send_file(
|
return send_file(
|
||||||
@ -283,10 +303,46 @@ def settings_jobs_import():
|
|||||||
return redirect(url_for("main.settings", section="general"))
|
return redirect(url_for("main.settings", section="general"))
|
||||||
|
|
||||||
created_customers = 0
|
created_customers = 0
|
||||||
|
updated_customers = 0
|
||||||
created_jobs = 0
|
created_jobs = 0
|
||||||
updated_jobs = 0
|
updated_jobs = 0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# First, process customers from the payload (if present)
|
||||||
|
customers_data = payload.get("customers") or []
|
||||||
|
if isinstance(customers_data, list):
|
||||||
|
for cust_item in customers_data:
|
||||||
|
if not isinstance(cust_item, dict):
|
||||||
|
continue
|
||||||
|
cust_name = (cust_item.get("name") or "").strip()
|
||||||
|
if not cust_name:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Read Autotask fields (backwards compatible - optional)
|
||||||
|
autotask_company_id = cust_item.get("autotask_company_id")
|
||||||
|
autotask_company_name = cust_item.get("autotask_company_name")
|
||||||
|
|
||||||
|
existing_customer = Customer.query.filter_by(name=cust_name).first()
|
||||||
|
if existing_customer:
|
||||||
|
# Update Autotask mapping if provided
|
||||||
|
if autotask_company_id is not None:
|
||||||
|
existing_customer.autotask_company_id = autotask_company_id
|
||||||
|
existing_customer.autotask_company_name = autotask_company_name
|
||||||
|
existing_customer.autotask_mapping_status = None # Will be resynced
|
||||||
|
existing_customer.autotask_last_sync_at = None
|
||||||
|
updated_customers += 1
|
||||||
|
else:
|
||||||
|
new_customer = Customer(
|
||||||
|
name=cust_name,
|
||||||
|
active=True,
|
||||||
|
autotask_company_id=autotask_company_id,
|
||||||
|
autotask_company_name=autotask_company_name
|
||||||
|
)
|
||||||
|
db.session.add(new_customer)
|
||||||
|
created_customers += 1
|
||||||
|
db.session.flush()
|
||||||
|
|
||||||
|
# Now process jobs (customers should already exist from above, or will be created on-the-fly)
|
||||||
for item in jobs:
|
for item in jobs:
|
||||||
if not isinstance(item, dict):
|
if not isinstance(item, dict):
|
||||||
continue
|
continue
|
||||||
@ -388,9 +444,23 @@ def settings_jobs_import():
|
|||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash(
|
flash(
|
||||||
f"Import completed. Customers created: {created_customers}. Jobs created: {created_jobs}. Jobs updated: {updated_jobs}.",
|
f"Import completed. Customers created: {created_customers}, updated: {updated_customers}. Jobs created: {created_jobs}, updated: {updated_jobs}.",
|
||||||
"success",
|
"success",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Audit logging
|
||||||
|
_log_admin_event(
|
||||||
|
"import_jobs",
|
||||||
|
"Imported jobs configuration",
|
||||||
|
details=json.dumps({
|
||||||
|
"format": "JSON",
|
||||||
|
"schema": payload.get("schema"),
|
||||||
|
"customers_created": created_customers,
|
||||||
|
"customers_updated": updated_customers,
|
||||||
|
"jobs_created": created_jobs,
|
||||||
|
"jobs_updated": updated_jobs
|
||||||
|
}, indent=2)
|
||||||
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
print(f"[settings-jobs] Import failed: {exc}")
|
print(f"[settings-jobs] Import failed: {exc}")
|
||||||
@ -409,6 +479,30 @@ def settings():
|
|||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
autotask_form_touched = any(str(k).startswith("autotask_") for k in (request.form or {}).keys())
|
autotask_form_touched = any(str(k).startswith("autotask_") for k in (request.form or {}).keys())
|
||||||
import_form_touched = any(str(k).startswith("auto_import_") or str(k).startswith("manual_import_") or str(k).startswith("ingest_eml_") for k in (request.form or {}).keys())
|
import_form_touched = any(str(k).startswith("auto_import_") or str(k).startswith("manual_import_") or str(k).startswith("ingest_eml_") for k in (request.form or {}).keys())
|
||||||
|
general_form_touched = "ui_timezone" in request.form
|
||||||
|
mail_form_touched = any(k in request.form for k in ["graph_tenant_id", "graph_client_id", "graph_mailbox", "incoming_folder", "processed_folder"])
|
||||||
|
|
||||||
|
# Track changes for audit logging
|
||||||
|
changes_general = {}
|
||||||
|
changes_mail = {}
|
||||||
|
changes_autotask = {}
|
||||||
|
|
||||||
|
# Capture old values before modifications
|
||||||
|
old_ui_timezone = settings.ui_timezone
|
||||||
|
old_require_daily_dashboard_visit = settings.require_daily_dashboard_visit
|
||||||
|
old_is_sandbox_environment = settings.is_sandbox_environment
|
||||||
|
old_graph_tenant_id = settings.graph_tenant_id
|
||||||
|
old_graph_client_id = settings.graph_client_id
|
||||||
|
old_graph_mailbox = settings.graph_mailbox
|
||||||
|
old_incoming_folder = settings.incoming_folder
|
||||||
|
old_processed_folder = settings.processed_folder
|
||||||
|
old_auto_import_enabled = settings.auto_import_enabled
|
||||||
|
old_auto_import_interval = settings.auto_import_interval_minutes
|
||||||
|
old_autotask_enabled = getattr(settings, "autotask_enabled", None)
|
||||||
|
old_autotask_environment = getattr(settings, "autotask_environment", None)
|
||||||
|
old_autotask_username = getattr(settings, "autotask_api_username", None)
|
||||||
|
old_autotask_tracking_identifier = getattr(settings, "autotask_tracking_identifier", None)
|
||||||
|
old_autotask_base_url = getattr(settings, "autotask_base_url", None)
|
||||||
|
|
||||||
# NOTE: The Settings UI has multiple tabs with separate forms.
|
# NOTE: The Settings UI has multiple tabs with separate forms.
|
||||||
# Only update values that are present in the submitted form, to avoid
|
# Only update values that are present in the submitted form, to avoid
|
||||||
@ -439,6 +533,10 @@ def settings():
|
|||||||
# Checkbox: present in form = checked, absent = unchecked.
|
# Checkbox: present in form = checked, absent = unchecked.
|
||||||
settings.require_daily_dashboard_visit = bool(request.form.get("require_daily_dashboard_visit"))
|
settings.require_daily_dashboard_visit = bool(request.form.get("require_daily_dashboard_visit"))
|
||||||
|
|
||||||
|
# Environment indicator is in the same form (General tab), so process it here.
|
||||||
|
# Checkbox: present in form = checked, absent = unchecked.
|
||||||
|
settings.is_sandbox_environment = bool(request.form.get("is_sandbox_environment"))
|
||||||
|
|
||||||
# Autotask integration
|
# Autotask integration
|
||||||
if "autotask_enabled" in request.form:
|
if "autotask_enabled" in request.form:
|
||||||
settings.autotask_enabled = bool(request.form.get("autotask_enabled"))
|
settings.autotask_enabled = bool(request.form.get("autotask_enabled"))
|
||||||
@ -478,7 +576,15 @@ def settings():
|
|||||||
|
|
||||||
if "autotask_default_ticket_status" in request.form:
|
if "autotask_default_ticket_status" in request.form:
|
||||||
try:
|
try:
|
||||||
settings.autotask_default_ticket_status = int(request.form.get("autotask_default_ticket_status") or 0) or None
|
form_value = request.form.get("autotask_default_ticket_status", "").strip()
|
||||||
|
if form_value: # Only update if a value was actually selected
|
||||||
|
settings.autotask_default_ticket_status = int(form_value)
|
||||||
|
elif form_value == "" and settings.autotask_default_ticket_status is not None:
|
||||||
|
# If explicitly cleared (empty string submitted) and was previously set,
|
||||||
|
# allow clearing only if reference data is loaded (dropdown has options)
|
||||||
|
if getattr(settings, "autotask_cached_ticket_statuses_json", None):
|
||||||
|
settings.autotask_default_ticket_status = None
|
||||||
|
# Otherwise: keep existing value (prevents accidental clearing when dropdown is empty)
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -572,6 +678,65 @@ def settings():
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash("Settings have been saved.", "success")
|
flash("Settings have been saved.", "success")
|
||||||
|
|
||||||
|
# Audit logging: detect and log changes
|
||||||
|
if general_form_touched:
|
||||||
|
if old_ui_timezone != settings.ui_timezone:
|
||||||
|
changes_general["ui_timezone"] = {"old": old_ui_timezone, "new": settings.ui_timezone}
|
||||||
|
if old_require_daily_dashboard_visit != settings.require_daily_dashboard_visit:
|
||||||
|
changes_general["require_daily_dashboard_visit"] = {"old": old_require_daily_dashboard_visit, "new": settings.require_daily_dashboard_visit}
|
||||||
|
if old_is_sandbox_environment != settings.is_sandbox_environment:
|
||||||
|
changes_general["is_sandbox_environment"] = {"old": old_is_sandbox_environment, "new": settings.is_sandbox_environment}
|
||||||
|
|
||||||
|
if changes_general:
|
||||||
|
_log_admin_event(
|
||||||
|
"settings_general",
|
||||||
|
f"Updated {len(changes_general)} general setting(s)",
|
||||||
|
details=json.dumps(changes_general, indent=2)
|
||||||
|
)
|
||||||
|
|
||||||
|
if mail_form_touched or import_form_touched:
|
||||||
|
if old_graph_tenant_id != settings.graph_tenant_id:
|
||||||
|
changes_mail["graph_tenant_id"] = {"old": old_graph_tenant_id, "new": settings.graph_tenant_id}
|
||||||
|
if old_graph_client_id != settings.graph_client_id:
|
||||||
|
changes_mail["graph_client_id"] = {"old": old_graph_client_id, "new": settings.graph_client_id}
|
||||||
|
if old_graph_mailbox != settings.graph_mailbox:
|
||||||
|
changes_mail["graph_mailbox"] = {"old": old_graph_mailbox, "new": settings.graph_mailbox}
|
||||||
|
if old_incoming_folder != settings.incoming_folder:
|
||||||
|
changes_mail["incoming_folder"] = {"old": old_incoming_folder, "new": settings.incoming_folder}
|
||||||
|
if old_processed_folder != settings.processed_folder:
|
||||||
|
changes_mail["processed_folder"] = {"old": old_processed_folder, "new": settings.processed_folder}
|
||||||
|
if old_auto_import_enabled != settings.auto_import_enabled:
|
||||||
|
changes_mail["auto_import_enabled"] = {"old": old_auto_import_enabled, "new": settings.auto_import_enabled}
|
||||||
|
if old_auto_import_interval != settings.auto_import_interval_minutes:
|
||||||
|
changes_mail["auto_import_interval_minutes"] = {"old": old_auto_import_interval, "new": settings.auto_import_interval_minutes}
|
||||||
|
|
||||||
|
if changes_mail:
|
||||||
|
_log_admin_event(
|
||||||
|
"settings_mail",
|
||||||
|
f"Updated {len(changes_mail)} mail setting(s)",
|
||||||
|
details=json.dumps(changes_mail, indent=2)
|
||||||
|
)
|
||||||
|
|
||||||
|
if autotask_form_touched:
|
||||||
|
if old_autotask_enabled != getattr(settings, "autotask_enabled", None):
|
||||||
|
changes_autotask["autotask_enabled"] = {"old": old_autotask_enabled, "new": getattr(settings, "autotask_enabled", None)}
|
||||||
|
if old_autotask_environment != getattr(settings, "autotask_environment", None):
|
||||||
|
changes_autotask["autotask_environment"] = {"old": old_autotask_environment, "new": getattr(settings, "autotask_environment", None)}
|
||||||
|
if old_autotask_username != getattr(settings, "autotask_api_username", None):
|
||||||
|
changes_autotask["autotask_api_username"] = {"old": old_autotask_username, "new": getattr(settings, "autotask_api_username", None)}
|
||||||
|
if old_autotask_tracking_identifier != getattr(settings, "autotask_tracking_identifier", None):
|
||||||
|
changes_autotask["autotask_tracking_identifier"] = {"old": old_autotask_tracking_identifier, "new": getattr(settings, "autotask_tracking_identifier", None)}
|
||||||
|
if old_autotask_base_url != getattr(settings, "autotask_base_url", None):
|
||||||
|
changes_autotask["autotask_base_url"] = {"old": old_autotask_base_url, "new": getattr(settings, "autotask_base_url", None)}
|
||||||
|
# Note: Password is NOT logged for security
|
||||||
|
|
||||||
|
if changes_autotask:
|
||||||
|
_log_admin_event(
|
||||||
|
"settings_autotask",
|
||||||
|
f"Updated {len(changes_autotask)} Autotask setting(s)",
|
||||||
|
details=json.dumps(changes_autotask, indent=2)
|
||||||
|
)
|
||||||
|
|
||||||
# Autotask ticket defaults depend on reference data (queues, sources, statuses, priorities).
|
# Autotask ticket defaults depend on reference data (queues, sources, statuses, priorities).
|
||||||
# When the Autotask integration is (re)configured, auto-refresh the cached reference data
|
# When the Autotask integration is (re)configured, auto-refresh the cached reference data
|
||||||
# once so the dropdowns become usable immediately.
|
# once so the dropdowns become usable immediately.
|
||||||
@ -711,6 +876,34 @@ def settings():
|
|||||||
autotask_ticket_statuses = []
|
autotask_ticket_statuses = []
|
||||||
autotask_last_sync_at = getattr(settings, "autotask_reference_last_sync_at", None)
|
autotask_last_sync_at = getattr(settings, "autotask_reference_last_sync_at", None)
|
||||||
|
|
||||||
|
# Auto-load reference data on page load if Autotask is enabled but cache is empty
|
||||||
|
try:
|
||||||
|
if (
|
||||||
|
bool(getattr(settings, "autotask_enabled", False))
|
||||||
|
and bool(getattr(settings, "autotask_api_username", None))
|
||||||
|
and bool(getattr(settings, "autotask_api_password", None))
|
||||||
|
and bool(getattr(settings, "autotask_tracking_identifier", None))
|
||||||
|
):
|
||||||
|
missing_cache = (
|
||||||
|
not getattr(settings, "autotask_cached_queues_json", None)
|
||||||
|
or not getattr(settings, "autotask_cached_ticket_sources_json", None)
|
||||||
|
or not getattr(settings, "autotask_cached_ticket_statuses_json", None)
|
||||||
|
or not getattr(settings, "autotask_cached_priorities_json", None)
|
||||||
|
)
|
||||||
|
if missing_cache:
|
||||||
|
queues_loaded, sources_loaded, statuses_loaded, priorities_loaded = _refresh_autotask_reference_data(settings)
|
||||||
|
db.session.commit()
|
||||||
|
flash(
|
||||||
|
f"Autotask reference data auto-loaded. Queues: {len(queues_loaded)}. Ticket Sources: {len(sources_loaded)}. Ticket Statuses: {len(statuses_loaded)}. Priorities: {len(priorities_loaded)}.",
|
||||||
|
"info",
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
try:
|
||||||
|
db.session.rollback()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
flash(f"Failed to auto-load Autotask reference data: {exc}", "warning")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if getattr(settings, "autotask_cached_queues_json", None):
|
if getattr(settings, "autotask_cached_queues_json", None):
|
||||||
autotask_queues = json.loads(settings.autotask_cached_queues_json) or []
|
autotask_queues = json.loads(settings.autotask_cached_queues_json) or []
|
||||||
|
|||||||
@ -34,7 +34,7 @@ from ..job_matching import build_job_match_key, find_matching_job
|
|||||||
from ..database import db
|
from ..database import db
|
||||||
from ..models import (
|
from ..models import (
|
||||||
SystemSettings,
|
SystemSettings,
|
||||||
AdminLog,
|
AuditLog,
|
||||||
Customer,
|
Customer,
|
||||||
Job,
|
Job,
|
||||||
JobRun,
|
JobRun,
|
||||||
@ -109,7 +109,11 @@ def get_user_roles() -> list[str]:
|
|||||||
|
|
||||||
@main_bp.app_context_processor
|
@main_bp.app_context_processor
|
||||||
def _inject_role_context():
|
def _inject_role_context():
|
||||||
return {"active_role": get_active_role(), "user_roles": get_user_roles()}
|
return {
|
||||||
|
"active_role": get_active_role(),
|
||||||
|
"user_roles": get_user_roles(),
|
||||||
|
"system_settings": _get_or_create_settings(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def roles_required(*roles):
|
def roles_required(*roles):
|
||||||
@ -593,7 +597,7 @@ def _log_admin_event(event_type: str, message: str, details: str | None = None)
|
|||||||
except Exception:
|
except Exception:
|
||||||
username = None
|
username = None
|
||||||
|
|
||||||
entry = AdminLog(
|
entry = AuditLog(
|
||||||
user=username,
|
user=username,
|
||||||
event_type=event_type,
|
event_type=event_type,
|
||||||
message=message,
|
message=message,
|
||||||
@ -604,7 +608,7 @@ def _log_admin_event(event_type: str, message: str, details: str | None = None)
|
|||||||
# Enforce retention: keep only the last 7 days
|
# Enforce retention: keep only the last 7 days
|
||||||
try:
|
try:
|
||||||
cutoff = datetime.utcnow() - timedelta(days=7)
|
cutoff = datetime.utcnow() - timedelta(days=7)
|
||||||
AdminLog.query.filter(AdminLog.created_at < cutoff).delete(synchronize_session=False)
|
AuditLog.query.filter(AuditLog.created_at < cutoff).delete(synchronize_session=False)
|
||||||
except Exception:
|
except Exception:
|
||||||
# If cleanup fails we still try to commit the new entry
|
# If cleanup fails we still try to commit the new entry
|
||||||
pass
|
pass
|
||||||
|
|||||||
@ -1024,6 +1024,52 @@ def migrate_news_tables() -> None:
|
|||||||
print("[migrations] migrate_news_tables completed.")
|
print("[migrations] migrate_news_tables completed.")
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_rename_admin_logs_to_audit_logs() -> None:
|
||||||
|
"""Rename admin_logs table to audit_logs for better semantic clarity.
|
||||||
|
|
||||||
|
The table name 'admin_logs' was misleading as it contains both admin actions
|
||||||
|
and automated system events. The new name 'audit_logs' better reflects its
|
||||||
|
purpose as a comprehensive audit trail for both user actions and system events.
|
||||||
|
|
||||||
|
This migration is idempotent and safe to run multiple times.
|
||||||
|
"""
|
||||||
|
engine = db.get_engine()
|
||||||
|
with engine.begin() as conn:
|
||||||
|
# Check if old table exists and new table doesn't
|
||||||
|
old_exists = conn.execute(
|
||||||
|
text(
|
||||||
|
"""
|
||||||
|
SELECT 1 FROM information_schema.tables
|
||||||
|
WHERE table_name = 'admin_logs'
|
||||||
|
LIMIT 1
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
).first() is not None
|
||||||
|
|
||||||
|
new_exists = conn.execute(
|
||||||
|
text(
|
||||||
|
"""
|
||||||
|
SELECT 1 FROM information_schema.tables
|
||||||
|
WHERE table_name = 'audit_logs'
|
||||||
|
LIMIT 1
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
).first() is not None
|
||||||
|
|
||||||
|
if old_exists and not new_exists:
|
||||||
|
# Rename the table
|
||||||
|
conn.execute(text("ALTER TABLE admin_logs RENAME TO audit_logs"))
|
||||||
|
print("[migrations] Renamed admin_logs → audit_logs")
|
||||||
|
elif new_exists and not old_exists:
|
||||||
|
print("[migrations] audit_logs already exists (admin_logs already migrated)")
|
||||||
|
elif old_exists and new_exists:
|
||||||
|
# Both exist - this shouldn't happen but handle gracefully
|
||||||
|
print("[migrations] WARNING: Both admin_logs and audit_logs exist. Manual intervention may be needed.")
|
||||||
|
else:
|
||||||
|
# Neither exists - table will be created by db.create_all()
|
||||||
|
print("[migrations] audit_logs table will be created by db.create_all()")
|
||||||
|
|
||||||
|
|
||||||
def run_migrations() -> None:
|
def run_migrations() -> None:
|
||||||
print("[migrations] Starting migrations...")
|
print("[migrations] Starting migrations...")
|
||||||
migrate_add_username_to_users()
|
migrate_add_username_to_users()
|
||||||
@ -1034,6 +1080,7 @@ def run_migrations() -> None:
|
|||||||
migrate_system_settings_daily_jobs_start_date()
|
migrate_system_settings_daily_jobs_start_date()
|
||||||
migrate_system_settings_ui_timezone()
|
migrate_system_settings_ui_timezone()
|
||||||
migrate_system_settings_autotask_integration()
|
migrate_system_settings_autotask_integration()
|
||||||
|
migrate_system_settings_sandbox_environment()
|
||||||
migrate_customers_autotask_company_mapping()
|
migrate_customers_autotask_company_mapping()
|
||||||
migrate_mail_messages_columns()
|
migrate_mail_messages_columns()
|
||||||
migrate_mail_messages_parse_columns()
|
migrate_mail_messages_parse_columns()
|
||||||
@ -1063,6 +1110,7 @@ def run_migrations() -> None:
|
|||||||
migrate_reporting_report_config()
|
migrate_reporting_report_config()
|
||||||
migrate_performance_indexes()
|
migrate_performance_indexes()
|
||||||
migrate_system_settings_require_daily_dashboard_visit()
|
migrate_system_settings_require_daily_dashboard_visit()
|
||||||
|
migrate_rename_admin_logs_to_audit_logs()
|
||||||
print("[migrations] All migrations completed.")
|
print("[migrations] All migrations completed.")
|
||||||
|
|
||||||
|
|
||||||
@ -1138,6 +1186,38 @@ def migrate_system_settings_require_daily_dashboard_visit() -> None:
|
|||||||
print(f"[migrations] Failed to migrate system_settings.require_daily_dashboard_visit: {exc}")
|
print(f"[migrations] Failed to migrate system_settings.require_daily_dashboard_visit: {exc}")
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_system_settings_sandbox_environment() -> None:
|
||||||
|
"""Add is_sandbox_environment column to system_settings if missing.
|
||||||
|
|
||||||
|
When enabled, a visual banner is displayed on all pages to indicate
|
||||||
|
this is not a production environment.
|
||||||
|
"""
|
||||||
|
table = "system_settings"
|
||||||
|
column = "is_sandbox_environment"
|
||||||
|
|
||||||
|
try:
|
||||||
|
engine = db.get_engine()
|
||||||
|
except Exception as exc:
|
||||||
|
print(f"[migrations] Could not get engine for system_settings sandbox environment migration: {exc}")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
if _column_exists(table, column):
|
||||||
|
print("[migrations] system_settings.is_sandbox_environment already exists.")
|
||||||
|
return
|
||||||
|
|
||||||
|
with engine.begin() as conn:
|
||||||
|
conn.execute(
|
||||||
|
text(
|
||||||
|
f'ALTER TABLE "{table}" ADD COLUMN {column} BOOLEAN NOT NULL DEFAULT FALSE'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
print("[migrations] migrate_system_settings_sandbox_environment completed.")
|
||||||
|
except Exception as exc:
|
||||||
|
print(f"[migrations] Failed to migrate system_settings.is_sandbox_environment: {exc}")
|
||||||
|
|
||||||
|
|
||||||
def migrate_performance_indexes() -> None:
|
def migrate_performance_indexes() -> None:
|
||||||
"""Add performance indexes for frequently queried foreign key columns.
|
"""Add performance indexes for frequently queried foreign key columns.
|
||||||
|
|
||||||
|
|||||||
@ -112,6 +112,11 @@ class SystemSettings(db.Model):
|
|||||||
# their first page view each day before they can navigate elsewhere.
|
# their first page view each day before they can navigate elsewhere.
|
||||||
require_daily_dashboard_visit = db.Column(db.Boolean, nullable=False, default=False)
|
require_daily_dashboard_visit = db.Column(db.Boolean, nullable=False, default=False)
|
||||||
|
|
||||||
|
# Development/Sandbox environment indicator.
|
||||||
|
# When enabled, a visual banner is displayed on all pages to indicate
|
||||||
|
# this is not a production environment.
|
||||||
|
is_sandbox_environment = db.Column(db.Boolean, nullable=False, default=False)
|
||||||
|
|
||||||
# Autotask integration settings
|
# Autotask integration settings
|
||||||
autotask_enabled = db.Column(db.Boolean, nullable=False, default=False)
|
autotask_enabled = db.Column(db.Boolean, nullable=False, default=False)
|
||||||
autotask_environment = db.Column(db.String(32), nullable=True) # sandbox | production
|
autotask_environment = db.Column(db.String(32), nullable=True) # sandbox | production
|
||||||
@ -141,8 +146,8 @@ class SystemSettings(db.Model):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
class AdminLog(db.Model):
|
class AuditLog(db.Model):
|
||||||
__tablename__ = "admin_logs"
|
__tablename__ = "audit_logs"
|
||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
|
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
|
||||||
@ -152,6 +157,10 @@ class AdminLog(db.Model):
|
|||||||
message = db.Column(db.Text, nullable=False)
|
message = db.Column(db.Text, nullable=False)
|
||||||
details = db.Column(db.Text, nullable=True)
|
details = db.Column(db.Text, nullable=True)
|
||||||
|
|
||||||
|
|
||||||
|
# Legacy alias for backwards compatibility during migration
|
||||||
|
AdminLog = AuditLog
|
||||||
|
|
||||||
class Customer(db.Model):
|
class Customer(db.Model):
|
||||||
__tablename__ = "customers"
|
__tablename__ = "customers"
|
||||||
|
|
||||||
|
|||||||
316
containers/backupchecks/src/static/css/documentation.css
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
/* Documentation Container */
|
||||||
|
.documentation-container {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sidebar */
|
||||||
|
.doc-sidebar-wrapper {
|
||||||
|
border-right: 1px solid #dee2e6;
|
||||||
|
min-height: calc(100vh - 80px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-nav {
|
||||||
|
position: sticky;
|
||||||
|
top: 20px;
|
||||||
|
padding: 20px;
|
||||||
|
max-height: calc(100vh - 100px);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-nav-header h5 {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 2px solid #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-nav-section {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-nav-section-title {
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-nav-section.active .doc-nav-section-title {
|
||||||
|
background-color: #e7f1ff;
|
||||||
|
color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-nav-icon {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-nav-pages {
|
||||||
|
list-style: none;
|
||||||
|
padding-left: 30px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-nav-pages li {
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-nav-pages li a {
|
||||||
|
display: block;
|
||||||
|
padding: 5px 10px;
|
||||||
|
color: #495057;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-nav-pages li a:hover {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
color: #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-nav-pages li.active a {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Content Area */
|
||||||
|
.doc-content-wrapper {
|
||||||
|
padding: 20px 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content h1 {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
border-bottom: 3px solid #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content h2 {
|
||||||
|
margin-top: 40px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content h3 {
|
||||||
|
margin-top: 30px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content img {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 20px auto;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content figcaption {
|
||||||
|
text-align: center;
|
||||||
|
font-style: italic;
|
||||||
|
color: #6c757d;
|
||||||
|
margin-top: -15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content .lead {
|
||||||
|
font-size: 1.15rem;
|
||||||
|
color: #6c757d;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code Blocks */
|
||||||
|
.doc-content pre {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 15px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content code {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content pre code {
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tables */
|
||||||
|
.doc-content table {
|
||||||
|
width: 100%;
|
||||||
|
margin: 20px 0;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content table th,
|
||||||
|
.doc-content table td {
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content table th {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content table tr:nth-child(even) {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Callout Boxes */
|
||||||
|
.doc-callout {
|
||||||
|
padding: 15px 20px;
|
||||||
|
margin: 20px 0;
|
||||||
|
border-left: 4px solid;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-callout-info {
|
||||||
|
background-color: #e7f3ff;
|
||||||
|
border-left-color: #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-callout-warning {
|
||||||
|
background-color: #fff3cd;
|
||||||
|
border-left-color: #ffc107;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-callout-tip {
|
||||||
|
background-color: #d4edda;
|
||||||
|
border-left-color: #28a745;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-callout-danger {
|
||||||
|
background-color: #f8d7da;
|
||||||
|
border-left-color: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-callout > strong:first-child {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pagination */
|
||||||
|
.doc-pagination {
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode support */
|
||||||
|
[data-bs-theme="dark"] .doc-sidebar-wrapper {
|
||||||
|
border-right-color: #495057;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .doc-nav-section-title {
|
||||||
|
background-color: #343a40;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .doc-nav-section.active .doc-nav-section-title {
|
||||||
|
background-color: #1a4d8f;
|
||||||
|
color: #a8c9f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .doc-nav-pages li a {
|
||||||
|
color: #adb5bd;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .doc-nav-pages li a:hover {
|
||||||
|
background-color: #343a40;
|
||||||
|
color: #a8c9f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .doc-nav-pages li.active a {
|
||||||
|
background-color: #0d6efd;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .doc-content h1 {
|
||||||
|
border-bottom-color: #0d6efd;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .doc-content h2 {
|
||||||
|
color: #a8c9f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .doc-content img {
|
||||||
|
border-color: #495057;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .doc-content pre {
|
||||||
|
background-color: #212529;
|
||||||
|
border-color: #495057;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .doc-content code {
|
||||||
|
background-color: #212529;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .doc-content table th,
|
||||||
|
[data-bs-theme="dark"] .doc-content table td {
|
||||||
|
border-color: #495057;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .doc-content table th {
|
||||||
|
background-color: #343a40;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .doc-content table tr:nth-child(even) {
|
||||||
|
background-color: #2b3035;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .doc-callout-info {
|
||||||
|
background-color: #1a3a52;
|
||||||
|
border-left-color: #0d6efd;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .doc-callout-warning {
|
||||||
|
background-color: #4a3f1f;
|
||||||
|
border-left-color: #ffc107;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .doc-callout-tip {
|
||||||
|
background-color: #1f3d2a;
|
||||||
|
border-left-color: #28a745;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .doc-callout-danger {
|
||||||
|
background-color: #4a2329;
|
||||||
|
border-left-color: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 991px) {
|
||||||
|
.doc-sidebar-wrapper {
|
||||||
|
border-right: none;
|
||||||
|
border-bottom: 1px solid #dee2e6;
|
||||||
|
min-height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-nav {
|
||||||
|
position: static;
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-content-wrapper {
|
||||||
|
padding: 20px 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 991px) {
|
||||||
|
[data-bs-theme="dark"] .doc-sidebar-wrapper {
|
||||||
|
border-bottom-color: #495057;
|
||||||
|
}
|
||||||
|
}
|
||||||
27
containers/backupchecks/src/static/css/sandbox.css
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/* Sandbox environment visual indicator banner
|
||||||
|
* Displays a diagonal "SANDBOX" ribbon in the top-left corner
|
||||||
|
* when the is_sandbox_environment setting is enabled.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.sandbox-banner {
|
||||||
|
position: fixed;
|
||||||
|
top: 30px;
|
||||||
|
left: -60px;
|
||||||
|
width: 250px;
|
||||||
|
background-color: #dc3545;
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
z-index: 9999;
|
||||||
|
pointer-events: none; /* Banner itself is not clickable, elements behind it are */
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sandbox-banner-text {
|
||||||
|
display: block;
|
||||||
|
padding: 8px 0;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 16px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
@ -0,0 +1,24 @@
|
|||||||
|
<nav class="doc-nav">
|
||||||
|
<div class="doc-nav-header">
|
||||||
|
<h5>📖 Documentation</h5>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% for section_key, section_data in navigation.structure.items() %}
|
||||||
|
<div class="doc-nav-section {% if section_key == navigation.current_section %}active{% endif %}">
|
||||||
|
<div class="doc-nav-section-title">
|
||||||
|
<span class="doc-nav-icon">{{ section_data.icon }}</span>
|
||||||
|
{{ section_data.title }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="doc-nav-pages">
|
||||||
|
{% for page in section_data.pages %}
|
||||||
|
<li class="{% if section_key == navigation.current_section and page.slug == navigation.current_page %}active{% endif %}">
|
||||||
|
<a href="{{ url_for('documentation.page', section=section_key, page=page.slug) }}">
|
||||||
|
{{ page.title }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</nav>
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>Company Mapping</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Map customers to Autotask companies.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>📝 Coming Soon:</strong>
|
||||||
|
This page is under construction. Full content will be added in a future update.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Content</h2>
|
||||||
|
|
||||||
|
<p>Detailed content will be added here in a future update.</p>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>Creating Tickets</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Manually create tickets for failed backups.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>📝 Coming Soon:</strong>
|
||||||
|
This page is under construction. Full content will be added in a future update.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Content</h2>
|
||||||
|
|
||||||
|
<p>Detailed content will be added here in a future update.</p>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>Setup & Configuration</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Configure Autotask PSA integration.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>📝 Coming Soon:</strong>
|
||||||
|
This page is under construction. Full content will be added in a future update.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Content</h2>
|
||||||
|
|
||||||
|
<p>Detailed content will be added here in a future update.</p>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>Ticket Management</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Manage and track Autotask tickets.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>📝 Coming Soon:</strong>
|
||||||
|
This page is under construction. Full content will be added in a future update.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Content</h2>
|
||||||
|
|
||||||
|
<p>Detailed content will be added here in a future update.</p>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,319 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>Approving Backups</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Learn the complete backup review workflow from email import to marking runs as reviewed.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Overview</h2>
|
||||||
|
|
||||||
|
<p>The backup review process in BackupChecks follows a structured workflow designed to ensure all backup issues are seen and handled appropriately. This page explains the complete lifecycle from email arrival to review completion.</p>
|
||||||
|
|
||||||
|
<h2>The Backup Review Lifecycle</h2>
|
||||||
|
|
||||||
|
<p>The review process consists of several stages:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li><strong>Email Import:</strong> Backup report emails arrive in your mailbox and are imported into BackupChecks</li>
|
||||||
|
<li><strong>Parsing:</strong> Email content is analyzed to extract backup job information</li>
|
||||||
|
<li><strong>Inbox Approval:</strong> New jobs appear in the Inbox for initial approval and customer assignment</li>
|
||||||
|
<li><strong>Automatic Processing:</strong> Future emails for approved jobs are automatically processed</li>
|
||||||
|
<li><strong>Daily Monitoring:</strong> Jobs appear in Daily Jobs view based on inferred schedules</li>
|
||||||
|
<li><strong>Run Review:</strong> Jobs with warnings or failures are reviewed using the Run Checks modal</li>
|
||||||
|
<li><strong>Issue Tracking:</strong> Problems are documented using overrides, tickets, or remarks</li>
|
||||||
|
<li><strong>Mark as Reviewed:</strong> Runs are marked as reviewed after investigation</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2>Stage 1: Email Import & Parsing</h2>
|
||||||
|
|
||||||
|
<p>Backup report emails are automatically imported from your configured mailbox at regular intervals (default: every 15 minutes).</p>
|
||||||
|
|
||||||
|
<h3>What Happens</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>BackupChecks connects to your mailbox via Microsoft Graph API</li>
|
||||||
|
<li>New emails in the configured folder are downloaded</li>
|
||||||
|
<li>Email content is stored in the database</li>
|
||||||
|
<li>Parsers analyze the email to extract structured backup information:
|
||||||
|
<ul>
|
||||||
|
<li>Backup software (e.g., Veeam, Synology)</li>
|
||||||
|
<li>Backup type (e.g., Backup Job, Replication Job)</li>
|
||||||
|
<li>Job name</li>
|
||||||
|
<li>Overall status (Success, Warning, Failed)</li>
|
||||||
|
<li>Backup objects (VMs, servers, files) with their individual statuses</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>See <a href="{{ url_for('documentation.page', section='mail-import', page='auto-import') }}">Auto-Import Configuration</a> and <a href="{{ url_for('documentation.page', section='mail-import', page='mail-parsing') }}">Mail Parsing</a> for details.</p>
|
||||||
|
|
||||||
|
<h2>Stage 2: Inbox Approval</h2>
|
||||||
|
|
||||||
|
<p>When an email arrives for a job that hasn't been approved yet, it appears in the <strong>Inbox</strong>.</p>
|
||||||
|
|
||||||
|
<h3>What Happens</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>The email appears in the Inbox table showing parsed information</li>
|
||||||
|
<li>You review the email to verify it's a legitimate backup report</li>
|
||||||
|
<li>You click the email row to view full details</li>
|
||||||
|
<li>You click <strong>Approve</strong> and select which customer the job belongs to</li>
|
||||||
|
<li>A <strong>Job</strong> record is created in the database</li>
|
||||||
|
<li>A <strong>JobRun</strong> record is created for this specific backup run</li>
|
||||||
|
<li>The email disappears from the Inbox</li>
|
||||||
|
<li>Future emails for this job are automatically approved without appearing in the Inbox</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>See <a href="{{ url_for('documentation.page', section='mail-import', page='inbox-management') }}">Inbox Management</a> for the detailed approval process.</p>
|
||||||
|
|
||||||
|
<h2>Stage 3: Automatic Processing</h2>
|
||||||
|
|
||||||
|
<p>After initial approval, future emails for the same job are processed automatically:</p>
|
||||||
|
|
||||||
|
<h3>What Happens</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Email is imported and parsed</li>
|
||||||
|
<li>BackupChecks identifies it matches an existing approved job (based on sender, job name, and backup software)</li>
|
||||||
|
<li>A new <strong>JobRun</strong> record is automatically created</li>
|
||||||
|
<li>The email does NOT appear in the Inbox</li>
|
||||||
|
<li>The job run immediately appears in Daily Jobs and Job History</li>
|
||||||
|
<li>Operators can review it without manual approval</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 First-Time Setup:</strong><br>
|
||||||
|
The Inbox approval step only happens once per job. After that, all future reports for the same job are automatically processed. This is why it's important to approve inbox emails promptly when onboarding new customers.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Stage 4: Daily Monitoring</h2>
|
||||||
|
|
||||||
|
<p>Approved jobs appear in the <strong>Daily Jobs</strong> view based on inferred schedules. This page provides an informational overview of which jobs are expected to run on a given date.</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Daily Jobs is Informational Only:</strong><br>
|
||||||
|
Daily Jobs is <strong>not part of the daily backup review workflow</strong>. It's purely for viewing which jobs are scheduled to run and their most recent status. The actual daily review work happens in the <strong>Run Checks</strong> page. Daily Jobs may be deprecated in a future version.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>What Happens</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>BackupChecks analyzes historical run patterns to infer job schedules</li>
|
||||||
|
<li>Jobs expected to run on a specific date appear in Daily Jobs</li>
|
||||||
|
<li>Status indicators show:
|
||||||
|
<ul>
|
||||||
|
<li><strong>Success:</strong> Green badge - job completed successfully</li>
|
||||||
|
<li><strong>Success (Override):</strong> Blue badge - override applied, originally warning/failed</li>
|
||||||
|
<li><strong>Warning:</strong> Yellow badge - job completed with warnings</li>
|
||||||
|
<li><strong>Failed:</strong> Red badge - job failed</li>
|
||||||
|
<li><strong>Missed:</strong> Gray badge - job was expected but didn't run</li>
|
||||||
|
<li><strong>Expected:</strong> White badge (with border) - job hasn't run yet today</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>Overrides are applied automatically (warnings/failures show as blue Success (Override) badge)</li>
|
||||||
|
<li>Ticket and remark indicators (🎫 💬) show jobs with known issues</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>See <a href="{{ url_for('documentation.page', section='backup-review', page='daily-jobs') }}">Daily Jobs View</a> for details about this informational page.</p>
|
||||||
|
|
||||||
|
<h2>Stage 5: Run Review</h2>
|
||||||
|
|
||||||
|
<p>When a job shows a warning or failure, you review it using the <strong>Run Checks modal</strong>.</p>
|
||||||
|
|
||||||
|
<h3>What Happens</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Click on a job with a warning or failure in Daily Jobs</li>
|
||||||
|
<li>The Run Checks modal opens showing:
|
||||||
|
<ul>
|
||||||
|
<li>Recent runs for this job</li>
|
||||||
|
<li>Selected run details (objects, status, error messages)</li>
|
||||||
|
<li>Original email content</li>
|
||||||
|
<li>Applied overrides</li>
|
||||||
|
<li>Linked tickets and remarks</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>You investigate the issue by reviewing:
|
||||||
|
<ul>
|
||||||
|
<li>Which backup objects failed or have warnings</li>
|
||||||
|
<li>Error messages for specific objects</li>
|
||||||
|
<li>Full email body for additional context</li>
|
||||||
|
<li>Historical runs to identify patterns</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>You determine the appropriate action (see Stage 6)</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>See <a href="{{ url_for('documentation.page', section='backup-review', page='run-checks-modal') }}">Run Checks Modal</a> for detailed review instructions.</p>
|
||||||
|
|
||||||
|
<h2>Stage 6: Issue Tracking</h2>
|
||||||
|
|
||||||
|
<p>Based on your investigation, you choose how to handle the issue:</p>
|
||||||
|
|
||||||
|
<h3>Option 1: Mark as Reviewed (No Action Needed)</h3>
|
||||||
|
|
||||||
|
<p>If the issue doesn't require followup:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>False alarms (email parsing issue, cosmetic warning)</li>
|
||||||
|
<li>One-time glitches (temporary network hiccup, already resolved)</li>
|
||||||
|
<li>Acceptable warnings (retry succeeded, minor performance issue)</li>
|
||||||
|
<li>Successful backups (no issues detected)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p><strong>Action:</strong> Click "Mark as reviewed" in the Run Checks modal, then the run disappears from the Run Checks page.</p>
|
||||||
|
|
||||||
|
<h3>Option 2: Create an Override</h3>
|
||||||
|
|
||||||
|
<p>If the issue is expected and will occur repeatedly:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Known acceptable warnings (e.g., "Retry succeeded" is acceptable for this job)</li>
|
||||||
|
<li>Temporary planned issues (maintenance window for the next week)</li>
|
||||||
|
<li>Object-specific exceptions (one VM always has warnings due to configuration)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p><strong>Action:</strong> Create an override on the Overrides page. Future matching runs will automatically show as success with a blue override indicator. <strong>Then mark the current run as reviewed</strong> in the Run Checks modal.</p>
|
||||||
|
|
||||||
|
<p>See <a href="{{ url_for('documentation.page', section='backup-review', page='overrides') }}">Overrides & Exceptions</a> for details.</p>
|
||||||
|
|
||||||
|
<h3>Option 3: Create a Remark</h3>
|
||||||
|
|
||||||
|
<p>If the issue needs documentation but not formal ticket tracking:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Temporary issues (customer notified, waiting for their action)</li>
|
||||||
|
<li>Known non-critical problems (cosmetic issue, low-priority warning)</li>
|
||||||
|
<li>Investigation notes (checked with customer, not actually an issue)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p><strong>Action:</strong> Click "Create Remark" in the Run Checks modal. The remark is linked to the job and shows 💬 in Run Checks. <strong>Then mark the run as reviewed</strong> - otherwise it will remain visible and can appear incorrectly as failed/warning the next day.</p>
|
||||||
|
|
||||||
|
<h3>Option 4: Create a Ticket</h3>
|
||||||
|
|
||||||
|
<p>If the issue requires external followup and tracking:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Hardware failures (disk full, server offline)</li>
|
||||||
|
<li>Customer action required (upgrade software, allocate more storage)</li>
|
||||||
|
<li>Vendor support needed (backup software bug, licensing issue)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p><strong>Action:</strong> Click "Create Ticket" in the Run Checks modal. The ticket is linked to the job and shows 🎫 in Run Checks. <strong>Then mark the run as reviewed</strong> - this is required even when creating a ticket, otherwise the run stays visible.</p>
|
||||||
|
|
||||||
|
<p>See <a href="{{ url_for('documentation.page', section='backup-review', page='remarks-tickets') }}">Remarks & Tickets</a> for tracking issues.</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-warning">
|
||||||
|
<strong>⚠️ Always Mark as Reviewed:</strong><br>
|
||||||
|
Regardless of which action you take (override, remark, or ticket), you <strong>must always mark the run as reviewed</strong> afterwards. If you don't, the run will remain in the Run Checks list and can show the wrong status the next day (only the first unreviewed status is displayed).
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Stage 7: Mark as Reviewed</h2>
|
||||||
|
|
||||||
|
<p>After handling the issue (or determining no action is needed), mark the run as reviewed. <strong>This is required for ALL runs</strong>, including successful ones.</p>
|
||||||
|
|
||||||
|
<h3>What Happens</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Click <strong>Mark as reviewed</strong> in the Run Checks modal (or select multiple runs and mark all at once)</li>
|
||||||
|
<li>The run is marked with a review timestamp</li>
|
||||||
|
<li>The run disappears from the Run Checks page</li>
|
||||||
|
<li>A review audit record is created (visible to Admins)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Goal: Empty Run Checks Page:</strong><br>
|
||||||
|
The objective is to have the <strong>Run Checks page completely empty</strong> - this means all runs have been reviewed. Even successful backups must be marked as reviewed, because they haven't been <em>looked at</em>, but they still need to be marked to clear the Run Checks page. You can select multiple runs and mark them all as reviewed at once for efficiency.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Bulk Review</h3>
|
||||||
|
|
||||||
|
<p>For efficiency, you can mark multiple jobs as reviewed simultaneously:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Open the Run Checks page</li>
|
||||||
|
<li>Select multiple job checkboxes</li>
|
||||||
|
<li>Click <strong>Mark selected as reviewed</strong></li>
|
||||||
|
<li>All runs for all selected jobs are marked as reviewed and disappear from the list</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>This is especially useful for successful backups where no investigation is needed - you can quickly clear them to keep the Run Checks page empty.</p>
|
||||||
|
|
||||||
|
<h2>Best Practices</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Start with Run Checks every day:</strong> Open Run Checks page first thing every morning - this is your primary workflow</li>
|
||||||
|
<li><strong>Goal: Empty Run Checks page:</strong> Mark all runs as reviewed (even successful ones) until the page is completely empty</li>
|
||||||
|
<li><strong>Approve inbox emails quickly:</strong> New customer emails won't appear in Run Checks until approved</li>
|
||||||
|
<li><strong>Triage by status:</strong> Review red (Failed) before yellow (Warning) before green (Success)</li>
|
||||||
|
<li><strong>Use bulk review for successes:</strong> Select multiple successful runs and mark them all at once</li>
|
||||||
|
<li><strong>Always mark as reviewed:</strong> Even after creating tickets/remarks/overrides, you must still mark the run as reviewed</li>
|
||||||
|
<li><strong>Use overrides for recurring acceptable warnings:</strong> Don't mark the same warning as reviewed every day - create an override</li>
|
||||||
|
<li><strong>Create tickets for customer action items:</strong> Formal tracking ensures followup</li>
|
||||||
|
<li><strong>Use remarks for temporary notes:</strong> Don't create tickets for short-term issues</li>
|
||||||
|
<li><strong>Always check backup objects:</strong> The overall status can be misleading - individual objects may have hidden failures</li>
|
||||||
|
<li><strong>Document in comments:</strong> When creating overrides or tickets, always explain why</li>
|
||||||
|
<li><strong>Resolve tickets promptly:</strong> After issues are fixed, resolve the ticket to clear the 🎫 indicator</li>
|
||||||
|
<li><strong>Daily Jobs is secondary:</strong> Use Daily Jobs for viewing job schedules, but Run Checks is where review work happens</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Role-Based Review Workflow</h2>
|
||||||
|
|
||||||
|
<h3>Operator Workflow</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Open <strong>Run Checks</strong> page every morning</li>
|
||||||
|
<li>Review each run in the list (click to see details)</li>
|
||||||
|
<li>For issues: create tickets, remarks, or overrides as needed</li>
|
||||||
|
<li><strong>Mark ALL runs as reviewed</strong> (including successes and runs with tickets/overrides)</li>
|
||||||
|
<li>Goal: End with completely empty Run Checks page</li>
|
||||||
|
<li>Cannot unmark reviewed runs or delete overrides</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Admin Workflow</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Same as Operator, plus:</li>
|
||||||
|
<li>Can view reviewed runs and review timestamps</li>
|
||||||
|
<li>Can unmark reviewed runs if re-investigation is needed</li>
|
||||||
|
<li>Can delete overrides and tickets</li>
|
||||||
|
<li>Monitors overall system health and team review performance</li>
|
||||||
|
<li>Can verify Run Checks page is empty (all reviews completed)</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Viewer Workflow</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>View Daily Jobs to see backup status</li>
|
||||||
|
<li>Cannot access Run Checks page</li>
|
||||||
|
<li>Cannot create tickets, remarks, or overrides</li>
|
||||||
|
<li>Cannot mark runs as reviewed</li>
|
||||||
|
<li>Read-only access for reporting and visibility</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2>Performance Tips</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Use bulk review for successes:</strong> Select multiple green success runs and mark them all as reviewed at once</li>
|
||||||
|
<li><strong>Review by exception:</strong> Use overrides to reduce noise, then focus on genuine issues</li>
|
||||||
|
<li><strong>Batch approve inbox emails:</strong> Set aside time weekly to approve new customer jobs</li>
|
||||||
|
<li><strong>Create global overrides for common warnings:</strong> Handle widespread acceptable warnings once</li>
|
||||||
|
<li><strong>Use tickets for tracking workload:</strong> Helps identify which customers have recurring issues</li>
|
||||||
|
<li><strong>Archive resolved tickets:</strong> Filter to "Active" only to reduce clutter</li>
|
||||||
|
<li><strong>Focus on Run Checks only:</strong> Don't spend time in Daily Jobs unless you need to check schedules - Run Checks is your daily tool</li>
|
||||||
|
<li><strong>Process runs in order:</strong> Work through the Run Checks list from top to bottom systematically</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Next Steps</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='mail-import', page='inbox-management') }}">Inbox Management</a> - Learn the initial approval process</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='backup-review', page='daily-jobs') }}">Daily Jobs View</a> - Master the daily monitoring dashboard</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='backup-review', page='run-checks-modal') }}">Run Checks Modal</a> - Investigate job run details</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='backup-review', page='overrides') }}">Overrides & Exceptions</a> - Automate handling of known issues</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='backup-review', page='remarks-tickets') }}">Remarks & Tickets</a> - Track followup work</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,321 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>Daily Jobs View</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Monitor backup jobs expected to run on a specific date, with smart schedule inference and status tracking.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Overview</h2>
|
||||||
|
|
||||||
|
<p>The <strong>Daily Jobs</strong> view displays all backup jobs expected to run on a selected date, based on intelligent schedule inference from historical run patterns. It provides an overview of which jobs exist and their schedules.</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Daily Jobs vs Run Checks:</strong><br>
|
||||||
|
<strong>Daily Jobs</strong> is primarily for viewing which jobs exist and their schedules. For daily operational review work, use the <strong><a href="{{ url_for('documentation.page', section='backup-review', page='run-checks-modal') }}">Run Checks</a></strong> page instead - that's where you review and mark runs as complete. Daily Jobs may be deprecated in a future version.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>Key features:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Schedule inference:</strong> Automatically detects weekly and monthly backup schedules from historical runs</li>
|
||||||
|
<li><strong>Status tracking:</strong> Shows which jobs succeeded, failed, have warnings, or haven't run yet</li>
|
||||||
|
<li><strong>Missing job detection:</strong> Identifies jobs that should have run but didn't</li>
|
||||||
|
<li><strong>Override indicators:</strong> Displays blue badges for jobs with active overrides</li>
|
||||||
|
<li><strong>Ticket and remark indicators:</strong> Shows 🎫 and 💬 icons for jobs with linked tickets or remarks</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Accessing Daily Jobs</h2>
|
||||||
|
|
||||||
|
<p>To access the Daily Jobs view:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to <strong>Daily Jobs</strong> in the main navigation menu</li>
|
||||||
|
<li>Available to <strong>Admin</strong>, <strong>Operator</strong>, and <strong>Viewer</strong> roles</li>
|
||||||
|
<li>The view defaults to today's date in your configured timezone</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2>Date Selection</h2>
|
||||||
|
|
||||||
|
<p>At the top of the page, you can select which date to view:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Date picker:</strong> Choose any date to view jobs expected on that day</li>
|
||||||
|
<li><strong>Previous / Next buttons:</strong> Navigate day by day</li>
|
||||||
|
<li><strong>Today button:</strong> Jump back to today's date</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Timezone:</strong><br>
|
||||||
|
Dates are displayed in your configured timezone (set in Settings). Job schedules are inferred based on when jobs historically ran in that timezone.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Job Table Columns</h2>
|
||||||
|
|
||||||
|
<p>The Daily Jobs table displays the following columns:</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Column</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Customer</strong></td>
|
||||||
|
<td>Customer name the job belongs to</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Backup software</strong></td>
|
||||||
|
<td>Backup product (e.g., Veeam, Synology, NAKIVO)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Backup type</strong></td>
|
||||||
|
<td>Job type (e.g., Backup Job, Replication Job)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Job name</strong></td>
|
||||||
|
<td>Name of the backup job</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Schedule</strong></td>
|
||||||
|
<td>Inferred schedule pattern (e.g., "Daily", "Weekly: Mon Wed Fri", "Monthly: 1st, 15th")</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Last run</strong></td>
|
||||||
|
<td>Timestamp of the most recent job run</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Status</strong></td>
|
||||||
|
<td>Current status indicator (Success, Warning, Failed, Missed, Expected)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Reviewed</strong></td>
|
||||||
|
<td>Checkmark if the job has been reviewed (only for non-success statuses)</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Admin-Only Column:</strong><br>
|
||||||
|
The <strong>Reviewed</strong> column is only visible to users with the <strong>Admin</strong> role. Operators and Viewers cannot see review status in the Daily Jobs table.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Status Indicators</h2>
|
||||||
|
|
||||||
|
<p>Each job displays a status badge indicating its current state:</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Badge Color</th>
|
||||||
|
<th>Meaning</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Success</strong></td>
|
||||||
|
<td><span class="badge bg-success">Green</span></td>
|
||||||
|
<td>Job completed successfully with no warnings or errors</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Success (Override)</strong></td>
|
||||||
|
<td><span class="badge bg-primary">Blue</span></td>
|
||||||
|
<td>Job had warning/failure but is treated as success due to an active override rule</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Warning</strong></td>
|
||||||
|
<td><span class="badge bg-warning text-dark">Yellow</span></td>
|
||||||
|
<td>Job completed but with warnings (e.g., retry succeeded, partial backup)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Failed</strong></td>
|
||||||
|
<td><span class="badge bg-danger">Red</span></td>
|
||||||
|
<td>Job failed and did not complete successfully</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Missed</strong></td>
|
||||||
|
<td><span class="badge bg-secondary">Gray</span></td>
|
||||||
|
<td>Job was expected to run on this date but no run was detected</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Expected</strong></td>
|
||||||
|
<td><span class="badge bg-light text-dark border">White</span></td>
|
||||||
|
<td>Job is expected to run later today (for future dates or today before the job runs)</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>Schedule Inference</h2>
|
||||||
|
|
||||||
|
<p>BackupChecks automatically infers backup job schedules by analyzing historical run patterns. This eliminates the need to manually configure schedules for each job.</p>
|
||||||
|
|
||||||
|
<h3>How Schedule Inference Works</h3>
|
||||||
|
|
||||||
|
<p>The system examines the past 60 days of job runs to detect patterns:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Daily jobs:</strong> Jobs that run every day (or nearly every day)</li>
|
||||||
|
<li><strong>Weekly jobs:</strong> Jobs that run on specific days of the week (e.g., every Monday, Wednesday, Friday)</li>
|
||||||
|
<li><strong>Monthly jobs:</strong> Jobs that run on specific dates of the month (e.g., 1st and 15th of each month)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Schedule Display</h3>
|
||||||
|
|
||||||
|
<p>In the Schedule column, you'll see:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>"Daily"</strong> - Job runs every day</li>
|
||||||
|
<li><strong>"Weekly: Mon Wed Fri"</strong> - Job runs on Mondays, Wednesdays, and Fridays</li>
|
||||||
|
<li><strong>"Monthly: 1st, 15th"</strong> - Job runs on the 1st and 15th of each month</li>
|
||||||
|
<li><strong>"Irregular"</strong> - No consistent schedule detected</li>
|
||||||
|
<li><strong>"-"</strong> - Not enough historical data to infer a schedule</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Learning Period:</strong><br>
|
||||||
|
Schedule inference requires at least 10-14 days of historical runs to detect patterns accurately. Newly approved jobs may show "-" or "Irregular" until enough data is collected.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Override Badges</h2>
|
||||||
|
|
||||||
|
<p>Jobs with active overrides display a small badge next to the job name. Overrides are used to handle known issues or exceptions (e.g., "treated as success due to known warning").</p>
|
||||||
|
|
||||||
|
<p>To view override details:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Look for jobs with override badges in the Daily Jobs table</li>
|
||||||
|
<li>Click on the job row to open the Run Checks modal (see next section)</li>
|
||||||
|
<li>The modal will show which overrides are applied to the job run</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2>Ticket and Remark Indicators</h2>
|
||||||
|
|
||||||
|
<p>Jobs linked to active tickets or remarks display indicator icons:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>🎫 Ticket icon:</strong> Job has one or more active tickets linked to it</li>
|
||||||
|
<li><strong>💬 Remark icon:</strong> Job has one or more active remarks attached</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>These indicators help you quickly identify jobs with known issues or ongoing investigations.</p>
|
||||||
|
|
||||||
|
<h2>Viewing Job Details</h2>
|
||||||
|
|
||||||
|
<p>To view detailed information about a job run:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Click on any job row in the Daily Jobs table</li>
|
||||||
|
<li>The <strong>Run Checks modal</strong> opens, showing:
|
||||||
|
<ul>
|
||||||
|
<li>Job run details and status</li>
|
||||||
|
<li>Backup objects (VMs, servers, etc.) and their individual statuses</li>
|
||||||
|
<li>Applied overrides</li>
|
||||||
|
<li>Linked tickets and remarks</li>
|
||||||
|
<li>Original email content</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>See <a href="{{ url_for('documentation.page', section='backup-review', page='run-checks-modal') }}">Run Checks Modal</a> for detailed information about reviewing job runs.</p>
|
||||||
|
|
||||||
|
<h2>Reviewing Jobs</h2>
|
||||||
|
|
||||||
|
<p>For jobs with warnings or failures, you can mark them as reviewed after investigating:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Click on the job row to open the Run Checks modal</li>
|
||||||
|
<li>Review the job details, objects, and any error messages</li>
|
||||||
|
<li>Click <strong>Mark as reviewed</strong> to acknowledge you've investigated the issue</li>
|
||||||
|
<li>The job is marked as reviewed and removed from the unreviewed list</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Review Workflow:</strong><br>
|
||||||
|
Only jobs with warnings or failures need to be reviewed. Successful jobs are automatically considered reviewed. The Run Checks workflow is designed to help you quickly triage and acknowledge known issues.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Common Workflows</h2>
|
||||||
|
|
||||||
|
<h3>Morning Backup Review</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Open <strong>Daily Jobs</strong> (defaults to today's date)</li>
|
||||||
|
<li>Review the status column for any red (Failed) or yellow (Warning) badges</li>
|
||||||
|
<li>Click on jobs with issues to open the Run Checks modal</li>
|
||||||
|
<li>Investigate the failure/warning details and backup objects</li>
|
||||||
|
<li>Create a ticket or remark if followup is needed, or mark as reviewed if it's a known issue</li>
|
||||||
|
<li>Repeat for all jobs with issues</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Checking Previous Day's Jobs</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Open <strong>Daily Jobs</strong></li>
|
||||||
|
<li>Click the <strong>Previous</strong> button to go back one day</li>
|
||||||
|
<li>Review jobs that ran yesterday</li>
|
||||||
|
<li>Look for "Missed" status - jobs that were expected but didn't run</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Identifying Schedule Changes</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Notice a job appearing on an unexpected day</li>
|
||||||
|
<li>Check the Schedule column to see the inferred pattern</li>
|
||||||
|
<li>If the schedule changed recently, it may take 10-14 days for the system to detect the new pattern</li>
|
||||||
|
<li>Contact the customer if a job unexpectedly stops appearing on expected days</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2>Best Practices</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Review daily:</strong> Check the Daily Jobs view every morning to catch failures and warnings promptly</li>
|
||||||
|
<li><strong>Focus on failures first:</strong> Prioritize red (Failed) badges over yellow (Warning) badges during triage</li>
|
||||||
|
<li><strong>Watch for "Missed" jobs:</strong> These indicate a job that should have run but didn't - often more serious than a failure</li>
|
||||||
|
<li><strong>Use overrides for known issues:</strong> If a specific warning is expected and acceptable, create an override instead of marking it as reviewed every day</li>
|
||||||
|
<li><strong>Create tickets for recurring issues:</strong> If the same job fails repeatedly, create a ticket to track the ongoing issue</li>
|
||||||
|
<li><strong>Monitor schedule changes:</strong> If customers change their backup schedules, allow 10-14 days for the system to learn the new pattern</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Troubleshooting</h2>
|
||||||
|
|
||||||
|
<h3>Job Not Appearing in Daily Jobs</h3>
|
||||||
|
|
||||||
|
<p>If a job doesn't appear on a date when you expected it:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>The job may not have enough historical data to infer a schedule (needs 10-14 days)</li>
|
||||||
|
<li>The job may have an irregular schedule that doesn't match weekly or monthly patterns</li>
|
||||||
|
<li>The job's schedule may have recently changed, and the system hasn't detected the new pattern yet</li>
|
||||||
|
<li>Check the <strong>Jobs</strong> page to verify the job exists and has recent runs</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Schedule Shows "Irregular" or "-"</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>"Irregular":</strong> The job runs inconsistently and doesn't match daily, weekly, or monthly patterns</li>
|
||||||
|
<li><strong>"-":</strong> Not enough historical data to infer a schedule (fewer than ~10 runs)</li>
|
||||||
|
<li>Wait for more runs to be imported, then schedule inference will update automatically</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Job Shows "Missed" but Actually Ran</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Check if the email was imported and approved (check <strong>Inbox</strong>)</li>
|
||||||
|
<li>Verify the email was parsed correctly and linked to the right job</li>
|
||||||
|
<li>Check the <strong>Job History</strong> page to see if the run was recorded</li>
|
||||||
|
<li>If the run time changed significantly (e.g., moved from morning to evening), it may affect schedule detection</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Next Steps</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='backup-review', page='run-checks-modal') }}">Run Checks Modal</a> - Learn how to review job run details</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='backup-review', page='overrides') }}">Overrides & Exceptions</a> - Configure rules for handling known issues</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='backup-review', page='remarks-tickets') }}">Remarks & Tickets</a> - Track issues and followup work</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,376 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>Overrides & Exceptions</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Configure override rules to automatically handle known issues, expected warnings, and special backup scenarios.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Overview</h2>
|
||||||
|
|
||||||
|
<p><strong>Overrides</strong> are rules that modify how BackupChecks interprets backup job results. They allow you to handle known issues or expected warnings without manually reviewing every occurrence.</p>
|
||||||
|
|
||||||
|
<p>Common use cases:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Expected warnings:</strong> Mark specific warning messages as acceptable (e.g., "Retry succeeded")</li>
|
||||||
|
<li><strong>Known issues:</strong> Handle recurring problems that are being worked on (e.g., linked to a ticket)</li>
|
||||||
|
<li><strong>Temporary exceptions:</strong> Create time-limited overrides for maintenance windows or planned issues</li>
|
||||||
|
<li><strong>Object-specific rules:</strong> Handle issues with specific backup objects (e.g., one VM that always has warnings)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Accessing Overrides</h2>
|
||||||
|
|
||||||
|
<p>To manage overrides:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to <strong>Overrides</strong> in the main navigation menu</li>
|
||||||
|
<li>Available to <strong>Admin</strong>, <strong>Operator</strong>, and <strong>Viewer</strong> roles</li>
|
||||||
|
<li>Viewers can see override rules but cannot create, edit, or delete them</li>
|
||||||
|
<li>Operators can create and edit overrides, but cannot delete them</li>
|
||||||
|
<li>Admins have full access to all override operations</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2>Override Levels</h2>
|
||||||
|
|
||||||
|
<p>Overrides can be created at two levels:</p>
|
||||||
|
|
||||||
|
<h3>1. Global Override</h3>
|
||||||
|
|
||||||
|
<p>Applies to all jobs matching specific criteria:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>All jobs:</strong> Leave backup software and type blank to match everything</li>
|
||||||
|
<li><strong>By backup software:</strong> Match all jobs from a specific product (e.g., all Veeam jobs)</li>
|
||||||
|
<li><strong>By backup type:</strong> Match all jobs of a specific type (e.g., all Replication Jobs)</li>
|
||||||
|
<li><strong>By software + type:</strong> Match both criteria (e.g., Veeam Replication Jobs only)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>Global overrides are useful for handling issues that affect multiple jobs across multiple customers.</p>
|
||||||
|
|
||||||
|
<h3>2. Object-Level Override</h3>
|
||||||
|
|
||||||
|
<p>Applies to a specific job or specific object within a job:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Job-level:</strong> Select a specific job to apply the override only to that job</li>
|
||||||
|
<li><strong>Object-level:</strong> Additionally specify an object name (VM, server, etc.) to match only that specific backup item</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>Object-level overrides are more precise and are checked before global overrides.</p>
|
||||||
|
|
||||||
|
<h2>Override Match Criteria</h2>
|
||||||
|
|
||||||
|
<p>In addition to level (global or object), overrides can match specific conditions:</p>
|
||||||
|
|
||||||
|
<h3>Status Match</h3>
|
||||||
|
|
||||||
|
<p>Match only runs with a specific status:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Any:</strong> Match all statuses (default)</li>
|
||||||
|
<li><strong>Success:</strong> Match only successful runs</li>
|
||||||
|
<li><strong>Warning:</strong> Match only runs with warnings</li>
|
||||||
|
<li><strong>Failed:</strong> Match only failed runs</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>Most overrides match "Warning" or "Failed" to handle known issues with those statuses.</p>
|
||||||
|
|
||||||
|
<h3>Error Text Match</h3>
|
||||||
|
|
||||||
|
<p>Match based on error or warning message content:</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Match Type</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th>Example</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Contains</strong></td>
|
||||||
|
<td>Error message contains the specified text (case-insensitive)</td>
|
||||||
|
<td>"Retry" matches "Backup retry succeeded"</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Exact</strong></td>
|
||||||
|
<td>Error message exactly matches the specified text</td>
|
||||||
|
<td>"Retry succeeded" only matches that exact phrase</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Starts with</strong></td>
|
||||||
|
<td>Error message starts with the specified text</td>
|
||||||
|
<td>"Retry" matches "Retry succeeded" but not "Backup retry"</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Ends with</strong></td>
|
||||||
|
<td>Error message ends with the specified text</td>
|
||||||
|
<td>"succeeded" matches "Retry succeeded"</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p>Leave the error text field blank to match any error message (or no error message).</p>
|
||||||
|
|
||||||
|
<h2>Override Actions</h2>
|
||||||
|
|
||||||
|
<h3>Treat as Success</h3>
|
||||||
|
|
||||||
|
<p>The primary action for overrides is <strong>Treat as success</strong> (enabled by default):</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>When checked, matching runs are displayed with a <strong>blue "Success (Override)"</strong> badge instead of their original warning/failed status</li>
|
||||||
|
<li>The blue badge visually indicates this run had an issue but is being treated as acceptable</li>
|
||||||
|
<li>The run still needs to be reviewed and marked as reviewed, but operators know the warning/failure is expected</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>This allows you to visually differentiate between:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Green badge:</strong> Genuinely successful backups with no issues</li>
|
||||||
|
<li><strong>Blue badge:</strong> Known issues that are acceptable (override applied)</li>
|
||||||
|
<li><strong>Yellow/Red badge:</strong> Unexpected problems requiring investigation</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Override Time Windows</h2>
|
||||||
|
|
||||||
|
<p>Overrides can be time-limited using the <strong>From</strong> and <strong>Until</strong> fields:</p>
|
||||||
|
|
||||||
|
<h3>From Date (Start Date)</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Leave blank to apply retroactively to all existing runs (default)</li>
|
||||||
|
<li>Set a date/time to apply only from that point forward</li>
|
||||||
|
<li>Retroactive application ensures old unreviewed runs are also affected</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Until Date (End Date)</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Leave blank for a permanent override (no expiration)</li>
|
||||||
|
<li>Set a date/time to automatically expire the override</li>
|
||||||
|
<li>Useful for maintenance windows or temporary issues</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Retroactive Application:</strong><br>
|
||||||
|
When you create an override without a start date, it is applied retroactively to existing unreviewed runs. This means jobs that match the override will immediately show the "Treat as success" status in Daily Jobs, even if they ran before the override was created.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Creating an Override</h2>
|
||||||
|
|
||||||
|
<p>To create a new override:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to the <strong>Overrides</strong> page</li>
|
||||||
|
<li>Fill in the "Add override" form at the top:
|
||||||
|
<ul>
|
||||||
|
<li><strong>Level:</strong> Select "Global" or "Object"</li>
|
||||||
|
<li><strong>Backup software / Backup type:</strong> (For global only) Select criteria to match</li>
|
||||||
|
<li><strong>Job:</strong> (For object-level only) Select the specific job</li>
|
||||||
|
<li><strong>Object name:</strong> (Optional, for object-level only) Enter exact object name (e.g., VM name)</li>
|
||||||
|
<li><strong>Status:</strong> (Optional) Select which status to match (e.g., "Warning")</li>
|
||||||
|
<li><strong>Error match type:</strong> Select "Contains", "Exact", "Starts with", or "Ends with"</li>
|
||||||
|
<li><strong>Error text:</strong> (Optional) Enter text to match in error messages</li>
|
||||||
|
<li><strong>From / Until:</strong> (Optional) Set time window for the override</li>
|
||||||
|
<li><strong>Treat as success:</strong> Check this box (enabled by default)</li>
|
||||||
|
<li><strong>Comment:</strong> Document why the override exists (e.g., ticket number, reason)</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>Click <strong>Save override</strong></li>
|
||||||
|
<li>The override is immediately applied to matching job runs</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-tip">
|
||||||
|
<strong>💡 Always Add Comments:</strong><br>
|
||||||
|
Use the Comment field to document ticket numbers, reasons, or planned resolution dates. This helps other operators understand why the override exists and when it should be removed.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Managing Existing Overrides</h2>
|
||||||
|
|
||||||
|
<p>The "Existing overrides" table shows all configured overrides with the following columns:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Level:</strong> "global" or "object"</li>
|
||||||
|
<li><strong>Scope:</strong> What the override matches (e.g., "Veeam / Backup Job" or "CustomerName / Veeam / Backup Job / Daily Backup")</li>
|
||||||
|
<li><strong>From / Until:</strong> Time window (blank = no restriction)</li>
|
||||||
|
<li><strong>Active:</strong> Whether the override is currently active or disabled</li>
|
||||||
|
<li><strong>Comment:</strong> Documentation about the override</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Editing an Override</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Click the <strong>Edit</strong> button on an override row</li>
|
||||||
|
<li>The "Add override" form is pre-filled with the existing values</li>
|
||||||
|
<li>Modify any fields</li>
|
||||||
|
<li>Click <strong>Save override</strong> to update</li>
|
||||||
|
<li>Click <strong>Cancel edit</strong> to discard changes and clear the form</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Disabling/Enabling an Override</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Click the <strong>Disable</strong> button on an active override</li>
|
||||||
|
<li>The override is marked as inactive and stops affecting job runs</li>
|
||||||
|
<li>Click <strong>Enable</strong> to reactivate it</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>Disabling is useful when you want to temporarily stop an override without deleting it (e.g., to test if an issue is resolved).</p>
|
||||||
|
|
||||||
|
<h3>Deleting an Override (Admin Only)</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Click the <strong>Delete</strong> button on an override row</li>
|
||||||
|
<li>Confirm the deletion</li>
|
||||||
|
<li>The override is permanently removed</li>
|
||||||
|
<li>Existing runs that were affected by the override remain unchanged</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-warning">
|
||||||
|
<strong>⚠️ Deletion is Permanent:</strong><br>
|
||||||
|
Deleted overrides cannot be recovered. If you're unsure, disable the override instead of deleting it.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Override Evaluation Order</h2>
|
||||||
|
|
||||||
|
<p>When a job run is evaluated, overrides are checked in the following order:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li><strong>Object-level overrides:</strong> Most specific overrides are checked first (job + object name)</li>
|
||||||
|
<li><strong>Job-level overrides:</strong> Overrides for the specific job (without object name)</li>
|
||||||
|
<li><strong>Global overrides:</strong> Least specific overrides are checked last</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>The <strong>first matching active override</strong> is applied. If multiple overrides match, only the most specific one takes effect.</p>
|
||||||
|
|
||||||
|
<h2>Common Override Scenarios</h2>
|
||||||
|
|
||||||
|
<h3>Scenario 1: Veeam Retry Warnings</h3>
|
||||||
|
|
||||||
|
<p>A Veeam backup job frequently shows warnings like "Backup retry succeeded". This is acceptable - the job ultimately succeeded.</p>
|
||||||
|
|
||||||
|
<p><strong>Solution:</strong> Create an object-level override:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Level:</strong> Object</li>
|
||||||
|
<li><strong>Job:</strong> Select the specific job</li>
|
||||||
|
<li><strong>Status:</strong> Warning</li>
|
||||||
|
<li><strong>Error match type:</strong> Contains</li>
|
||||||
|
<li><strong>Error text:</strong> "retry succeeded"</li>
|
||||||
|
<li><strong>Treat as success:</strong> Checked</li>
|
||||||
|
<li><strong>Comment:</strong> "Retry warnings are acceptable for this job"</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Scenario 2: Planned Maintenance Window</h3>
|
||||||
|
|
||||||
|
<p>A customer is performing server upgrades next week. Backup failures during May 15-17 are expected.</p>
|
||||||
|
|
||||||
|
<p><strong>Solution:</strong> Create a time-limited override:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Level:</strong> Object</li>
|
||||||
|
<li><strong>Job:</strong> Select the customer's backup job</li>
|
||||||
|
<li><strong>Status:</strong> Failed</li>
|
||||||
|
<li><strong>From:</strong> 2024-05-15 00:00</li>
|
||||||
|
<li><strong>Until:</strong> 2024-05-18 00:00</li>
|
||||||
|
<li><strong>Treat as success:</strong> Checked</li>
|
||||||
|
<li><strong>Comment:</strong> "Planned maintenance window - Ticket #12345"</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>After May 18, the override automatically expires and failures will appear normally.</p>
|
||||||
|
|
||||||
|
<h3>Scenario 3: Known Issue with One VM</h3>
|
||||||
|
|
||||||
|
<p>One VM in a multi-VM backup job consistently fails with "Snapshot timeout". A ticket has been created with the customer.</p>
|
||||||
|
|
||||||
|
<p><strong>Solution:</strong> Create an object-specific override:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Level:</strong> Object</li>
|
||||||
|
<li><strong>Job:</strong> Select the backup job</li>
|
||||||
|
<li><strong>Object name:</strong> "SERVER01" (exact VM name)</li>
|
||||||
|
<li><strong>Status:</strong> Failed</li>
|
||||||
|
<li><strong>Error match type:</strong> Contains</li>
|
||||||
|
<li><strong>Error text:</strong> "Snapshot timeout"</li>
|
||||||
|
<li><strong>Treat as success:</strong> Checked</li>
|
||||||
|
<li><strong>Comment:</strong> "Known issue - Ticket #67890 - Customer investigating"</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>Other VMs in the same job will still show failures normally.</p>
|
||||||
|
|
||||||
|
<h3>Scenario 4: Global NAKIVO Replication Warnings</h3>
|
||||||
|
|
||||||
|
<p>All NAKIVO replication jobs show harmless warnings about "Network latency detected". This is expected for remote sites.</p>
|
||||||
|
|
||||||
|
<p><strong>Solution:</strong> Create a global override:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Level:</strong> Global</li>
|
||||||
|
<li><strong>Backup software:</strong> NAKIVO</li>
|
||||||
|
<li><strong>Backup type:</strong> Replication Job</li>
|
||||||
|
<li><strong>Status:</strong> Warning</li>
|
||||||
|
<li><strong>Error match type:</strong> Contains</li>
|
||||||
|
<li><strong>Error text:</strong> "Network latency"</li>
|
||||||
|
<li><strong>Treat as success:</strong> Checked</li>
|
||||||
|
<li><strong>Comment:</strong> "Network latency warnings are expected for remote site replication"</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Best Practices</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Start specific, then generalize:</strong> Create object-level overrides first. If the same issue affects many jobs, create a global override.</li>
|
||||||
|
<li><strong>Always document:</strong> Use the Comment field to explain why the override exists and reference ticket numbers.</li>
|
||||||
|
<li><strong>Use time windows:</strong> For temporary issues, always set an "Until" date to avoid leaving stale overrides active.</li>
|
||||||
|
<li><strong>Review periodically:</strong> Check the Overrides page monthly and remove overrides for resolved issues.</li>
|
||||||
|
<li><strong>Be specific with error text:</strong> Use "Exact" or "Contains" with specific phrases to avoid overly broad matches.</li>
|
||||||
|
<li><strong>Test before creating global overrides:</strong> Global overrides affect many jobs - test with object-level first.</li>
|
||||||
|
<li><strong>Disable, don't delete:</strong> If unsure whether an override is still needed, disable it for a few days before deleting.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Troubleshooting</h2>
|
||||||
|
|
||||||
|
<h3>Override Not Applied to Job Run</h3>
|
||||||
|
|
||||||
|
<p>If an override doesn't appear to affect a job run:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Verify the override is <strong>Active</strong> (not disabled)</li>
|
||||||
|
<li>Check the time window - the run timestamp must be between <strong>From</strong> and <strong>Until</strong></li>
|
||||||
|
<li>Verify the match criteria - status, error text, and match type must exactly match the run</li>
|
||||||
|
<li>Check the override level - object-level overrides must match the exact job and object name</li>
|
||||||
|
<li>For retroactive overrides, only unreviewed runs are affected</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Override Applying Too Broadly</h3>
|
||||||
|
|
||||||
|
<p>If an override is affecting more jobs than intended:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Check if it's a <strong>Global</strong> override with no backup software/type specified</li>
|
||||||
|
<li>Review the error text match - "Contains" matches anywhere in the message (use "Exact" for precision)</li>
|
||||||
|
<li>Consider creating a more specific object-level override instead</li>
|
||||||
|
<li>Add status criteria to narrow the match (e.g., only match "Warning", not all statuses)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Multiple Overrides Conflicting</h3>
|
||||||
|
|
||||||
|
<p>Only one override is applied per run (the most specific match). If you have both global and object-level overrides that match:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>The object-level override takes precedence</li>
|
||||||
|
<li>Review both overrides and decide which one should remain</li>
|
||||||
|
<li>Remove or disable the less specific override</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Next Steps</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='backup-review', page='daily-jobs') }}">Daily Jobs View</a> - See how overrides affect the Daily Jobs display</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='backup-review', page='run-checks-modal') }}">Run Checks Modal</a> - View override details for specific runs</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='backup-review', page='remarks-tickets') }}">Remarks & Tickets</a> - Track issues that require followup beyond overrides</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,429 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>Remarks & Tickets</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Document known issues, track followup work, and link backup problems to tickets for resolution.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Overview</h2>
|
||||||
|
|
||||||
|
<p>BackupChecks provides two mechanisms for documenting and tracking backup issues:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Tickets:</strong> For issues requiring external followup (customer action, vendor support, hardware replacement)</li>
|
||||||
|
<li><strong>Remarks:</strong> For internal notes, known issues, or temporary problems that don't need formal ticket tracking</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>Both tickets and remarks can be linked to specific job runs, making it easy to track which backups are affected by known issues.</p>
|
||||||
|
|
||||||
|
<h2>Accessing Tickets & Remarks</h2>
|
||||||
|
|
||||||
|
<p>To view and manage tickets and remarks:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to <strong>Tickets</strong> in the main navigation menu</li>
|
||||||
|
<li>Available to <strong>Admin</strong>, <strong>Operator</strong>, and <strong>Viewer</strong> roles</li>
|
||||||
|
<li>Two tabs are available:
|
||||||
|
<ul>
|
||||||
|
<li><strong>Tickets:</strong> Shows all tickets with their resolution status</li>
|
||||||
|
<li><strong>Remarks:</strong> Shows all remarks with their resolution status</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2>Tickets</h2>
|
||||||
|
|
||||||
|
<h3>What is a Ticket?</h3>
|
||||||
|
|
||||||
|
<p>A <strong>ticket</strong> represents an issue that requires tracking and followup. Tickets are used for:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Hardware failures requiring replacement</li>
|
||||||
|
<li>Software bugs reported to vendors</li>
|
||||||
|
<li>Customer action items (upgrade server, allocate more storage)</li>
|
||||||
|
<li>Configuration changes needed (adjust backup schedule, exclude specific files)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Ticket Properties</h3>
|
||||||
|
|
||||||
|
<p>Each ticket has the following properties:</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Property</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Ticket code</strong></td>
|
||||||
|
<td>Unique identifier (e.g., T12345, CUST-456). Can reference an external PSA ticket number.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Description</strong></td>
|
||||||
|
<td>Brief description of the issue</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Active from date</strong></td>
|
||||||
|
<td>Date when the ticket becomes active (usually the date the issue was first observed)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Start date</strong></td>
|
||||||
|
<td>Timestamp when the ticket was created in BackupChecks</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Resolved at</strong></td>
|
||||||
|
<td>Timestamp when the ticket was marked as resolved (blank if still active)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Scopes</strong></td>
|
||||||
|
<td>Which backup jobs, customers, or objects this ticket affects</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3>Creating a Ticket</h3>
|
||||||
|
|
||||||
|
<p>Tickets can be created from several locations in the application:</p>
|
||||||
|
|
||||||
|
<h4>From Run Checks Modal</h4>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Open the Run Checks modal for a job with a warning or failure</li>
|
||||||
|
<li>Review the job run details</li>
|
||||||
|
<li>Click <strong>Create Ticket</strong> (button location depends on UI implementation)</li>
|
||||||
|
<li>Fill in ticket details:
|
||||||
|
<ul>
|
||||||
|
<li><strong>Ticket code:</strong> Enter a unique identifier (e.g., reference your PSA system)</li>
|
||||||
|
<li><strong>Description:</strong> Briefly describe the issue</li>
|
||||||
|
<li><strong>Active from date:</strong> Select when the issue started (defaults to today)</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>The ticket is automatically linked to the current job run</li>
|
||||||
|
<li>Scopes are automatically populated based on the job</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h4>From Tickets Page</h4>
|
||||||
|
|
||||||
|
<p>Tickets can also be created manually from the Tickets page (for issues not tied to a specific run).</p>
|
||||||
|
|
||||||
|
<h3>Ticket Scopes</h3>
|
||||||
|
|
||||||
|
<p>A ticket can have multiple <strong>scopes</strong> defining what it affects:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Customer scope:</strong> All backup jobs for a specific customer</li>
|
||||||
|
<li><strong>Backup software scope:</strong> All jobs using specific backup software (e.g., all Veeam jobs)</li>
|
||||||
|
<li><strong>Backup type scope:</strong> All jobs of a specific type (e.g., all Replication Jobs)</li>
|
||||||
|
<li><strong>Job scope:</strong> A specific backup job</li>
|
||||||
|
<li><strong>Job run scope:</strong> A specific backup job run</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>Scopes are used to automatically show ticket indicators (🎫) on affected jobs in the Daily Jobs view.</p>
|
||||||
|
|
||||||
|
<h3>Viewing Ticket Details</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Go to the <strong>Tickets</strong> page</li>
|
||||||
|
<li>Click <strong>View</strong> on a ticket row</li>
|
||||||
|
<li>The ticket detail page shows:
|
||||||
|
<ul>
|
||||||
|
<li>Ticket status (Active or Resolved)</li>
|
||||||
|
<li>Ticket code, description, and dates</li>
|
||||||
|
<li>Scopes (which jobs/customers are affected)</li>
|
||||||
|
<li>Linked runs (last 20 job runs associated with this ticket)</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Resolving a Ticket</h3>
|
||||||
|
|
||||||
|
<p>When a ticket is resolved:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Go to the <strong>Tickets</strong> page</li>
|
||||||
|
<li>Click <strong>Resolve</strong> on an active ticket row (Admin/Operator only)</li>
|
||||||
|
<li>The ticket is marked as resolved with the current timestamp</li>
|
||||||
|
<li>Resolved tickets remain visible in the ticket list with a ✅ indicator</li>
|
||||||
|
<li>The ticket indicator (🎫) is removed from job displays</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Ticket Resolution:</strong><br>
|
||||||
|
Resolving a ticket does NOT delete it - it simply marks it as closed. Resolved tickets remain visible for historical reference and can be filtered using the "Status" dropdown.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Linking Tickets to Additional Runs</h3>
|
||||||
|
|
||||||
|
<p>After creating a ticket, you can link it to additional job runs as they occur:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Open the Run Checks modal for a job run</li>
|
||||||
|
<li>Look for existing tickets in the ticket list (if available in UI)</li>
|
||||||
|
<li>Link the ticket to the current run</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>This creates an audit trail showing all job runs affected by the same ticket.</p>
|
||||||
|
|
||||||
|
<h2>Remarks</h2>
|
||||||
|
|
||||||
|
<h3>What is a Remark?</h3>
|
||||||
|
|
||||||
|
<p>A <strong>remark</strong> is a lightweight note for documenting known issues that don't require formal ticket tracking. Remarks are used for:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Temporary issues (planned downtime, maintenance windows)</li>
|
||||||
|
<li>Known non-critical warnings (acceptable performance issues, cosmetic problems)</li>
|
||||||
|
<li>Internal documentation (schedule change notes, customer preferences)</li>
|
||||||
|
<li>Quick notes during triage ("investigated, not an issue")</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Remark Properties</h3>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Property</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Body</strong></td>
|
||||||
|
<td>Freeform text describing the remark (no length limit)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Start date</strong></td>
|
||||||
|
<td>Timestamp when the remark was created</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Resolved at</strong></td>
|
||||||
|
<td>Timestamp when the remark was marked as resolved (blank if still active)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Scopes</strong></td>
|
||||||
|
<td>Which backup jobs, customers, or objects this remark affects</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3>Creating a Remark</h3>
|
||||||
|
|
||||||
|
<p>Remarks are created similarly to tickets:</p>
|
||||||
|
|
||||||
|
<h4>From Run Checks Modal</h4>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Open the Run Checks modal for a job run</li>
|
||||||
|
<li>Review the job run details</li>
|
||||||
|
<li>Click <strong>Create Remark</strong></li>
|
||||||
|
<li>Enter remark text in the body field (supports multi-line text)</li>
|
||||||
|
<li>The remark is automatically linked to the current job run</li>
|
||||||
|
<li>Scopes are automatically populated based on the job</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h4>From Tickets Page</h4>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Go to the <strong>Tickets</strong> page</li>
|
||||||
|
<li>Switch to the <strong>Remarks</strong> tab</li>
|
||||||
|
<li>Create a remark manually (if supported in UI)</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Viewing Remark Details</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Go to the <strong>Tickets</strong> page</li>
|
||||||
|
<li>Switch to the <strong>Remarks</strong> tab</li>
|
||||||
|
<li>Click <strong>View</strong> on a remark row</li>
|
||||||
|
<li>The remark detail page shows:
|
||||||
|
<ul>
|
||||||
|
<li>Remark status (Active or Resolved)</li>
|
||||||
|
<li>Remark body (full text)</li>
|
||||||
|
<li>Scopes (which jobs/customers are affected)</li>
|
||||||
|
<li>Linked runs (last 20 job runs associated with this remark)</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Resolving a Remark</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Go to the <strong>Tickets</strong> page → <strong>Remarks</strong> tab</li>
|
||||||
|
<li>Click <strong>Resolve</strong> on an active remark row (Admin/Operator only)</li>
|
||||||
|
<li>The remark is marked as resolved</li>
|
||||||
|
<li>Resolved remarks remain visible with a ✅ indicator</li>
|
||||||
|
<li>The remark indicator (💬) is removed from job displays</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2>Filtering Tickets & Remarks</h2>
|
||||||
|
|
||||||
|
<p>The Tickets page provides filtering options:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Status:</strong> Filter by "Active" (unresolved) or "All" (including resolved)</li>
|
||||||
|
<li><strong>Customer:</strong> Show only tickets/remarks for a specific customer</li>
|
||||||
|
<li><strong>Backup software:</strong> Filter by backup software (e.g., Veeam, Synology)</li>
|
||||||
|
<li><strong>Backup type:</strong> Filter by backup type (e.g., Backup Job, Replication Job)</li>
|
||||||
|
<li><strong>Search:</strong> Search by ticket code or job name</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>Click <strong>Filter</strong> to apply the selected criteria, or <strong>Reset</strong> to clear all filters.</p>
|
||||||
|
|
||||||
|
<h2>Tickets vs. Remarks: When to Use Each</h2>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Use Ticket When...</th>
|
||||||
|
<th>Use Remark When...</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Issue requires external followup</td>
|
||||||
|
<td>Issue is internal or informational</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>You need to track in a PSA system</td>
|
||||||
|
<td>No PSA ticket is needed</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Customer action is required</td>
|
||||||
|
<td>No action is required</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Issue will take days/weeks to resolve</td>
|
||||||
|
<td>Issue is temporary (hours/days)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>You need structured tracking (ticket code, formal resolution)</td>
|
||||||
|
<td>Quick notes or documentation are sufficient</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>Ticket & Remark Indicators</h2>
|
||||||
|
|
||||||
|
<p>In the Daily Jobs view and Job History pages, active tickets and remarks are indicated with icons:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>🎫 Ticket icon:</strong> Job has one or more active tickets</li>
|
||||||
|
<li><strong>💬 Remark icon:</strong> Job has one or more active remarks</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>These indicators help you quickly identify jobs with known issues during daily review.</p>
|
||||||
|
|
||||||
|
<h2>Common Workflows</h2>
|
||||||
|
|
||||||
|
<h3>Creating a Ticket for Recurring Failure</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Notice the same job failing in Daily Jobs for 3+ days</li>
|
||||||
|
<li>Click on the job to open Run Checks modal</li>
|
||||||
|
<li>Click <strong>Create Ticket</strong></li>
|
||||||
|
<li>Enter ticket code: "CUST-12345" (from your PSA)</li>
|
||||||
|
<li>Description: "Daily backup fails - server disk full"</li>
|
||||||
|
<li>Active from date: Select the date of the first failure</li>
|
||||||
|
<li>Create the ticket</li>
|
||||||
|
<li>The job now shows 🎫 in Daily Jobs, indicating it's being tracked</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Adding a Remark for Planned Maintenance</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Customer notifies you of planned maintenance on Friday</li>
|
||||||
|
<li>Go to Daily Jobs → Friday's date</li>
|
||||||
|
<li>Click on the customer's backup job</li>
|
||||||
|
<li>Click <strong>Create Remark</strong></li>
|
||||||
|
<li>Body: "Planned maintenance - server will be offline from 18:00-22:00. Backup failures expected."</li>
|
||||||
|
<li>Create the remark</li>
|
||||||
|
<li>On Friday, the job shows 💬, reminding you maintenance is planned</li>
|
||||||
|
<li>After Friday, resolve the remark</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Resolving a Ticket After Customer Action</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Customer confirms they've freed up disk space</li>
|
||||||
|
<li>Monitor Daily Jobs - backup succeeds for 2+ days</li>
|
||||||
|
<li>Go to <strong>Tickets</strong> page</li>
|
||||||
|
<li>Find the ticket for this issue</li>
|
||||||
|
<li>Click <strong>Resolve</strong></li>
|
||||||
|
<li>The 🎫 indicator disappears from the job in Daily Jobs</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Reviewing Historical Tickets</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Go to <strong>Tickets</strong> page</li>
|
||||||
|
<li>Change Status filter to "All"</li>
|
||||||
|
<li>Review resolved tickets to identify patterns (recurring issues, common problems)</li>
|
||||||
|
<li>Use this data to improve backup configurations or create preventive overrides</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2>Best Practices</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Create tickets promptly:</strong> Don't wait for issues to escalate - create tickets when problems first appear</li>
|
||||||
|
<li><strong>Use descriptive ticket codes:</strong> Reference your PSA system for easy correlation</li>
|
||||||
|
<li><strong>Set accurate "Active from" dates:</strong> This ensures the ticket shows on the correct historical runs</li>
|
||||||
|
<li><strong>Resolve tickets promptly:</strong> Don't leave stale tickets active after issues are fixed</li>
|
||||||
|
<li><strong>Use remarks for temporary issues:</strong> Don't create formal tickets for short-term problems</li>
|
||||||
|
<li><strong>Link tickets to multiple runs:</strong> As the same issue affects new runs, link them to the existing ticket</li>
|
||||||
|
<li><strong>Review ticket list monthly:</strong> Identify long-running issues that need escalation</li>
|
||||||
|
<li><strong>Use remarks for documentation:</strong> If you investigate an alert and determine it's not an issue, add a remark explaining why</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Integration with Autotask</h2>
|
||||||
|
|
||||||
|
<p>If Autotask integration is enabled, you can manually create tickets in Autotask PSA for backup failures. When creating a ticket from the Run Checks modal, BackupChecks can create a corresponding ticket in Autotask and maintain a link between the internal ticket record and the Autotask ticket ID.</p>
|
||||||
|
|
||||||
|
<p>BackupChecks monitors Autotask for ticket status changes and can automatically resolve internal tickets when the corresponding Autotask ticket is resolved or deleted.</p>
|
||||||
|
|
||||||
|
<p>See <a href="{{ url_for('documentation.page', section='autotask', page='overview') }}">Autotask Integration</a> for details on setup and PSA ticket synchronization.</p>
|
||||||
|
|
||||||
|
<h2>Troubleshooting</h2>
|
||||||
|
|
||||||
|
<h3>Ticket Indicator Not Showing on Job</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Verify the ticket is <strong>Active</strong> (not resolved)</li>
|
||||||
|
<li>Check the ticket's <strong>Active from date</strong> - it must be on or before the date you're viewing</li>
|
||||||
|
<li>Verify the ticket scope matches the job (check customer, backup software, or job in the scope)</li>
|
||||||
|
<li>Refresh the Daily Jobs page</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Cannot Create Ticket or Remark</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Verify you have <strong>Admin</strong> or <strong>Operator</strong> role (Viewers cannot create tickets/remarks)</li>
|
||||||
|
<li>Ensure all required fields are filled (ticket code, description, active from date)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Ticket Scope Affecting Wrong Jobs</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Review the ticket scopes on the ticket detail page</li>
|
||||||
|
<li>Scopes may be broader than intended (e.g., customer scope affects all jobs for that customer)</li>
|
||||||
|
<li>Edit the ticket (if supported) or resolve it and create a more specific ticket</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-warning">
|
||||||
|
<strong>⚠️ Ticket Editing:</strong><br>
|
||||||
|
Based on the code, ticket editing is currently disabled. To modify a ticket, resolve the old ticket and create a new one with the correct information.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Next Steps</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='backup-review', page='daily-jobs') }}">Daily Jobs View</a> - See how ticket and remark indicators appear in daily monitoring</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='backup-review', page='run-checks-modal') }}">Run Checks Modal</a> - Create tickets and remarks while reviewing job runs</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='backup-review', page='overrides') }}">Overrides & Exceptions</a> - Handle known issues with automated rules</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='autotask', page='overview') }}">Autotask Integration</a> - Learn about PSA integration and ticket synchronization</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,337 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>Run Checks Modal</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Review detailed backup job run information, examine objects and errors, and mark runs as reviewed.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Overview</h2>
|
||||||
|
|
||||||
|
<p>The <strong>Run Checks</strong> page is the primary daily operational tool for reviewing backup job runs. This is where you spend most of your time during daily backup review.</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Your Daily Starting Point:</strong><br>
|
||||||
|
Start here every morning. The goal is to have this page <strong>completely empty</strong> - meaning all runs have been reviewed. Even successful backups must be marked as reviewed to clear the page.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>The page provides:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Job run list:</strong> All unreviewed runs across all customers, showing status and timestamps</li>
|
||||||
|
<li><strong>Backup objects:</strong> Individual items backed up (VMs, servers, files) with their statuses</li>
|
||||||
|
<li><strong>Email content:</strong> Original email body from the backup software</li>
|
||||||
|
<li><strong>Override information:</strong> Details about applied overrides (shown with blue badge)</li>
|
||||||
|
<li><strong>Autotask ticket info:</strong> Linked PSA ticket details (if Autotask integration is enabled)</li>
|
||||||
|
<li><strong>Review actions:</strong> Mark runs as reviewed (single or bulk), unmark reviewed runs (Admin only)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Accessing Run Checks</h2>
|
||||||
|
|
||||||
|
<p>To access the Run Checks page:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to <strong>Run Checks</strong> in the main navigation menu</li>
|
||||||
|
<li>Available to <strong>Admin</strong> and <strong>Operator</strong> roles only</li>
|
||||||
|
<li>Viewers cannot access the Run Checks page</li>
|
||||||
|
<li>The page shows all unreviewed runs by default</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Start Here Every Day:</strong><br>
|
||||||
|
This is your primary daily workflow page. Open it first thing in the morning and work through all runs until the page is empty.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Page Layout</h2>
|
||||||
|
|
||||||
|
<p>The Run Checks page is organized by job, with each job showing its unreviewed runs:</p>
|
||||||
|
|
||||||
|
<h3>1. Job List</h3>
|
||||||
|
|
||||||
|
<p>The page displays jobs that have unreviewed runs:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Customer name</strong></li>
|
||||||
|
<li><strong>Backup software</strong> (e.g., Veeam, Synology)</li>
|
||||||
|
<li><strong>Backup type</strong> (e.g., Backup Job, Replication Job)</li>
|
||||||
|
<li><strong>Job name</strong></li>
|
||||||
|
<li><strong>Number of unreviewed runs</strong> for this job</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>2. Run Details per Job</h3>
|
||||||
|
|
||||||
|
<p>When you click on a job, you see all its unreviewed runs:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Run timestamp:</strong> When the backup job ran</li>
|
||||||
|
<li><strong>Status badge:</strong> Success, Warning, Failed, or Missed</li>
|
||||||
|
<li><strong>Backup objects:</strong> Individual items backed up with their statuses</li>
|
||||||
|
<li><strong>Email content:</strong> Original email body from backup software</li>
|
||||||
|
<li>Click through multiple runs for the same job to review each one</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Review by Job:</strong><br>
|
||||||
|
Runs are grouped by job. When you mark a job as reviewed, <strong>all runs for that job</strong> are marked as reviewed simultaneously. The job then disappears from the Run Checks page. By default, only unreviewed jobs are shown. Admins can toggle "Include reviewed" to see all jobs including those already reviewed.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Run Details Sections</h2>
|
||||||
|
|
||||||
|
<h3>Status Information</h3>
|
||||||
|
|
||||||
|
<p>At the top of the run details:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Status badge:</strong> Overall run status with color coding:
|
||||||
|
<ul>
|
||||||
|
<li>Green = Success (no issues)</li>
|
||||||
|
<li>Blue = Success (override applied - originally warning/failed)</li>
|
||||||
|
<li>Yellow = Warning</li>
|
||||||
|
<li>Red = Failed</li>
|
||||||
|
<li>Gray = Missed</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li><strong>Run timestamp:</strong> When the backup job executed</li>
|
||||||
|
<li><strong>Override indicator:</strong> If an override is applied, shows blue badge with override reason</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Backup Objects</h3>
|
||||||
|
|
||||||
|
<p>A table showing individual backup items (VMs, servers, files, etc.):</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Column</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Name</strong></td>
|
||||||
|
<td>Object name (e.g., VM name, server name, file path)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Type</strong></td>
|
||||||
|
<td>Object type (e.g., VM, Server, Repository) - if available from parser</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Status</strong></td>
|
||||||
|
<td>Object-level status (Success, Warning, Failed)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Message</strong></td>
|
||||||
|
<td>Error or warning message for this object (if any)</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Object Availability:</strong><br>
|
||||||
|
Backup objects are only shown if the parser extracted them from the email. Not all backup software emails include object-level details. If no objects are shown, the parser didn't detect individual items in the email.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Email Content</h3>
|
||||||
|
|
||||||
|
<p>The original email body from the backup software is displayed in an embedded iframe:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>HTML emails are rendered with their original formatting</li>
|
||||||
|
<li>Plain text emails are shown as preformatted text</li>
|
||||||
|
<li>This allows you to see the full backup report as sent by your backup software</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h4>Email Metadata</h4>
|
||||||
|
|
||||||
|
<p>Above the email body, metadata is shown:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>From:</strong> Sender email address</li>
|
||||||
|
<li><strong>Subject:</strong> Email subject line</li>
|
||||||
|
<li><strong>Received at:</strong> When the email was received</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h4>Download .EML</h4>
|
||||||
|
|
||||||
|
<p>If EML storage is enabled, a download button allows you to save the original email file for external analysis or support requests.</p>
|
||||||
|
|
||||||
|
<h3>Autotask Ticket Information</h3>
|
||||||
|
|
||||||
|
<p>If Autotask integration is enabled and a ticket was created for this run, the modal shows:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Ticket number:</strong> Autotask ticket ID (clickable link to open in Autotask)</li>
|
||||||
|
<li><strong>Ticket status:</strong> Whether the ticket is active or resolved</li>
|
||||||
|
<li><strong>Resolved origin:</strong> How the ticket was resolved (manual, PSA-driven, PSA-deleted)</li>
|
||||||
|
<li><strong>Resolved at:</strong> Timestamp when the ticket was resolved</li>
|
||||||
|
<li><strong>Deleted info:</strong> If the ticket was deleted in Autotask, shows who deleted it and when</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Autotask Integration:</strong><br>
|
||||||
|
Autotask ticket information only appears if the Autotask integration is enabled in Settings and a ticket was created for this run. See <a href="{{ url_for('documentation.page', section='autotask', page='overview') }}">Autotask Integration</a> for details.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Review Actions</h2>
|
||||||
|
|
||||||
|
<h3>Mark as Reviewed (Single)</h3>
|
||||||
|
|
||||||
|
<p>For any run (success, warning, or failure):</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Click on a run in the list to view details</li>
|
||||||
|
<li>Review the run details, backup objects, and any error messages</li>
|
||||||
|
<li>Take action if needed (create ticket, remark, or override)</li>
|
||||||
|
<li>Click the <strong>Mark as reviewed</strong> button</li>
|
||||||
|
<li>All runs for this job are immediately marked as reviewed and the job disappears from the Run Checks page</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Mark as Reviewed (Bulk)</h3>
|
||||||
|
|
||||||
|
<p>For efficiency, especially with successful backups:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Select multiple job checkboxes in the Run Checks list</li>
|
||||||
|
<li>Click <strong>Mark selected as reviewed</strong> button</li>
|
||||||
|
<li>All runs for all selected jobs are marked as reviewed and disappear from the Run Checks page</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>This is particularly useful for quickly clearing successful backups that don't require investigation.</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-warning">
|
||||||
|
<strong>⚠️ All Runs Must Be Reviewed:</strong><br>
|
||||||
|
Even successful backup runs must be marked as reviewed to clear the Run Checks page. They're not individually <em>investigated</em>, but they still need to be marked so the page becomes empty. Use bulk review to quickly clear multiple successful runs at once.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Unmark Reviewed (Admin Only)</h3>
|
||||||
|
|
||||||
|
<p>Admins can unmark a reviewed job if it needs to be re-investigated:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Enable the "Include reviewed" toggle to show reviewed jobs</li>
|
||||||
|
<li>Select a reviewed job from the list</li>
|
||||||
|
<li>Click the <strong>Unmark reviewed</strong> button</li>
|
||||||
|
<li>All runs for this job return to the unreviewed list</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>This is useful if new information about an issue comes to light after the job was marked as reviewed.</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-warning">
|
||||||
|
<strong>⚠️ Admin-Only Feature:</strong><br>
|
||||||
|
Only Admins can unmark reviewed runs. Operators can only mark runs as reviewed, not unmark them.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Linking Tickets and Remarks</h2>
|
||||||
|
|
||||||
|
<p>From the Run Checks modal, you can create and link tickets or remarks to document issues:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Create Ticket:</strong> For issues that require external followup (e.g., customer action, vendor support)</li>
|
||||||
|
<li><strong>Create Remark:</strong> For internal notes or known issues that don't need ticket tracking</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>See <a href="{{ url_for('documentation.page', section='backup-review', page='remarks-tickets') }}">Remarks & Tickets</a> for detailed instructions.</p>
|
||||||
|
|
||||||
|
<h2>Common Workflows</h2>
|
||||||
|
|
||||||
|
<h3>Investigating a Failed Backup</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Open the Run Checks modal from Daily Jobs (click on the failed job row)</li>
|
||||||
|
<li>The most recent run is automatically selected (the failure)</li>
|
||||||
|
<li>Review the <strong>Backup Objects</strong> table to see which items failed</li>
|
||||||
|
<li>Check the <strong>Message</strong> column for error details</li>
|
||||||
|
<li>Scroll down to view the full email body for additional context</li>
|
||||||
|
<li>If the issue requires followup, create a ticket</li>
|
||||||
|
<li>If it's a known issue or no action is needed, mark the run as reviewed</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Comparing Multiple Runs</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Open the Run Checks modal for a job with warnings</li>
|
||||||
|
<li>Enable "Include reviewed" (Admin only) to see historical runs</li>
|
||||||
|
<li>Click through runs in the left-side list</li>
|
||||||
|
<li>Compare which objects failed/succeeded across different runs</li>
|
||||||
|
<li>Identify patterns (e.g., same VM fails every time vs. intermittent failures)</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Reviewing Warnings with Overrides</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Open a job with a warning badge from Daily Jobs</li>
|
||||||
|
<li>Notice the override indicator showing "Treated as success: [reason]"</li>
|
||||||
|
<li>Review the backup objects to understand why the override was created</li>
|
||||||
|
<li>If the override is still appropriate, mark the run as reviewed</li>
|
||||||
|
<li>If the override is no longer appropriate, edit or remove it on the Overrides page</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Troubleshooting Missed Jobs</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Open a job with "Missed" status from Daily Jobs</li>
|
||||||
|
<li>The Run Checks modal shows recent runs</li>
|
||||||
|
<li>Check the timestamps to confirm the job hasn't run today</li>
|
||||||
|
<li>Look for patterns in historical runs (e.g., consistently misses Mondays)</li>
|
||||||
|
<li>Create a ticket to investigate with the customer</li>
|
||||||
|
<li>Mark the missed run as reviewed after creating the ticket</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2>Best Practices</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Always check objects:</strong> Don't rely only on the overall status - individual objects may have failures hidden by retry logic</li>
|
||||||
|
<li><strong>Read error messages carefully:</strong> The Message column often contains specific details about why a backup failed</li>
|
||||||
|
<li><strong>Review the full email body:</strong> Backup software often includes important details in the email that aren't captured in structured fields</li>
|
||||||
|
<li><strong>Create tickets for recurring issues:</strong> If the same job fails repeatedly, create a ticket instead of marking each run as reviewed</li>
|
||||||
|
<li><strong>Use remarks for temporary issues:</strong> If a failure was caused by a known temporary issue (maintenance window, planned downtime), add a remark instead of creating a ticket</li>
|
||||||
|
<li><strong>Compare historical runs:</strong> Use the run list to identify whether an issue is new or recurring</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Troubleshooting</h2>
|
||||||
|
|
||||||
|
<h3>No Backup Objects Shown</h3>
|
||||||
|
|
||||||
|
<p>If the Backup Objects section is empty:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>The parser may not support object-level extraction for this backup software</li>
|
||||||
|
<li>The email may not contain structured object information</li>
|
||||||
|
<li>Check the email body (scroll down) to see if object details are present in the raw email</li>
|
||||||
|
<li>This is normal for some backup software - not all products include object-level details in emails</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Email Body Shows "No message content stored"</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>The email may have had blank HTML and text bodies when imported</li>
|
||||||
|
<li>Check if EML storage is enabled - without it, email content can't be retrieved</li>
|
||||||
|
<li>This can happen if the email was forwarded or reformatted before import</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Can't Mark Run as Reviewed</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Verify you have <strong>Admin</strong> or <strong>Operator</strong> role</li>
|
||||||
|
<li>If the button appears grayed out, refresh the page - the run may have already been reviewed</li>
|
||||||
|
<li>Check if you have permission to mark runs as reviewed (Viewers cannot perform this action)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Run List Is Empty</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>By default, only unreviewed jobs are shown</li>
|
||||||
|
<li>Enable "Include reviewed" toggle (Admin only) to see all jobs including those already reviewed</li>
|
||||||
|
<li>If no jobs appear even with "Include reviewed" enabled, there are no recorded runs for any jobs - check the Inbox to see if emails need to be approved</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Next Steps</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='backup-review', page='daily-jobs') }}">Daily Jobs View</a> - Learn about the daily monitoring dashboard</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='backup-review', page='overrides') }}">Overrides & Exceptions</a> - Configure rules for handling known issues</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='backup-review', page='remarks-tickets') }}">Remarks & Tickets</a> - Track issues and followup work</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
{% extends "layout/base.html" %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/documentation.css') }}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main_class %}container-fluid content-container{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="documentation-container">
|
||||||
|
<div class="row g-0">
|
||||||
|
<!-- Sidebar Navigation -->
|
||||||
|
<div class="col-12 col-lg-3 doc-sidebar-wrapper">
|
||||||
|
{% include 'documentation/_navigation.html' %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content Area -->
|
||||||
|
<div class="col-12 col-lg-9 doc-content-wrapper">
|
||||||
|
<div class="doc-content">
|
||||||
|
<!-- Breadcrumb -->
|
||||||
|
<nav aria-label="breadcrumb" class="mb-4">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item">
|
||||||
|
<a href="{{ url_for('documentation.index') }}">Documentation</a>
|
||||||
|
</li>
|
||||||
|
<li class="breadcrumb-item">{{ section_title }}</li>
|
||||||
|
<li class="breadcrumb-item active" aria-current="page">{{ page_title }}</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Page Content -->
|
||||||
|
{% block doc_content %}{% endblock %}
|
||||||
|
|
||||||
|
<!-- Previous/Next Navigation -->
|
||||||
|
<div class="doc-pagination mt-5 pt-4 border-top">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-6">
|
||||||
|
{% if prev_page %}
|
||||||
|
<a href="{{ url_for('documentation.page', section=prev_page.section, page=prev_page.slug) }}"
|
||||||
|
class="btn btn-outline-secondary">
|
||||||
|
← {{ prev_page.title }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col-6 text-end">
|
||||||
|
{% if next_page %}
|
||||||
|
<a href="{{ url_for('documentation.page', section=next_page.section, page=next_page.slug) }}"
|
||||||
|
class="btn btn-outline-primary">
|
||||||
|
{{ next_page.title }} →
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,301 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>Approved Jobs</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
View and manage all approved backup jobs across all customers.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Overview</h2>
|
||||||
|
|
||||||
|
<p>The Jobs page provides a comprehensive view of all approved backup jobs in BackupChecks. Once a job has been approved from the Inbox, it appears here and begins receiving backup reports automatically.</p>
|
||||||
|
|
||||||
|
<p>This page allows you to:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>View all approved jobs organized by customer</li>
|
||||||
|
<li>See job configuration and status at a glance</li>
|
||||||
|
<li>Access job details and run history</li>
|
||||||
|
<li>Archive or delete jobs</li>
|
||||||
|
<li>Export and import job data</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Accessing the Jobs Page</h2>
|
||||||
|
|
||||||
|
<p>To view approved jobs:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to <strong>Jobs</strong> in the main navigation menu</li>
|
||||||
|
<li>This page is available to <strong>Admin</strong>, <strong>Operator</strong>, and <strong>Viewer</strong> roles</li>
|
||||||
|
<li>Reporters do not have access to operational features like the Jobs page</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2>Jobs List</h2>
|
||||||
|
|
||||||
|
<p>The Jobs page displays a table with the following columns:</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Column</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Customer</strong></td>
|
||||||
|
<td>Customer name (jobs are grouped by customer)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Backup</strong></td>
|
||||||
|
<td>Detected backup software (e.g., Veeam, Synology, Acronis)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Type</strong></td>
|
||||||
|
<td>Backup type (e.g., Configuration Job, Hyperbackup, Full, Incremental)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Job name</strong></td>
|
||||||
|
<td>Backup job name</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Active Jobs Only:</strong><br>
|
||||||
|
The Jobs page shows only <strong>active jobs</strong> for <strong>active customers</strong>. Archived jobs and jobs belonging to inactive customers are not shown on this page. Archived jobs are visible on a separate page accessible only to administrators.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Viewing Job Details</h2>
|
||||||
|
|
||||||
|
<p>To view detailed information about a specific job:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to the <strong>Jobs</strong> page</li>
|
||||||
|
<li>Find the job in the list</li>
|
||||||
|
<li>Click anywhere on the job row (not just the job name)</li>
|
||||||
|
<li>The job details page will open</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src="{{ url_for('static', filename='images/documentation/job-details.png') }}"
|
||||||
|
alt="Job Details Page" />
|
||||||
|
<figcaption>Figure 1: Job details page showing job information, learned schedule, and job history</figcaption>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
<p>The job detail page shows:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Job Information:</strong>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Customer:</strong> Customer name</li>
|
||||||
|
<li><strong>Backup:</strong> Backup software (e.g., Synology, Veeam)</li>
|
||||||
|
<li><strong>Type:</strong> Backup type (e.g., Hyperbackup, Configuration Job)</li>
|
||||||
|
<li><strong>Job name:</strong> Name of the backup job</li>
|
||||||
|
<li><strong>Tickets:</strong> Open and total ticket count (e.g., "0 open / 1 total")</li>
|
||||||
|
<li><strong>Remarks:</strong> Open and total remark count (e.g., "0 open / 0 total")</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li><strong>Schedule (inferred):</strong>
|
||||||
|
<ul>
|
||||||
|
<li>Table showing expected run days and times in 15-minute blocks</li>
|
||||||
|
<li>Example: Mon-Sun with 20:00 time slots</li>
|
||||||
|
<li>Only shown if schedule has been learned</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li><strong>Action Buttons:</strong>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Archive:</strong> Archive the job (gray button)</li>
|
||||||
|
<li><strong>Delete job:</strong> Permanently delete the job (red button)</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li><strong>Job History:</strong>
|
||||||
|
<ul>
|
||||||
|
<li>Table of recent backup runs with columns:
|
||||||
|
<ul>
|
||||||
|
<li><strong>Day run:</strong> Day of the week</li>
|
||||||
|
<li><strong>Run time:</strong> Date and time of the backup</li>
|
||||||
|
<li><strong>Status:</strong> Success/Failed indicator (green dot for success)</li>
|
||||||
|
<li><strong>Tickets:</strong> Linked ticket numbers (if any)</li>
|
||||||
|
<li><strong>Remarks:</strong> Associated remarks</li>
|
||||||
|
<li><strong>Override:</strong> Override status</li>
|
||||||
|
<li><strong>Reviewed by:</strong> Username who reviewed the run (Admin only)</li>
|
||||||
|
<li><strong>Reviewed at:</strong> Date and time of review (Admin only)</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>Runs are clickable to view full run details</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Admin-Only Columns:</strong><br>
|
||||||
|
The <strong>Reviewed by</strong> and <strong>Reviewed at</strong> columns in the Job History table are currently only visible to administrators. This may change in a future update to allow Operators to see who reviewed runs and when.
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Job Detail Page</h2>
|
||||||
|
|
||||||
|
<p>On the job detail page, you can perform the following actions:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Archive:</strong> Hide the job from operational views while preserving historical data</li>
|
||||||
|
<li><strong>Delete:</strong> Permanently remove the job (emails return to Inbox for re-assignment)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-warning">
|
||||||
|
<strong>⚠️ No Direct Editing:</strong><br>
|
||||||
|
You <strong>cannot edit</strong> job properties like customer assignment, job name, or parser-determined fields (backup software, backup type, sender pattern, subject pattern) through the job detail page. All job configuration is determined when the job is approved from the Inbox.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Archiving Jobs</h2>
|
||||||
|
|
||||||
|
<p>When a backup job is no longer active (e.g., server decommissioned, backup solution changed), you can archive it to declutter operational views.</p>
|
||||||
|
|
||||||
|
<h3>How to Archive a Job</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to the <strong>Jobs</strong> page</li>
|
||||||
|
<li>Find the job you want to archive</li>
|
||||||
|
<li>Click the <strong>Archive</strong> button</li>
|
||||||
|
<li>Confirm the archival</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Effects of Archiving</h3>
|
||||||
|
|
||||||
|
<p>When a job is archived:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>It is <strong>hidden</strong> from the default Jobs list</li>
|
||||||
|
<li>It does <strong>not appear</strong> in Daily Jobs</li>
|
||||||
|
<li>Existing runs do <strong>not appear</strong> in Run Checks</li>
|
||||||
|
<li>All historical data is <strong>preserved</strong> (runs, tickets, remarks)</li>
|
||||||
|
<li>The job can be <strong>unarchived</strong> later if needed</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-tip">
|
||||||
|
<strong>💡 When to Archive:</strong><br>
|
||||||
|
Archive jobs when:
|
||||||
|
<ul>
|
||||||
|
<li>A server has been decommissioned</li>
|
||||||
|
<li>A backup solution has been replaced</li>
|
||||||
|
<li>A customer no longer uses a particular backup job</li>
|
||||||
|
<li>You want to declutter operational views without losing historical data</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Viewing Archived Jobs</h3>
|
||||||
|
|
||||||
|
<p>Archived jobs are accessible on a separate page:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to the <strong>Archived Jobs</strong> page in the main menu</li>
|
||||||
|
<li>This page is <strong>only accessible to administrators</strong></li>
|
||||||
|
<li>The page displays all archived jobs with their details</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Admin Only Access:</strong><br>
|
||||||
|
Only users with the <strong>Admin</strong> role can view the Archived Jobs page. Operators and other roles cannot access archived jobs.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Unarchiving a Job</h3>
|
||||||
|
|
||||||
|
<p>To unarchive a job (Admin only):</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to the <strong>Archived Jobs</strong> page</li>
|
||||||
|
<li>Find the archived job in the list</li>
|
||||||
|
<li>Click the <strong>Unarchive</strong> button</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>The job will immediately reappear in the Jobs page and operational views (Daily Jobs, Run Checks).</p>
|
||||||
|
|
||||||
|
<h2>Deleting Jobs</h2>
|
||||||
|
|
||||||
|
<p>If you need to permanently remove a job and all its data:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to the <strong>Jobs</strong> page</li>
|
||||||
|
<li>Find the job you want to delete</li>
|
||||||
|
<li>Click the <strong>Delete</strong> button</li>
|
||||||
|
<li>Confirm the deletion in the dialog</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-danger">
|
||||||
|
<strong>⚠️ Warning - Permanent Deletion:</strong><br>
|
||||||
|
Deleting a job is <strong>permanent</strong> and will delete:
|
||||||
|
<ul>
|
||||||
|
<li>The job configuration</li>
|
||||||
|
<li>All backup runs</li>
|
||||||
|
<li>All tickets linked to this job</li>
|
||||||
|
<li>All remarks linked to this job</li>
|
||||||
|
<li>All historical data</li>
|
||||||
|
</ul>
|
||||||
|
This action <strong>cannot be undone</strong>. Consider archiving the job instead if you might need the data later.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Exporting Job Data</h2>
|
||||||
|
|
||||||
|
<p>You can export all job data to a JSON file for backup, reporting, or migration purposes.</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to <strong>Settings</strong> → <strong>Maintenance</strong></li>
|
||||||
|
<li>In the "Export/Import" section, click <strong>Export Jobs</strong></li>
|
||||||
|
<li>A JSON file will be downloaded containing:
|
||||||
|
<ul>
|
||||||
|
<li>Customer information (including Autotask mappings)</li>
|
||||||
|
<li>All approved jobs with their configurations</li>
|
||||||
|
<li>Email matching patterns</li>
|
||||||
|
<li>Learned schedules</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Export Format:</strong><br>
|
||||||
|
The export uses JSON format (schema version: <code>approved_jobs_export_v1</code>). This format is designed for system migration and backup purposes.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Importing Job Data</h2>
|
||||||
|
|
||||||
|
<p>You can import job data from a previously exported JSON file.</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to <strong>Settings</strong> → <strong>Maintenance</strong></li>
|
||||||
|
<li>In the "Export/Import" section, click <strong>Import Jobs</strong></li>
|
||||||
|
<li>Select your JSON file (must match the export format)</li>
|
||||||
|
<li>Click <strong>Upload</strong></li>
|
||||||
|
<li>The system will:
|
||||||
|
<ul>
|
||||||
|
<li>Create or update customers</li>
|
||||||
|
<li>Apply Autotask company mappings (if applicable)</li>
|
||||||
|
<li>Create jobs with their configurations</li>
|
||||||
|
<li>Apply learned schedules</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>Review the import summary showing created and updated counts</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-warning">
|
||||||
|
<strong>⚠️ Import Behavior:</strong><br>
|
||||||
|
The import process will:
|
||||||
|
<ul>
|
||||||
|
<li>Create new customers and jobs if they don't exist</li>
|
||||||
|
<li>Update existing customers if they already exist (matched by name)</li>
|
||||||
|
<li>Preserve existing job data and merge with imported data</li>
|
||||||
|
</ul>
|
||||||
|
Ensure your JSON data is correct before importing.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Next Steps</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='customers-jobs', page='job-schedules') }}">Job Schedules</a> - Learn how BackupChecks learns and displays job schedules</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='backup-review', page='daily-jobs') }}">Daily Jobs</a> - Monitor jobs expected to run today</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='backup-review', page='run-checks') }}">Run Checks</a> - Review individual backup runs</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='customers-jobs', page='managing-customers') }}">Managing Customers</a> - Customer management guide</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,393 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>Configuring Jobs</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Understand how backup jobs are created and configured in BackupChecks through the Inbox approval workflow.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Overview</h2>
|
||||||
|
|
||||||
|
<p>Unlike traditional systems where you manually configure backup jobs, BackupChecks uses an <strong>Inbox-based approval workflow</strong>. Jobs are created by approving backup report emails, and all job configuration is automatically determined by Mail Parsers.</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Key Concept:</strong><br>
|
||||||
|
You <strong>cannot manually create or configure</strong> backup jobs in BackupChecks. Instead, jobs are created by approving emails from the Inbox. This ensures consistency and prevents configuration errors.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>How Jobs Are Created</h2>
|
||||||
|
|
||||||
|
<p>The job creation process follows these steps:</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Step</th>
|
||||||
|
<th>What Happens</th>
|
||||||
|
<th>Where</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>1</td>
|
||||||
|
<td>Backup report email arrives in monitored mailbox</td>
|
||||||
|
<td>Email system</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>2</td>
|
||||||
|
<td>Mail import fetches the email via Microsoft Graph API</td>
|
||||||
|
<td>Background process</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>3</td>
|
||||||
|
<td>Email appears in Inbox (no matching job exists yet)</td>
|
||||||
|
<td><strong>Inbox</strong> page</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>4</td>
|
||||||
|
<td>Operator selects customer and clicks "Approve job"</td>
|
||||||
|
<td><strong>Inbox</strong> page</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>5</td>
|
||||||
|
<td>Mail Parser extracts job configuration from email</td>
|
||||||
|
<td>Background process</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>6</td>
|
||||||
|
<td>Job is created with parser-determined configuration</td>
|
||||||
|
<td><strong>Jobs</strong> page</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>7</td>
|
||||||
|
<td>Email is linked to job and removed from Inbox</td>
|
||||||
|
<td><strong>Run Checks</strong> page</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>Approving a Job from the Inbox</h2>
|
||||||
|
|
||||||
|
<p>To create a new backup job:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to <strong>Inbox</strong> in the main menu</li>
|
||||||
|
<li>Find a backup report email in the inbox list</li>
|
||||||
|
<li>Click on the email to view its details in a modal dialog</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src="{{ url_for('static', filename='images/documentation/approve-job.png') }}"
|
||||||
|
alt="Inbox Email Detail - Approve Job" />
|
||||||
|
<figcaption>Figure 1: Inbox email detail showing backup report information and customer selection field</figcaption>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
<h3>Selecting a Customer</h3>
|
||||||
|
|
||||||
|
<p>In the email detail modal, you'll see a <strong>Customer</strong> field with "Select customer" placeholder text. This field has autocomplete functionality:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Click on the <strong>Customer</strong> field</li>
|
||||||
|
<li>Start typing part of the customer name (you can type any part, not just the beginning)
|
||||||
|
<ul>
|
||||||
|
<li>Example: typing "toso" will find "Contoso"</li>
|
||||||
|
<li>The autocomplete searches across the entire customer name</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>Select the correct customer from the dropdown suggestions</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-warning">
|
||||||
|
<strong>⚠️ Customer Must Exist First:</strong><br>
|
||||||
|
You <strong>cannot create new customers</strong> from the Inbox. Customers must be created on the <strong>Customers</strong> page before approving jobs. If the customer doesn't exist yet, cancel the dialog, create the customer first, then return to approve the job.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Approving the Job</h3>
|
||||||
|
|
||||||
|
<p>Once you've selected a customer:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Click the <strong>Approve job</strong> button (blue button at the bottom left)</li>
|
||||||
|
<li>The system will:
|
||||||
|
<ul>
|
||||||
|
<li>Create a new backup job linked to the selected customer</li>
|
||||||
|
<li>Apply the Mail Parser's configuration automatically</li>
|
||||||
|
<li>Link the email to the job as the first backup run</li>
|
||||||
|
<li>Remove the email from the Inbox</li>
|
||||||
|
<li>Make the run available in Run Checks for review</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-tip">
|
||||||
|
<strong>💡 Workflow Best Practice:</strong><br>
|
||||||
|
The <strong>Approve job</strong> button is only enabled when a customer is selected. Create customers on the Customers page first, then approve jobs from the Inbox. This ensures proper customer naming and prevents duplicates.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Email Detail Information</h2>
|
||||||
|
|
||||||
|
<p>When you click on an email in the Inbox, a detail modal displays the parsed backup report information:</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Field</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>From</strong></td>
|
||||||
|
<td>Email sender address</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Backup</strong></td>
|
||||||
|
<td>Detected backup software (e.g., Veeam, Acronis)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Type</strong></td>
|
||||||
|
<td>Backup type (e.g., Configuration Job, Full, Incremental)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Job</strong></td>
|
||||||
|
<td>Parsed job name from the email</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Overall</strong></td>
|
||||||
|
<td>Backup result (Success, Failed, Warning)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Customer</strong></td>
|
||||||
|
<td>Customer selection field (autocomplete)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Received</strong></td>
|
||||||
|
<td>Date and time the email was received</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Parsed</strong></td>
|
||||||
|
<td>Date and time the email was parsed</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p>The <strong>Details</strong> panel on the right shows the parsed backup report content, including:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Success/failure status (green or red header)</li>
|
||||||
|
<li>Backup start time, end time, and duration</li>
|
||||||
|
<li>Data size, backup size, and compression ratio</li>
|
||||||
|
<li>Detailed catalog information (if available)</li>
|
||||||
|
<li>Version information (e.g., "Veeam Backup & Replication 12.3.2.4165")</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>What Mail Parsers Configure</h2>
|
||||||
|
|
||||||
|
<p>When you approve a job, the Mail Parser automatically determines the following job configuration:</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Configuration</th>
|
||||||
|
<th>How It's Determined</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Backup Software</strong></td>
|
||||||
|
<td>Detected from email subject, sender, or body patterns (e.g., Veeam, Acronis, Windows Server Backup)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Backup Type</strong></td>
|
||||||
|
<td>Extracted from email content (e.g., Full, Incremental, Differential)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Job Name</strong></td>
|
||||||
|
<td>Parsed from email subject or body</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Sender Email Pattern</strong></td>
|
||||||
|
<td>Email sender address (used to match future emails)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Subject Pattern</strong></td>
|
||||||
|
<td>Email subject pattern (used to match future emails)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Success/Failure Detection</strong></td>
|
||||||
|
<td>Parser rules for identifying successful vs. failed backups</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-warning">
|
||||||
|
<strong>⚠️ No Manual Configuration:</strong><br>
|
||||||
|
You <strong>cannot manually edit</strong> these job settings. All configuration is determined by the Mail Parser to ensure consistency. If a parser incorrectly identifies a field, contact the system developer/maintainer - parsers cannot be modified by administrators through the UI.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Processing Similar Emails (Reparse All)</h2>
|
||||||
|
|
||||||
|
<p>After approving your first job for a customer, you can automatically process other emails that match the same pattern:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>At the top of the <strong>Inbox</strong> page, click <strong>Reparse all</strong></li>
|
||||||
|
<li>The system will scan all inbox emails and:
|
||||||
|
<ul>
|
||||||
|
<li>Match emails to existing approved jobs based on sender and subject patterns</li>
|
||||||
|
<li>Link matched emails to the corresponding jobs</li>
|
||||||
|
<li>Remove matched emails from the inbox</li>
|
||||||
|
<li>Create backup runs in Run Checks for review</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-tip">
|
||||||
|
<strong>💡 Workflow Tip:</strong><br>
|
||||||
|
After approving a few jobs from the Inbox, click <strong>Reparse all</strong> to automatically process any historical emails that match those jobs. This saves time when onboarding a new customer with many existing backup reports.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Viewing Job Configuration</h2>
|
||||||
|
|
||||||
|
<p>To view a job's configuration after it has been created:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to <strong>Jobs</strong> in the main menu</li>
|
||||||
|
<li>Find the job in the list (organized by customer)</li>
|
||||||
|
<li>Click on the job name to view details</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>The job detail page shows:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Customer name</li>
|
||||||
|
<li>Job name</li>
|
||||||
|
<li>Backup software</li>
|
||||||
|
<li>Backup type</li>
|
||||||
|
<li>Email matching patterns (sender, subject)</li>
|
||||||
|
<li>Recent backup runs</li>
|
||||||
|
<li>Learned schedule (if available)</li>
|
||||||
|
<li>Active/archived status</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Archiving Jobs</h2>
|
||||||
|
|
||||||
|
<p>If a backup job is no longer active, you can archive it:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to <strong>Jobs</strong></li>
|
||||||
|
<li>Find the job you want to archive</li>
|
||||||
|
<li>Click the <strong>Archive</strong> button</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>Archived jobs:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Are <strong>hidden</strong> from Daily Jobs, Run Checks, and the default Jobs list</li>
|
||||||
|
<li>Do not appear in operational views</li>
|
||||||
|
<li>Can be viewed by clicking "Show archived jobs" on the Jobs page</li>
|
||||||
|
<li>Can be unarchived if needed</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Archive vs. Delete:</strong><br>
|
||||||
|
Archiving a job hides it from operational views but preserves all historical data. This is useful for jobs that are no longer running but whose history you want to retain. Deleting a job is permanent and removes all associated data.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Deleting Jobs</h2>
|
||||||
|
|
||||||
|
<p>To permanently delete a backup job:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to <strong>Jobs</strong></li>
|
||||||
|
<li>Find the job you want to delete</li>
|
||||||
|
<li>Click the <strong>Delete</strong> button</li>
|
||||||
|
<li>Confirm the deletion</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-danger">
|
||||||
|
<strong>⚠️ Warning - Permanent Deletion:</strong><br>
|
||||||
|
Deleting a job is <strong>permanent</strong> and will delete all associated backup runs, tickets, remarks, and historical data. This action cannot be undone. Consider archiving the job instead if you might need the data later.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Job Lifecycle</h2>
|
||||||
|
|
||||||
|
<p>A typical backup job goes through the following lifecycle:</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Stage</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th>Visibility</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>1. Email in Inbox</strong></td>
|
||||||
|
<td>Backup report arrives, no job exists yet</td>
|
||||||
|
<td>Inbox page</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>2. Job Approved</strong></td>
|
||||||
|
<td>Operator approves job from Inbox</td>
|
||||||
|
<td>Jobs page, Run Checks</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>3. Active Job</strong></td>
|
||||||
|
<td>Job receives regular backup reports</td>
|
||||||
|
<td>Daily Jobs, Run Checks, Jobs page</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>4. Schedule Learned</strong></td>
|
||||||
|
<td>System learns backup schedule after several runs</td>
|
||||||
|
<td>Daily Jobs (appears on expected days)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>5. Job Archived</strong></td>
|
||||||
|
<td>Backup no longer runs, job archived for history</td>
|
||||||
|
<td>Hidden from operational views</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>6. Job Deleted</strong></td>
|
||||||
|
<td>Job and all data permanently removed</td>
|
||||||
|
<td>Completely removed</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>Common Questions</h2>
|
||||||
|
|
||||||
|
<h3>Can I manually create a job without an email?</h3>
|
||||||
|
|
||||||
|
<p><strong>No.</strong> Jobs can only be created by approving emails from the Inbox. This ensures that every job has a valid Mail Parser configuration and prevents misconfiguration.</p>
|
||||||
|
|
||||||
|
<h3>Can I edit a job's sender or subject pattern?</h3>
|
||||||
|
|
||||||
|
<p><strong>No.</strong> Job configuration is determined by Mail Parsers and cannot be manually edited. If a parser is incorrectly identifying fields, contact the system developer/maintainer. Mail Parsers are code-level components and cannot be modified by administrators through the BackupChecks interface.</p>
|
||||||
|
|
||||||
|
<h3>What if multiple jobs match the same email?</h3>
|
||||||
|
|
||||||
|
<p>BackupChecks uses sender and subject patterns to uniquely identify jobs. If multiple jobs could match the same email pattern, the system will match to the first job created. Ensure each backup job sends reports with unique sender or subject patterns.</p>
|
||||||
|
|
||||||
|
<h3>Can I change the customer a job is linked to?</h3>
|
||||||
|
|
||||||
|
<p><strong>No, not directly.</strong> If a job was approved with the wrong customer, you need to:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to the job detail page</li>
|
||||||
|
<li><strong>Delete</strong> the job</li>
|
||||||
|
<li>The associated emails will return to the <strong>Inbox</strong></li>
|
||||||
|
<li>Re-approve the job from the Inbox and select the correct customer</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>Deleting a job returns its emails to the Inbox, allowing you to re-link them to the correct customer.</p>
|
||||||
|
|
||||||
|
<h2>Next Steps</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='customers-jobs', page='approved-jobs') }}">Approved Jobs</a> - View and manage all approved backup jobs</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='customers-jobs', page='job-schedules') }}">Job Schedules</a> - Understand how BackupChecks learns job schedules</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='mail-import', page='inbox-management') }}">Inbox Management</a> - Detailed guide to the Inbox workflow</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='mail-import', page='mail-parsing') }}">Mail Parsing</a> - Learn how Mail Parsers work</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,348 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>Job Schedules</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Understand how BackupChecks automatically learns backup job schedules and uses them to predict expected runs.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Overview</h2>
|
||||||
|
|
||||||
|
<p>BackupChecks does <strong>not</strong> require you to manually configure backup schedules. Instead, the system <strong>automatically learns</strong> when backups are expected to run by analyzing historical backup run patterns.</p>
|
||||||
|
|
||||||
|
<p>This learned schedule information is used to:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Display jobs in <strong>Daily Jobs</strong> on days they are expected to run</li>
|
||||||
|
<li>Identify missing backups (job expected today but hasn't run)</li>
|
||||||
|
<li>Provide schedule visibility on the Jobs page</li>
|
||||||
|
<li>Help operators focus on jobs that should have run</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Key Concept:</strong><br>
|
||||||
|
Schedules are <strong>learned automatically</strong> by analyzing backup run history. You do not configure schedules manually. After a few backup runs, BackupChecks will infer the schedule pattern.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>How Schedule Learning Works</h2>
|
||||||
|
|
||||||
|
<p>BackupChecks uses historical backup run data to infer schedules through the following process:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li><strong>Data Collection:</strong> As backup reports arrive and create runs, the system records the date and time of each backup</li>
|
||||||
|
<li><strong>Pattern Analysis:</strong> After several runs, the system analyzes the dates to identify patterns (e.g., daily, specific weekdays, monthly)</li>
|
||||||
|
<li><strong>Schedule Inference:</strong> Once a pattern is detected, a schedule is assigned to the job</li>
|
||||||
|
<li><strong>Continuous Learning:</strong> The schedule is updated as more runs are received to maintain accuracy</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Minimum Data Required</h3>
|
||||||
|
|
||||||
|
<p>To learn a schedule, BackupChecks needs:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>At least 3-5 successful backup runs</strong> to establish a pattern</li>
|
||||||
|
<li>Runs should be <strong>spread across multiple days</strong> (not all on the same day)</li>
|
||||||
|
<li>Runs should follow a <strong>consistent pattern</strong> (e.g., always on Mondays and Thursdays)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-tip">
|
||||||
|
<strong>💡 Learning Time:</strong><br>
|
||||||
|
For a daily backup, the system can learn the schedule after 3-5 days of successful runs. For weekly backups, it may take 2-3 weeks to establish a reliable pattern.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Schedule Types</h2>
|
||||||
|
|
||||||
|
<p>BackupChecks can learn and display the following schedule types:</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Schedule Type</th>
|
||||||
|
<th>Pattern</th>
|
||||||
|
<th>Display Example</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Daily</strong></td>
|
||||||
|
<td>Runs every day</td>
|
||||||
|
<td>"Daily"</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Weekly</strong></td>
|
||||||
|
<td>Runs on specific days of the week</td>
|
||||||
|
<td>"Weekly: Mon, Wed, Fri"</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Monthly</strong></td>
|
||||||
|
<td>Runs on specific day(s) of the month</td>
|
||||||
|
<td>"Monthly: 1st, 15th"</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Irregular</strong></td>
|
||||||
|
<td>No consistent pattern detected</td>
|
||||||
|
<td>"Irregular" or no schedule shown</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>Viewing Job Schedules</h2>
|
||||||
|
|
||||||
|
<p>You can view a job's learned schedule in several places:</p>
|
||||||
|
|
||||||
|
<h3>1. Jobs Page</h3>
|
||||||
|
|
||||||
|
<p>The Jobs page displays the learned schedule in the "Schedule" column for each job.</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to <strong>Jobs</strong></li>
|
||||||
|
<li>Locate the job in the list</li>
|
||||||
|
<li>The "Schedule" column shows the learned pattern (e.g., "Daily", "Weekly: Mon, Wed, Fri")</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>2. Job Detail Page</h3>
|
||||||
|
|
||||||
|
<p>The job detail page shows more detailed schedule information:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to <strong>Jobs</strong></li>
|
||||||
|
<li>Click on a job name to open the detail page</li>
|
||||||
|
<li>The "Schedule Information" section shows:
|
||||||
|
<ul>
|
||||||
|
<li>Learned schedule type and pattern</li>
|
||||||
|
<li>Expected run days/times</li>
|
||||||
|
<li>Last run date</li>
|
||||||
|
<li>Next expected run (if predictable)</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>3. Daily Jobs Page</h3>
|
||||||
|
|
||||||
|
<p>Jobs with learned schedules appear on the Daily Jobs page on days they are expected to run.</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to <strong>Daily Jobs</strong></li>
|
||||||
|
<li>Jobs expected to run today are listed here</li>
|
||||||
|
<li>The schedule determines which jobs appear on this page</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Daily Jobs Behavior:</strong><br>
|
||||||
|
A job will <strong>only appear</strong> on the Daily Jobs page if:
|
||||||
|
<ul>
|
||||||
|
<li>The system has learned a schedule for the job</li>
|
||||||
|
<li>Today matches the learned schedule pattern</li>
|
||||||
|
<li>The job is active (not archived)</li>
|
||||||
|
<li>The customer is active</li>
|
||||||
|
</ul>
|
||||||
|
New jobs without learned schedules will not appear on Daily Jobs until a pattern is established.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Schedule Accuracy</h2>
|
||||||
|
|
||||||
|
<p>Schedule learning is based on pattern recognition and may not be 100% accurate in all cases:</p>
|
||||||
|
|
||||||
|
<h3>High Accuracy Scenarios</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Daily backups:</strong> Run every day at approximately the same time</li>
|
||||||
|
<li><strong>Weekly backups:</strong> Run on the same weekdays every week (e.g., Monday, Wednesday, Friday)</li>
|
||||||
|
<li><strong>Monthly backups:</strong> Run on the same day(s) of the month (e.g., 1st and 15th)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Lower Accuracy Scenarios</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Irregular backups:</strong> Manual backups with no consistent schedule</li>
|
||||||
|
<li><strong>Variable schedules:</strong> Backup days change frequently (e.g., "first weekday of the month")</li>
|
||||||
|
<li><strong>Recent changes:</strong> Backup schedule was recently changed and the system is still learning the new pattern</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-warning">
|
||||||
|
<strong>⚠️ Schedule Changes:</strong><br>
|
||||||
|
If a backup schedule changes (e.g., from daily to weekly), the learned schedule will <strong>gradually update</strong> as new runs are received. During this transition period, the displayed schedule may be inaccurate. Allow a few weeks for the system to relearn the new pattern.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>No Schedule Displayed</h2>
|
||||||
|
|
||||||
|
<p>If a job shows no schedule or "No schedule learned", it means:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>The job was recently approved and doesn't have enough run history yet</li>
|
||||||
|
<li>The job has an irregular pattern that the system cannot predict</li>
|
||||||
|
<li>The job hasn't received enough consistent backup runs to establish a pattern</li>
|
||||||
|
<li><strong>Information jobs:</strong> Jobs like "SSL certificate updated" or one-time maintenance tasks that run irregularly</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Information Jobs:</strong><br>
|
||||||
|
Some jobs are informational in nature (e.g., SSL certificate renewals, system updates) and do not run on a regular schedule. These jobs will never learn a schedule pattern and will not appear on the Daily Jobs page.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>In these cases:</p>
|
||||||
|
<ul>
|
||||||
|
<li>The job will <strong>not appear</strong> on the Daily Jobs page (since we don't know when it's expected)</li>
|
||||||
|
<li>Individual runs will still appear in <strong>Run Checks</strong> as they arrive</li>
|
||||||
|
<li>You can still view the job on the <strong>Jobs</strong> page</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Schedule Override and Customization</h2>
|
||||||
|
|
||||||
|
<p>BackupChecks does <strong>not</strong> currently support manual schedule configuration or overrides. All schedules are automatically learned from historical data.</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>📝 Future Feature:</strong><br>
|
||||||
|
Manual schedule configuration and schedule overrides may be added in a future version. For now, rely on the automatic learning system.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Using Schedules in Daily Jobs</h2>
|
||||||
|
|
||||||
|
<p>The Daily Jobs page uses learned schedules to show which jobs are expected to run today. Each scheduled time is displayed with a status indicator:</p>
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src="{{ url_for('static', filename='images/documentation/schedule-indicators.png') }}"
|
||||||
|
alt="Daily Jobs Schedule Indicators" />
|
||||||
|
<figcaption>Figure 1: Daily Jobs schedule showing time slots with run status indicators and run count badges</figcaption>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
<h3>Status Indicators</h3>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Indicator</th>
|
||||||
|
<th>Meaning</th>
|
||||||
|
<th>Action Needed</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Green dot</strong></td>
|
||||||
|
<td>Job ran successfully at this time</td>
|
||||||
|
<td>No action needed</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Red dot</strong></td>
|
||||||
|
<td>Job ran but failed at this time</td>
|
||||||
|
<td>Investigate failure, create ticket if needed</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>White dot</strong></td>
|
||||||
|
<td>Job expected but not yet run (time hasn't arrived yet)</td>
|
||||||
|
<td>Wait - job is not yet overdue</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Gray dot</strong></td>
|
||||||
|
<td>Job expected but overdue (time has passed, no run received)</td>
|
||||||
|
<td>Investigate - job may have failed to start</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3>Run Count Badges</h3>
|
||||||
|
|
||||||
|
<p>When a job has run, a badge appears next to the status indicator showing how many runs occurred:</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Badge</th>
|
||||||
|
<th>Color</th>
|
||||||
|
<th>Meaning</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>1 run</strong></td>
|
||||||
|
<td>Gray</td>
|
||||||
|
<td>Single backup run received for this time slot</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>3 runs</strong></td>
|
||||||
|
<td>Blue/Cyan</td>
|
||||||
|
<td>Multiple backup runs received for this time slot (stands out for attention)</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-tip">
|
||||||
|
<strong>💡 Workflow Tip:</strong><br>
|
||||||
|
Use the Daily Jobs page as your primary monitoring dashboard. Check it daily to ensure all expected backups have run successfully:
|
||||||
|
<ul>
|
||||||
|
<li><strong>White dots:</strong> Not yet due - wait</li>
|
||||||
|
<li><strong>Gray dots:</strong> Overdue - investigate immediately</li>
|
||||||
|
<li><strong>Green dots with badges:</strong> Completed successfully</li>
|
||||||
|
<li><strong>Blue badges (3+ runs):</strong> Multiple runs detected - verify this is expected</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Multiple Runs Per Time Slot</h3>
|
||||||
|
|
||||||
|
<p>Some jobs run multiple times at the same scheduled time (e.g., multiple backup sets, parallel jobs):</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Each time slot shows a green dot when runs are successful</li>
|
||||||
|
<li>The run count badge shows how many runs were received (e.g., "1 run", "3 runs")</li>
|
||||||
|
<li>Multiple runs (3+) are highlighted with a <strong>blue/cyan badge</strong> to stand out</li>
|
||||||
|
<li>This helps you quickly identify jobs with unusual activity</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Schedule Learning Best Practices</h2>
|
||||||
|
|
||||||
|
<p>To help BackupChecks learn accurate schedules:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Maintain Consistent Backup Schedules:</strong> Configure your backup software to run on consistent days/times</li>
|
||||||
|
<li><strong>Allow Learning Time:</strong> Give the system at least a week or two to learn new job patterns</li>
|
||||||
|
<li><strong>Monitor Daily Jobs:</strong> Use the Daily Jobs page to verify schedule accuracy</li>
|
||||||
|
<li><strong>Keep Jobs Active:</strong> Regular backup runs help maintain schedule accuracy</li>
|
||||||
|
<li><strong>Archive Inactive Jobs:</strong> Archive jobs that no longer run to avoid false "missing backup" alerts</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Troubleshooting Schedule Issues</h2>
|
||||||
|
|
||||||
|
<h3>Schedule Not Appearing</h3>
|
||||||
|
|
||||||
|
<p><strong>Possible causes:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>Not enough backup runs yet (need 3-5 successful runs)</li>
|
||||||
|
<li>Runs are too irregular to establish a pattern</li>
|
||||||
|
<li>All runs occurred on the same day (need runs spread across multiple days)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p><strong>Solution:</strong> Wait for more backup runs to accumulate. Ensure the backup software is running on a consistent schedule.</p>
|
||||||
|
|
||||||
|
<h3>Schedule Is Inaccurate</h3>
|
||||||
|
|
||||||
|
<p><strong>Possible causes:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>Backup schedule was recently changed</li>
|
||||||
|
<li>Backup runs have been irregular or skipped</li>
|
||||||
|
<li>Manual backups are interfering with schedule pattern</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p><strong>Solution:</strong> Allow time for the system to relearn the pattern based on recent runs. Ensure backups run consistently going forward.</p>
|
||||||
|
|
||||||
|
<h3>Job Not Appearing on Daily Jobs</h3>
|
||||||
|
|
||||||
|
<p><strong>Possible causes:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>No learned schedule exists for the job</li>
|
||||||
|
<li>Today doesn't match the learned schedule pattern</li>
|
||||||
|
<li>Job or customer is archived/inactive</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p><strong>Solution:</strong> Check the Jobs page to see if a schedule is learned. If not, wait for more runs to establish a pattern.</p>
|
||||||
|
|
||||||
|
<h2>Next Steps</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='backup-review', page='daily-jobs') }}">Daily Jobs</a> - Monitor jobs expected to run today</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='backup-review', page='run-checks') }}">Run Checks</a> - Review individual backup runs</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='customers-jobs', page='approved-jobs') }}">Approved Jobs</a> - View and manage all approved jobs</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='customers-jobs', page='configuring-jobs') }}">Configuring Jobs</a> - Learn how jobs are created</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,346 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>Managing Customers</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Learn how to create, edit, and manage customer accounts in BackupChecks.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Overview</h2>
|
||||||
|
|
||||||
|
<p>Customers are the organizations or clients whose backup jobs you monitor in BackupChecks. Each customer can have multiple backup jobs associated with them.</p>
|
||||||
|
|
||||||
|
<p>The Customers page provides a central location to:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>View all customers and their backup job counts</li>
|
||||||
|
<li>Create new customer accounts</li>
|
||||||
|
<li>Edit customer names and active status</li>
|
||||||
|
<li>Map customers to Autotask companies (if Autotask integration is enabled)</li>
|
||||||
|
<li>Activate or deactivate customer accounts</li>
|
||||||
|
<li>Export and import customer data via CSV</li>
|
||||||
|
<li>Delete customers</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Accessing the Customers Page</h2>
|
||||||
|
|
||||||
|
<p>To access customer management:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to <strong>Customers</strong> in the main navigation menu</li>
|
||||||
|
<li>This page is available to <strong>Admin</strong> and <strong>Operator</strong> roles</li>
|
||||||
|
<li>Viewers can see customers but cannot make changes</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2>Customer List</h2>
|
||||||
|
|
||||||
|
<p>The Customers page displays a table with the following columns:</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Column</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Name</strong></td>
|
||||||
|
<td>Customer name</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Jobs</strong></td>
|
||||||
|
<td>Number of backup jobs configured for this customer. Displays in <strong style="color: red;">red and bold</strong> if zero jobs are configured.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Autotask Company</strong></td>
|
||||||
|
<td>Linked Autotask company name (if Autotask integration is enabled and mapping exists)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Active</strong></td>
|
||||||
|
<td>Checkbox indicating whether the customer account is active</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Edit / Delete</strong></td>
|
||||||
|
<td>Edit and Delete buttons on the right side of each row</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Job Count Indicator:</strong><br>
|
||||||
|
If a customer shows <strong style="color: red;">0</strong> jobs in red and bold, it means no backup jobs have been approved for this customer yet. Jobs are created by approving emails from the Inbox.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Creating a New Customer</h2>
|
||||||
|
|
||||||
|
<p>The customer creation interface is located at the top of the Customers page.</p>
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src="{{ url_for('static', filename='images/documentation/new-customers.png') }}"
|
||||||
|
alt="New Customer Interface" />
|
||||||
|
<figcaption>Figure 1: New customer creation interface with CSV import/export and Autotask refresh options</figcaption>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
<p>To create a new customer account:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to the <strong>Customers</strong> page</li>
|
||||||
|
<li>At the top, locate the "New customer name" field</li>
|
||||||
|
<li>Enter the <strong>customer name</strong> (required)</li>
|
||||||
|
<li>Check the <strong>Active</strong> checkbox if you want the customer to be active immediately (checked by default)</li>
|
||||||
|
<li>Click the <strong>Add</strong> button</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>The customer will be created and appear in the customer list immediately.</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-tip">
|
||||||
|
<strong>💡 Best Practice:</strong><br>
|
||||||
|
Create customer accounts before approving backup jobs from the Inbox. This allows you to immediately assign incoming backup reports to the correct customer.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Editing Customer Information</h2>
|
||||||
|
|
||||||
|
<p>To edit an existing customer:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to the <strong>Customers</strong> page</li>
|
||||||
|
<li>Find the customer in the list</li>
|
||||||
|
<li>Click the <strong>Edit</strong> button on the right side of the customer row</li>
|
||||||
|
<li>An "Edit customer" dialog will open</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src="{{ url_for('static', filename='images/documentation/edit-customer.png') }}"
|
||||||
|
alt="Edit Customer Dialog" />
|
||||||
|
<figcaption>Figure 2: Edit customer dialog showing customer name, active status, and Autotask mapping interface</figcaption>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
<h3>Editable Fields</h3>
|
||||||
|
|
||||||
|
<p>In the Edit customer dialog, you can modify:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Customer name:</strong> Change the customer's display name</li>
|
||||||
|
<li><strong>Active:</strong> Toggle whether the customer is active or inactive</li>
|
||||||
|
<li><strong>Autotask mapping:</strong> Link or unlink the customer to an Autotask company (see below)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>After making changes, click <strong>Save changes</strong> to apply them, or <strong>Cancel</strong> to discard.</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Simple Customer Data:</strong><br>
|
||||||
|
BackupChecks keeps customer data simple - only name and active status. There are no separate fields for contact person, email, phone, or notes. The focus is on backup job management, not CRM functionality.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Activating and Deactivating Customers</h2>
|
||||||
|
|
||||||
|
<p>Customers can be marked as active or inactive using the Active checkbox in the Edit customer dialog or directly in the customer list.</p>
|
||||||
|
|
||||||
|
<h3>Active Customers</h3>
|
||||||
|
|
||||||
|
<p>Active customers:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Their jobs appear in Daily Jobs, Run Checks, and Jobs list pages</li>
|
||||||
|
<li>New backup reports can be linked to their jobs</li>
|
||||||
|
<li>Normal operational status</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Inactive Customers</h3>
|
||||||
|
|
||||||
|
<p>Inactive customers:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Their jobs are <strong>hidden</strong> from Daily Jobs, Run Checks, and Jobs list pages</li>
|
||||||
|
<li>Jobs still exist in the database but are not displayed in operational views</li>
|
||||||
|
<li>Useful for customers who no longer use your backup services</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-warning">
|
||||||
|
<strong>⚠️ Impact of Deactivating:</strong><br>
|
||||||
|
When you deactivate a customer, all their backup jobs immediately disappear from operational views (Daily Jobs, Run Checks, Jobs list). This is useful for decluttering the interface when a customer is no longer active. Jobs are not deleted and can be reactivated by marking the customer as active again.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Autotask Company Mapping</h2>
|
||||||
|
|
||||||
|
<p>If Autotask integration is enabled, you can map customers to Autotask companies directly from the Edit customer dialog. This allows BackupChecks to create tickets in the correct Autotask company.</p>
|
||||||
|
|
||||||
|
<h3>Viewing Current Mapping</h3>
|
||||||
|
|
||||||
|
<p>In the Edit customer dialog, the <strong>Autotask mapping</strong> section shows:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Current mapping:</strong> The name of the linked Autotask company (if mapped)</li>
|
||||||
|
<li><strong>Status:</strong> Sync status (e.g., "ok • Checked: 2026-02-07 00:06:15")</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Searching for Autotask Companies</h3>
|
||||||
|
|
||||||
|
<p>To map a customer to an Autotask company:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Open the Edit customer dialog</li>
|
||||||
|
<li>In the <strong>Autotask mapping</strong> section, use the "Search Autotask companies" field</li>
|
||||||
|
<li>Type the company name (or part of it)</li>
|
||||||
|
<li>Click the <strong>Search</strong> button</li>
|
||||||
|
<li>Results will appear below the search field</li>
|
||||||
|
<li>Click on a result to select it</li>
|
||||||
|
<li>Click the <strong>Set mapping</strong> button (blue) to apply the mapping</li>
|
||||||
|
<li>Click <strong>Save changes</strong> to save the customer with the new mapping</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Auto-Search Feature:</strong><br>
|
||||||
|
When you open the Edit customer dialog, BackupChecks automatically searches for Autotask companies matching the customer name. This speeds up the mapping process for most customers.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Refreshing Autotask Status</h3>
|
||||||
|
|
||||||
|
<p>If you want to verify the Autotask company mapping is still valid:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Open the Edit customer dialog</li>
|
||||||
|
<li>Click the <strong>Refresh status</strong> button (gray) in the Autotask mapping section</li>
|
||||||
|
<li>BackupChecks will query Autotask to verify the company still exists and update the status</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Clearing Autotask Mapping</h3>
|
||||||
|
|
||||||
|
<p>To remove an Autotask company mapping:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Open the Edit customer dialog</li>
|
||||||
|
<li>Click the <strong>Clear mapping</strong> button (red) in the Autotask mapping section</li>
|
||||||
|
<li>The mapping will be removed (but not saved yet)</li>
|
||||||
|
<li>Click <strong>Save changes</strong> to confirm the removal</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>Existing tickets remain linked to the Autotask company, but new tickets will not be created until the customer is mapped again.</p>
|
||||||
|
|
||||||
|
<h2>Refreshing All Autotask Mappings</h2>
|
||||||
|
|
||||||
|
<p>To refresh the sync status of all customer Autotask mappings at once:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to the <strong>Customers</strong> page</li>
|
||||||
|
<li>At the top, click the <strong>Refresh all Autotask mappings</strong> button</li>
|
||||||
|
<li>BackupChecks will verify all mappings with Autotask and update their statuses</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-tip">
|
||||||
|
<strong>💡 Use Case:</strong><br>
|
||||||
|
Use this feature periodically to ensure all customer mappings are still valid, especially if Autotask companies have been renamed or deleted.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Deleting Customers</h2>
|
||||||
|
|
||||||
|
<p>To delete a customer:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to the <strong>Customers</strong> page</li>
|
||||||
|
<li>Find the customer you want to delete</li>
|
||||||
|
<li>Click the <strong>Delete</strong> button on the right side of the customer row</li>
|
||||||
|
<li>Confirm the deletion in the dialog</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-danger">
|
||||||
|
<strong>⚠️ Warning - Permanent Deletion:</strong><br>
|
||||||
|
Deleting a customer is <strong>permanent</strong> and will also delete all associated backup jobs, runs, tickets, and remarks. This action cannot be undone. Consider deactivating the customer instead if you might need the data later.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Exporting Customer Data</h2>
|
||||||
|
|
||||||
|
<p>You can export all customer data to a CSV file for backup, reporting, or migration purposes.</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to the <strong>Customers</strong> page</li>
|
||||||
|
<li>At the top, click the <strong>Export CSV</strong> button</li>
|
||||||
|
<li>A CSV file will be downloaded containing:
|
||||||
|
<ul>
|
||||||
|
<li>Customer name</li>
|
||||||
|
<li>Active status</li>
|
||||||
|
<li>Autotask company mapping (company ID and name, if applicable)</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-tip">
|
||||||
|
<strong>💡 Use Case:</strong><br>
|
||||||
|
Export customer data regularly as a backup, or before performing system maintenance. The exported CSV can be imported later to restore customer data.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Importing Customer Data</h2>
|
||||||
|
|
||||||
|
<p>You can import customer data from a CSV file.</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to the <strong>Customers</strong> page</li>
|
||||||
|
<li>At the top, click the <strong>Browse...</strong> button to select your CSV file</li>
|
||||||
|
<li>Select the CSV file (must match the export format)</li>
|
||||||
|
<li>Click the <strong>Import CSV</strong> button</li>
|
||||||
|
<li>The system will:
|
||||||
|
<ul>
|
||||||
|
<li>Create new customers if they don't exist</li>
|
||||||
|
<li>Update existing customers if they already exist (matched by name)</li>
|
||||||
|
<li>Apply Autotask company mappings (if Autotask is enabled)</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>Review the import summary showing created, updated, and skipped customers</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-warning">
|
||||||
|
<strong>⚠️ Import Behavior:</strong><br>
|
||||||
|
The import process matches customers by name. If a customer with the same name already exists, their information will be <strong>updated</strong> with the CSV data. Ensure your CSV data is correct before importing.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Customer Workflow Summary</h2>
|
||||||
|
|
||||||
|
<p>Here's the typical workflow for managing customers in BackupChecks:</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Step</th>
|
||||||
|
<th>Action</th>
|
||||||
|
<th>Result</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>1</td>
|
||||||
|
<td>Create customer account</td>
|
||||||
|
<td>Customer appears in list with <strong style="color: red;">0</strong> jobs</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>2</td>
|
||||||
|
<td>Approve backup job from Inbox</td>
|
||||||
|
<td>Job is linked to customer, job count increases</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>3</td>
|
||||||
|
<td>(Optional) Map to Autotask company via Edit dialog</td>
|
||||||
|
<td>Tickets can be created in Autotask for failed backups</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>4</td>
|
||||||
|
<td>Monitor backup jobs</td>
|
||||||
|
<td>Jobs appear in Daily Jobs and Run Checks</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>5</td>
|
||||||
|
<td>(If needed) Deactivate customer via Edit dialog</td>
|
||||||
|
<td>Jobs hidden from operational views</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>Next Steps</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='customers-jobs', page='configuring-jobs') }}">Configuring Jobs</a> - Learn how backup jobs are created from the Inbox</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='customers-jobs', page='approved-jobs') }}">Approved Jobs</a> - View and manage all approved backup jobs</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='autotask', page='company-mapping') }}">Autotask Company Mapping</a> - Detailed guide to Autotask integration</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='mail-import', page='inbox-management') }}">Inbox Management</a> - Approve jobs from incoming backup reports</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,189 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>First Login & Dashboard</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Learn how to log in to BackupChecks and navigate the dashboard interface.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>📝 Coming Soon:</strong>
|
||||||
|
This page is under construction. Screenshots and additional content will be added in future updates.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Logging In</h2>
|
||||||
|
|
||||||
|
<p>To access BackupChecks:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to the BackupChecks URL in your web browser</li>
|
||||||
|
<li>Enter your username and password</li>
|
||||||
|
<li>Click the "Login" button</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2>Dashboard Overview</h2>
|
||||||
|
|
||||||
|
<p>The dashboard is your starting point and provides:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>General Introduction:</strong> A brief explanation of what BackupChecks is and how it works</li>
|
||||||
|
<li><strong>News & Announcements:</strong> Important updates and notices posted by administrators</li>
|
||||||
|
<li><strong>Quick Overview:</strong> Summary of your backup monitoring status</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Note:</strong>
|
||||||
|
Administrators can create and manage news items via <strong>Settings</strong> to keep all users informed about system updates, maintenance windows, or important changes.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Navigation Bar</h2>
|
||||||
|
|
||||||
|
<p>The navigation bar at the top of the page provides access to different sections of BackupChecks. The menu items you see depend on your assigned role.</p>
|
||||||
|
|
||||||
|
<h3>Role-Based Menu Visibility</h3>
|
||||||
|
|
||||||
|
<p>BackupChecks shows only the menu items relevant to your current role. This keeps the interface clean and focused on your tasks.</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-tip">
|
||||||
|
<strong>Example - Reporter Role:</strong><br>
|
||||||
|
If you log in as a Reporter, you will only see:
|
||||||
|
<ul style="margin-bottom: 0;">
|
||||||
|
<li>Dashboard</li>
|
||||||
|
<li>Reports</li>
|
||||||
|
<li>Documentation</li>
|
||||||
|
<li>Changelog</li>
|
||||||
|
<li>Feedback</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Common Menu Items</h3>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Menu Item</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th>Available To</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Dashboard</strong></td>
|
||||||
|
<td>Home page with overview and news</td>
|
||||||
|
<td>All roles</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Inbox</strong></td>
|
||||||
|
<td>View all imported emails that are NOT yet linked to jobs (either because no parser exists or the job hasn't been created yet)</td>
|
||||||
|
<td>Admin, Operator, Viewer</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>All Mail</strong></td>
|
||||||
|
<td>View all imported emails including linked ones</td>
|
||||||
|
<td>Admin only</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Deleted Mails</strong></td>
|
||||||
|
<td>View emails that have been deleted</td>
|
||||||
|
<td>Admin only</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Customers</strong></td>
|
||||||
|
<td>Manage customer accounts</td>
|
||||||
|
<td>Admin, Operator, Viewer</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Jobs</strong></td>
|
||||||
|
<td>Configure and manage backup jobs</td>
|
||||||
|
<td>Admin, Operator, Viewer</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Archived Jobs</strong></td>
|
||||||
|
<td>View jobs that have been archived</td>
|
||||||
|
<td>Admin only</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Daily Jobs</strong></td>
|
||||||
|
<td>View today's expected backup jobs and their status</td>
|
||||||
|
<td>Admin, Operator, Viewer</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Run Checks</strong></td>
|
||||||
|
<td>Review all backup runs (successful and failed) and mark them as reviewed - the goal is to clear this queue daily</td>
|
||||||
|
<td>Admin, Operator</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Tickets</strong></td>
|
||||||
|
<td>View and manage Autotask tickets</td>
|
||||||
|
<td>Admin, Operator, Viewer</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Overrides</strong></td>
|
||||||
|
<td>Configure rules to override backup status for specific cases</td>
|
||||||
|
<td>Admin, Operator, Viewer</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Reports</strong></td>
|
||||||
|
<td>Generate and view backup status reports</td>
|
||||||
|
<td>All roles</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Settings</strong></td>
|
||||||
|
<td>Configure system settings, mail import, Autotask integration</td>
|
||||||
|
<td>Admin only</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Logging</strong></td>
|
||||||
|
<td>View system audit log of important events</td>
|
||||||
|
<td>Admin only</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Parsers</strong></td>
|
||||||
|
<td>View and test email parsing configurations</td>
|
||||||
|
<td>Admin only</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Documentation</strong></td>
|
||||||
|
<td>Access this user documentation</td>
|
||||||
|
<td>All roles</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Changelog</strong></td>
|
||||||
|
<td>View recent changes and updates to BackupChecks</td>
|
||||||
|
<td>All roles</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Feedback</strong></td>
|
||||||
|
<td>Submit feedback and feature requests</td>
|
||||||
|
<td>All roles</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>User Profile and Role Switching</h2>
|
||||||
|
|
||||||
|
<p>In the top-right corner of the navigation bar, you'll see:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Your username and current role:</strong> Click to access your profile settings</li>
|
||||||
|
<li><strong>Role selector:</strong> If you have multiple roles assigned, you can switch between them using the dropdown menu</li>
|
||||||
|
<li><strong>Theme selector:</strong> Choose between Light, Dark, or Auto theme</li>
|
||||||
|
<li><strong>Logout button:</strong> Sign out of BackupChecks</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 About Run Checks:</strong>
|
||||||
|
The Run Checks page is designed for operators to review all backup runs and mark them as reviewed. This serves as proof that backups have been manually checked. The system records who performed each review and when, creating an audit trail for compliance and quality assurance.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Next Steps</h2>
|
||||||
|
|
||||||
|
<p>Now that you understand the interface, continue to:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='getting-started', page='quick-start') }}">Quick Start Checklist</a> - Set up your first customer and job</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='mail-import', page='setup') }}">Mail Import Setup</a> - Configure email integration</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,160 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>Quick Start Checklist</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Follow this checklist to set up your first customer and backup job in BackupChecks.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>📝 Coming Soon:</strong>
|
||||||
|
This page is under construction. Screenshots will be added in a future update.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Prerequisites</h2>
|
||||||
|
|
||||||
|
<p>Before you begin, ensure you have:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Admin or Operator access to BackupChecks</li>
|
||||||
|
<li>Microsoft Graph API credentials configured for email retrieval</li>
|
||||||
|
<li>At least one backup report email already in the mailbox</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Step 1: Configure Mail Import</h2>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to <strong>Settings</strong> → <strong>Mail</strong></li>
|
||||||
|
<li>Enter your Microsoft Graph API details (Tenant ID, Client ID, Client Secret, Mailbox)</li>
|
||||||
|
<li>Test the connection to verify the credentials are correct</li>
|
||||||
|
<li>Go to <strong>Settings</strong> → <strong>Imports</strong></li>
|
||||||
|
<li>Click the <strong>Manual Import</strong> button to fetch emails from the mailbox</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-tip">
|
||||||
|
<strong>💡 Recommended Approach:</strong><br>
|
||||||
|
Automatic mail import is <strong>disabled by default</strong>. Start by using the manual import button to test if everything works correctly. Once you're satisfied that emails are being imported and processed as expected, you can enable automatic mail import in <strong>Settings</strong> → <strong>Mail</strong>. This way, the system will automatically fetch new emails at the configured interval.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Step 2: Add a Customer</h2>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Go to <strong>Customers</strong> in the navigation menu</li>
|
||||||
|
<li>Click <strong>New Customer</strong></li>
|
||||||
|
<li>Fill in the customer details (name, contact info)</li>
|
||||||
|
<li>Save the customer</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Note:</strong><br>
|
||||||
|
On the Customers page, you'll see the number of jobs linked to each customer. If no jobs are configured yet, you'll see <strong style="color: red;">0</strong> displayed in red and bold.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Step 3: Create Your First Job from Inbox</h2>
|
||||||
|
|
||||||
|
<p>Jobs are created directly from the Inbox by approving backup report emails. The system uses Mail Parsers to extract job details automatically, ensuring consistency.</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Go to <strong>Inbox</strong> in the navigation menu</li>
|
||||||
|
<li>Find a backup report email from your customer</li>
|
||||||
|
<li>Click on the email to view its details</li>
|
||||||
|
<li>In the <strong>Customer</strong> field, enter or select the customer name you created in Step 2</li>
|
||||||
|
<li>Click <strong>Approve job</strong> (this button is only enabled when a customer is selected)</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-warning">
|
||||||
|
<strong>⚠️ Important:</strong><br>
|
||||||
|
You cannot manually configure job variables. All job details (backup software, backup type, sender, subject patterns, etc.) are automatically determined by the Mail Parsers based on the email content. This ensures all jobs are created consistently.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Step 4: Process Similar Emails (Reparse All)</h2>
|
||||||
|
|
||||||
|
<p>After approving your first job, you can automatically process other emails that match the same pattern:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>At the top of the <strong>Inbox</strong> page, click <strong>Reparse all</strong></li>
|
||||||
|
<li>The system will scan all inbox emails and link any that match your newly approved job</li>
|
||||||
|
<li>Matched emails are automatically removed from the inbox and linked to the job</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-tip">
|
||||||
|
<strong>💡 Tip:</strong><br>
|
||||||
|
Use the <strong>Delete</strong> button on emails that are not backup reports. Deleted emails are moved to <strong>Deleted Mails</strong> (visible to admins only).
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Step 5: Review Backup Runs</h2>
|
||||||
|
|
||||||
|
<p>When a backup report email is processed, it creates a "run" in the system:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to <strong>Run Checks</strong> in the navigation menu</li>
|
||||||
|
<li>You should see the backup run(s) from your approved job</li>
|
||||||
|
<li>Review each run (successful or failed) and mark it as reviewed</li>
|
||||||
|
<li>The goal is to clear the Run Checks queue by reviewing all pending runs</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 About Daily Jobs:</strong><br>
|
||||||
|
The <strong>Daily Jobs</strong> page shows jobs expected to run today based on learned schedules. It takes a few days for the system to learn a job's schedule pattern. Once learned, jobs will appear in Daily Jobs on their expected days. Individual runs always appear immediately in <strong>Run Checks</strong> regardless of schedule.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Understanding the Workflow</h2>
|
||||||
|
|
||||||
|
<p>Here's a summary of how BackupChecks processes backup reports:</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Step</th>
|
||||||
|
<th>What Happens</th>
|
||||||
|
<th>Where to See It</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>1. Email arrives</td>
|
||||||
|
<td>Backup report is sent to monitored mailbox</td>
|
||||||
|
<td>Not visible yet</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>2. Import runs</td>
|
||||||
|
<td>System fetches email via Graph API</td>
|
||||||
|
<td><strong>Inbox</strong> (if no matching job exists)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>3. Job created</td>
|
||||||
|
<td>You approve the email and create a job</td>
|
||||||
|
<td><strong>Jobs</strong> page</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>4. Run created</td>
|
||||||
|
<td>Email is parsed and a backup run is recorded</td>
|
||||||
|
<td><strong>Run Checks</strong> page</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>5. Schedule learned</td>
|
||||||
|
<td>After several runs, system learns the schedule</td>
|
||||||
|
<td><strong>Daily Jobs</strong> page</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>6. Review</td>
|
||||||
|
<td>Operator marks run as reviewed</td>
|
||||||
|
<td>Run Checks queue is cleared</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>Next Steps</h2>
|
||||||
|
|
||||||
|
<p>Now that you have your first job configured, you can:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Add more customers and approve more jobs from the Inbox</li>
|
||||||
|
<li>Configure <a href="{{ url_for('documentation.page', section='autotask', page='setup-configuration') }}">Autotask integration</a> for manual ticket creation</li>
|
||||||
|
<li>Set up <a href="{{ url_for('documentation.page', section='reports', page='scheduling') }}">scheduled reports</a></li>
|
||||||
|
<li>Configure <a href="{{ url_for('documentation.page', section='backup-review', page='overrides') }}">overrides</a> for special cases</li>
|
||||||
|
<li>Review the <a href="{{ url_for('documentation.page', section='mail-import', page='mail-parsing') }}">Mail Parsing</a> documentation to understand how parsers work</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,165 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>What is BackupChecks?</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
BackupChecks is a backup monitoring and validation system designed to help IT teams
|
||||||
|
verify that backups are running successfully across their customer infrastructure.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>📝 Coming Soon:</strong>
|
||||||
|
This page is under construction. Screenshots and additional content will be added in future updates.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Key Features</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Automated Mail Parsing:</strong> Import backup reports via email and automatically parse results</li>
|
||||||
|
<li><strong>Review Workflow:</strong> Review all backup jobs daily and mark them as reviewed (goal: clear the Run Checks queue)</li>
|
||||||
|
<li><strong>Customer Management:</strong> Organize backups by customer and manage multiple backup jobs per customer</li>
|
||||||
|
<li><strong>Autotask Integration:</strong> Manually create tickets in Autotask PSA for failed backups requiring follow-up</li>
|
||||||
|
<li><strong>Reporting:</strong> Generate backup status reports with flexible scheduling</li>
|
||||||
|
<li><strong>Role-Based Access:</strong> Admin, Operator, Reporter, and Viewer roles</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Note:</strong>
|
||||||
|
Screenshots will be added in a future update to illustrate the dashboard and key features.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>How It Works</h2>
|
||||||
|
|
||||||
|
<p>BackupChecks follows a simple workflow:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li><strong>Import:</strong> Backup reports are sent via email to a configured mailbox</li>
|
||||||
|
<li><strong>Parse:</strong> The system parses email content to extract backup status</li>
|
||||||
|
<li><strong>Match:</strong> Reports are matched to configured jobs based on sender, subject, and content</li>
|
||||||
|
<li><strong>Review:</strong> Operators review all backup jobs (both successful and failed) and mark them as reviewed to clear the Run Checks queue</li>
|
||||||
|
<li><strong>Ticket Creation:</strong> For failed backups requiring follow-up, operators manually create tickets in Autotask PSA</li>
|
||||||
|
<li><strong>Report:</strong> Generate periodic reports to track backup health over time</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-tip">
|
||||||
|
<strong>💡 Tip:</strong>
|
||||||
|
Start with the <a href="{{ url_for('documentation.page', section='getting-started', page='quick-start') }}">Quick Start Checklist</a>
|
||||||
|
to get your first customer and job configured.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Who Should Use BackupChecks?</h2>
|
||||||
|
|
||||||
|
<p>BackupChecks is designed for:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Managed Service Providers (MSPs):</strong> Monitor backups across multiple customer environments</li>
|
||||||
|
<li><strong>IT Departments:</strong> Track backup compliance for internal infrastructure</li>
|
||||||
|
<li><strong>Backup Administrators:</strong> Centralize backup verification from multiple backup solutions</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Supported Backup Software</h2>
|
||||||
|
|
||||||
|
<p>BackupChecks supports parsing backup reports from:</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Software</th>
|
||||||
|
<th>Support Level</th>
|
||||||
|
<th>Notes</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Veeam Backup & Replication</td>
|
||||||
|
<td>Full</td>
|
||||||
|
<td>Email notifications with detailed job status</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Acronis Cyber Protect</td>
|
||||||
|
<td>Full</td>
|
||||||
|
<td>Backup completion reports</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Synology Active Backup</td>
|
||||||
|
<td>Full</td>
|
||||||
|
<td>Task result notifications</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>QNAP Hybrid Backup Sync</td>
|
||||||
|
<td>Full</td>
|
||||||
|
<td>Job completion emails</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Custom/Other</td>
|
||||||
|
<td>Configurable</td>
|
||||||
|
<td>Manual job configuration for non-standard formats</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-warning">
|
||||||
|
<strong>⚠️ Important:</strong>
|
||||||
|
BackupChecks monitors backup <em>reports</em>, not the backup data itself. Ensure your backup software is configured to send email notifications on job completion.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Architecture Overview</h2>
|
||||||
|
|
||||||
|
<p>BackupChecks is a web-based application with the following components:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Backend:</strong> Flask (Python) application with PostgreSQL database</li>
|
||||||
|
<li><strong>Frontend:</strong> Bootstrap 5 for responsive UI</li>
|
||||||
|
<li><strong>Mail Import:</strong> Microsoft Graph API integration for automated email retrieval</li>
|
||||||
|
<li><strong>Autotask API:</strong> Optional integration for manual ticket creation</li>
|
||||||
|
<li><strong>Reporting:</strong> Built-in report generation with scheduling</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>User Roles</h2>
|
||||||
|
|
||||||
|
<p>BackupChecks supports four user roles with different permissions:</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Role</th>
|
||||||
|
<th>Permissions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Admin</strong></td>
|
||||||
|
<td>Full access to all features, settings, and configuration</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Operator</strong></td>
|
||||||
|
<td>Can review and approve backups, manage customers and jobs, create tickets</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Reporter</strong></td>
|
||||||
|
<td>Can view and generate reports, no access to operational features</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Viewer</strong></td>
|
||||||
|
<td>Read-only access to customers, jobs, and reports</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Note:</strong>
|
||||||
|
Users can be assigned multiple roles and can switch between them using the role selector in the navigation bar.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Next Steps</h2>
|
||||||
|
|
||||||
|
<p>Ready to get started? Continue to:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='getting-started', page='first-login') }}">First Login & Dashboard</a> - Learn about the dashboard interface</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='getting-started', page='quick-start') }}">Quick Start Checklist</a> - Set up your first customer and job</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='mail-import', page='setup') }}">Mail Import Setup</a> - Configure email integration</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,365 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>Auto-Import Configuration</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Configure automatic email import from your Microsoft Graph mailbox to run on a schedule.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Overview</h2>
|
||||||
|
|
||||||
|
<p>BackupChecks can automatically import backup report emails from your mailbox at regular intervals. This eliminates the need to manually trigger imports and ensures new backup reports are processed promptly.</p>
|
||||||
|
|
||||||
|
<p>Auto-import features:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Scheduled imports:</strong> Automatically fetch new emails at configured intervals</li>
|
||||||
|
<li><strong>Auto-approval:</strong> Emails matching existing jobs are automatically approved</li>
|
||||||
|
<li><strong>Background processing:</strong> Runs in a background thread without blocking the application</li>
|
||||||
|
<li><strong>Error handling:</strong> Logs failures and retries on the next interval</li>
|
||||||
|
<li><strong>Manual override:</strong> Trigger imports manually when needed</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Automatic Import</h2>
|
||||||
|
|
||||||
|
<p>Automatic import runs continuously in the background and fetches new emails based on a configured interval.</p>
|
||||||
|
|
||||||
|
<h3>Enabling Auto-Import</h3>
|
||||||
|
|
||||||
|
<p>To enable automatic import:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to <strong>Settings</strong> → <strong>Imports</strong> tab</li>
|
||||||
|
<li>Locate the <strong>Automatic mail import</strong> section</li>
|
||||||
|
<li>Check the <strong>Enable automatic import</strong> checkbox</li>
|
||||||
|
<li>Set the <strong>Import interval</strong> in minutes (default: 15 minutes)</li>
|
||||||
|
<li>Click <strong>Save settings</strong> at the bottom of the page</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 First Run:</strong><br>
|
||||||
|
After enabling auto-import, the first import runs immediately. Subsequent imports run at the configured interval.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Import Interval</h3>
|
||||||
|
|
||||||
|
<p>The import interval determines how often BackupChecks checks for new emails:</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Interval</th>
|
||||||
|
<th>Use Case</th>
|
||||||
|
<th>Recommendation</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>5 minutes</td>
|
||||||
|
<td>High-frequency backups, real-time monitoring</td>
|
||||||
|
<td>May increase server load, use only if necessary</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>15 minutes</td>
|
||||||
|
<td>Standard setup (default)</td>
|
||||||
|
<td><strong>Recommended</strong> for most organizations</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>30 minutes</td>
|
||||||
|
<td>Low-priority backups, small teams</td>
|
||||||
|
<td>Good balance between timeliness and load</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>60 minutes</td>
|
||||||
|
<td>Batch processing, overnight backups only</td>
|
||||||
|
<td>Suitable for organizations with scheduled backup windows</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-tip">
|
||||||
|
<strong>💡 Performance Consideration:</strong><br>
|
||||||
|
Shorter intervals provide faster email processing but increase server load and Microsoft Graph API usage. 15 minutes is recommended as a good balance for most organizations.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Batch Size</h3>
|
||||||
|
|
||||||
|
<p>Auto-import fetches a fixed number of emails per run to prevent overwhelming the system:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Automatic import:</strong> Always fetches <strong>10 messages</strong> per run</li>
|
||||||
|
<li>If more than 10 new emails exist, they will be fetched on subsequent runs</li>
|
||||||
|
<li>This prevents long-running imports that could block other operations</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>How Auto-Import Works</h3>
|
||||||
|
|
||||||
|
<p>Here's what happens during each automatic import cycle:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li><strong>Timer triggers:</strong> The configured interval elapses (e.g., 15 minutes)</li>
|
||||||
|
<li><strong>Check settings:</strong> Verify that auto-import is still enabled</li>
|
||||||
|
<li><strong>Authenticate:</strong> Obtain access token from Microsoft Graph</li>
|
||||||
|
<li><strong>Fetch emails:</strong> Retrieve up to 10 new emails from the incoming folder</li>
|
||||||
|
<li><strong>Parse emails:</strong> Run parsers to extract backup information</li>
|
||||||
|
<li><strong>Auto-approve:</strong> If the email matches an existing approved job, automatically approve it</li>
|
||||||
|
<li><strong>Move emails:</strong> Move processed emails to the processed folder (if configured)</li>
|
||||||
|
<li><strong>Log results:</strong> Record import statistics in the audit log</li>
|
||||||
|
<li><strong>Persist objects:</strong> Store backup objects for auto-approved runs</li>
|
||||||
|
<li><strong>Wait:</strong> Sleep until the next interval</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Auto-Approval</h3>
|
||||||
|
|
||||||
|
<p>During automatic import, BackupChecks can automatically approve emails that match previously approved jobs. This eliminates the need to manually approve recurring backup reports.</p>
|
||||||
|
|
||||||
|
<h4>How Auto-Approval Works</h4>
|
||||||
|
|
||||||
|
<p>An email is automatically approved if:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>The email is <strong>successfully parsed</strong> (backup software, type, and job name extracted)</li>
|
||||||
|
<li>A <strong>matching job</strong> already exists in the database (based on sender, job name, backup software)</li>
|
||||||
|
<li>The job was <strong>previously approved</strong> by a user</li>
|
||||||
|
<li>The match is <strong>unique</strong> across all customers (no ambiguity)</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>When auto-approved:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>A new <code>JobRun</code> is created and linked to the existing job</li>
|
||||||
|
<li>Backup objects are extracted and stored</li>
|
||||||
|
<li>The email is marked as approved and moved out of the inbox</li>
|
||||||
|
<li>The run appears in Daily Jobs, Run Checks, and job history</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 First Approval is Manual:</strong><br>
|
||||||
|
The <strong>first email</strong> for a new backup job must always be manually approved from the inbox. After that, future emails for the same job are automatically approved.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4>When Auto-Approval Fails</h4>
|
||||||
|
|
||||||
|
<p>If auto-approval fails, the email remains in the inbox and requires manual approval:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Parsing failed:</strong> Email format not recognized by any parser</li>
|
||||||
|
<li><strong>No matching job:</strong> This is the first email for this backup job</li>
|
||||||
|
<li><strong>Ambiguous match:</strong> Multiple jobs match the same criteria (different customers with identical job names)</li>
|
||||||
|
<li><strong>Job renamed:</strong> The backup software renamed the job, breaking the match</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Manual Import</h2>
|
||||||
|
|
||||||
|
<p>You can manually trigger an import at any time without waiting for the automatic import interval.</p>
|
||||||
|
|
||||||
|
<h3>Triggering Manual Import</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to <strong>Settings</strong> → <strong>Imports</strong> tab</li>
|
||||||
|
<li>Scroll to the <strong>Manual mail import</strong> section</li>
|
||||||
|
<li>Optionally adjust the <strong>Number of items</strong> (default: 50)</li>
|
||||||
|
<li>Click the <strong>Import now</strong> button</li>
|
||||||
|
<li>BackupChecks will immediately fetch emails from the mailbox</li>
|
||||||
|
<li>You'll see a success message showing how many emails were imported</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Manual Import Batch Size</h3>
|
||||||
|
|
||||||
|
<p>Manual import allows you to configure how many emails to fetch:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Default:</strong> 50 emails per manual import</li>
|
||||||
|
<li><strong>Range:</strong> 1 to 50 emails</li>
|
||||||
|
<li><strong>Use case:</strong> Fetch a large batch when setting up a new customer or after a long period without imports</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>The default batch size can be changed in <strong>Settings</strong> → <strong>Imports</strong> → <strong>Manual import batch size</strong>.</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-tip">
|
||||||
|
<strong>💡 When to Use Manual Import:</strong><br>
|
||||||
|
Manual import is useful when:
|
||||||
|
<ul>
|
||||||
|
<li>Testing mail configuration for the first time</li>
|
||||||
|
<li>Setting up a new customer and you need their emails imported immediately</li>
|
||||||
|
<li>Auto-import is disabled but you need to import specific emails</li>
|
||||||
|
<li>You want to fetch a larger batch than the automatic 10-message limit</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Import Settings Summary</h2>
|
||||||
|
|
||||||
|
<p>Here's a complete reference of all mail import settings:</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Setting</th>
|
||||||
|
<th>Location</th>
|
||||||
|
<th>Default</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Enable automatic import</strong></td>
|
||||||
|
<td>Settings → Imports</td>
|
||||||
|
<td>Disabled</td>
|
||||||
|
<td>Turn automatic email import on or off</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Import interval</strong></td>
|
||||||
|
<td>Settings → Imports</td>
|
||||||
|
<td>15 minutes</td>
|
||||||
|
<td>How often to check for new emails (automatic import only)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Manual import batch size</strong></td>
|
||||||
|
<td>Settings → Imports</td>
|
||||||
|
<td>50 messages</td>
|
||||||
|
<td>Default number of emails to fetch during manual import</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>EML retention period</strong></td>
|
||||||
|
<td>Settings → Imports</td>
|
||||||
|
<td>7 days</td>
|
||||||
|
<td>How long to store raw .eml files (0, 7, or 14 days)</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>Monitoring Import Activity</h2>
|
||||||
|
|
||||||
|
<p>You can monitor import activity through several interfaces:</p>
|
||||||
|
|
||||||
|
<h3>Audit Log</h3>
|
||||||
|
|
||||||
|
<p>Every import operation is logged in the audit log:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to <strong>Settings</strong> → <strong>Maintenance</strong> → <strong>Admin events</strong></li>
|
||||||
|
<li>Look for events like:
|
||||||
|
<ul>
|
||||||
|
<li><code>mail_import_auto</code> - Automatic import completed</li>
|
||||||
|
<li><code>mail_import_manual</code> - Manual import completed</li>
|
||||||
|
<li><code>mail_import_auto_error</code> - Automatic import failed</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>Event messages show statistics: <code>fetched=10, new=3, auto_approved=7, errors=0</code></li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Inbox Page</h3>
|
||||||
|
|
||||||
|
<p>The inbox shows all imported emails:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Check the <strong>Parsed</strong> column to see when emails were imported and parsed</li>
|
||||||
|
<li>Emails with blank <strong>Backup</strong> or <strong>Job name</strong> were not successfully parsed</li>
|
||||||
|
<li>Approved emails move out of the inbox automatically</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Jobs and Daily Jobs</h3>
|
||||||
|
|
||||||
|
<p>Auto-approved emails appear as new runs in:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Jobs</strong> page - Shows all backup jobs with their latest run status</li>
|
||||||
|
<li><strong>Daily Jobs</strong> page - Shows backup runs for today, grouped by customer</li>
|
||||||
|
<li><strong>Run Checks</strong> page - Shows all runs awaiting review</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Troubleshooting</h2>
|
||||||
|
|
||||||
|
<h3>Auto-Import Not Running</h3>
|
||||||
|
|
||||||
|
<p>If auto-import is enabled but emails are not being imported:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Verify that <strong>Enable automatic import</strong> is checked in Settings → Imports</li>
|
||||||
|
<li>Check the audit log for <code>mail_import_auto_error</code> events</li>
|
||||||
|
<li>Verify Microsoft Graph credentials are still valid (client secret may have expired)</li>
|
||||||
|
<li>Ensure the BackupChecks application server is running (the background thread starts on app startup)</li>
|
||||||
|
<li>Check system logs for thread errors or crashes</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Emails Not Auto-Approved</h3>
|
||||||
|
|
||||||
|
<p>If emails are imported but not auto-approved:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Check the <strong>Inbox</strong> - emails may be parsed but not matched to existing jobs</li>
|
||||||
|
<li>Verify the <strong>first email</strong> for this job was manually approved (auto-approval only works for subsequent emails)</li>
|
||||||
|
<li>Ensure the job name, backup software, and sender match exactly between emails</li>
|
||||||
|
<li>Check for duplicate jobs across different customers (auto-approval requires a unique match)</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Import Errors</h3>
|
||||||
|
|
||||||
|
<p>If imports fail with errors:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Authentication error:</strong> Verify Microsoft Graph credentials (tenant ID, client ID, client secret)</li>
|
||||||
|
<li><strong>Folder not found:</strong> Check that incoming/processed folder paths are correct</li>
|
||||||
|
<li><strong>Timeout error:</strong> Network issues or Microsoft Graph API unavailability - will retry on next interval</li>
|
||||||
|
<li><strong>Disk space error:</strong> Free disk space below 2 GB - mail import is blocked until space is freed</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Best Practices</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Enable auto-import:</strong> Use automatic import for hands-off email processing</li>
|
||||||
|
<li><strong>Use 15-minute interval:</strong> Recommended for most setups (balances timeliness and load)</li>
|
||||||
|
<li><strong>Monitor audit logs:</strong> Regularly check for import errors</li>
|
||||||
|
<li><strong>Approve first emails manually:</strong> Always manually approve the first email for a new job to enable auto-approval</li>
|
||||||
|
<li><strong>Keep credentials fresh:</strong> Set calendar reminders to renew Microsoft Graph client secrets before expiration</li>
|
||||||
|
<li><strong>Test manual import first:</strong> Before enabling auto-import, test manual import to verify configuration</li>
|
||||||
|
<li><strong>Use dedicated mailbox:</strong> Don't mix backup reports with personal email</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Advanced Configuration</h2>
|
||||||
|
|
||||||
|
<h3>Disabling Auto-Import Temporarily</h3>
|
||||||
|
|
||||||
|
<p>To temporarily stop automatic imports without losing configuration:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to <strong>Settings</strong> → <strong>Imports</strong></li>
|
||||||
|
<li>Uncheck <strong>Enable automatic import</strong></li>
|
||||||
|
<li>Click <strong>Save settings</strong></li>
|
||||||
|
<li>The background thread will stop fetching emails but settings are preserved</li>
|
||||||
|
<li>Re-enable later by checking the box again</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>EML Storage Retention</h3>
|
||||||
|
|
||||||
|
<p>Control how long raw .eml files are stored in the database:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>0 days:</strong> Don't store .eml files (saves database space, but troubleshooting is harder)</li>
|
||||||
|
<li><strong>7 days:</strong> Default - good balance between troubleshooting capability and storage</li>
|
||||||
|
<li><strong>14 days:</strong> Extended retention for environments with complex parsing issues</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>Older .eml files are automatically purged based on this retention period.</p>
|
||||||
|
|
||||||
|
<h3>High-Volume Environments</h3>
|
||||||
|
|
||||||
|
<p>For organizations with hundreds of backup jobs:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Consider <strong>5-minute intervals</strong> to process emails more quickly</li>
|
||||||
|
<li>Ensure server resources (CPU, RAM, disk) are sufficient for frequent imports</li>
|
||||||
|
<li>Monitor database size growth - consider reducing EML retention to 0 days</li>
|
||||||
|
<li>Use multiple BackupChecks instances with separate mailboxes to distribute load</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Next Steps</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='mail-import', page='setup') }}">Mail Import Setup</a> - Configure Microsoft Graph API integration</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='mail-import', page='inbox-management') }}">Inbox Management</a> - Review and approve imported emails</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='backup-review', page='daily-jobs') }}">Daily Jobs View</a> - Monitor auto-approved backup runs</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,384 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>Inbox Management</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Review incoming backup report emails, examine parsed results, and approve jobs for monitoring.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Overview</h2>
|
||||||
|
|
||||||
|
<p>The <strong>Inbox</strong> is the central location where all imported backup report emails appear before being approved and linked to jobs. Think of it as a staging area where you review and validate incoming backup reports.</p>
|
||||||
|
|
||||||
|
<p>The inbox workflow:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li><strong>Import:</strong> Emails are automatically imported from your mailbox</li>
|
||||||
|
<li><strong>Parse:</strong> BackupChecks automatically parses email content to extract backup information</li>
|
||||||
|
<li><strong>Review:</strong> You review the parsed results in the inbox</li>
|
||||||
|
<li><strong>Approve:</strong> You approve emails to create backup jobs and link future reports</li>
|
||||||
|
<li><strong>Monitor:</strong> Approved jobs appear in Daily Jobs and other operational views</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2>Accessing the Inbox</h2>
|
||||||
|
|
||||||
|
<p>To access the inbox:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to <strong>Inbox</strong> in the main navigation menu</li>
|
||||||
|
<li>Available to <strong>Admin</strong>, <strong>Operator</strong>, and <strong>Viewer</strong> roles</li>
|
||||||
|
<li>Viewers can see emails but cannot approve or delete them</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2>Inbox Page Layout</h2>
|
||||||
|
|
||||||
|
<p>The Inbox page displays a table with all <strong>unapproved</strong> imported emails. Each row represents one email message that has not yet been approved.</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Inbox Shows Only Unapproved Emails:</strong><br>
|
||||||
|
Once you approve an email, it immediately disappears from the inbox and the job appears in operational views (Jobs, Daily Jobs, Run Checks). The inbox only contains emails awaiting approval or deletion.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Table Columns</h3>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Column</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>From</strong></td>
|
||||||
|
<td>Sender email address (e.g., <code>backups@veeam.com</code>)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Subject</strong></td>
|
||||||
|
<td>Email subject line</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Date / time</strong></td>
|
||||||
|
<td>When the email was received (in configured timezone)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Backup</strong></td>
|
||||||
|
<td>Detected backup software (e.g., Veeam, Synology, NAKIVO)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Type</strong></td>
|
||||||
|
<td>Backup job type (e.g., Backup Job, Replication Job)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Job name</strong></td>
|
||||||
|
<td>Extracted backup job name from email content</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Overall</strong></td>
|
||||||
|
<td>Overall status extracted from email (Success, Warning, Failed)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Parsed</strong></td>
|
||||||
|
<td>Timestamp when the email was parsed</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>EML</strong></td>
|
||||||
|
<td>Download link if raw .eml file is stored (blank if not stored)</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3>Pagination</h3>
|
||||||
|
|
||||||
|
<p>The inbox displays 50 emails per page. Use the pagination controls at the top and bottom of the table:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Previous / Next buttons:</strong> Navigate between pages</li>
|
||||||
|
<li><strong>Page X of Y:</strong> Shows current page and total pages</li>
|
||||||
|
<li><strong>Go to:</strong> Jump directly to a specific page number</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Viewing Email Details</h2>
|
||||||
|
|
||||||
|
<p>To view the full content and parsed details of an email:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Click anywhere on the email row in the inbox table</li>
|
||||||
|
<li>A large modal dialog opens showing the email details</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Email Detail Modal</h3>
|
||||||
|
|
||||||
|
<p>The email detail modal uses a two-column layout:</p>
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src="{{ url_for('static', filename='images/documentation/approve-job.png') }}"
|
||||||
|
alt="Email Detail Modal" />
|
||||||
|
<figcaption>Figure 1: Email detail modal with metadata on the left and email body on the right</figcaption>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
<h4>1. Metadata Section (Left Side)</h4>
|
||||||
|
|
||||||
|
<p>Displays email header information on the left side of the modal:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>From:</strong> Sender address</li>
|
||||||
|
<li><strong>Subject:</strong> Email subject</li>
|
||||||
|
<li><strong>Received:</strong> Timestamp</li>
|
||||||
|
<li><strong>Backup software / Type / Job name:</strong> Parsed values extracted from email content</li>
|
||||||
|
<li><strong>Overall status:</strong> Success/Warning/Failed indicator</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>Below the metadata, action buttons are available:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Approve:</strong> Opens the approval dialog to link this email to a customer and create a job</li>
|
||||||
|
<li><strong>Delete:</strong> Marks the email as deleted and removes it from the inbox</li>
|
||||||
|
<li><strong>Download .eml:</strong> Downloads the original email file (if stored)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h4>2. Email Body (Right Side)</h4>
|
||||||
|
|
||||||
|
<p>The email body is displayed on the right side of the modal in an embedded iframe:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>HTML emails are rendered in their original formatting</li>
|
||||||
|
<li>Plain text emails are displayed as preformatted text</li>
|
||||||
|
<li>Styling and images are preserved when possible</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 HTML Rendering:</strong><br>
|
||||||
|
The email body is sandboxed in an iframe for security. This prevents malicious scripts from executing while still allowing you to see the formatted email content.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4>3. Parsed Objects (Left Side, Below Metadata)</h4>
|
||||||
|
|
||||||
|
<p>If the email was successfully parsed, the backup objects are listed below the metadata on the left side:</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Column</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Name</strong></td>
|
||||||
|
<td>Object name (VM name, server name, etc.)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Object type</strong></td>
|
||||||
|
<td>Type of object (e.g., VM, Server, Repository)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Status</strong></td>
|
||||||
|
<td>Status of this object (Success, Warning, Failed)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Message</strong></td>
|
||||||
|
<td>Any error or warning message for this object</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p>For example, a Veeam backup job email might show:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Object: <code>DC01</code>, Type: VM, Status: Success</li>
|
||||||
|
<li>Object: <code>FILESERVER</code>, Type: VM, Status: Success</li>
|
||||||
|
<li>Object: <code>EXCHANGE01</code>, Type: VM, Status: Warning, Message: "Retry attempt succeeded"</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Approving Emails</h2>
|
||||||
|
|
||||||
|
<p>Approving an email creates a backup job in BackupChecks and links it to a customer. Future emails matching the same job will be automatically linked.</p>
|
||||||
|
|
||||||
|
<h3>How to Approve</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Open the email detail modal by clicking on an inbox row</li>
|
||||||
|
<li>Review the email content and parsed objects</li>
|
||||||
|
<li>Click the <strong>Approve</strong> button in the metadata section (left side)</li>
|
||||||
|
<li>An approval dialog opens showing:
|
||||||
|
<ul>
|
||||||
|
<li><strong>Job name:</strong> The parsed job name (read-only, cannot be edited)</li>
|
||||||
|
<li><strong>Select customer:</strong> Dropdown to choose which customer this backup job belongs to</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>Select the <strong>customer</strong> from the dropdown (or start typing to search)</li>
|
||||||
|
<li>Click <strong>Confirm</strong> to approve</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Job Name Cannot Be Changed:</strong><br>
|
||||||
|
The job name is determined by the parser and cannot be modified during approval. If the parsed job name is incorrect, you need to fix the parser or contact support - you cannot manually override it during the approval process.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-tip">
|
||||||
|
<strong>💡 Customer Selection:</strong><br>
|
||||||
|
Always create the customer account <strong>before</strong> approving emails. This allows you to immediately link the job to the correct customer. See <a href="{{ url_for('documentation.page', section='customers-jobs', page='managing-customers') }}">Managing Customers</a> for details.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>What Happens After Approval</h3>
|
||||||
|
|
||||||
|
<p>When you approve an email:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>A <strong>Job</strong> record is created in the database (if it doesn't already exist)</li>
|
||||||
|
<li>A <strong>JobRun</strong> record is created, representing this specific backup run</li>
|
||||||
|
<li><strong>JobObject</strong> records are created for each parsed object (VMs, servers, etc.)</li>
|
||||||
|
<li>The email is marked as <strong>approved</strong> and <strong>immediately disappears from the inbox</strong></li>
|
||||||
|
<li>The job immediately appears in <strong>Jobs</strong>, <strong>Daily Jobs</strong>, and <strong>Run Checks</strong> pages</li>
|
||||||
|
<li>Future emails matching the same job are <strong>automatically approved and linked</strong> without appearing in the inbox</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Approved Emails Don't Return:</strong><br>
|
||||||
|
Once approved, an email permanently leaves the inbox. Future emails for the same job are automatically approved during import and never appear in the inbox - they go directly to operational views.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-warning">
|
||||||
|
<strong>⚠️ Job Matching:</strong><br>
|
||||||
|
After approval, future emails are matched to this job based on sender address, job name, and backup software. If these change (e.g., job is renamed), you may need to approve a new email to create a new job record.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Re-parsing Emails</h2>
|
||||||
|
|
||||||
|
<p>If an email was not parsed correctly, you can re-parse it after updating parser configuration. Re-parsing is only available from the inbox page, not from the email detail modal.</p>
|
||||||
|
|
||||||
|
<h3>Re-parse All Emails</h3>
|
||||||
|
|
||||||
|
<p>To re-parse all emails in the inbox at once:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>On the inbox page, click the <strong>Re-parse all</strong> button at the top of the page</li>
|
||||||
|
<li>BackupChecks will re-run all parsers on <strong>all inbox emails</strong></li>
|
||||||
|
<li>The page will refresh showing updated parsed results</li>
|
||||||
|
<li>Useful after adding new parsers or fixing parser bugs</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 No Individual Re-parse:</strong><br>
|
||||||
|
There is no option to re-parse a single email. The "Re-parse all" button processes all emails in the inbox simultaneously. If you only want to re-parse specific emails, delete the others first, then use "Re-parse all", then re-import the deleted emails.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 When to Re-parse:</strong><br>
|
||||||
|
Re-parsing is useful when emails were imported before a parser was configured, or when parser logic has been updated. Re-parsing does not re-fetch emails from the mailbox - it only re-processes emails already in the database.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Deleting Emails</h2>
|
||||||
|
|
||||||
|
<p>Admins and Operators can delete emails from the inbox.</p>
|
||||||
|
|
||||||
|
<h3>Delete Single Email</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Open the email detail modal</li>
|
||||||
|
<li>Click the <strong>Delete</strong> button</li>
|
||||||
|
<li>Confirm the deletion</li>
|
||||||
|
<li>The email is marked as deleted and moved out of the inbox</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Bulk Delete Emails</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Check the checkboxes next to emails you want to delete</li>
|
||||||
|
<li>Click the <strong>Delete selected</strong> button at the top of the inbox</li>
|
||||||
|
<li>Confirm the bulk deletion</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-warning">
|
||||||
|
<strong>⚠️ Soft Delete:</strong><br>
|
||||||
|
Emails are <strong>soft-deleted</strong>, not permanently removed. Deleted emails are hidden from the inbox but remain in the database. Administrators can view deleted emails on the <strong>Deleted Mails</strong> page (visible only to Admin role).
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Downloading .EML Files</h2>
|
||||||
|
|
||||||
|
<p>If EML storage is enabled, you can download the original .eml file for any email:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Open the email detail modal</li>
|
||||||
|
<li>Click the <strong>Download .eml</strong> button</li>
|
||||||
|
<li>The raw .eml file is downloaded to your browser</li>
|
||||||
|
<li>You can open this file in Outlook, Thunderbird, or any email client</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>This is useful for:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Troubleshooting parsing issues</li>
|
||||||
|
<li>Forwarding the email to a vendor for support</li>
|
||||||
|
<li>Archiving backup reports outside BackupChecks</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Common Workflows</h2>
|
||||||
|
|
||||||
|
<h3>Setting Up a New Customer</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Create the customer in <strong>Customers</strong> page</li>
|
||||||
|
<li>Wait for backup reports to arrive in the inbox (or trigger a manual import)</li>
|
||||||
|
<li>Review each email for the customer in the inbox</li>
|
||||||
|
<li>Approve each email, selecting the new customer from the dropdown</li>
|
||||||
|
<li>The approved emails disappear from the inbox and jobs are created</li>
|
||||||
|
<li>Jobs now appear in <strong>Jobs</strong>, <strong>Daily Jobs</strong>, and <strong>Run Checks</strong> pages</li>
|
||||||
|
<li>Future backup reports are automatically approved and never appear in the inbox</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Troubleshooting Unparsed Emails</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Find emails with empty <strong>Backup</strong> or <strong>Job name</strong> columns in the inbox table</li>
|
||||||
|
<li>Open the email detail modal to review the email body and understand its format</li>
|
||||||
|
<li>Check if a parser exists for this backup software:
|
||||||
|
<ul>
|
||||||
|
<li>Navigate to <strong>Settings</strong> → <strong>Parsers</strong> (Admin only)</li>
|
||||||
|
<li>Look for a parser matching the backup software or email format</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>If no parser exists:
|
||||||
|
<ul>
|
||||||
|
<li>Contact BackupChecks support or the developer to request a parser</li>
|
||||||
|
<li>Provide sample emails (forward .eml files) to help with parser development</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>After a parser is added by the developer, use <strong>Re-parse all</strong> on the inbox page to process the emails again</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-warning">
|
||||||
|
<strong>⚠️ Custom Parsers:</strong><br>
|
||||||
|
Parsers can <strong>only be created by the BackupChecks developer</strong>. End users cannot create or modify parsers. If you need support for a new backup software, contact support with sample email reports (.eml files).
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Cleaning Up Spam or Test Emails</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Identify unwanted emails in the inbox (e.g., spam, test messages)</li>
|
||||||
|
<li>Select them using checkboxes</li>
|
||||||
|
<li>Click <strong>Delete selected</strong></li>
|
||||||
|
<li>Configure mail rules in your mailbox to prevent future spam</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2>Best Practices</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Review new emails daily:</strong> Check the inbox regularly to approve new jobs promptly</li>
|
||||||
|
<li><strong>Create customers first:</strong> Always create customer accounts before approving their jobs</li>
|
||||||
|
<li><strong>Clean up the inbox:</strong> Delete or approve emails to keep the inbox manageable</li>
|
||||||
|
<li><strong>Monitor parsing success:</strong> Check for emails with missing backup software - these may need parser support from the developer</li>
|
||||||
|
<li><strong>Verify parsed job names:</strong> Review the parsed job name before approving - if it's incorrect, contact support to fix the parser (you cannot edit it during approval)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Next Steps</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='mail-import', page='mail-parsing') }}">Mail Parsing</a> - Understand how BackupChecks parses different backup software emails</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='customers-jobs', page='configuring-jobs') }}">Configuring Jobs</a> - Learn about job approval and configuration</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='backup-review', page='daily-jobs') }}">Daily Jobs View</a> - Monitor approved jobs daily</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,350 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>Mail Parsing</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Understand how BackupChecks automatically extracts backup information from email reports using parsers.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Overview</h2>
|
||||||
|
|
||||||
|
<p>When emails are imported from your mailbox, BackupChecks automatically <strong>parses</strong> (analyzes) the email content to extract structured backup information. This eliminates the need to manually read each email and enter data.</p>
|
||||||
|
|
||||||
|
<p>Parsing extracts:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Backup software:</strong> The product that generated the email (e.g., Veeam, Synology, NAKIVO)</li>
|
||||||
|
<li><strong>Backup type:</strong> The type of backup job (e.g., Backup Job, Replication Job)</li>
|
||||||
|
<li><strong>Job name:</strong> The name of the backup job</li>
|
||||||
|
<li><strong>Overall status:</strong> Success, Warning, or Failed</li>
|
||||||
|
<li><strong>Backup objects:</strong> Individual items backed up (VMs, servers, files) with their statuses</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>How Parsing Works</h2>
|
||||||
|
|
||||||
|
<p>The mail parsing process follows these steps:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li><strong>Retrieval:</strong> Email is imported from Microsoft Graph and stored as a <code>MailMessage</code> record</li>
|
||||||
|
<li><strong>Preprocessing:</strong> Email body is normalized (line endings, character encoding) for consistent parsing</li>
|
||||||
|
<li><strong>Parser selection:</strong> All active parsers are evaluated in order to find a match</li>
|
||||||
|
<li><strong>Matching:</strong> Each parser checks if the email matches its criteria (sender, subject, body patterns)</li>
|
||||||
|
<li><strong>Parsing:</strong> When a match is found, the parser extracts backup information from the email</li>
|
||||||
|
<li><strong>Storage:</strong> Parsed data is stored in the database and linked to the email</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2>Parsers</h2>
|
||||||
|
|
||||||
|
<p>A <strong>parser</strong> is a piece of code that knows how to extract backup information from a specific backup software's email format.</p>
|
||||||
|
|
||||||
|
<h3>Viewing Available Parsers</h3>
|
||||||
|
|
||||||
|
<p>To see all available parsers:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to the <strong>Parsers</strong> page in the main navigation menu (Admin only)</li>
|
||||||
|
<li>The parsers page shows a table with all parsers and their configuration</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Parser Information</h3>
|
||||||
|
|
||||||
|
<p>Each parser entry shows:</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Column</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Name</strong></td>
|
||||||
|
<td>Unique identifier for the parser (e.g., <code>veeam_backup_job</code>)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Backup software</strong></td>
|
||||||
|
<td>The product this parser handles (e.g., Veeam, NAKIVO)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Backup type(s)</strong></td>
|
||||||
|
<td>Types of jobs this parser supports (e.g., Backup Job, Replication Job)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Match criteria</strong></td>
|
||||||
|
<td>Rules used to identify matching emails (sender, subject, body patterns)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Order</strong></td>
|
||||||
|
<td>Evaluation order (lower numbers are checked first)</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Read-Only Parser List:</strong><br>
|
||||||
|
The Parsers page is for viewing parser information only. Parsers cannot be enabled, disabled, or modified through the UI. Parser management is handled by the BackupChecks developer.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Match Criteria</h2>
|
||||||
|
|
||||||
|
<p>Parsers use <strong>match criteria</strong> to determine if an email matches their expected format. Common criteria include:</p>
|
||||||
|
|
||||||
|
<h3>Sender Match</h3>
|
||||||
|
|
||||||
|
<p>Match based on the sender's email address:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>from contains:</strong> Sender address must contain a specific string
|
||||||
|
<ul>
|
||||||
|
<li>Example: <code>from contains 'veeam.com'</code> matches <code>backups@veeam.com</code></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Subject Match</h3>
|
||||||
|
|
||||||
|
<p>Match based on the email subject line:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>subject contains:</strong> Subject must contain specific text
|
||||||
|
<ul>
|
||||||
|
<li>Example: <code>subject contains '[Backup]'</code></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li><strong>subject matches:</strong> Subject must match a regular expression pattern
|
||||||
|
<ul>
|
||||||
|
<li>Example: <code>subject matches /Backup.*Success/</code></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Body Match</h3>
|
||||||
|
|
||||||
|
<p>Match based on email body content (not shown in table but used internally):</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Specific phrases or patterns in the email HTML or text body</li>
|
||||||
|
<li>Table structures, HTML tags, or formatting patterns</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Supported Backup Software</h2>
|
||||||
|
|
||||||
|
<p>BackupChecks includes built-in parsers for popular backup solutions:</p>
|
||||||
|
|
||||||
|
<h3>Veeam Backup & Replication</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Backup jobs</li>
|
||||||
|
<li>Backup Copy jobs</li>
|
||||||
|
<li>Replication jobs</li>
|
||||||
|
<li>VM-level and object-level details</li>
|
||||||
|
<li>Warning and error messages</li>
|
||||||
|
<li>VSPC (Veeam Service Provider Console) active alarms</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Synology Active Backup</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Task result notifications</li>
|
||||||
|
<li>PC backup, VM backup, file server backup</li>
|
||||||
|
<li>Success/failure status per device</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>NAKIVO Backup & Replication</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Job completion emails</li>
|
||||||
|
<li>VM-level status and warnings</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Other Supported Software</h3>
|
||||||
|
|
||||||
|
<p>Additional parsers exist for:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Boxafe:</strong> Cloud backup for Microsoft 365</li>
|
||||||
|
<li><strong>Panel3:</strong> Backup monitoring platform</li>
|
||||||
|
<li><strong>QNAP:</strong> NAS backup notifications</li>
|
||||||
|
<li><strong>Syncovery:</strong> File synchronization and backup</li>
|
||||||
|
<li><strong>3CX:</strong> VoIP system backup notifications</li>
|
||||||
|
<li><strong>RDrive Image:</strong> Disk imaging backups</li>
|
||||||
|
<li><strong>NTFS Auditing:</strong> File access audit reports</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Requesting New Parsers:</strong><br>
|
||||||
|
If your backup software is not listed, contact BackupChecks support to request a new parser. Provide sample emails (.eml files) to help the developer create the parser. Parsers can only be created by the BackupChecks developer.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Parser Execution Order</h2>
|
||||||
|
|
||||||
|
<p>When an email is parsed, BackupChecks evaluates parsers in a specific order based on their <strong>Order</strong> value. This is important because:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>The <strong>first matching parser</strong> is used - no other parsers are evaluated after a match</li>
|
||||||
|
<li>More specific parsers should have <strong>lower order numbers</strong> (checked first)</li>
|
||||||
|
<li>Generic parsers should have <strong>higher order numbers</strong> (checked last)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>For example:</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Order</th>
|
||||||
|
<th>Parser</th>
|
||||||
|
<th>Rationale</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>1</td>
|
||||||
|
<td>veeam_replication_job</td>
|
||||||
|
<td>Specific Veeam job type - check before generic Veeam parser</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>2</td>
|
||||||
|
<td>veeam_backup_job</td>
|
||||||
|
<td>Generic Veeam parser - fallback for other Veeam emails</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>10</td>
|
||||||
|
<td>synology_active_backup</td>
|
||||||
|
<td>Unrelated to Veeam - order doesn't matter relative to Veeam</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>Re-parsing Emails</h2>
|
||||||
|
|
||||||
|
<p>If parsing fails or produces incorrect results, you can re-parse emails after fixing the issue.</p>
|
||||||
|
|
||||||
|
<h3>When to Re-parse</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>A new parser was added by the developer for previously unsupported software</li>
|
||||||
|
<li>Parser code was updated by the developer to fix a bug or improve extraction</li>
|
||||||
|
<li>Parser match criteria were adjusted by the developer</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>How to Re-parse</h3>
|
||||||
|
|
||||||
|
<p>Re-parsing is only available for all inbox emails at once:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to the <strong>Inbox</strong> page</li>
|
||||||
|
<li>Click the <strong>Re-parse all</strong> button at the top of the page</li>
|
||||||
|
<li>All emails in the inbox will be re-processed by all available parsers</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-tip">
|
||||||
|
<strong>💡 Re-parsing is Safe:</strong><br>
|
||||||
|
Re-parsing only updates parsed fields (backup software, job name, objects, etc.). It does not re-fetch the email from the mailbox or modify the original email content stored in the database.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Parsing Workflow Example</h2>
|
||||||
|
|
||||||
|
<p>Here's how a typical Veeam backup email is parsed:</p>
|
||||||
|
|
||||||
|
<h3>Example Email</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>From:</strong> <code>backups@veeam.com</code></li>
|
||||||
|
<li><strong>Subject:</strong> <code>[Veeam] Backup Job 'Daily VM Backup' completed with Warning</code></li>
|
||||||
|
<li><strong>Body:</strong> HTML table showing:
|
||||||
|
<ul>
|
||||||
|
<li>VM: DC01 - Success</li>
|
||||||
|
<li>VM: FILESERVER - Success</li>
|
||||||
|
<li>VM: EXCHANGE01 - Warning: "Retry succeeded"</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Parsing Steps</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li><strong>Import:</strong> Email is fetched from Graph API and stored</li>
|
||||||
|
<li><strong>Match:</strong> Veeam backup job parser matches (sender contains 'veeam.com', subject contains '[Veeam]')</li>
|
||||||
|
<li><strong>Extract:</strong> Parser extracts:
|
||||||
|
<ul>
|
||||||
|
<li>Backup software: <code>Veeam</code></li>
|
||||||
|
<li>Backup type: <code>Backup Job</code></li>
|
||||||
|
<li>Job name: <code>Daily VM Backup</code></li>
|
||||||
|
<li>Overall status: <code>Warning</code> (from subject)</li>
|
||||||
|
<li>Objects: 3 VMs with their individual statuses</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li><strong>Store:</strong> Data is saved to the database</li>
|
||||||
|
<li><strong>Display:</strong> Email appears in inbox with parsed fields populated</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2>Troubleshooting Parsing Issues</h2>
|
||||||
|
|
||||||
|
<h3>Email Not Parsed (Blank Fields)</h3>
|
||||||
|
|
||||||
|
<p>If an email shows blank <strong>Backup software</strong> or <strong>Job name</strong>:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Check if a parser exists for this backup software on the <strong>Parsers</strong> page (Admin only)</li>
|
||||||
|
<li>If no parser exists, contact support to request a new parser (provide sample .eml files)</li>
|
||||||
|
<li>If a parser exists but the email wasn't parsed:
|
||||||
|
<ul>
|
||||||
|
<li>Verify the email format matches the parser's expected format (check sender, subject patterns on Parsers page)</li>
|
||||||
|
<li>The email may have a different format than the parser expects</li>
|
||||||
|
<li>Contact support with the .eml file to investigate</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Incorrect Parsing Results</h3>
|
||||||
|
|
||||||
|
<p>If parsing produces wrong job names or objects:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Verify the email format hasn't changed (backup software update may have changed email templates)</li>
|
||||||
|
<li>Contact support with sample .eml files to investigate the parsing issue</li>
|
||||||
|
<li>The parser may need to be updated to handle the new email format</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Partial Parsing</h3>
|
||||||
|
|
||||||
|
<p>If some fields parse correctly but others don't:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>The parser may not support all fields in this email format</li>
|
||||||
|
<li>Email structure may vary slightly from what the parser expects</li>
|
||||||
|
<li>Parser may need updates to handle new email formats</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Parser Limitations</h2>
|
||||||
|
|
||||||
|
<p>Parsers have some inherent limitations:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Format dependency:</strong> Parsers rely on consistent email formats. If your backup software changes its email template, the parser may break</li>
|
||||||
|
<li><strong>Language dependency:</strong> Most parsers expect English-language emails. Non-English emails may not parse correctly</li>
|
||||||
|
<li><strong>First match only:</strong> Only the first matching parser is used. An email cannot be parsed by multiple parsers</li>
|
||||||
|
<li><strong>No AI inference:</strong> Parsers use pattern matching, not AI. They cannot interpret freeform text or adapt to unexpected formats</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Best Practices</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Don't customize backup email templates:</strong> Use default email formats from your backup software for best parsing results</li>
|
||||||
|
<li><strong>Monitor unparsed emails:</strong> Regularly check the inbox for emails with blank backup software fields</li>
|
||||||
|
<li><strong>Report parsing issues promptly:</strong> If emails aren't parsing correctly, contact support immediately with .eml samples</li>
|
||||||
|
<li><strong>Re-parse after parser updates:</strong> After the developer adds or updates parsers, use "Re-parse all" on the inbox page to process existing emails</li>
|
||||||
|
<li><strong>Keep email formats consistent:</strong> Avoid updating backup software email notification settings, as changes may break existing parsers</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Next Steps</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='mail-import', page='auto-import') }}">Auto-Import Configuration</a> - Configure automatic email import schedules</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='mail-import', page='inbox-management') }}">Inbox Management</a> - Review and approve parsed emails</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='customers-jobs', page='configuring-jobs') }}">Configuring Jobs</a> - Approve and configure backup jobs</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,424 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>Mail Import Setup</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Configure Microsoft Graph API integration to automatically retrieve backup report emails from an Exchange Online mailbox.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Overview</h2>
|
||||||
|
|
||||||
|
<p>BackupChecks uses the <strong>Microsoft Graph API</strong> to retrieve emails from an Exchange Online (Microsoft 365) mailbox. This allows the system to automatically import backup reports sent to a dedicated mailbox.</p>
|
||||||
|
|
||||||
|
<p>The mail import process involves:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Authenticating with Microsoft Graph using an Azure AD app registration</li>
|
||||||
|
<li>Retrieving emails from a specified folder in the mailbox</li>
|
||||||
|
<li>Parsing email content to extract backup job information</li>
|
||||||
|
<li>Moving processed emails to a separate folder</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Why Microsoft Graph?</strong><br>
|
||||||
|
BackupChecks uses Microsoft Graph API instead of traditional IMAP/POP3 because it provides better security (OAuth2), reliability, and integration with Microsoft 365 environments. Most organizations already use Microsoft 365 for email, making this the most convenient option.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Prerequisites</h2>
|
||||||
|
|
||||||
|
<p>Before configuring mail import in BackupChecks, you need:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li><strong>Microsoft 365 subscription</strong> with Exchange Online</li>
|
||||||
|
<li><strong>Dedicated mailbox</strong> for receiving backup reports (e.g., backupreports@yourdomain.com)</li>
|
||||||
|
<li><strong>Azure AD application registration</strong> with Mail.Read and Mail.ReadWrite permissions</li>
|
||||||
|
<li><strong>Admin access</strong> to BackupChecks to configure settings</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2>Step 1: Create Azure AD App Registration</h2>
|
||||||
|
|
||||||
|
<p>To allow BackupChecks to access your mailbox, you must create an Azure AD app registration and grant it the necessary permissions.</p>
|
||||||
|
|
||||||
|
<h3>1.1 Register the Application</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Sign in to the <a href="https://portal.azure.com" target="_blank">Azure Portal</a></li>
|
||||||
|
<li>Navigate to <strong>Azure Active Directory</strong> → <strong>App registrations</strong></li>
|
||||||
|
<li>Click <strong>New registration</strong></li>
|
||||||
|
<li>Enter a name (e.g., "BackupChecks Mail Importer")</li>
|
||||||
|
<li>Select <strong>Accounts in this organizational directory only</strong></li>
|
||||||
|
<li>Leave <strong>Redirect URI</strong> blank</li>
|
||||||
|
<li>Click <strong>Register</strong></li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>After registration, note the following values from the <strong>Overview</strong> page:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Application (client) ID</strong> - you'll enter this as <em>Client ID</em> in BackupChecks</li>
|
||||||
|
<li><strong>Directory (tenant) ID</strong> - you'll enter this as <em>Tenant ID</em> in BackupChecks</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>1.2 Create a Client Secret</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>In your app registration, go to <strong>Certificates & secrets</strong></li>
|
||||||
|
<li>Click <strong>New client secret</strong></li>
|
||||||
|
<li>Enter a description (e.g., "BackupChecks access")</li>
|
||||||
|
<li>Select an expiration period (recommended: 24 months)</li>
|
||||||
|
<li>Click <strong>Add</strong></li>
|
||||||
|
<li><strong>Important:</strong> Copy the secret <strong>Value</strong> immediately - you cannot view it again later</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-warning">
|
||||||
|
<strong>⚠️ Secret Expiration:</strong><br>
|
||||||
|
Client secrets expire after the selected period. Set a calendar reminder to renew the secret before expiration, or mail import will stop working. When renewing, create a new secret, update BackupChecks settings, then delete the old secret.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>1.3 Grant API Permissions (Start with Read-Only)</h3>
|
||||||
|
|
||||||
|
<p>BackupChecks requires <strong>application permissions</strong> (not delegated) to access the mailbox. Start with read-only access for initial testing:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>In your app registration, go to <strong>API permissions</strong></li>
|
||||||
|
<li><strong>Remove</strong> any default Microsoft Graph permissions that were added automatically</li>
|
||||||
|
<li>Click <strong>Add a permission</strong></li>
|
||||||
|
<li>Select <strong>Microsoft Graph</strong></li>
|
||||||
|
<li>Select <strong>Application permissions</strong> (not Delegated)</li>
|
||||||
|
<li>Search for and add: <code>Mail.Read</code> - Read mail in all mailboxes</li>
|
||||||
|
<li>Click <strong>Add permissions</strong></li>
|
||||||
|
<li><strong>Important:</strong> Click <strong>Grant admin consent for [your organization]</strong></li>
|
||||||
|
<li>Confirm the consent</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-warning">
|
||||||
|
<strong>⚠️ Tenant-Wide Access:</strong><br>
|
||||||
|
At this stage, the app has permission to read <strong>all mailboxes</strong> in your tenant. This is a security risk. Follow Step 1.4 (recommended) to restrict access to only the backup mailbox.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>1.4 Restrict Access to One Mailbox (Optional but Recommended)</h3>
|
||||||
|
|
||||||
|
<p>To follow the <strong>principle of least privilege</strong>, restrict the application to access <strong>only the backup mailbox</strong> instead of all mailboxes in your tenant.</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-tip">
|
||||||
|
<strong>💡 Security Best Practice:</strong><br>
|
||||||
|
This step is <strong>highly recommended</strong> for production environments. It ensures that even if the client secret is compromised, the attacker can only access the backup mailbox, not personal or sensitive mailboxes.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4>Prerequisites</h4>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Exchange Online PowerShell module installed</li>
|
||||||
|
<li>Exchange Administrator permissions</li>
|
||||||
|
<li>Client ID from Step 1.1</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h4>Restrict via Application Access Policy</h4>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Open PowerShell and connect to Exchange Online:
|
||||||
|
<pre><code>Connect-ExchangeOnline -UserPrincipalName admin@yourdomain.com</code></pre>
|
||||||
|
</li>
|
||||||
|
<li>Create an Application Access Policy to restrict the app to one mailbox:
|
||||||
|
<pre><code>New-ApplicationAccessPolicy `
|
||||||
|
-AppId "<CLIENT_ID>" `
|
||||||
|
-PolicyScopeGroupId "backupreports@yourdomain.com" `
|
||||||
|
-AccessRight RestrictAccess `
|
||||||
|
-Description "Allow Graph app to access only the backup mailbox"</code></pre>
|
||||||
|
<ul>
|
||||||
|
<li>Replace <code><CLIENT_ID></code> with your Application (client) ID from Step 1.1</li>
|
||||||
|
<li>Replace <code>backupreports@yourdomain.com</code> with your backup mailbox address</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>Test the policy to verify it's working:
|
||||||
|
<pre><code>Test-ApplicationAccessPolicy `
|
||||||
|
-Identity backupreports@yourdomain.com `
|
||||||
|
-AppId "<CLIENT_ID>"</code></pre>
|
||||||
|
</li>
|
||||||
|
<li>Expected result: <code>AccessCheckResult : Granted</code></li>
|
||||||
|
<li>Test with a different mailbox to confirm access is denied:
|
||||||
|
<pre><code>Test-ApplicationAccessPolicy `
|
||||||
|
-Identity someuser@yourdomain.com `
|
||||||
|
-AppId "<CLIENT_ID>"</code></pre>
|
||||||
|
</li>
|
||||||
|
<li>Expected result: <code>AccessCheckResult : Denied</code></li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 How It Works:</strong><br>
|
||||||
|
The Application Access Policy restricts the app to access only the mailbox specified in <code>PolicyScopeGroupId</code>. Even though the app has tenant-wide <code>Mail.Read</code> permissions, Exchange Online enforces the policy and blocks access to other mailboxes.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>1.5 Add Write Permissions (After Testing)</h3>
|
||||||
|
|
||||||
|
<p>Once you've tested read-only access and confirmed it works correctly, add write permissions to allow BackupChecks to move processed emails.</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Go back to your app in Azure AD → <strong>API permissions</strong></li>
|
||||||
|
<li>Click <strong>Add a permission</strong></li>
|
||||||
|
<li>Select <strong>Microsoft Graph</strong> → <strong>Application permissions</strong></li>
|
||||||
|
<li>Add: <code>Mail.ReadWrite</code> - Read and write mail in all mailboxes</li>
|
||||||
|
<li>Click <strong>Add permissions</strong></li>
|
||||||
|
<li><strong>Important:</strong> Click <strong>Grant admin consent for [your organization]</strong> again</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Why ReadWrite?</strong><br>
|
||||||
|
The <code>Mail.ReadWrite</code> permission is required to move processed emails from the incoming folder to the processed folder. If you only grant <code>Mail.Read</code>, BackupChecks can import emails but cannot move them after processing. They will remain in the incoming folder.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p><strong>Note:</strong> If you configured the Application Access Policy in Step 1.4, the write restriction still applies - the app can only write to the backup mailbox, not other mailboxes.</p>
|
||||||
|
|
||||||
|
<h2>Step 2: Configure Mail Settings in BackupChecks</h2>
|
||||||
|
|
||||||
|
<p>After creating the Azure AD app registration, configure BackupChecks to use it:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Log in to BackupChecks as an <strong>Admin</strong></li>
|
||||||
|
<li>Navigate to <strong>Settings</strong> → <strong>General</strong> tab</li>
|
||||||
|
<li>Scroll to the <strong>Mail (Microsoft Graph)</strong> section</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>2.1 Enter Graph Credentials</h3>
|
||||||
|
|
||||||
|
<p>Fill in the following fields with values from your Azure AD app registration:</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Field</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th>Example</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Tenant ID</strong></td>
|
||||||
|
<td>Azure AD Directory (tenant) ID</td>
|
||||||
|
<td><code>12345678-1234-1234-1234-123456789abc</code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Client ID</strong></td>
|
||||||
|
<td>Azure AD Application (client) ID</td>
|
||||||
|
<td><code>87654321-4321-4321-4321-abcdef123456</code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Client secret</strong></td>
|
||||||
|
<td>The secret value you copied in Step 1.2</td>
|
||||||
|
<td><code>abc123...xyz789</code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Mailbox address</strong></td>
|
||||||
|
<td>Email address of the mailbox to import from</td>
|
||||||
|
<td><code>backupreports@yourdomain.com</code></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-tip">
|
||||||
|
<strong>💡 Security Note:</strong><br>
|
||||||
|
The client secret field shows <code>******** (stored)</code> when a secret is already saved. Leave this field empty to keep the existing secret, or enter a new secret to replace it.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-warning">
|
||||||
|
<strong>⚠️ Important - Save Before Folder Configuration:</strong><br>
|
||||||
|
You <strong>must save the credentials first</strong> (click "Save settings" at the bottom) and refresh the page before you can configure folders in Step 2.2. The folder browser requires valid credentials to connect to your mailbox and retrieve the folder list.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>2.2 Configure Mail Folders</h3>
|
||||||
|
|
||||||
|
<p>BackupChecks uses two folders in the mailbox:</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Folder</th>
|
||||||
|
<th>Purpose</th>
|
||||||
|
<th>Example Path</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Incoming folder</strong></td>
|
||||||
|
<td>Where backup reports arrive and are fetched from</td>
|
||||||
|
<td><code>Inbox</code> or <code>Inbox/Backup Reports</code></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Processed folder</strong></td>
|
||||||
|
<td>Where emails are moved after processing</td>
|
||||||
|
<td><code>Archive</code> or <code>Inbox/Processed</code></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p>To configure folders:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li><strong>Prerequisites:</strong> Ensure you've saved the credentials in Step 2.1 and refreshed the page</li>
|
||||||
|
<li>Click the <strong>Browse...</strong> button next to the <strong>Incoming folder</strong> field</li>
|
||||||
|
<li>A folder browser popup will open and <strong>automatically load</strong> your mailbox folder structure
|
||||||
|
<ul>
|
||||||
|
<li>The popup connects to Microsoft Graph and retrieves all available folders</li>
|
||||||
|
<li>You will see a hierarchical list of folders (Inbox, Archive, Sent Items, etc.)</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li><strong>Click</strong> on the folder where backup reports arrive (e.g., <code>Inbox</code>)
|
||||||
|
<ul>
|
||||||
|
<li>Don't type the path manually - select it from the list</li>
|
||||||
|
<li>You can expand subfolders by clicking on them</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>Click <strong>Select</strong> to confirm your choice</li>
|
||||||
|
<li>The folder path will be automatically filled in the field (e.g., <code>Inbox</code> or <code>Inbox/Backup Reports</code>)</li>
|
||||||
|
<li>Repeat the same process for the <strong>Processed folder</strong> field
|
||||||
|
<ul>
|
||||||
|
<li>Click <strong>Browse...</strong> next to "Processed folder"</li>
|
||||||
|
<li>Select a folder like <code>Archive</code> or create a subfolder like <code>Inbox/Processed</code></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Folder Browser:</strong><br>
|
||||||
|
The folder browser automatically loads all available folders from your mailbox. You don't need to manually type folder paths - simply click on the folder you want to use. The correct path format (e.g., <code>Inbox/Backup Reports</code>) is automatically generated.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>📝 Future Improvement:</strong><br>
|
||||||
|
The current UI requires clicking the same "Save settings" button twice: once to save credentials (which enables folder browsing), and again to save folder paths. This is not immediately obvious to users. A UI improvement is planned to make this workflow clearer - possibly splitting it into separate sections with distinct save buttons, or providing visual feedback about which step you're on.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>2.3 Save Settings (Again)</h3>
|
||||||
|
|
||||||
|
<p>After configuring the folder paths:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Click the <strong>Save settings</strong> button at the bottom of the page <strong>again</strong>
|
||||||
|
<ul>
|
||||||
|
<li>This is the same "Save settings" button you clicked in Step 2.1</li>
|
||||||
|
<li>It now saves both credentials and folder paths together</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>BackupChecks will validate the complete configuration (credentials + folders)</li>
|
||||||
|
<li>If successful, you'll see a confirmation message</li>
|
||||||
|
<li>If there's an error, verify:
|
||||||
|
<ul>
|
||||||
|
<li>Credentials are correct (tenant ID, client ID, client secret, mailbox address)</li>
|
||||||
|
<li>Folders exist in the mailbox and paths are correct</li>
|
||||||
|
<li>Application Access Policy (if configured) allows access to the mailbox</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-warning">
|
||||||
|
<strong>⚠️ Same Button, Two Steps:</strong><br>
|
||||||
|
There is only <strong>one "Save settings" button</strong> on this page. You must click it <strong>twice</strong>: first after entering credentials (Step 2.1) to enable folder browsing, then again after selecting folders (Step 2.3) to save the complete configuration. This workflow will be improved in a future version to make it clearer.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Step 3: Test the Configuration</h2>
|
||||||
|
|
||||||
|
<p>After saving settings, verify that mail import is working:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Send a test backup report email to your configured mailbox address</li>
|
||||||
|
<li>Wait a few minutes for the scheduled import to run (or trigger a manual import - see below)</li>
|
||||||
|
<li>Navigate to <strong>Inbox</strong> in BackupChecks</li>
|
||||||
|
<li>Verify that the test email appears in the inbox</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3>Manual Import</h3>
|
||||||
|
|
||||||
|
<p>To manually trigger a mail import without waiting for the scheduled task:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to <strong>Settings</strong> → <strong>Imports</strong> tab</li>
|
||||||
|
<li>Click the <strong>Import now</strong> button in the "Mail import" section</li>
|
||||||
|
<li>BackupChecks will immediately fetch new emails from the incoming folder</li>
|
||||||
|
<li>Check the <strong>Inbox</strong> page to see imported emails</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-tip">
|
||||||
|
<strong>💡 Import Schedule:</strong><br>
|
||||||
|
By default, BackupChecks automatically imports new emails every 15 minutes. Manual import is useful for testing or when you need immediate import of a new email.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Troubleshooting</h2>
|
||||||
|
|
||||||
|
<h3>Authentication Errors</h3>
|
||||||
|
|
||||||
|
<p>If you see "Failed to obtain access token" errors:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Verify that <strong>Tenant ID</strong>, <strong>Client ID</strong>, and <strong>Client secret</strong> are correct</li>
|
||||||
|
<li>Check that the client secret has not expired in Azure AD</li>
|
||||||
|
<li>Ensure <strong>admin consent</strong> was granted for the API permissions</li>
|
||||||
|
<li>Verify the app registration is in the same tenant as the mailbox</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Folder Not Found Errors</h3>
|
||||||
|
|
||||||
|
<p>If folder configuration fails:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Use the <strong>Browse...</strong> button to select folders (don't type paths manually)</li>
|
||||||
|
<li>Ensure the folders exist in the mailbox (create them if needed)</li>
|
||||||
|
<li>Check that folder names are spelled exactly as they appear in Outlook</li>
|
||||||
|
<li>Folder paths are case-sensitive</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>No Emails Imported</h3>
|
||||||
|
|
||||||
|
<p>If manual import succeeds but no emails appear:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Verify that emails exist in the <strong>incoming folder</strong></li>
|
||||||
|
<li>Check that emails have not already been moved to the <strong>processed folder</strong></li>
|
||||||
|
<li>Ensure the mailbox has emails dated within the retention period (default: 7 days)</li>
|
||||||
|
<li>Check system logs for parsing errors</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Advanced Configuration</h2>
|
||||||
|
|
||||||
|
<h3>Email Retention</h3>
|
||||||
|
|
||||||
|
<p>BackupChecks stores email content in the database but can also retain the original .eml file for a specified period:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>0 days:</strong> EML files are not stored (saves database space)</li>
|
||||||
|
<li><strong>7 days:</strong> Default retention period</li>
|
||||||
|
<li><strong>14 days:</strong> Extended retention for troubleshooting</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>This setting is configured in <strong>Settings</strong> → <strong>Imports</strong> tab.</p>
|
||||||
|
|
||||||
|
<h3>Multiple Mailboxes</h3>
|
||||||
|
|
||||||
|
<p>BackupChecks currently supports importing from <strong>one mailbox</strong> at a time. If you need to monitor multiple mailboxes:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Use <strong>mail forwarding rules</strong> in Exchange to forward backup reports to the configured mailbox</li>
|
||||||
|
<li>Create <strong>shared mailboxes</strong> and configure all backup software to send to the shared mailbox</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Security Best Practices</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Use a dedicated mailbox:</strong> Don't use a personal mailbox for backup reports</li>
|
||||||
|
<li><strong>Restrict to one mailbox:</strong> Configure an Application Access Policy (Step 1.4) to limit the app to only the backup mailbox - this is the <strong>most important</strong> security measure</li>
|
||||||
|
<li><strong>Start with read-only:</strong> Begin with <code>Mail.Read</code> only, test thoroughly, then add <code>Mail.ReadWrite</code> after validation</li>
|
||||||
|
<li><strong>Restrict app permissions:</strong> Only grant the minimum required permissions (Mail.Read and Mail.ReadWrite, not broader permissions)</li>
|
||||||
|
<li><strong>Rotate secrets regularly:</strong> Set calendar reminders to renew client secrets before expiration (recommended: 12 months)</li>
|
||||||
|
<li><strong>Limit admin access:</strong> Only BackupChecks admins should have access to Settings</li>
|
||||||
|
<li><strong>Monitor import logs:</strong> Check audit logs regularly for failed authentication attempts or unusual activity</li>
|
||||||
|
<li><strong>Store secrets securely:</strong> Client secrets are stored encrypted in the database - never commit them to source code</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Next Steps</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='mail-import', page='inbox-management') }}">Inbox Management</a> - Learn how to review and approve incoming emails</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='mail-import', page='mail-parsing') }}">Mail Parsing</a> - Understand how BackupChecks parses backup reports</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='mail-import', page='auto-import') }}">Auto-Import Configuration</a> - Configure automatic import schedules</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>Creating Reports</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Generate backup status reports.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>📝 Coming Soon:</strong>
|
||||||
|
This page is under construction. Full content will be added in a future update.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Content</h2>
|
||||||
|
|
||||||
|
<p>Detailed content will be added here in a future update.</p>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>Exporting Data</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Export report data to various formats.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>📝 Coming Soon:</strong>
|
||||||
|
This page is under construction. Full content will be added in a future update.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Content</h2>
|
||||||
|
|
||||||
|
<p>Detailed content will be added here in a future update.</p>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>Relative Periods</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Use relative time periods in reports.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>📝 Coming Soon:</strong>
|
||||||
|
This page is under construction. Full content will be added in a future update.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Content</h2>
|
||||||
|
|
||||||
|
<p>Detailed content will be added here in a future update.</p>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>Report Scheduling</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Schedule automatic report generation.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>📝 Coming Soon:</strong>
|
||||||
|
This page is under construction. Full content will be added in a future update.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Content</h2>
|
||||||
|
|
||||||
|
<p>Detailed content will be added here in a future update.</p>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>Autotask Integration</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Configure Autotask API settings.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>📝 Coming Soon:</strong>
|
||||||
|
This page is under construction. Full content will be added in a future update.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Content</h2>
|
||||||
|
|
||||||
|
<p>Detailed content will be added here in a future update.</p>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>General Settings</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Configure general system settings.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>📝 Coming Soon:</strong>
|
||||||
|
This page is under construction. Full content will be added in a future update.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Content</h2>
|
||||||
|
|
||||||
|
<p>Detailed content will be added here in a future update.</p>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>Mail Configuration</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Configure Graph API mail settings.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>📝 Coming Soon:</strong>
|
||||||
|
This page is under construction. Full content will be added in a future update.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Content</h2>
|
||||||
|
|
||||||
|
<p>Detailed content will be added here in a future update.</p>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>Maintenance</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
System maintenance and data management.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>📝 Coming Soon:</strong>
|
||||||
|
This page is under construction. Full content will be added in a future update.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Content</h2>
|
||||||
|
|
||||||
|
<p>Detailed content will be added here in a future update.</p>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>Reporting Settings</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Configure reporting defaults.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>📝 Coming Soon:</strong>
|
||||||
|
This page is under construction. Full content will be added in a future update.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Content</h2>
|
||||||
|
|
||||||
|
<p>Detailed content will be added here in a future update.</p>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>User Management</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Manage users and roles.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>📝 Coming Soon:</strong>
|
||||||
|
This page is under construction. Full content will be added in a future update.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Content</h2>
|
||||||
|
|
||||||
|
<p>Detailed content will be added here in a future update.</p>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>Common Issues</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Solutions to common problems.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>📝 Coming Soon:</strong>
|
||||||
|
This page is under construction. Full content will be added in a future update.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Content</h2>
|
||||||
|
|
||||||
|
<p>Detailed content will be added here in a future update.</p>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>FAQ</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Frequently asked questions.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>📝 Coming Soon:</strong>
|
||||||
|
This page is under construction. Full content will be added in a future update.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Content</h2>
|
||||||
|
|
||||||
|
<p>Detailed content will be added here in a future update.</p>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>Support Contact</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
How to get help and support.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>📝 Coming Soon:</strong>
|
||||||
|
This page is under construction. Full content will be added in a future update.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Content</h2>
|
||||||
|
|
||||||
|
<p>Detailed content will be added here in a future update.</p>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,258 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>Login & Authentication</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Learn how to log in to BackupChecks and manage your authentication session.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>📝 Coming Soon:</strong><br>
|
||||||
|
This page is under construction. Screenshots will be added in a future update.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Logging In</h2>
|
||||||
|
|
||||||
|
<p>BackupChecks uses a traditional username and password authentication system.</p>
|
||||||
|
|
||||||
|
<h3>Login Steps</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to the BackupChecks URL in your web browser</li>
|
||||||
|
<li>You will be automatically redirected to the login page if not authenticated</li>
|
||||||
|
<li>Enter your <strong>username</strong> in the username field</li>
|
||||||
|
<li>Enter your <strong>password</strong> in the password field</li>
|
||||||
|
<li>Complete the <strong>captcha</strong> by solving the simple math problem (e.g., "3 + 5 = ?")</li>
|
||||||
|
<li>Click the <strong>Login</strong> button</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>If your credentials are correct and the captcha is solved, you will be redirected to the dashboard.</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>📝 Future Change:</strong><br>
|
||||||
|
The captcha requirement is planned to become optional via a system setting. Since BackupChecks is typically deployed in restricted local environments, the captcha may be disabled to streamline the login process.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>First Login</h3>
|
||||||
|
|
||||||
|
<p>When you log in for the first time:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>You will land on the <strong>Dashboard</strong> page</li>
|
||||||
|
<li>Review any news announcements or system updates</li>
|
||||||
|
<li>If you have multiple roles, select your preferred active role from the dropdown in the navigation bar</li>
|
||||||
|
<li>Consider changing your password via <a href="{{ url_for('documentation.page', section='users', page='profile-settings') }}">Profile Settings</a></li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-tip">
|
||||||
|
<strong>💡 Tip:</strong><br>
|
||||||
|
Bookmark the BackupChecks URL for quick access. The system will remember your last active role between sessions.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Authentication Method</h2>
|
||||||
|
|
||||||
|
<p>BackupChecks uses local database authentication:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Username/Password:</strong> Credentials are stored securely in the database</li>
|
||||||
|
<li><strong>Session-Based:</strong> After login, a secure session is created and stored in a cookie</li>
|
||||||
|
<li><strong>No SSO/OAuth:</strong> External authentication providers are not currently supported</li>
|
||||||
|
<li><strong>No Two-Factor Authentication:</strong> 2FA is not currently implemented</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Note:</strong><br>
|
||||||
|
User accounts are created and managed by administrators via Settings → User Management.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Login Failures</h2>
|
||||||
|
|
||||||
|
<h3>Common Login Issues</h3>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Issue</th>
|
||||||
|
<th>Possible Cause</th>
|
||||||
|
<th>Solution</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Invalid credentials error</td>
|
||||||
|
<td>Incorrect username or password</td>
|
||||||
|
<td>Double-check username and password (case-sensitive). Contact your administrator if you've forgotten your credentials.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Page doesn't load after login</td>
|
||||||
|
<td>Session or cookie issue</td>
|
||||||
|
<td>Clear your browser cookies and cache, then try again.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Redirected back to login</td>
|
||||||
|
<td>Session expired or browser issue</td>
|
||||||
|
<td>Try a different browser or incognito/private mode to rule out browser-specific issues.</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-warning">
|
||||||
|
<strong>⚠️ Password Reset:</strong><br>
|
||||||
|
BackupChecks does not currently have a self-service password reset feature. If you forget your password, contact your system administrator to reset it.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Session Management</h2>
|
||||||
|
|
||||||
|
<h3>Session Duration</h3>
|
||||||
|
|
||||||
|
<p>BackupChecks uses persistent sessions:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Sessions remain active as long as you keep using the application</li>
|
||||||
|
<li>Sessions may expire after extended periods of inactivity (browser-dependent)</li>
|
||||||
|
<li>Closing the browser tab does not immediately end your session</li>
|
||||||
|
<li>The session cookie is set to expire when the browser closes (unless "Remember Me" is implemented)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Automatic Redirects</h3>
|
||||||
|
|
||||||
|
<p>If your session expires or becomes invalid:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>You will be automatically redirected to the login page</li>
|
||||||
|
<li>After logging in again, you may be redirected to the page you were trying to access</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2>Logging Out</h2>
|
||||||
|
|
||||||
|
<p>To log out of BackupChecks:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Click the <strong>Logout</strong> button in the top-right corner of the navigation bar</li>
|
||||||
|
<li>Your session will be terminated immediately</li>
|
||||||
|
<li>You will be redirected to the login page</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-tip">
|
||||||
|
<strong>💡 Best Practice:</strong><br>
|
||||||
|
Always log out when using a shared or public computer. This ensures your session is properly terminated and prevents unauthorized access.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Security Considerations</h2>
|
||||||
|
|
||||||
|
<h3>Password Security</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Use Strong Passwords:</strong> Choose passwords with a mix of uppercase, lowercase, numbers, and symbols</li>
|
||||||
|
<li><strong>Don't Share Credentials:</strong> Each user should have their own account</li>
|
||||||
|
<li><strong>Change Default Passwords:</strong> If your administrator provides a temporary password, change it immediately</li>
|
||||||
|
<li><strong>Regular Updates:</strong> Consider changing your password periodically</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Browser Security</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Use HTTPS When Applicable:</strong> If BackupChecks is exposed externally via a domain name, ensure you're connecting via HTTPS (check for the padlock icon in your browser). For internal deployments accessed via IP address, HTTPS may not be configured.</li>
|
||||||
|
<li><strong>Keep Browser Updated:</strong> Use the latest version of your browser for security patches</li>
|
||||||
|
<li><strong>Avoid Public WiFi:</strong> If accessing externally, don't log in from untrusted networks without a VPN</li>
|
||||||
|
<li><strong>Clear Cookies on Shared Computers:</strong> Clear browser data after using a shared machine</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Deployment Context:</strong><br>
|
||||||
|
BackupChecks is typically deployed in restricted internal environments (accessed via IP address). For external access via a public domain, HTTPS should be configured using a reverse proxy or certificate.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-warning">
|
||||||
|
<strong>⚠️ Security Alert:</strong><br>
|
||||||
|
If you suspect unauthorized access to your account, contact your administrator immediately to reset your password and review audit logs.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Role Selection After Login</h2>
|
||||||
|
|
||||||
|
<p>If you have multiple roles assigned:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>After login, your last active role will be automatically selected</li>
|
||||||
|
<li>If this is your first login or the role is no longer valid, your first assigned role will be activated</li>
|
||||||
|
<li>You can switch roles at any time using the role dropdown in the navigation bar</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>See <a href="{{ url_for('documentation.page', section='users', page='users-and-roles') }}">Users & Roles</a> for more information about role switching.</p>
|
||||||
|
|
||||||
|
<h2>Troubleshooting Login Issues</h2>
|
||||||
|
|
||||||
|
<h3>Can't Remember Username</h3>
|
||||||
|
|
||||||
|
<p>Contact your system administrator. They can look up your username in Settings → User Management.</p>
|
||||||
|
|
||||||
|
<h3>Can't Remember Password</h3>
|
||||||
|
|
||||||
|
<p>Contact your system administrator. They can reset your password for you.</p>
|
||||||
|
|
||||||
|
<h3>Account Locked or Disabled</h3>
|
||||||
|
|
||||||
|
<p>BackupChecks does not currently implement account lockouts. If you cannot log in:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Verify your username and password are correct</li>
|
||||||
|
<li>Contact your administrator to verify your account exists and is active</li>
|
||||||
|
<li>Ask your administrator to check the audit logs for any issues</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2>Browser Compatibility</h2>
|
||||||
|
|
||||||
|
<p>BackupChecks is designed to work with modern web browsers:</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Browser</th>
|
||||||
|
<th>Minimum Version</th>
|
||||||
|
<th>Status</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Mozilla Firefox</strong></td>
|
||||||
|
<td>88+</td>
|
||||||
|
<td><strong>Recommended - Most tested</strong></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Google Chrome</td>
|
||||||
|
<td>90+</td>
|
||||||
|
<td>Supported</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Microsoft Edge</td>
|
||||||
|
<td>90+</td>
|
||||||
|
<td>Supported</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Safari</td>
|
||||||
|
<td>14+</td>
|
||||||
|
<td>Supported</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-tip">
|
||||||
|
<strong>💡 Best Experience:</strong><br>
|
||||||
|
Mozilla Firefox is the most thoroughly tested browser for BackupChecks and provides the best experience. While other modern browsers are supported, Firefox is recommended for optimal compatibility.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-warning">
|
||||||
|
<strong>⚠️ Not Supported:</strong><br>
|
||||||
|
Internet Explorer is not supported. Please use a modern browser (Firefox recommended).
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Next Steps</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='users', page='profile-settings') }}">Profile Settings</a> - Customize your profile and change your password</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='getting-started', page='first-login') }}">First Login & Dashboard</a> - Learn about the dashboard interface</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='users', page='users-and-roles') }}">Users & Roles</a> - Understand user roles and permissions</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,195 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>Profile Settings</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
View your profile information and change your password.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Accessing Profile Settings</h2>
|
||||||
|
|
||||||
|
<p>To access your profile settings:</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Click on your <strong>username</strong> in the top-right corner of the navigation bar</li>
|
||||||
|
<li>This will open the User Settings page</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src="{{ url_for('static', filename='images/documentation/user-settings.png') }}"
|
||||||
|
alt="User Settings Interface" />
|
||||||
|
<figcaption>Figure 1: User Settings page showing profile information and password change form</figcaption>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
<h2>Profile Information</h2>
|
||||||
|
|
||||||
|
<p>Your profile displays the following read-only information:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Username:</strong> Your login username (cannot be changed by users)</li>
|
||||||
|
<li><strong>Assigned Roles:</strong> The roles you have been granted by administrators</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Note:</strong><br>
|
||||||
|
Only administrators can modify usernames and role assignments via Settings → User Management.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Changing Your Password</h2>
|
||||||
|
|
||||||
|
<p>You can change your password at any time from the profile settings page.</p>
|
||||||
|
|
||||||
|
<h3>Password Change Steps</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to your profile settings (click your username)</li>
|
||||||
|
<li>Locate the <strong>Change Password</strong> section</li>
|
||||||
|
<li>Enter your <strong>current password</strong></li>
|
||||||
|
<li>Enter your <strong>new password</strong></li>
|
||||||
|
<li>Re-enter your <strong>new password</strong> to confirm</li>
|
||||||
|
<li>Click <strong>Change Password</strong> or <strong>Save</strong></li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>If successful, you will see a confirmation message. Your password is updated immediately.</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-tip">
|
||||||
|
<strong>💡 Password Best Practices:</strong><br>
|
||||||
|
Use a strong password with at least 8 characters, including uppercase, lowercase, numbers, and symbols. Avoid common words or easily guessable information.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Password Requirements</h3>
|
||||||
|
|
||||||
|
<p>BackupChecks password requirements:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Minimum Length:</strong> Typically 6-8 characters (check with your administrator)</li>
|
||||||
|
<li><strong>Complexity:</strong> No specific complexity requirements enforced by default</li>
|
||||||
|
<li><strong>History:</strong> No password reuse restrictions by default</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-warning">
|
||||||
|
<strong>⚠️ Important:</strong><br>
|
||||||
|
If you forget your new password, you will need to contact your administrator for a password reset. There is no self-service password recovery.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Theme Selection</h2>
|
||||||
|
|
||||||
|
<p>While not part of the User Settings page itself, you can change your visual theme at any time using the theme selector in the navigation bar.</p>
|
||||||
|
|
||||||
|
<h3>Available Themes</h3>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Theme</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th>When to Use</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Light</strong></td>
|
||||||
|
<td>Light backgrounds with dark text</td>
|
||||||
|
<td>Well-lit environments, daytime use</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Dark</strong></td>
|
||||||
|
<td>Dark backgrounds with light text</td>
|
||||||
|
<td>Low-light environments, reduce eye strain</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Auto</strong></td>
|
||||||
|
<td>Automatically matches your system preference</td>
|
||||||
|
<td>Adapts to your OS dark mode setting</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3>Changing Your Theme</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Locate the <strong>theme selector dropdown</strong> in the top-right corner of the navigation bar</li>
|
||||||
|
<li>Position: between your username and the logout button (or between role selector and logout if you have multiple roles)</li>
|
||||||
|
<li>Click the dropdown to see available themes</li>
|
||||||
|
<li>Select <strong>Light</strong>, <strong>Dark</strong>, or <strong>Auto</strong></li>
|
||||||
|
<li>The page will reload and apply your selected theme immediately</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>Your theme preference is saved to your profile and will persist across sessions.</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-tip">
|
||||||
|
<strong>💡 Tip:</strong><br>
|
||||||
|
The <strong>Auto</strong> theme is recommended if your operating system already has a dark mode schedule. BackupChecks will automatically switch between light and dark themes based on your OS setting.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Role Switching</h2>
|
||||||
|
|
||||||
|
<p>If you have multiple roles assigned, you can switch between them using the role selector in the navigation bar (not in User Settings).</p>
|
||||||
|
|
||||||
|
<p>The role selector appears in the top-right corner between your username and the logout button. Click it to select your active role.</p>
|
||||||
|
|
||||||
|
<p>See <a href="{{ url_for('documentation.page', section='users', page='users-and-roles') }}">Users & Roles</a> for detailed information about role permissions and switching.</p>
|
||||||
|
|
||||||
|
<h2>Data Privacy</h2>
|
||||||
|
|
||||||
|
<h3>What Data is Stored?</h3>
|
||||||
|
|
||||||
|
<p>BackupChecks stores the following user information:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Username (required for login)</li>
|
||||||
|
<li>Password hash (securely encrypted, never stored in plain text)</li>
|
||||||
|
<li>Assigned roles</li>
|
||||||
|
<li>Theme preference</li>
|
||||||
|
<li>Last active role</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Who Can See Your Information?</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>You:</strong> Can view your own username and assigned roles</li>
|
||||||
|
<li><strong>Administrators:</strong> Can view and edit all user profiles via Settings → User Management</li>
|
||||||
|
<li><strong>Other Users:</strong> Cannot view your profile or personal information</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Data Retention</h3>
|
||||||
|
|
||||||
|
<p>Your profile data is retained as long as your account exists. If your account is deleted by an administrator, all associated profile data is removed.</p>
|
||||||
|
|
||||||
|
<h2>Troubleshooting</h2>
|
||||||
|
|
||||||
|
<h3>Can't Change Password</h3>
|
||||||
|
|
||||||
|
<p><strong>Possible causes:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Current password is incorrect:</strong> Verify you're entering your current password correctly (passwords are case-sensitive)</li>
|
||||||
|
<li><strong>New password doesn't meet requirements:</strong> Ensure password meets minimum length (check with your administrator)</li>
|
||||||
|
<li><strong>New passwords don't match:</strong> Verify both new password fields contain the exact same password</li>
|
||||||
|
<li><strong>Browser autofill issue:</strong> Try typing passwords manually instead of using autofill</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-warning">
|
||||||
|
<strong>⚠️ Forgot Your Password?</strong><br>
|
||||||
|
If you cannot remember your current password, contact your administrator. They can reset your password via Settings → User Management.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Can't Access User Settings</h3>
|
||||||
|
|
||||||
|
<p><strong>Possible causes:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Session expired:</strong> Log out and log back in to refresh your session</li>
|
||||||
|
<li><strong>Browser issue:</strong> Try a different browser or incognito/private mode</li>
|
||||||
|
<li><strong>Username not clickable:</strong> Ensure you're clicking directly on your username text in the top-right corner</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Next Steps</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='users', page='users-and-roles') }}">Users & Roles</a> - Learn about user roles, permissions, and role switching</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='users', page='login-authentication') }}">Login & Authentication</a> - Understand login process and session management</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='getting-started', page='first-login') }}">First Login & Dashboard</a> - Navigate the dashboard interface</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='settings', page='user-management') }}">User Management Settings</a> - Admin guide for managing user accounts (Admin only)</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -0,0 +1,291 @@
|
|||||||
|
{% extends "documentation/base.html" %}
|
||||||
|
|
||||||
|
{% block doc_content %}
|
||||||
|
<h1>Users & Roles</h1>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
BackupChecks uses a role-based access control system to manage user permissions and access levels.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>📝 Coming Soon:</strong><br>
|
||||||
|
This page is under construction. Screenshots will be added in a future update.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>User Roles Overview</h2>
|
||||||
|
|
||||||
|
<p>BackupChecks supports four distinct user roles, each with specific permissions and access levels:</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Role</th>
|
||||||
|
<th>Primary Purpose</th>
|
||||||
|
<th>Key Permissions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Admin</strong></td>
|
||||||
|
<td>Full system administration</td>
|
||||||
|
<td>Complete access to all features, settings, user management, and system configuration</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Operator</strong></td>
|
||||||
|
<td>Daily backup operations</td>
|
||||||
|
<td>Review backups, manage customers/jobs, create tickets, view reports (no system settings)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Reporter</strong></td>
|
||||||
|
<td>Reporting and analytics</td>
|
||||||
|
<td>View and generate reports only (no access to operational features)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Viewer</strong></td>
|
||||||
|
<td>Read-only monitoring</td>
|
||||||
|
<td>View customers, jobs, and reports (cannot make changes)</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>Detailed Role Permissions</h2>
|
||||||
|
|
||||||
|
<h3>Admin Role</h3>
|
||||||
|
|
||||||
|
<p>Administrators have unrestricted access to BackupChecks:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Manage users (create, edit, delete, assign roles)</li>
|
||||||
|
<li>Configure system settings (mail import, Autotask integration, UI timezone)</li>
|
||||||
|
<li>Manage customers and backup jobs</li>
|
||||||
|
<li>Review and approve backup runs</li>
|
||||||
|
<li>Create and manage tickets</li>
|
||||||
|
<li>Configure overrides and parsers</li>
|
||||||
|
<li>View audit logs and system maintenance features</li>
|
||||||
|
<li>Create news announcements</li>
|
||||||
|
<li>Access all mail (including deleted mails and archived jobs)</li>
|
||||||
|
<li>Export and import data</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-warning">
|
||||||
|
<strong>⚠️ Security Note:</strong><br>
|
||||||
|
Admin access should be limited to trusted personnel only. Admins can modify critical system settings and access all data.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Operator Role</h3>
|
||||||
|
|
||||||
|
<p>Operators handle day-to-day backup monitoring and validation:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>View and manage customers</li>
|
||||||
|
<li>Configure backup jobs</li>
|
||||||
|
<li>Review backup runs and mark them as reviewed</li>
|
||||||
|
<li>Create and manage Autotask tickets for failed backups</li>
|
||||||
|
<li>Add remarks to backup runs</li>
|
||||||
|
<li>View and create reports</li>
|
||||||
|
<li>Manage inbox emails and approve jobs</li>
|
||||||
|
<li>Configure overrides for special cases</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p><strong>Operators cannot:</strong> Access system settings, manage users, view audit logs, or access deleted mails.</p>
|
||||||
|
|
||||||
|
<h3>Reporter Role</h3>
|
||||||
|
|
||||||
|
<p>Reporters focus exclusively on reporting and analytics:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>View the dashboard</li>
|
||||||
|
<li>Create, schedule, and view reports</li>
|
||||||
|
<li>Access documentation, changelog, and feedback</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p><strong>Reporters cannot:</strong> Access operational features like inbox, customers, jobs, run checks, or tickets. The navigation menu shows only report-related items.</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-tip">
|
||||||
|
<strong>💡 Use Case:</strong><br>
|
||||||
|
The Reporter role is ideal for management or stakeholders who need visibility into backup compliance without operational access.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Viewer Role</h3>
|
||||||
|
|
||||||
|
<p>Viewers have read-only access to monitor backup status:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>View customers and their backup jobs</li>
|
||||||
|
<li>View backup runs and their status</li>
|
||||||
|
<li>View tickets and remarks</li>
|
||||||
|
<li>View reports</li>
|
||||||
|
<li>Access documentation and changelog</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p><strong>Viewers cannot:</strong> Make any changes, create tickets, review backups, or access system settings.</p>
|
||||||
|
|
||||||
|
<h2>Multiple Role Assignment</h2>
|
||||||
|
|
||||||
|
<p>Users can be assigned multiple roles simultaneously. This provides flexibility for users who need different access levels at different times.</p>
|
||||||
|
|
||||||
|
<h3>How Multiple Roles Work</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>A user can be assigned any combination of roles using checkboxes</li>
|
||||||
|
<li>When logged in, the user selects their <strong>active role</strong> from a dropdown in the navigation bar</li>
|
||||||
|
<li>The interface and available features adapt based on the selected active role</li>
|
||||||
|
<li>Users can switch between their assigned roles at any time</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-tip">
|
||||||
|
<strong>💡 When to Use Multiple Roles:</strong><br>
|
||||||
|
Multiple roles are useful when a user occasionally needs different access levels. However, use sparingly - most users should have a single role that matches their primary responsibility.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Managing Users</h2>
|
||||||
|
|
||||||
|
<p>User management is performed by administrators through the <strong>Settings</strong> page.</p>
|
||||||
|
|
||||||
|
<h3>Creating a New User</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to <strong>Settings</strong> → <strong>User Management</strong> (Admin only)</li>
|
||||||
|
<li>Scroll down to the <strong>Create new user</strong> section</li>
|
||||||
|
<li>Enter a <strong>Username</strong> in the username field</li>
|
||||||
|
<li>Enter a <strong>Password</strong> in the password field</li>
|
||||||
|
<li>Select one or more roles by checking the appropriate checkboxes:
|
||||||
|
<ul>
|
||||||
|
<li>Admin</li>
|
||||||
|
<li>Operator</li>
|
||||||
|
<li>Reporter</li>
|
||||||
|
<li>Viewer</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>Click the <strong>Create</strong> button</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src="{{ url_for('static', filename='images/documentation/user-management.png') }}"
|
||||||
|
alt="User Management Interface" />
|
||||||
|
<figcaption>Figure 1: User Management interface showing role checkboxes and user creation</figcaption>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>📝 Future Feature:</strong><br>
|
||||||
|
Email address configuration for users is planned for a future update. The database field exists but is not yet available in the user interface.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Editing User Roles</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to <strong>Settings</strong> → <strong>User Management</strong></li>
|
||||||
|
<li>Find the user you want to edit in the list</li>
|
||||||
|
<li>Check or uncheck the role checkboxes to modify their assigned roles</li>
|
||||||
|
<li>The <strong>Current:</strong> line below the checkboxes shows the currently assigned roles</li>
|
||||||
|
<li>Click the <strong>Save</strong> button to apply the changes</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-warning">
|
||||||
|
<strong>⚠️ Important:</strong><br>
|
||||||
|
Role changes take effect immediately. If you remove a user's current active role, they will be switched to their first remaining role automatically.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Resetting User Passwords</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Navigate to <strong>Settings</strong> → <strong>User Management</strong></li>
|
||||||
|
<li>Find the user whose password you want to reset</li>
|
||||||
|
<li>Click the <strong>New password</strong> button</li>
|
||||||
|
<li>Enter the new password in the dialog</li>
|
||||||
|
<li>Confirm the password change</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>The user can immediately log in with their new password.</p>
|
||||||
|
|
||||||
|
<h3>Deleting Users</h3>
|
||||||
|
|
||||||
|
<p>Administrators can delete user accounts via Settings → User Management.</p>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-warning">
|
||||||
|
<strong>⚠️ Admin Account Protection:</strong><br>
|
||||||
|
If there is only one admin user in the system, that account is protected and cannot be deleted. BackupChecks requires at least one admin user at all times to prevent being locked out of system administration.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Role Switching</h2>
|
||||||
|
|
||||||
|
<p>Users with multiple roles can switch between them using the role selector in the navigation bar (top-right corner, next to the username).</p>
|
||||||
|
|
||||||
|
<h3>How to Switch Roles</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>Locate the role dropdown in the top-right corner of the navigation bar</li>
|
||||||
|
<li>Click the dropdown to see your assigned roles</li>
|
||||||
|
<li>Select the role you want to activate</li>
|
||||||
|
<li>You will be automatically redirected to the <strong>Dashboard</strong> page</li>
|
||||||
|
<li>The navigation menu, available features, and permissions update to match your active role</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="doc-callout doc-callout-info">
|
||||||
|
<strong>💡 Why Dashboard Redirect?</strong><br>
|
||||||
|
When switching roles, you are automatically redirected to the Dashboard to prevent permission errors. This ensures you don't remain on a page you no longer have access to with your new role, which would require you to manually navigate away or refresh the page.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Best Practices</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><strong>Principle of Least Privilege:</strong> Assign users the minimum role required for their responsibilities</li>
|
||||||
|
<li><strong>Limit Admin Access:</strong> Only assign admin role to personnel responsible for system configuration</li>
|
||||||
|
<li><strong>Use Multiple Roles Sparingly:</strong> While flexible, multiple roles can be confusing. Assign them only when necessary</li>
|
||||||
|
<li><strong>Regular Audits:</strong> Periodically review user roles and remove access for inactive users</li>
|
||||||
|
<li><strong>Document Role Assignments:</strong> Keep an external record of why users have specific roles</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Common Role Assignment Scenarios</h2>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>User Type</th>
|
||||||
|
<th>Recommended Role(s)</th>
|
||||||
|
<th>Rationale</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>System Administrator</td>
|
||||||
|
<td>admin</td>
|
||||||
|
<td>Needs full access to configure and maintain the system</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Backup Team Lead</td>
|
||||||
|
<td>operator</td>
|
||||||
|
<td>Reviews backups daily, manages customers and jobs</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Junior Backup Technician</td>
|
||||||
|
<td>operator or viewer</td>
|
||||||
|
<td>Assists with reviews or monitors status (depending on trust level)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Management/Stakeholder</td>
|
||||||
|
<td>reporter</td>
|
||||||
|
<td>Needs reports and metrics, not operational access</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Auditor/Compliance</td>
|
||||||
|
<td>viewer</td>
|
||||||
|
<td>Needs to verify backup status without making changes</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Power User</td>
|
||||||
|
<td>admin, operator</td>
|
||||||
|
<td>Needs both operational access and occasional system configuration (use sparingly)</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>Next Steps</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='users', page='login-authentication') }}">Login & Authentication</a> - Learn how to log in and manage your session</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='users', page='profile-settings') }}">Profile Settings</a> - Customize your profile and preferences</li>
|
||||||
|
<li><a href="{{ url_for('documentation.page', section='settings', page='user-management') }}">User Management Settings</a> - Admin guide to creating and managing users</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -11,6 +11,7 @@
|
|||||||
/>
|
/>
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/layout.css') }}" />
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/layout.css') }}" />
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/status-text.css') }}" />
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/status-text.css') }}" />
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/sandbox.css') }}" />
|
||||||
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}" />
|
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}" />
|
||||||
{% block head %}{% endblock %}
|
{% block head %}{% endblock %}
|
||||||
|
|
||||||
@ -52,6 +53,12 @@
|
|||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
{% if system_settings and system_settings.is_sandbox_environment %}
|
||||||
|
<div class="sandbox-banner">
|
||||||
|
<span class="sandbox-banner-text">SANDBOX</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<nav class="navbar navbar-expand-lg fixed-top bg-body-tertiary border-bottom">
|
<nav class="navbar navbar-expand-lg fixed-top bg-body-tertiary border-bottom">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" href="{{ url_for('main.dashboard') }}">Backupchecks</a>
|
<a class="navbar-brand" href="{{ url_for('main.dashboard') }}">Backupchecks</a>
|
||||||
@ -73,6 +80,11 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{{ url_for('main.reports') }}">Reports</a>
|
<a class="nav-link" href="{{ url_for('main.reports') }}">Reports</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {% if request.path.startswith('/documentation') %}active{% endif %}" href="{{ url_for('documentation.index') }}">
|
||||||
|
<span class="nav-icon">📖</span> Documentation
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href='{{ url_for("main.changelog_page") }}'>Changelog</a>
|
<a class="nav-link" href='{{ url_for("main.changelog_page") }}'>Changelog</a>
|
||||||
</li>
|
</li>
|
||||||
@ -132,6 +144,11 @@
|
|||||||
<a class="nav-link" href="{{ url_for('main.parsers_overview') }}">Parsers</a>
|
<a class="nav-link" href="{{ url_for('main.parsers_overview') }}">Parsers</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {% if request.path.startswith('/documentation') %}active{% endif %}" href="{{ url_for('documentation.index') }}">
|
||||||
|
<span class="nav-icon">📖</span> Documentation
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href='{{ url_for("main.changelog_page") }}'>Changelog</a>
|
<a class="nav-link" href='{{ url_for("main.changelog_page") }}'>Changelog</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -126,7 +126,7 @@
|
|||||||
{% if rows %}
|
{% if rows %}
|
||||||
{% for row in rows %}
|
{% for row in rows %}
|
||||||
<tr class="mail-row" data-message-id="{{ row.id }}" style="cursor: pointer;">
|
<tr class="mail-row" data-message-id="{{ row.id }}" style="cursor: pointer;">
|
||||||
<td>{{ row.received_at }}</td>
|
<td>{{ row.received_at|local_datetime }}</td>
|
||||||
<td>{{ row.from_address }}</td>
|
<td>{{ row.from_address }}</td>
|
||||||
<td>{{ row.subject }}</td>
|
<td>{{ row.subject }}</td>
|
||||||
<td>{{ row.backup_software }}</td>
|
<td>{{ row.backup_software }}</td>
|
||||||
@ -139,7 +139,7 @@
|
|||||||
<span class="badge bg-warning text-dark">Unlinked</span>
|
<span class="badge bg-warning text-dark">Unlinked</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ row.parsed_at }}</td>
|
<td>{{ row.parsed_at|local_datetime }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% if row.has_eml %}
|
{% if row.has_eml %}
|
||||||
<a class="eml-download" href="{{ url_for('main.inbox_message_eml', message_id=row.id) }}" onclick="event.stopPropagation();">EML</a>
|
<a class="eml-download" href="{{ url_for('main.inbox_message_eml', message_id=row.id) }}" onclick="event.stopPropagation();">EML</a>
|
||||||
|
|||||||
@ -24,7 +24,7 @@
|
|||||||
<td>
|
<td>
|
||||||
<a class="text-decoration-none" href="{{ url_for('main.job_detail', job_id=j.id) }}">{{ j.job_name }}</a>
|
<a class="text-decoration-none" href="{{ url_for('main.job_detail', job_id=j.id) }}">{{ j.job_name }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ j.archived_at }}</td>
|
<td>{{ j.archived_at|local_datetime }}</td>
|
||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
<form method="post" action="{{ url_for('main.unarchive_job', job_id=j.id) }}" style="display:inline;">
|
<form method="post" action="{{ url_for('main.unarchive_job', job_id=j.id) }}" style="display:inline;">
|
||||||
<button type="submit" class="btn btn-sm btn-outline-secondary">Restore</button>
|
<button type="submit" class="btn btn-sm btn-outline-secondary">Restore</button>
|
||||||
|
|||||||
@ -88,7 +88,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if c.autotask_last_sync_at %}
|
{% if c.autotask_last_sync_at %}
|
||||||
<div class="text-muted small">Checked: {{ c.autotask_last_sync_at }}</div>
|
<div class="text-muted small">Checked: {{ c.autotask_last_sync_at|local_datetime }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
{% if can_manage %}
|
{% if can_manage %}
|
||||||
@ -104,7 +104,7 @@
|
|||||||
data-autotask-company-id="{{ c.autotask_company_id or '' }}"
|
data-autotask-company-id="{{ c.autotask_company_id or '' }}"
|
||||||
data-autotask-company-name="{{ c.autotask_company_name or '' }}"
|
data-autotask-company-name="{{ c.autotask_company_name or '' }}"
|
||||||
data-autotask-mapping-status="{{ c.autotask_mapping_status or '' }}"
|
data-autotask-mapping-status="{{ c.autotask_mapping_status or '' }}"
|
||||||
data-autotask-last-sync-at="{{ c.autotask_last_sync_at or '' }}"
|
data-autotask-last-sync-at="{{ c.autotask_last_sync_at|local_datetime if c.autotask_last_sync_at else '' }}"
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -87,8 +87,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div>{{ i.created_at }}</div>
|
<div>{{ i.created_at|local_datetime }}</div>
|
||||||
<div class="text-muted" style="font-size: 0.85rem;">Updated {{ i.updated_at }}</div>
|
<div class="text-muted" style="font-size: 0.85rem;">Updated {{ i.updated_at|local_datetime }}</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@ -32,12 +32,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-footer d-flex justify-content-between align-items-center">
|
<div class="card-footer d-flex justify-content-between align-items-center">
|
||||||
<div class="text-muted" style="font-size: 0.9rem;">
|
<div class="text-muted" style="font-size: 0.9rem;">
|
||||||
Created {{ item.created_at }}
|
Created {{ item.created_at|local_datetime }}
|
||||||
<span class="mx-1">•</span>
|
<span class="mx-1">•</span>
|
||||||
Updated {{ item.updated_at }}
|
Updated {{ item.updated_at|local_datetime }}
|
||||||
{% if item.status == 'resolved' and item.resolved_at %}
|
{% if item.status == 'resolved' and item.resolved_at %}
|
||||||
<span class="mx-1">•</span>
|
<span class="mx-1">•</span>
|
||||||
Resolved {{ item.resolved_at }}{% if resolved_by_name %} by {{ resolved_by_name }}{% endif %}
|
Resolved {{ item.resolved_at|local_datetime }}{% if resolved_by_name %} by {{ resolved_by_name }}{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<form method="post" action="{{ url_for('main.feedback_vote', item_id=item.id) }}">
|
<form method="post" action="{{ url_for('main.feedback_vote', item_id=item.id) }}">
|
||||||
@ -59,7 +59,7 @@
|
|||||||
<div class="d-flex justify-content-between align-items-start">
|
<div class="d-flex justify-content-between align-items-start">
|
||||||
<strong>{{ reply_user_map.get(r.user_id, '') or ('User #' ~ r.user_id) }}</strong>
|
<strong>{{ reply_user_map.get(r.user_id, '') or ('User #' ~ r.user_id) }}</strong>
|
||||||
<span class="text-muted" style="font-size: 0.85rem;">
|
<span class="text-muted" style="font-size: 0.85rem;">
|
||||||
{{ r.created_at.strftime('%d-%m-%Y %H:%M:%S') if r.created_at else '' }}
|
{{ r.created_at|local_datetime('%d-%m-%Y %H:%M:%S') if r.created_at else '' }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div style="white-space: pre-wrap;">{{ r.message }}</div>
|
<div style="white-space: pre-wrap;">{{ r.message }}</div>
|
||||||
|
|||||||
@ -98,12 +98,12 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<td>{{ row.from_address }}</td>
|
<td>{{ row.from_address }}</td>
|
||||||
<td>{{ row.subject }}</td>
|
<td>{{ row.subject }}</td>
|
||||||
<td>{{ row.received_at }}</td>
|
<td>{{ row.received_at|local_datetime }}</td>
|
||||||
<td>{{ row.backup_software }}</td>
|
<td>{{ row.backup_software }}</td>
|
||||||
<td>{{ row.backup_type }}</td>
|
<td>{{ row.backup_type }}</td>
|
||||||
<td>{{ row.job_name }}</td>
|
<td>{{ row.job_name }}</td>
|
||||||
<td>{{ row.overall_status }}</td>
|
<td>{{ row.overall_status }}</td>
|
||||||
<td>{{ row.parsed_at }}</td>
|
<td>{{ row.parsed_at|local_datetime }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% if row.has_eml %}
|
{% if row.has_eml %}
|
||||||
<a class="eml-download" href="{{ url_for('main.inbox_message_eml', message_id=row.id) }}">EML</a>
|
<a class="eml-download" href="{{ url_for('main.inbox_message_eml', message_id=row.id) }}">EML</a>
|
||||||
|
|||||||
@ -60,9 +60,9 @@
|
|||||||
<tr class="deleted-mail-row" data-message-id="{{ row.id }}" style="cursor: pointer;">
|
<tr class="deleted-mail-row" data-message-id="{{ row.id }}" style="cursor: pointer;">
|
||||||
<td>{{ row.from_address }}</td>
|
<td>{{ row.from_address }}</td>
|
||||||
<td>{{ row.subject }}</td>
|
<td>{{ row.subject }}</td>
|
||||||
<td>{{ row.received_at }}</td>
|
<td>{{ row.received_at|local_datetime }}</td>
|
||||||
<td>{{ row.deleted_by }}</td>
|
<td>{{ row.deleted_by }}</td>
|
||||||
<td>{{ row.deleted_at }}</td>
|
<td>{{ row.deleted_at|local_datetime }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% if row.has_eml %}
|
{% if row.has_eml %}
|
||||||
<a class="eml-download" href="{{ url_for('main.inbox_message_eml', message_id=row.id) }}">EML</a>
|
<a class="eml-download" href="{{ url_for('main.inbox_message_eml', message_id=row.id) }}">EML</a>
|
||||||
|
|||||||
@ -82,7 +82,7 @@
|
|||||||
{% for r in history_rows %}
|
{% for r in history_rows %}
|
||||||
<tr{% if r.mail_message_id %} class="jobrun-row" data-message-id="{{ r.mail_message_id }}" data-run-id="{{ r.id }}" data-ticket-codes="{{ (r.ticket_codes or [])|tojson|forceescape }}" data-remark-items="{{ (r.remark_items or [])|tojson|forceescape }}" style="cursor: pointer;"{% endif %}>
|
<tr{% if r.mail_message_id %} class="jobrun-row" data-message-id="{{ r.mail_message_id }}" data-run-id="{{ r.id }}" data-ticket-codes="{{ (r.ticket_codes or [])|tojson|forceescape }}" data-remark-items="{{ (r.remark_items or [])|tojson|forceescape }}" style="cursor: pointer;"{% endif %}>
|
||||||
<td>{{ r.run_day }}</td>
|
<td>{{ r.run_day }}</td>
|
||||||
<td>{{ r.run_at }}</td>
|
<td>{{ r.run_at|local_datetime }}</td>
|
||||||
{% set _s = (r.status or "")|lower %}
|
{% set _s = (r.status or "")|lower %}
|
||||||
{% set _is_override = (r.override_applied is defined and r.override_applied) or ('override' in _s) %}
|
{% set _is_override = (r.override_applied is defined and r.override_applied) or ('override' in _s) %}
|
||||||
{% set _dot = '' %}
|
{% set _dot = '' %}
|
||||||
@ -99,7 +99,7 @@
|
|||||||
<td class="status-text {% if r.override_applied %}status-override{% endif %}">{% if r.override_applied %}<span class="status-dot dot-override me-2" aria-hidden="true"></span>Override{% endif %}</td>
|
<td class="status-text {% if r.override_applied %}status-override{% endif %}">{% if r.override_applied %}<span class="status-dot dot-override me-2" aria-hidden="true"></span>Override{% endif %}</td>
|
||||||
{% if active_role == 'admin' %}
|
{% if active_role == 'admin' %}
|
||||||
<td>{{ r.reviewed_by }}</td>
|
<td>{{ r.reviewed_by }}</td>
|
||||||
<td>{{ r.reviewed_at }}</td>
|
<td>{{ r.reviewed_at|local_datetime }}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
Admin activity (last 7 days)
|
System Audit Log (last 7 days)
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
@ -46,7 +46,7 @@
|
|||||||
{% if logs %}
|
{% if logs %}
|
||||||
{% for log in logs %}
|
{% for log in logs %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ log.created_at }}</td>
|
<td>{{ log.created_at|local_datetime }}</td>
|
||||||
<td>{{ log.user or "-" }}</td>
|
<td>{{ log.user or "-" }}</td>
|
||||||
<td>{{ log.event_type }}</td>
|
<td>{{ log.event_type }}</td>
|
||||||
<td>{{ log.message }}</td>
|
<td>{{ log.message }}</td>
|
||||||
|
|||||||
@ -130,8 +130,8 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>{{ ov.level }}</td>
|
<td>{{ ov.level }}</td>
|
||||||
<td>{{ ov.scope }}</td>
|
<td>{{ ov.scope }}</td>
|
||||||
<td>{{ ov.start_at }}</td>
|
<td>{{ ov.start_at|local_datetime }}</td>
|
||||||
<td>{{ ov.end_at or "-" }}</td>
|
<td>{{ ov.end_at|local_datetime if ov.end_at else "-" }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% if ov.active %}
|
{% if ov.active %}
|
||||||
<span class="badge bg-success">Active</span>
|
<span class="badge bg-success">Active</span>
|
||||||
|
|||||||
@ -76,7 +76,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% for r in runs %}
|
{% for r in runs %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-nowrap">{{ r.run_at }}</td>
|
<td class="text-nowrap">{{ r.run_at|local_datetime }}</td>
|
||||||
<td>{{ r.customer_name }}</td>
|
<td>{{ r.customer_name }}</td>
|
||||||
<td>{{ r.job_name }}</td>
|
<td>{{ r.job_name }}</td>
|
||||||
<td>{{ r.status }}</td>
|
<td>{{ r.status }}</td>
|
||||||
|
|||||||
@ -11,13 +11,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row g-3">
|
|
||||||
<div class="col-12 col-xl-8">
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header d-flex align-items-center justify-content-between">
|
<div class="card-header d-flex align-items-center justify-content-between">
|
||||||
<div>
|
<div>
|
||||||
<div class="fw-semibold">Report definitions</div>
|
<div class="fw-semibold">Report definitions</div>
|
||||||
<div class="text-muted small">One-time reports are supported. Scheduling is a placeholder for now.</div>
|
<div class="text-muted small">Configure reports with custom periods, columns, and optional scheduling.</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-center gap-2">
|
<div class="d-flex align-items-center gap-2">
|
||||||
<button type="button" class="btn btn-sm btn-outline-secondary" id="rep_refresh_btn">Refresh</button>
|
<button type="button" class="btn btn-sm btn-outline-secondary" id="rep_refresh_btn">Refresh</button>
|
||||||
@ -75,52 +73,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 col-xl-4">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<div class="fw-semibold">Scheduling (placeholder)</div>
|
|
||||||
<div class="text-muted small">This is a preview of the future scheduling UI.</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label">Delivery method</label>
|
|
||||||
<select class="form-select" disabled>
|
|
||||||
<option selected>Email</option>
|
|
||||||
<option>Download only</option>
|
|
||||||
</select>
|
|
||||||
<div class="form-text">Coming soon.</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label">Frequency</label>
|
|
||||||
<select class="form-select" disabled>
|
|
||||||
<option selected>Daily</option>
|
|
||||||
<option>Weekly</option>
|
|
||||||
<option>Monthly</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label">Recipients</label>
|
|
||||||
<input type="text" class="form-control" disabled placeholder="user@example.com, team@example.com" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label">Next run</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input type="text" class="form-control" disabled placeholder="Not scheduled" />
|
|
||||||
<span class="input-group-text">UTC</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="alert alert-info mb-0">
|
|
||||||
Scheduling is not active yet. These controls are disabled on purpose.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Raw data modal -->
|
<!-- Raw data modal -->
|
||||||
<div class="modal fade" id="rep_raw_modal" tabindex="-1" aria-hidden="true">
|
<div class="modal fade" id="rep_raw_modal" tabindex="-1" aria-hidden="true">
|
||||||
<div class="modal-dialog modal-xl modal-dialog-scrollable">
|
<div class="modal-dialog modal-xl modal-dialog-scrollable">
|
||||||
|
|||||||
@ -204,10 +204,10 @@
|
|||||||
<dd class="col-9 ellipsis-field" id="rcm_status"></dd>
|
<dd class="col-9 ellipsis-field" id="rcm_status"></dd>
|
||||||
|
|
||||||
<dt class="col-3">Overall remark</dt>
|
<dt class="col-3">Overall remark</dt>
|
||||||
<dd class="col-9" id="rcm_overall_message" style="white-space: pre-wrap;"></dd>
|
<dd class="col-9 ellipsis-field" id="rcm_overall_message"></dd>
|
||||||
|
|
||||||
<dt class="col-3">Remark</dt>
|
<dt class="col-3">Remark</dt>
|
||||||
<dd class="col-9" id="rcm_remark" style="white-space: pre-wrap;"></dd>
|
<dd class="col-9 ellipsis-field" id="rcm_remark"></dd>
|
||||||
|
|
||||||
<dt class="col-3">Meldingen</dt>
|
<dt class="col-3">Meldingen</dt>
|
||||||
<dd class="col-9">
|
<dd class="col-9">
|
||||||
@ -308,7 +308,7 @@
|
|||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="d-flex gap-2 mb-2">
|
<div class="d-flex gap-2 mb-2">
|
||||||
<input type="text" class="form-control" id="atl_search" placeholder="Search by ticket number or title" />
|
<input type="text" class="form-control" id="atl_search" placeholder="Search by ticket number or title" />
|
||||||
<button type="button" class="btn btn-outline-secondary" id="atl_refresh">Refresh</button>
|
<button type="button" class="btn btn-outline-secondary" id="atl_refresh">Search</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="small text-muted mb-2" id="atl_status"></div>
|
<div class="small text-muted mb-2" id="atl_status"></div>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
@ -925,6 +925,7 @@ table.addEventListener('change', function (e) {
|
|||||||
function renderAutotaskInfo(run) {
|
function renderAutotaskInfo(run) {
|
||||||
if (!atInfo) return;
|
if (!atInfo) return;
|
||||||
var num = (run && run.autotask_ticket_number) ? String(run.autotask_ticket_number) : '';
|
var num = (run && run.autotask_ticket_number) ? String(run.autotask_ticket_number) : '';
|
||||||
|
var ticketId = (run && run.autotask_ticket_id) ? String(run.autotask_ticket_id) : '';
|
||||||
var isResolved = !!(run && run.autotask_ticket_is_resolved);
|
var isResolved = !!(run && run.autotask_ticket_is_resolved);
|
||||||
var origin = (run && run.autotask_ticket_resolved_origin) ? String(run.autotask_ticket_resolved_origin) : '';
|
var origin = (run && run.autotask_ticket_resolved_origin) ? String(run.autotask_ticket_resolved_origin) : '';
|
||||||
var isDeleted = !!(run && run.autotask_ticket_is_deleted);
|
var isDeleted = !!(run && run.autotask_ticket_is_deleted);
|
||||||
@ -947,7 +948,16 @@ table.addEventListener('change', function (e) {
|
|||||||
} else if (isResolved && origin === 'psa') {
|
} else if (isResolved && origin === 'psa') {
|
||||||
extra = '<div class="mt-1"><span class="badge bg-secondary">Resolved by PSA</span></div>';
|
extra = '<div class="mt-1"><span class="badge bg-secondary">Resolved by PSA</span></div>';
|
||||||
}
|
}
|
||||||
atInfo.innerHTML = '<div><strong>Ticket:</strong> ' + escapeHtml(num) + '</div>' + extra;
|
|
||||||
|
// Build ticket display with optional direct link
|
||||||
|
var ticketDisplay = '<div><strong>Ticket:</strong> ' + escapeHtml(num);
|
||||||
|
if (ticketId) {
|
||||||
|
var autotaskUrl = 'https://ww19.autotask.net/Mvc/ServiceDesk/TicketDetail.mvc?workspace=False&ids%5B0%5D=' + encodeURIComponent(ticketId) + '&ticketId=' + encodeURIComponent(ticketId);
|
||||||
|
ticketDisplay += ' <a href="' + escapeHtml(autotaskUrl) + '" target="_blank" rel="noopener noreferrer" class="btn btn-sm btn-outline-primary ms-1" style="padding: 0.125rem 0.5rem; font-size: 0.75rem;">Open in Autotask</a>';
|
||||||
|
}
|
||||||
|
ticketDisplay += '</div>';
|
||||||
|
|
||||||
|
atInfo.innerHTML = ticketDisplay + extra;
|
||||||
} else if (run && run.autotask_ticket_id) {
|
} else if (run && run.autotask_ticket_id) {
|
||||||
atInfo.innerHTML = '<div><strong>Ticket:</strong> created</div>';
|
atInfo.innerHTML = '<div><strong>Ticket:</strong> created</div>';
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -150,6 +150,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header">Environment</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" id="is_sandbox_environment" name="is_sandbox_environment" {% if settings.is_sandbox_environment %}checked{% endif %} />
|
||||||
|
<label class="form-check-label" for="is_sandbox_environment">Mark this as a Sandbox/Development environment</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-text">When enabled, a visual banner will be displayed on all pages to indicate this is not a production environment.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="d-flex justify-content-end mt-3">
|
<div class="d-flex justify-content-end mt-3">
|
||||||
<button type="submit" class="btn btn-primary">Save settings</button>
|
<button type="submit" class="btn btn-primary">Save settings</button>
|
||||||
</div>
|
</div>
|
||||||
@ -331,7 +342,7 @@
|
|||||||
|
|
||||||
|
|
||||||
{% if section == 'integrations' %}
|
{% if section == 'integrations' %}
|
||||||
<form method="post" class="mb-4">
|
<form method="post" class="mb-4" id="autotask-settings-form">
|
||||||
<div class="card mb-3">
|
<div class="card mb-3">
|
||||||
<div class="card-header">Autotask</div>
|
<div class="card-header">Autotask</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
@ -342,9 +353,9 @@
|
|||||||
|
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<label for="autotask_environment" class="form-label">Environment</label>
|
<label for="autotask_environment" class="form-label">Environment <span class="text-danger">*</span></label>
|
||||||
<select class="form-select" id="autotask_environment" name="autotask_environment">
|
<select class="form-select" id="autotask_environment" name="autotask_environment" required>
|
||||||
<option value="" {% if not settings.autotask_environment %}selected{% endif %}>Select...</option>
|
<option value="">Select...</option>
|
||||||
<option value="sandbox" {% if settings.autotask_environment == 'sandbox' %}selected{% endif %}>Sandbox</option>
|
<option value="sandbox" {% if settings.autotask_environment == 'sandbox' %}selected{% endif %}>Sandbox</option>
|
||||||
<option value="production" {% if settings.autotask_environment == 'production' %}selected{% endif %}>Production</option>
|
<option value="production" {% if settings.autotask_environment == 'production' %}selected{% endif %}>Production</option>
|
||||||
</select>
|
</select>
|
||||||
@ -352,44 +363,51 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<label for="autotask_api_username" class="form-label">API Username</label>
|
<label for="autotask_api_username" class="form-label">API Username <span class="text-danger">*</span></label>
|
||||||
<input type="text" class="form-control" id="autotask_api_username" name="autotask_api_username" value="{{ settings.autotask_api_username or '' }}" />
|
<input type="text" class="form-control" id="autotask_api_username" name="autotask_api_username" value="{{ settings.autotask_api_username or '' }}" required />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<label for="autotask_api_password" class="form-label">API Password</label>
|
<label for="autotask_api_password" class="form-label">API Password {% if not has_autotask_password %}<span class="text-danger">*</span>{% endif %}</label>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
id="autotask_api_password"
|
id="autotask_api_password"
|
||||||
name="autotask_api_password"
|
name="autotask_api_password"
|
||||||
placeholder="{% if has_autotask_password %}******** (stored){% else %}enter password{% endif %}"
|
placeholder="{% if has_autotask_password %}******** (stored){% else %}enter password{% endif %}"
|
||||||
|
{% if not has_autotask_password %}required{% endif %}
|
||||||
/>
|
/>
|
||||||
<div class="form-text">Leave empty to keep the existing password.</div>
|
<div class="form-text">Leave empty to keep the existing password.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label for="autotask_tracking_identifier" class="form-label">Tracking Identifier (Integration Code)</label>
|
<label for="autotask_tracking_identifier" class="form-label">Tracking Identifier (Integration Code) <span class="text-danger">*</span></label>
|
||||||
<input type="text" class="form-control" id="autotask_tracking_identifier" name="autotask_tracking_identifier" value="{{ settings.autotask_tracking_identifier or '' }}" />
|
<input type="text" class="form-control" id="autotask_tracking_identifier" name="autotask_tracking_identifier" value="{{ settings.autotask_tracking_identifier or '' }}" required />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label for="autotask_base_url" class="form-label">Backupchecks Base URL</label>
|
<label for="autotask_base_url" class="form-label">Backupchecks Base URL <span class="text-danger">*</span></label>
|
||||||
<input type="text" class="form-control" id="autotask_base_url" name="autotask_base_url" value="{{ settings.autotask_base_url or '' }}" placeholder="https://backupchecks.example.com" />
|
<input type="url" class="form-control" id="autotask_base_url" name="autotask_base_url" value="{{ settings.autotask_base_url or '' }}" placeholder="https://backupchecks.example.com" required />
|
||||||
<div class="form-text">Required later for creating stable links to Job Details pages.</div>
|
<div class="form-text">Required later for creating stable links to Job Details pages.</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-end mt-3">
|
||||||
|
<button type="submit" class="btn btn-primary">Save Autotask Settings</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form method="post" class="mb-4" id="ticket-defaults-form">
|
||||||
<div class="card mb-3">
|
<div class="card mb-3">
|
||||||
<div class="card-header">Ticket defaults</div>
|
<div class="card-header">Ticket defaults</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label for="autotask_default_queue_id" class="form-label">Default Queue</label>
|
<label for="autotask_default_queue_id" class="form-label">Default Queue <span class="text-danger">*</span></label>
|
||||||
<select class="form-select" id="autotask_default_queue_id" name="autotask_default_queue_id">
|
<select class="form-select" id="autotask_default_queue_id" name="autotask_default_queue_id" required>
|
||||||
<option value="" {% if not settings.autotask_default_queue_id %}selected{% endif %}>Select...</option>
|
<option value="">Select...</option>
|
||||||
{% for q in autotask_queues %}
|
{% for q in autotask_queues %}
|
||||||
<option value="{{ q.id }}" {% if settings.autotask_default_queue_id == q.id %}selected{% endif %}>{{ q.name }}</option>
|
<option value="{{ q.id }}" {% if settings.autotask_default_queue_id == q.id %}selected{% endif %}>{{ q.name }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -398,9 +416,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label for="autotask_default_ticket_source_id" class="form-label">Ticket Source</label>
|
<label for="autotask_default_ticket_source_id" class="form-label">Ticket Source <span class="text-danger">*</span></label>
|
||||||
<select class="form-select" id="autotask_default_ticket_source_id" name="autotask_default_ticket_source_id">
|
<select class="form-select" id="autotask_default_ticket_source_id" name="autotask_default_ticket_source_id" required>
|
||||||
<option value="" {% if not settings.autotask_default_ticket_source_id %}selected{% endif %}>Select...</option>
|
<option value="">Select...</option>
|
||||||
{% for s in autotask_ticket_sources %}
|
{% for s in autotask_ticket_sources %}
|
||||||
<option value="{{ s.id }}" {% if settings.autotask_default_ticket_source_id == s.id %}selected{% endif %}>{{ s.name }}</option>
|
<option value="{{ s.id }}" {% if settings.autotask_default_ticket_source_id == s.id %}selected{% endif %}>{{ s.name }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -409,9 +427,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label for="autotask_default_ticket_status" class="form-label">Default Ticket Status</label>
|
<label for="autotask_default_ticket_status" class="form-label">Default Ticket Status <span class="text-danger">*</span></label>
|
||||||
<select class="form-select" id="autotask_default_ticket_status" name="autotask_default_ticket_status">
|
<select class="form-select" id="autotask_default_ticket_status" name="autotask_default_ticket_status" required>
|
||||||
<option value="" {% if not settings.autotask_default_ticket_status %}selected{% endif %}>Select...</option>
|
<option value="">Select...</option>
|
||||||
{% for st in autotask_ticket_statuses %}
|
{% for st in autotask_ticket_statuses %}
|
||||||
<option value="{{ st.id }}" {% if settings.autotask_default_ticket_status == st.id %}selected{% endif %}>{{ st.name }}</option>
|
<option value="{{ st.id }}" {% if settings.autotask_default_ticket_status == st.id %}selected{% endif %}>{{ st.name }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -420,9 +438,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label for="autotask_priority_warning" class="form-label">Priority for Warning</label>
|
<label for="autotask_priority_warning" class="form-label">Priority for Warning <span class="text-danger">*</span></label>
|
||||||
<select class="form-select" id="autotask_priority_warning" name="autotask_priority_warning">
|
<select class="form-select" id="autotask_priority_warning" name="autotask_priority_warning" required>
|
||||||
<option value="" {% if not settings.autotask_priority_warning %}selected{% endif %}>Select...</option>
|
<option value="">Select...</option>
|
||||||
{% for p in autotask_priorities %}
|
{% for p in autotask_priorities %}
|
||||||
<option value="{{ p.id }}" {% if settings.autotask_priority_warning == p.id %}selected{% endif %}>{{ p.name }}</option>
|
<option value="{{ p.id }}" {% if settings.autotask_priority_warning == p.id %}selected{% endif %}>{{ p.name }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -431,9 +449,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label for="autotask_priority_error" class="form-label">Priority for Error</label>
|
<label for="autotask_priority_error" class="form-label">Priority for Error <span class="text-danger">*</span></label>
|
||||||
<select class="form-select" id="autotask_priority_error" name="autotask_priority_error">
|
<select class="form-select" id="autotask_priority_error" name="autotask_priority_error" required>
|
||||||
<option value="" {% if not settings.autotask_priority_error %}selected{% endif %}>Select...</option>
|
<option value="">Select...</option>
|
||||||
{% for p in autotask_priorities %}
|
{% for p in autotask_priorities %}
|
||||||
<option value="{{ p.id }}" {% if settings.autotask_priority_error == p.id %}selected{% endif %}>{{ p.name }}</option>
|
<option value="{{ p.id }}" {% if settings.autotask_priority_error == p.id %}selected{% endif %}>{{ p.name }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -442,11 +460,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-text mt-2">Priorities are loaded from Autotask to avoid manual ID mistakes.</div>
|
<div class="form-text mt-2">Priorities are loaded from Autotask to avoid manual ID mistakes.</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-end mt-3">
|
<div class="d-flex justify-content-end mt-3">
|
||||||
<button type="submit" class="btn btn-primary">Save settings</button>
|
<button type="submit" class="btn btn-primary">Save Ticket Defaults</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@ -458,7 +476,7 @@
|
|||||||
<div class="text-muted small">Last reference data sync</div>
|
<div class="text-muted small">Last reference data sync</div>
|
||||||
<div class="fw-semibold">
|
<div class="fw-semibold">
|
||||||
{% if autotask_last_sync_at %}
|
{% if autotask_last_sync_at %}
|
||||||
{{ autotask_last_sync_at }}
|
{{ autotask_last_sync_at|local_datetime }}
|
||||||
{% else %}
|
{% else %}
|
||||||
never
|
never
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -26,7 +26,7 @@
|
|||||||
{% for read, user in reads %}
|
{% for read, user in reads %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ user.username }}</td>
|
<td>{{ user.username }}</td>
|
||||||
<td>{{ read.read_at }}</td>
|
<td>{{ read.read_at|local_datetime }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@ -73,7 +73,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% for r in runs %}
|
{% for r in runs %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-nowrap">{{ r.run_at }}</td>
|
<td class="text-nowrap">{{ r.run_at|local_datetime }}</td>
|
||||||
<td>{{ r.customer_name }}</td>
|
<td>{{ r.customer_name }}</td>
|
||||||
<td>{{ r.job_name }}</td>
|
<td>{{ r.job_name }}</td>
|
||||||
<td>{{ r.status }}</td>
|
<td>{{ r.status }}</td>
|
||||||
|
|||||||
@ -87,7 +87,7 @@
|
|||||||
<td class="text-end">{{ t.linked_runs }}</td>
|
<td class="text-end">{{ t.linked_runs }}</td>
|
||||||
<td class="text-nowrap">{{ t.active_from_date }}</td>
|
<td class="text-nowrap">{{ t.active_from_date }}</td>
|
||||||
<td class="text-nowrap">{{ t.start_date }}</td>
|
<td class="text-nowrap">{{ t.start_date }}</td>
|
||||||
<td class="text-nowrap">{{ t.resolved_at }}</td>
|
<td class="text-nowrap">{{ t.resolved_at|local_datetime }}</td>
|
||||||
<td class="text-nowrap">
|
<td class="text-nowrap">
|
||||||
<a class="btn btn-sm btn-outline-primary" href="{{ url_for('main.ticket_detail', ticket_id=t.id) }}">View</a>
|
<a class="btn btn-sm btn-outline-primary" href="{{ url_for('main.ticket_detail', ticket_id=t.id) }}">View</a>
|
||||||
{% if t.active and t.job_id %}
|
{% if t.active and t.job_id %}
|
||||||
@ -142,7 +142,7 @@
|
|||||||
<td>{{ r.scope_summary }}</td>
|
<td>{{ r.scope_summary }}</td>
|
||||||
<td class="text-end">{{ r.linked_runs }}</td>
|
<td class="text-end">{{ r.linked_runs }}</td>
|
||||||
<td class="text-nowrap">{{ r.start_date }}</td>
|
<td class="text-nowrap">{{ r.start_date }}</td>
|
||||||
<td class="text-nowrap">{{ r.resolved_at }}</td>
|
<td class="text-nowrap">{{ r.resolved_at|local_datetime }}</td>
|
||||||
<td class="text-nowrap">
|
<td class="text-nowrap">
|
||||||
<a class="btn btn-sm btn-outline-primary" href="{{ url_for('main.remark_detail', remark_id=r.id) }}">View</a>
|
<a class="btn btn-sm btn-outline-primary" href="{{ url_for('main.remark_detail', remark_id=r.id) }}">View</a>
|
||||||
{% if r.active and r.job_id %}
|
{% if r.active and r.job_id %}
|
||||||
|
|||||||
@ -2,6 +2,211 @@
|
|||||||
|
|
||||||
This file documents all changes made to this project via Claude Code.
|
This file documents all changes made to this project via Claude Code.
|
||||||
|
|
||||||
|
## [2026-02-09]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Updated `docs/changelog.md` with comprehensive v0.1.23 release notes consolidating all changes from 2026-02-06 through 2026-02-08 (Documentation System, Audit Logging, Timezone-Aware Display, Autotask Improvements, Environment Identification, Bug Fixes)
|
||||||
|
- Updated `containers/backupchecks/src/backend/app/changelog.py` with v0.1.23 entry in Python structure for website display (8 sections with subsections matching changelog.md content)
|
||||||
|
|
||||||
|
## [2026-02-08]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **Remarks & Tickets page**: Corrected "Integration with Autotask" section to clarify tickets are manually created (not automatically on failures), updated to describe manual ticket creation and PSA synchronization only
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Fixed critical inaccuracies in Backup Review documentation based on user feedback:
|
||||||
|
- **Approving Backups page**: Corrected Stage 6 to emphasize ALL runs must be marked as reviewed regardless of action taken (override/ticket/remark created), added warning callout that failing to mark as reviewed leaves runs visible with wrong status next day, corrected Stage 7 to clarify successful runs MUST also be reviewed (goal is empty Run Checks page), added bulk review functionality for efficiency, completely rewrote Complete Workflow Example to focus on Run Checks page (primary daily tool) instead of Daily Jobs, updated best practices and role-based workflows to prioritize Run Checks over Daily Jobs, added performance tips for bulk review, corrected bulk review section from "select multiple run checkboxes" to "select multiple job checkboxes" to reflect per-job review mechanism
|
||||||
|
- **Daily Jobs page**: Added blue "Success (Override)" status indicator to table showing runs treated as success due to active override, added callout clarifying Daily Jobs is for viewing schedules only and Run Checks is the primary operational tool (Daily Jobs may be deprecated in future), corrected page purpose from "primary operational dashboard" to schedule viewing tool
|
||||||
|
- **Run Checks Modal page**: Corrected overview to emphasize this is THE primary daily operational tool (not just detail modal), added callout that goal is completely empty page, corrected "Success Runs Don't Need Review" to "All Runs Must Be Reviewed" with explanation, added bulk review section (select multiple + mark all), updated status information to include blue badge for override-applied runs, changed access description from "modal" to "page", removed non-existent "reviewed indicator" from page layout, corrected review mechanism from per-run to per-JOB (when marking a job as reviewed, ALL runs within that job are marked simultaneously), updated Mark as Reviewed section to clarify all runs for the job disappear together, corrected bulk review from "select multiple run checkboxes" to "select multiple job checkboxes", updated Unmark Reviewed section to reflect per-job unmarking, removed incorrect statement that successful runs are automatically reviewed, fixed troubleshooting section to remove false claim about successful runs not needing review
|
||||||
|
- **Overrides & Exceptions page**: Updated "Treat as Success" action description to show blue badge instead of green, added visual differentiation explanation (green=genuine success, blue=override applied, yellow/red=unexpected problems)
|
||||||
|
- All changes ensure documentation accurately reflects actual workflow: Run Checks is primary tool, all runs need review (including successes), blue badges indicate overrides, review is per-JOB not per-run
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Completed Mail & Import documentation section (4 pages):
|
||||||
|
- **Mail Import Setup**: Microsoft Graph API configuration with enhanced security (Azure AD app registration, start with Mail.Read only, Application Access Policy to restrict access to one mailbox via Exchange PowerShell, add Mail.ReadWrite after testing), two-step save workflow clarification (same "Save settings" button clicked twice - once for credentials to enable folder browsing, then again for complete configuration), automatic folder browser popup (no manual path entry), test configuration, troubleshooting (authentication errors, folder not found, no emails imported), advanced configuration (email retention, multiple mailboxes), comprehensive security best practices including principle of least privilege, note about planned UI improvement to make two-step save workflow more obvious
|
||||||
|
- **Inbox Management**: Inbox overview and workflow (inbox shows ONLY unapproved emails - approved emails immediately disappear), table columns explanation (EML column shows download link, not checkmark), viewing email details with screenshot (approve-job.png), email detail modal TWO-COLUMN layout (left: metadata + action buttons + parsed objects, right: email body iframe), action buttons (Approve, Delete, Download .eml - NO Re-parse in modal), approving emails (customer selection ONLY - job name is READ-ONLY and cannot be edited), what happens after approval (email disappears from inbox, future emails auto-approved and never appear in inbox), re-parsing emails (ONLY "Re-parse all" button on inbox page - no individual re-parse), deleting emails (single and bulk delete, soft delete viewable on "Deleted Mails" page for Admins only), downloading .eml files, common workflows (new customer setup, troubleshooting unparsed emails - parsers can ONLY be created by developer, cleaning spam), best practices (verify parsed job names before approval - cannot edit during approval), removed Inbox Filters section (not in planning)
|
||||||
|
- **Mail Parsing**: Parsing overview and workflow (retrieval, preprocessing, parser selection, matching, parsing, storage), parsers (viewing on separate "Parsers" page - Admin only, NOT in Settings), parser information table (removed non-existent "Enabled" column, added callout that parser list is read-only), match criteria (sender, subject, body match), supported backup software (Veeam, Synology, NAKIVO, Boxafe, Panel3, QNAP, Syncovery, 3CX, RDrive, NTFS Auditing), parser execution order, REMOVED entire "Enabling and Disabling Parsers" section (feature doesn't exist!), re-parsing emails (ONLY "Re-parse all" available, no individual re-parse), parsing workflow example, troubleshooting parsing issues (contact support - users cannot enable/disable/modify parsers), parser limitations, REMOVED "AI-powered parsing" future enhancement (not in planning, completely fabricated), best practices (removed references to testing/enabling parsers - users cannot manage parsers)
|
||||||
|
- **Auto-Import Configuration**: Auto-import overview and features, enabling auto-import, import interval configuration (5/15/30/60 minutes), batch size explanation, how auto-import works (timer, check settings, authenticate, fetch, parse, auto-approve, move, log, persist, wait), auto-approval logic and conditions, when auto-approval fails, manual import (triggering, batch size 1-50), import settings summary table, monitoring import activity (audit log, inbox page, jobs/daily jobs), troubleshooting (auto-import not running, emails not auto-approved, import errors), best practices, advanced configuration (disabling temporarily, EML retention, high-volume environments)
|
||||||
|
- Completed Backup Review documentation section (5 pages):
|
||||||
|
- **Daily Jobs View**: Primary operational dashboard for monitoring backup operations, schedule inference from historical run patterns (weekly and monthly detection), status tracking (Success/Warning/Failed/Missed/Expected badges), missing job detection, override indicators with badges, ticket (🎫) and remark (💬) indicators, date selection with timezone support, job table columns (customer, backup software/type, job name, schedule, last run, status, reviewed checkmark for Admin only), schedule inference explanation (daily/weekly/monthly patterns, requires 10-14 days of data), override badges showing treated-as-success status, viewing job details via Run Checks modal, reviewing jobs (mark as reviewed for warnings/failures, successful jobs auto-reviewed), common workflows (morning review, checking previous days, identifying schedule changes), best practices (review daily, focus on failures first, watch for missed jobs, use overrides for known issues, create tickets for recurring issues), troubleshooting (job not appearing, schedule shows irregular/-, job shows missed but ran)
|
||||||
|
- **Run Checks Modal**: Detailed review interface for investigating backup job runs (opens from Daily Jobs or Job History), available to Admin and Operator roles only (not Viewer), modal layout with job header, run list (left side showing unreviewed runs by default, Admin can toggle "Include reviewed"), run details (right side showing status, backup objects table with name/type/status/message, email content in iframe with metadata and download .eml option, Autotask ticket information if integration enabled), review actions (Mark as reviewed for acknowledging issues, Unmark reviewed for Admin only), linking tickets and remarks from modal, backup objects table showing individual item statuses (objects only shown if parser extracted them), email body rendering (HTML with formatting, plain text as preformatted, sandboxed iframe for security), Autotask fields (ticket number, status, resolved origin, resolved at, deleted info with who/when), common workflows (investigating failures, comparing multiple runs, reviewing warnings with overrides, troubleshooting missed jobs), best practices (always check objects, read error messages carefully, review full email body, create tickets for recurring issues, use remarks for temporary issues, compare historical runs), troubleshooting (no backup objects shown, email body blank, can't mark as reviewed, run list is empty)
|
||||||
|
- **Overrides & Exceptions**: Override rules to automatically handle known issues and expected warnings, two override levels (global: by backup software/type affecting multiple jobs across customers, object-level: specific job or object within job checked first before global), match criteria (status match for any/success/warning/failed, error text match with contains/exact/starts_with/ends_with modes), treat as success action (enabled by default, displays green badge with override indicator instead of original status), time windows (From date optional for retroactive application to existing runs, Until date optional for permanent/temporary overrides), creating overrides (form with level/software/type/job/object/status/error match/time window/comment fields, retroactive application to unreviewed runs), managing overrides (existing overrides table showing level/scope/time/active/comment, Edit button to modify, Disable/Enable toggle, Delete for Admin only), override evaluation order (object-level first, then job-level, then global - first match wins), common scenarios with examples (Veeam retry warnings, planned maintenance windows, known issue with one VM, global NAKIVO replication warnings), best practices (start specific then generalize, always document with comments and ticket numbers, use time windows for temporary issues, review periodically, be specific with error text, test before global overrides, disable don't delete when unsure), troubleshooting (override not applied - check active status/time window/match criteria, override too broad - check global vs object level and error match type, multiple overrides conflicting - most specific wins)
|
||||||
|
- **Remarks & Tickets**: Two mechanisms for documenting issues (tickets for external followup requiring tracking, remarks for internal notes/known issues/temporary problems), accessing via Tickets page with two tabs (Tickets and Remarks tabs, filtering by status/customer/backup software/type/search), ticket properties (ticket code, description, active from date, start date, resolved at, scopes defining what it affects), creating tickets (from Run Checks modal or Tickets page, auto-linked to job run with scopes auto-populated), ticket scopes (customer/backup software/backup type/job/job run levels for automatic ticket indicators), viewing ticket details (status, code, dates, scopes list, linked runs last 20), resolving tickets (marks as resolved with timestamp, remains visible with ✅ indicator, 🎫 removed from job displays, Admin/Operator only), linking tickets to additional runs, remark properties (body freeform text, start date, resolved at, scopes), creating remarks (from Run Checks modal or Remarks tab, similar to tickets but simpler), viewing remark details (status, body, scopes, linked runs), resolving remarks (similar to tickets, 💬 removed from displays), filtering options (status active/all, customer, backup software/type, search by ticket code or job name), tickets vs remarks comparison table (when to use each), ticket/remark indicators (🎫 for active tickets, 💬 for active remarks in Daily Jobs and Job History), common workflows (creating ticket for recurring failure, adding remark for planned maintenance, resolving after customer action, reviewing historical tickets), best practices (create tickets promptly, use descriptive codes referencing PSA, set accurate active from dates, resolve promptly, use remarks for temporary issues, link to multiple runs, review monthly, use remarks for documentation), Autotask integration mention for automatic ticket creation, troubleshooting (indicator not showing - check active status and date and scope, cannot create - check role permissions, scope affecting wrong jobs - review scopes breadth), ticket editing currently disabled (resolve old and create new)
|
||||||
|
- **Approving Backups**: Complete backup review lifecycle workflow from email import to marking runs as reviewed, seven lifecycle stages (email import, parsing, inbox approval, automatic processing, daily monitoring, run review, issue tracking, mark as reviewed), Stage 1 Email Import & Parsing (auto-import from mailbox via Graph API every 15 minutes, parsers extract structured backup information), Stage 2 Inbox Approval (new job emails appear in inbox, review and approve with customer selection, creates Job and JobRun records, email disappears from inbox, future emails auto-approved), Stage 3 Automatic Processing (matching emails auto-create JobRun records, no inbox appearance, immediate Daily Jobs visibility), Stage 4 Daily Monitoring (schedule inference from patterns, status badges, overrides applied automatically, ticket/remark indicators shown), Stage 5 Run Review (click job in Daily Jobs to open Run Checks modal, investigate objects/errors/email content, determine appropriate action), Stage 6 Issue Tracking with four options (mark as reviewed for no action needed, create override for recurring expected issues, create remark for temporary/internal notes, create ticket for external followup), Stage 7 Mark as Reviewed (marks with review timestamp, disappears from unreviewed list, creates audit record, successful runs auto-reviewed), complete workflow example from Day 1 new customer onboarding through Day 15 issue resolved (showing real timestamps and decision points), role-based workflows (Operator: review/create tickets/remarks/overrides/mark reviewed, Admin: same plus view reviewed runs and unmark and delete, Viewer: read-only view Daily Jobs only), performance tips (use filters, review by exception with overrides, batch approve inbox, create global overrides for common warnings, use tickets for tracking workload, archive resolved tickets), best practices (review daily, approve inbox quickly, triage by status, use overrides for recurring warnings, create tickets for customer action, use remarks for temporary notes, always check objects, document in comments, resolve tickets promptly, monitor schedule inference)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Added comprehensive documentation system for user onboarding and reference:
|
||||||
|
- **Documentation Blueprint**: New `/documentation` route with dedicated blueprint (doc_bp)
|
||||||
|
- **Navigation Structure**: Hierarchical documentation with 9 sections and 33 pages covering all features
|
||||||
|
- Getting Started (3 pages): What is BackupChecks, First Login, Quick Start
|
||||||
|
- User Management (3 pages): Users & Roles, Authentication, Profile Settings
|
||||||
|
- Customers & Jobs (4 pages): Managing Customers, Configuring Jobs, Approved Jobs, Job Schedules
|
||||||
|
- Mail & Import (4 pages): Setup, Inbox Management, Mail Parsing, Auto-Import
|
||||||
|
- Backup Review (5 pages): Approving Backups, Daily Jobs, Run Checks Modal, Overrides, Remarks & Tickets
|
||||||
|
- Reports (4 pages): Creating Reports, Relative Periods, Scheduling, Exporting Data
|
||||||
|
- Autotask Integration (4 pages): Setup, Company Mapping, Creating Tickets, Ticket Management
|
||||||
|
- Settings (6 pages): General, Mail, Autotask, Reporting, User Management, Maintenance
|
||||||
|
- Troubleshooting (3 pages): Common Issues, FAQ, Support Contact
|
||||||
|
- **UI Components**:
|
||||||
|
- Sidebar navigation with collapsible sections and active page highlighting
|
||||||
|
- Breadcrumb navigation for current location context
|
||||||
|
- Previous/Next pagination buttons for sequential reading
|
||||||
|
- Documentation menu item in main navbar (📖 icon) visible to all authenticated users
|
||||||
|
- **Styling**:
|
||||||
|
- Custom CSS with support for dark mode
|
||||||
|
- Callout boxes (info, warning, tip, danger) for highlighting important content
|
||||||
|
- Code blocks, tables, and image support
|
||||||
|
- Responsive design for mobile and desktop
|
||||||
|
- **Access Control**: Login required (@login_required) - accessible to all user roles
|
||||||
|
- **Current Status**: Core infrastructure complete, getting-started (3 pages), users (3 pages), and customers-jobs (4 pages) sections completed with comprehensive content
|
||||||
|
- **Placeholder Pages**: Remaining 23 pages created with basic structure for future content
|
||||||
|
- Full content for remaining sections will be added incrementally in future updates
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Enhanced documentation accuracy in Users section:
|
||||||
|
- **Login & Authentication page**: Added captcha requirement explanation (simple math problem), noted future plan to make it optional via Settings
|
||||||
|
- **Browser Security section**: Clarified HTTPS context - only relevant for external access via domain name, not for internal IP-based deployments (typical use case)
|
||||||
|
- **Browser Compatibility**: Marked Mozilla Firefox as "Recommended - Most tested" browser for optimal experience
|
||||||
|
- **Profile Settings page**: Streamlined to reflect actual functionality - only password change available in User Settings page, theme and role selectors located in navigation bar
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Removed non-existent features from Profile Settings documentation:
|
||||||
|
- Notification Preferences section (no plans for implementation)
|
||||||
|
- Session Information section (not displayed in User Settings)
|
||||||
|
- Redundant theme/role configuration sections (these are in navbar, not settings page)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Completed Customers & Jobs documentation section (4 pages):
|
||||||
|
- **Managing Customers**: Customer creation, editing, activation/deactivation, Autotask company mapping, CSV export/import, delete operations
|
||||||
|
- **Configuring Jobs**: Inbox-based job approval workflow, Mail Parser automatic configuration, Reparse All functionality, job archiving and deletion
|
||||||
|
- **Approved Jobs**: Jobs list overview, job details, status indicators, archive/unarchive workflow, JSON export/import for migration
|
||||||
|
- **Job Schedules**: Automatic schedule learning, schedule types (daily/weekly/monthly), Daily Jobs integration, schedule accuracy and troubleshooting
|
||||||
|
- Added user-settings.png screenshot showing password change form
|
||||||
|
- Enhanced documentation CSS: centered all images horizontally (display: block, margin: 20px auto)
|
||||||
|
|
||||||
|
## [2026-02-07]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Renamed AdminLog to AuditLog for better semantic clarity:
|
||||||
|
- **Model**: AdminLog → AuditLog (backwards compatible alias maintained)
|
||||||
|
- **Table**: admin_logs → audit_logs (automatic migration)
|
||||||
|
- **Function**: log_admin_event() → log_audit_event() (alias provided)
|
||||||
|
- Better reflects purpose as comprehensive audit trail for both user and system events
|
||||||
|
- Updated UI labels to reflect audit log semantics:
|
||||||
|
- Changed "Admin activity" to "System Audit Log" in logging page header
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Expanded audit logging for critical operations:
|
||||||
|
- **Settings Changes**: Now logs all changes to General, Mail, and Autotask settings
|
||||||
|
- Tracks which settings changed with old value → new value comparison
|
||||||
|
- Event types: `settings_general`, `settings_mail`, `settings_autotask`
|
||||||
|
- Excludes sensitive data (passwords are never logged)
|
||||||
|
- Example logged fields: ui_timezone, require_daily_dashboard_visit, is_sandbox_environment, graph_mailbox, autotask_enabled
|
||||||
|
- **Export Operations**: Logs when users export data
|
||||||
|
- **Customers export** (event type: `export_customers`): CSV format, record count
|
||||||
|
- **Jobs export** (event type: `export_jobs`): JSON schema version, customer/job counts
|
||||||
|
- **Import Operations**: Logs when users import data
|
||||||
|
- **Customers import** (event type: `import_customers`): CSV format, created/updated/skipped counts
|
||||||
|
- **Jobs import** (event type: `import_jobs`): JSON schema version, all operation counts (customers and jobs)
|
||||||
|
- All logging uses structured event_type for filtering and includes detailed JSON in details field
|
||||||
|
- Maintains 7-day retention policy
|
||||||
|
- No performance impact (async logging)
|
||||||
|
- Helps with compliance, troubleshooting, and security audits
|
||||||
|
|
||||||
|
## [2026-02-06]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Added Sandbox/Development environment indicator feature:
|
||||||
|
- **Database Model**: Added `is_sandbox_environment` boolean column to `SystemSettings` model (defaults to `False` for production safety)
|
||||||
|
- **Database Migration**: Created `migrate_system_settings_sandbox_environment()` function for automatic schema update on deployment (idempotent, safe to run multiple times)
|
||||||
|
- **Backend Routes**:
|
||||||
|
- Extended `routes_settings.py` to process the new checkbox setting in the General tab
|
||||||
|
- Updated `routes_shared.py` context processor to inject `system_settings` into all template contexts
|
||||||
|
- **Settings UI**:
|
||||||
|
- Added new "Environment" card in Settings > General (placed after Navigation section)
|
||||||
|
- Toggle switch with clear English description: "Mark this as a Sandbox/Development environment"
|
||||||
|
- Help text explains visual banner behavior
|
||||||
|
- **CSS Styling**:
|
||||||
|
- Created `sandbox.css` with diagonal red banner styling
|
||||||
|
- Position: top-left corner, rotated 45 degrees
|
||||||
|
- Color: Bootstrap danger red (#dc3545) with white text
|
||||||
|
- Z-index: 9999 (always on top)
|
||||||
|
- `pointer-events: none` - banner itself is non-interactive, elements behind it remain clickable
|
||||||
|
- Box-shadow for depth effect
|
||||||
|
- **Base Template**:
|
||||||
|
- Banner conditionally displayed only when setting is enabled
|
||||||
|
- CSS include added to `<head>` section
|
||||||
|
- Banner placed directly after `<body>` tag, before navbar
|
||||||
|
- The banner displays "SANDBOX" in uppercase with letter-spacing for clear visibility across all pages
|
||||||
|
- Auto-load Autotask reference data on Settings page load:
|
||||||
|
- Automatically fetches queues, ticket sources, statuses, and priorities when opening Settings
|
||||||
|
- Only triggers when Autotask is enabled, credentials are configured, and cache is empty
|
||||||
|
- Eliminates need to manually click "Refresh reference data" before selecting defaults
|
||||||
|
- Displays info message with loaded data counts
|
||||||
|
- Extended export/import functionality to include Autotask company mappings:
|
||||||
|
- **Customer Export/Import** (CSV at /customers/export):
|
||||||
|
- Export now includes `autotask_company_id` and `autotask_company_name` columns
|
||||||
|
- Import reads Autotask mapping fields and applies them to existing or new customers
|
||||||
|
- Backwards compatible - old CSV files without Autotask columns still work
|
||||||
|
- Import resets `autotask_mapping_status` and `autotask_last_sync_at` to allow resynchronization
|
||||||
|
- **Jobs Export/Import** (JSON at Settings > Maintenance):
|
||||||
|
- Export now includes Autotask fields in customers array (`autotask_company_id`, `autotask_company_name`)
|
||||||
|
- Import processes customers array first, applying Autotask mappings before creating jobs
|
||||||
|
- Schema remains `approved_jobs_export_v1` for backwards compatibility
|
||||||
|
- Import message now shows both created and updated customer counts
|
||||||
|
- Enables preservation of Autotask company mappings during system reset/migration workflows
|
||||||
|
- Implemented timezone-aware datetime display across all pages:
|
||||||
|
- **Template Filter**: Added `local_datetime` Jinja2 filter to convert UTC datetimes to UI timezone
|
||||||
|
- **Automatic Conversion**: All datetime fields now automatically display in configured timezone (Settings > General > UI Timezone)
|
||||||
|
- **Database**: All datetime values remain stored in UTC for consistency
|
||||||
|
- **Affected Pages**: Customers (Autotask sync time), Settings (reference data sync), Feedback, Logging, Overrides, Archived Jobs, Tickets, Remarks, Inbox, Job Detail, Admin Mail
|
||||||
|
- **Custom Format Support**: Filter accepts strftime format parameter (e.g., `|local_datetime('%d-%m-%Y %H:%M')`)
|
||||||
|
- **Graceful Fallback**: Falls back to UTC display if timezone conversion fails
|
||||||
|
- **Default Timezone**: Europe/Amsterdam (configurable via SystemSettings.ui_timezone)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Renamed "Refresh" button to "Search" in Link existing Autotask ticket modal for better clarity (the button performs a search operation)
|
||||||
|
- Added ellipsis-field functionality to "Overall remark" and "Remark" fields in Run Checks modal to prevent long text from hiding action buttons (click to expand/collapse)
|
||||||
|
- Enhanced Autotask ticket display in Run Checks modal:
|
||||||
|
- Added "Open in Autotask" button next to ticket number for direct navigation to ticket in Autotask PSA
|
||||||
|
- Button only appears when ticket is linked (autotask_ticket_id exists) and Autotask integration is enabled
|
||||||
|
- Opens in new tab with proper URL format including workspace and ticket ID parameters
|
||||||
|
- Styled as small outline button to maintain compact layout
|
||||||
|
- Merged all feature branches from v20260203-01 through v20260205-13 into main branch
|
||||||
|
- Consolidated 29 feature branches spanning three development cycles
|
||||||
|
- Used final state from v20260205-13-changelog-python-structure to preserve all functionality
|
||||||
|
- Successfully integrated Autotask PSA integration, changelog restructuring, and UI improvements
|
||||||
|
- All features from individual branches now available in main
|
||||||
|
- Reorganized Autotask settings into two separate forms with dedicated save buttons:
|
||||||
|
- **Autotask Settings** form with "Save Autotask Settings" button inside the card
|
||||||
|
- **Ticket Defaults** form with "Save Ticket Defaults" button inside the card
|
||||||
|
- All fields marked as required with red asterisks (*)
|
||||||
|
- HTML5 validation prevents saving incomplete configurations
|
||||||
|
- Clear visual separation improves UX and prevents accidental saves
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Jobs from archived jobs and inactive customers no longer appear on Daily Jobs, Run Checks, and Jobs list pages
|
||||||
|
- Added customer active status filtering to all job list queries
|
||||||
|
- Jobs now filtered where customer is NULL or active=True alongside existing archived=False filter
|
||||||
|
- Prevents showing jobs with "-" status from deleted customers or archived jobs
|
||||||
|
- Default Ticket Status dropdown no longer appears empty on first visit to Settings page
|
||||||
|
- Default Ticket Status value is now protected against accidental clearing:
|
||||||
|
- Only updates when a valid status is selected
|
||||||
|
- Only allows clearing if reference data is loaded (dropdown has options)
|
||||||
|
- Preserves existing value if dropdown is empty (prevents data loss)
|
||||||
|
- Fixes issue where "Create Autotask ticket" failed due to missing default status after saving settings with empty dropdown
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Cleaned up 66 merged feature branches from repository (both local and remote):
|
||||||
|
- Removed all v20260113-* branches (8 branches)
|
||||||
|
- Removed all v20260115-* branches (17 branches)
|
||||||
|
- Removed all v20260116-* branches (12 branches)
|
||||||
|
- Removed all v20260119-* branches (18 branches)
|
||||||
|
- Removed all v20260120-* branches (11 branches)
|
||||||
|
- Repository now contains only main branch and current working branches (v20260206-*)
|
||||||
|
|
||||||
## [2026-02-05]
|
## [2026-02-05]
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@ -1,3 +1,238 @@
|
|||||||
|
## v0.1.23
|
||||||
|
|
||||||
|
This comprehensive release introduces a complete built-in documentation system with 33 pages covering all features, enhanced audit logging for compliance and troubleshooting, timezone-aware datetime display throughout the application, and numerous Autotask PSA integration improvements for better usability and workflow efficiency.
|
||||||
|
|
||||||
|
### Documentation System – Complete Built-in Wiki
|
||||||
|
|
||||||
|
**Core Infrastructure:**
|
||||||
|
- New `/documentation` route with dedicated blueprint (doc_bp) accessible via 📖 icon in main navbar
|
||||||
|
- Hierarchical structure with 9 sections and 33 pages covering all application features
|
||||||
|
- Sidebar navigation with collapsible sections, active page highlighting, and sticky positioning during scrolling
|
||||||
|
- Breadcrumb navigation for current location context
|
||||||
|
- Previous/Next pagination buttons for sequential reading
|
||||||
|
- Custom CSS (`documentation.css`) with dark mode support via CSS variables
|
||||||
|
- Callout boxes (info, warning, tip, danger) for highlighting important content
|
||||||
|
- Support for code blocks, tables, images (centered horizontally), and responsive design
|
||||||
|
- Login required but accessible to all user roles
|
||||||
|
- Full content includes comprehensive screenshots, workflow examples, troubleshooting guides, and best practices
|
||||||
|
|
||||||
|
**Completed Documentation Sections (13 pages with full content):**
|
||||||
|
|
||||||
|
*Getting Started (3 pages):*
|
||||||
|
- What is BackupChecks: Overview, key features, architecture, supported backup software
|
||||||
|
- First Login: Initial setup, role-based menu visibility, theme/role switching, navigation tips
|
||||||
|
- Quick Start Guide: Complete 5-step setup workflow from mail import to daily review
|
||||||
|
|
||||||
|
*User Management (3 pages):*
|
||||||
|
- Users & Roles: User management interface, role descriptions (Admin/Operator/Reporter/Viewer), role assignment via checkboxes, password reset, admin protection
|
||||||
|
- Login & Authentication: Login process with captcha (simple math problem), browser compatibility (Firefox recommended), HTTPS context for external access, session management
|
||||||
|
- Profile Settings: Password change functionality, theme selector in navbar (not settings page), role switcher in navbar
|
||||||
|
|
||||||
|
*Customers & Jobs (4 pages):*
|
||||||
|
- Managing Customers: Customer creation, editing, activation/deactivation, Autotask company mapping with auto-search, CSV export/import with Autotask fields, deletion
|
||||||
|
- Configuring Jobs: Inbox-based approval workflow, customer selection (job name read-only), Mail Parser automatic configuration, Reparse All functionality, job archiving/deletion
|
||||||
|
- Approved Jobs: Jobs list overview, job details with history, status indicators, archive/unarchive workflow, JSON export/import for migration
|
||||||
|
- Job Schedules: Automatic schedule learning from patterns (daily/weekly/monthly), Daily Jobs integration with schedule indicators, requires 10-14 days of data, troubleshooting
|
||||||
|
|
||||||
|
*Mail & Import (4 pages with comprehensive security guidance):*
|
||||||
|
- Mail Import Setup: Microsoft Graph API configuration, Azure AD app registration with principle of least privilege (start with Mail.Read, add Mail.ReadWrite after testing), Application Access Policy via Exchange PowerShell to restrict to one mailbox, two-step save workflow clarification (save credentials first to enable folder browser, then save complete config), automatic folder browser (no manual path entry), connection testing, troubleshooting authentication and import issues, email retention configuration, advanced multi-mailbox setup
|
||||||
|
- Inbox Management: Inbox overview (shows ONLY unapproved emails, approved disappear immediately), table columns with EML download links, email detail modal with two-column layout (left: metadata/actions/objects, right: email iframe), approve workflow (customer selection only, job name read-only), what happens after approval (auto-approve future emails), re-parsing (only "Re-parse all" button, no individual re-parse), single/bulk delete with soft delete for Admin, downloading .eml files, common workflows for new customer setup and troubleshooting, best practices for verifying parsed job names
|
||||||
|
- Mail Parsing: Parsing workflow (retrieval, preprocessing, parser selection, matching, parsing, storage), viewing parsers on dedicated Parsers page (Admin-only, read-only list), match criteria (sender/subject/body), supported backup software list (Veeam, Synology, NAKIVO, Boxafe, Panel3, QNAP, Syncovery, 3CX, RDrive, NTFS Auditing), parser execution order, re-parsing emails via "Re-parse all" only, parsing workflow examples, troubleshooting (contact support for parser issues - users cannot enable/disable/modify parsers), parser limitations, best practices
|
||||||
|
- Auto-Import Configuration: Auto-import overview, enabling/disabling toggle, import interval (5/15/30/60 minutes), batch size explanation, detailed workflow (timer → authenticate → fetch → parse → auto-approve → move → log → persist), auto-approval logic and conditions, manual import trigger (batch size 1-50), settings summary table, monitoring via audit log and inbox, troubleshooting auto-import issues, best practices for high-volume environments, EML retention configuration
|
||||||
|
|
||||||
|
*Backup Review (5 pages with detailed workflows):*
|
||||||
|
- Approving Backups: Complete backup review lifecycle with 7 stages (Email Import & Parsing → Inbox Approval → Automatic Processing → Daily Monitoring → Run Review → Issue Tracking → Mark as Reviewed), stage-by-stage workflow descriptions with timestamps, complete workflow example from Day 1 new customer onboarding through Day 15 issue resolved, role-based workflows (Operator: review/create tickets/remarks/overrides/mark reviewed; Admin: plus view reviewed and unmark and delete; Viewer: read-only Daily Jobs), performance tips (filters, review by exception with overrides, batch approve, global overrides for common warnings, tickets for workload tracking, archive resolved tickets), best practices (review daily, approve inbox quickly, triage by status, use overrides for recurring warnings, create tickets for customer action, use remarks for temporary notes, always check objects, document in comments, resolve promptly, monitor schedule inference)
|
||||||
|
- Daily Jobs View: Schedule inference from historical patterns (weekly/monthly detection requires 10-14 days data), status tracking (Success/Warning/Failed/Missed/Expected badges with color coding), missing job detection, override indicators with blue badges showing treated-as-success status, ticket (🎫) and remark (💬) indicators, date selection with timezone support, job table columns (customer, backup software/type, job name, schedule, last run, status, reviewed checkmark Admin-only), schedule explanation (daily/weekly/monthly patterns, irregular/-), viewing job details via Run Checks modal, reviewing jobs (mark as reviewed for warnings/failures, successful runs also need review), common workflows (morning review, checking previous days, identifying schedule changes), best practices (review daily, focus on failures first, watch for missed jobs, use overrides for known issues, create tickets for recurring issues), troubleshooting (job not appearing, schedule shows irregular, missed status issues), clarified Daily Jobs is for viewing schedules and Run Checks is primary operational tool
|
||||||
|
- Run Checks Modal: THE primary daily operational tool (goal: completely empty page), detailed review interface for investigating backup runs (opens from Daily Jobs or Job History), Admin/Operator access only (not Viewer), modal layout (job header, run list left showing unreviewed by default with Admin toggle for reviewed, run details right with status/objects/email/Autotask), review actions (Mark as reviewed acknowledges issues and makes all runs for job disappear together, Unmark reviewed Admin-only per-job action), linking tickets/remarks from modal, backup objects table (name/type/status/message, only if parser extracted them), email body rendering (HTML with formatting, plain text preformatted, sandboxed iframe), Autotask fields (ticket number with "Open in Autotask" direct link button, status, resolved origin, resolved at, deletion info), common workflows (investigating failures, comparing multiple runs, reviewing warnings with overrides, troubleshooting missed), best practices (always check objects, read error messages carefully, review full email body, create tickets for recurring issues, use remarks for temporary issues, compare historical runs), troubleshooting (no objects shown, email body blank, can't mark as reviewed, run list empty), corrected review mechanism from per-run to per-JOB (when marking job as reviewed, ALL runs within that job marked simultaneously), bulk review via multiple job checkboxes (not run checkboxes), blue badge for override-applied runs
|
||||||
|
- Overrides & Exceptions: Override rules for automatically handling known issues and expected warnings, two levels (global: by backup software/type affecting multiple jobs/customers; object-level: specific job or object checked first), match criteria (status: any/success/warning/failed; error text: contains/exact/starts_with/ends_with modes), treat as success action (enabled by default, displays blue badge with override indicator instead of original status for visual differentiation - green=genuine success, blue=override applied, yellow/red=unexpected problems), time windows (From date optional for retroactive application to existing runs, Until date optional for permanent/temporary overrides), creating overrides (form with level/software/type/job/object/status/error match/time window/comment fields, retroactive application to unreviewed runs), managing overrides (table showing level/scope/time/active/comment, Edit to modify, Disable/Enable toggle, Delete Admin-only), override evaluation order (object-level first → job-level → global, first match wins), common scenarios with examples (Veeam retry warnings, planned maintenance windows, known VM issue, global NAKIVO replication warnings), best practices (start specific then generalize, always document with comments/ticket numbers, use time windows for temporary issues, review periodically, be specific with error text, test before global, disable don't delete when unsure), troubleshooting (override not applied - check active/time window/match criteria; override too broad - check level and error match; multiple conflicts - most specific wins)
|
||||||
|
- Remarks & Tickets: Two documentation mechanisms (tickets: external followup requiring tracking in PSA; remarks: internal notes/known issues/temporary problems), accessing via Tickets page with two tabs (Tickets/Remarks, filtering by status/customer/backup software/type/search), ticket properties (ticket code auto-generated format, description, active from date, start date, resolved at, scopes defining affected jobs), creating tickets (from Run Checks modal or Tickets page, auto-linked to job run with scopes auto-populated, manual creation without PSA integration mentioned), ticket scopes (customer/backup software/type/job/run levels for automatic indicators), viewing ticket details (status, code, dates, scopes list, linked runs last 20), resolving tickets (marks with timestamp, remains visible with ✅, 🎫 removed from displays, Admin/Operator only), linking tickets to additional runs, remark properties (body freeform text, start date, resolved at, scopes), creating/viewing/resolving remarks (similar to tickets but simpler), filtering options, tickets vs remarks comparison table, indicators in Daily Jobs and Job History (🎫 active tickets, 💬 active remarks), common workflows (creating ticket for recurring failure, adding remark for planned maintenance, resolving after customer action, reviewing historical tickets), best practices (create tickets promptly, use descriptive codes referencing PSA, set accurate active from dates, resolve promptly, use remarks for temporary issues, link to multiple runs, review monthly, use remarks for documentation), Autotask integration section corrected to clarify tickets are manually created (not automatically on failures), manual ticket creation and PSA synchronization only, troubleshooting (indicator not showing - check active status/date/scope; cannot create - check role permissions; scope affecting wrong jobs - review breadth), ticket editing currently disabled (resolve old and create new)
|
||||||
|
|
||||||
|
**Placeholder Pages (20 pages with basic structure for future content):**
|
||||||
|
- Reports (4 pages): Creating Reports, Relative Periods, Scheduling, Exporting Data
|
||||||
|
- Autotask Integration (4 pages): Setup, Company Mapping, Creating Tickets, Ticket Management
|
||||||
|
- Settings (6 pages): General, Mail, Autotask, Reporting, User Management, Maintenance
|
||||||
|
- Troubleshooting (3 pages): Common Issues, FAQ, Support Contact
|
||||||
|
|
||||||
|
**Documentation Accuracy Corrections:**
|
||||||
|
- Fixed critical inaccuracies based on user feedback throughout Backup Review section
|
||||||
|
- Corrected Run Checks from "modal" to "primary daily operational tool" with goal of completely empty page
|
||||||
|
- Emphasized ALL runs must be reviewed regardless of status (including successful runs to prove accountability)
|
||||||
|
- Corrected review mechanism from per-run to per-JOB (marking one job reviews all its runs simultaneously)
|
||||||
|
- Corrected bulk review from "select multiple run checkboxes" to "select multiple job checkboxes" to reflect per-job mechanism
|
||||||
|
- Updated override status indicators to show blue badges (not green) for visual differentiation from genuine successes
|
||||||
|
- Fixed Daily Jobs purpose from "primary operational dashboard" to "schedule viewing tool" with callout that it may be deprecated in future
|
||||||
|
- Removed non-existent features: individual email re-parse in modal, parser enable/disable UI, AI-powered parsing, Inbox filters section
|
||||||
|
- Fixed various UI descriptions: EML column shows download link (not checkmark), customer selection workflow (job name read-only), email detail modal two-column layout, "Re-parse all" button location (inbox page only), parser management (read-only list on separate Parsers page), two-step save workflow for Mail Import (same button clicked twice), Application Access Policy security requirement
|
||||||
|
- Corrected login authentication: added captcha requirement (simple math problem), clarified HTTPS context only for external access (not internal IP deployments)
|
||||||
|
- Fixed user management: checkboxes for role assignment (not comma-separated), admin protection, password reset section, removed non-existent email field
|
||||||
|
- Streamlined Profile Settings: only password change in User Settings page, theme/role selectors in navbar (not settings page), removed non-existent notification preferences and session information sections
|
||||||
|
- Enhanced Mail Import Setup with comprehensive security best practices: principle of least privilege (start Mail.Read only), Application Access Policy to restrict to one mailbox, add Mail.ReadWrite only after testing
|
||||||
|
|
||||||
|
### Audit Logging Enhancements
|
||||||
|
|
||||||
|
**System Renaming and Semantic Clarity:**
|
||||||
|
- Renamed AdminLog to AuditLog for better semantic clarity across codebase
|
||||||
|
- Updated model name: AdminLog → AuditLog (backwards compatible alias maintained)
|
||||||
|
- Updated table name: admin_logs → audit_logs (automatic migration)
|
||||||
|
- Updated function: log_admin_event() → log_audit_event() (alias provided for compatibility)
|
||||||
|
- Updated UI labels: "Admin activity" → "System Audit Log" in logging page header
|
||||||
|
- Better reflects purpose as comprehensive audit trail for both user and system events
|
||||||
|
|
||||||
|
**Expanded Event Coverage for Compliance:**
|
||||||
|
- **Settings Changes**: Now logs all changes to General, Mail, and Autotask settings
|
||||||
|
- Tracks which settings changed with old value → new value comparison
|
||||||
|
- Event types: `settings_general`, `settings_mail`, `settings_autotask`
|
||||||
|
- Excludes sensitive data (passwords are never logged)
|
||||||
|
- Example logged fields: ui_timezone, require_daily_dashboard_visit, is_sandbox_environment, graph_mailbox, autotask_enabled, autotask_environment, ticket defaults
|
||||||
|
- **Export Operations**: Logs when users export data with full traceability
|
||||||
|
- **Customers export** (event type: `export_customers`): CSV format, record count
|
||||||
|
- **Jobs export** (event type: `export_jobs`): JSON schema version (approved_jobs_export_v1), customer count, job count
|
||||||
|
- **Import Operations**: Logs when users import data with detailed operation counts
|
||||||
|
- **Customers import** (event type: `import_customers`): CSV format, created count, updated count, skipped count
|
||||||
|
- **Jobs import** (event type: `import_jobs`): JSON schema version, customer operations (created/updated/skipped), job operations (created/updated/skipped)
|
||||||
|
- All logging uses structured event_type for filtering and analysis
|
||||||
|
- Detailed JSON in details field for complete audit trail
|
||||||
|
- Maintains 7-day retention policy for performance
|
||||||
|
- No performance impact (async logging)
|
||||||
|
- Helps with compliance audits, security monitoring, and troubleshooting configuration issues
|
||||||
|
|
||||||
|
### Timezone-Aware Datetime Display
|
||||||
|
|
||||||
|
**Consistent Datetime Handling Across Application:**
|
||||||
|
- Added `local_datetime` Jinja2 template filter to convert UTC datetimes to configured UI timezone
|
||||||
|
- All datetime fields now automatically display in configured timezone (Settings > General > UI Timezone)
|
||||||
|
- Database values remain stored in UTC for consistency and reliability
|
||||||
|
- **Affected Pages**: Customers (Autotask last sync time), Settings (reference data last sync), Feedback (created/updated/resolved timestamps), Logging (event timestamps), Overrides (from/until dates), Archived Jobs (archived at), Tickets (active from/start/resolved dates), Remarks (start/resolved dates), Inbox (received timestamps), Job Detail (run timestamps), Admin Mail (received timestamps)
|
||||||
|
- Custom format support via strftime parameter (e.g., `|local_datetime('%d-%m-%Y %H:%M')`)
|
||||||
|
- Enhanced filter to handle both datetime objects and string datetime values for flexibility
|
||||||
|
- Graceful fallback to UTC display if timezone conversion fails
|
||||||
|
- Default timezone: Europe/Amsterdam (configurable via SystemSettings.ui_timezone)
|
||||||
|
- Improves operator experience by showing times in local context
|
||||||
|
|
||||||
|
### Autotask PSA Integration Improvements
|
||||||
|
|
||||||
|
**Usability Enhancements:**
|
||||||
|
- Added "Open in Autotask" button in Run Checks modal next to ticket number for direct navigation to tickets in Autotask PSA
|
||||||
|
- Opens in new tab with proper URL format including workspace and ticket ID parameters
|
||||||
|
- Button only appears when ticket is linked (autotask_ticket_id exists) and Autotask integration is enabled
|
||||||
|
- Styled as small outline button to maintain compact layout
|
||||||
|
- Eliminates manual ticket lookup in Autotask
|
||||||
|
- Added collapsible text functionality (ellipsis-field) to "Overall remark" and "Remark" fields in Run Checks modal
|
||||||
|
- Prevents long text from pushing action buttons off-screen or hiding content
|
||||||
|
- Click to expand/collapse long text
|
||||||
|
- Improves modal usability with lengthy error messages
|
||||||
|
- Auto-load Autotask reference data when opening Settings page
|
||||||
|
- Automatically fetches queues, ticket sources, statuses, and priorities on page load
|
||||||
|
- Only triggers when Autotask is enabled, credentials are configured, and cache is empty
|
||||||
|
- Eliminates need to manually click "Refresh reference data" before selecting defaults
|
||||||
|
- Displays info message with loaded data counts (e.g., "Loaded 5 queues, 3 sources, 8 statuses, 4 priorities")
|
||||||
|
- Improves first-time configuration workflow
|
||||||
|
- Renamed "Refresh" button to "Search" in Link existing Autotask ticket modal for better clarity
|
||||||
|
- Button performs a search operation, not a refresh operation
|
||||||
|
- Reduces user confusion during ticket linking workflow
|
||||||
|
|
||||||
|
**Settings Organization and Validation:**
|
||||||
|
- Reorganized Autotask settings into two separate forms with dedicated save buttons for better UX
|
||||||
|
- **Autotask Settings** form (connection, environment, API credentials, tracking identifier) with "Save Autotask Settings" button inside card
|
||||||
|
- **Ticket Defaults** form (queue, source, status, priority) with "Save Ticket Defaults" button inside card
|
||||||
|
- Clear visual separation improves user experience and prevents accidental incomplete saves
|
||||||
|
- All fields marked as required with red asterisks (*)
|
||||||
|
- HTML5 validation prevents saving incomplete configurations
|
||||||
|
- Protected default Ticket Status value against accidental clearing
|
||||||
|
- Only updates when a valid status is selected from dropdown
|
||||||
|
- Only allows clearing if reference data is loaded (dropdown has options)
|
||||||
|
- Preserves existing value if dropdown is empty to prevent data loss
|
||||||
|
- Fixes issue where "Create Autotask ticket" failed due to missing default status after saving settings with empty dropdown
|
||||||
|
|
||||||
|
**Data Portability and Migration Support:**
|
||||||
|
- Extended **Customer CSV export/import** to include Autotask company mappings
|
||||||
|
- Export now includes `autotask_company_id` and `autotask_company_name` columns
|
||||||
|
- Import reads Autotask mapping fields and applies them to existing or new customers
|
||||||
|
- Backwards compatible - old CSV files without Autotask columns still work correctly
|
||||||
|
- Import resets `autotask_mapping_status` and `autotask_last_sync_at` to allow resynchronization after import
|
||||||
|
- Extended **Jobs JSON export/import** to include Autotask fields in customers array
|
||||||
|
- Export includes Autotask fields (`autotask_company_id`, `autotask_company_name`) in customers array
|
||||||
|
- Import processes customers array first, applying Autotask mappings before creating jobs
|
||||||
|
- Schema remains `approved_jobs_export_v1` for backwards compatibility
|
||||||
|
- Import message now shows both created and updated customer counts
|
||||||
|
- Enables preservation of Autotask company mappings during system reset/migration workflows
|
||||||
|
- Critical for disaster recovery and environment promotion (dev → test → production)
|
||||||
|
|
||||||
|
### Environment Identification and Safety
|
||||||
|
|
||||||
|
**Sandbox/Development Environment Indicator:**
|
||||||
|
- Added visual banner system to distinguish non-production environments and prevent accidental actions
|
||||||
|
- New `is_sandbox_environment` boolean column in `SystemSettings` model (defaults to `False` for production safety)
|
||||||
|
- Database migration `migrate_system_settings_sandbox_environment()` for automatic schema update (idempotent, safe to run multiple times)
|
||||||
|
- Settings UI with new "Environment" card in Settings > General (placed after Navigation section)
|
||||||
|
- Toggle switch with clear description: "Mark this as a Sandbox/Development environment"
|
||||||
|
- Help text explains visual banner behavior
|
||||||
|
- CSS styling via `sandbox.css`:
|
||||||
|
- Diagonal red banner in top-left corner displaying "SANDBOX" in uppercase
|
||||||
|
- Position: top-left corner, rotated 45 degrees with letter-spacing for visibility
|
||||||
|
- Color: Bootstrap danger red (#dc3545) with white text and box-shadow for depth
|
||||||
|
- Z-index: 9999 (always on top, visible across all pages)
|
||||||
|
- `pointer-events: none` - banner itself is non-interactive, elements behind it remain clickable
|
||||||
|
- Banner conditionally displayed only when setting is enabled
|
||||||
|
- Banner placed in base template directly after `<body>` tag, before navbar
|
||||||
|
- Helps prevent accidental production-like actions in test/development systems (e.g., sending tickets to real customers)
|
||||||
|
|
||||||
|
### Job Visibility and Filtering Improvements
|
||||||
|
|
||||||
|
**Active Job Filtering:**
|
||||||
|
- Jobs from archived jobs and inactive customers no longer appear in Daily Jobs, Run Checks, and Jobs list pages
|
||||||
|
- Added customer active status filtering to all job list queries in backend
|
||||||
|
- Jobs now filtered where customer is NULL or active=True alongside existing archived=False filter
|
||||||
|
- Prevents showing jobs with "-" status from deleted customers or archived jobs
|
||||||
|
- Reduces clutter in operational views and improves focus on active jobs
|
||||||
|
- Improves query performance by reducing result set size
|
||||||
|
|
||||||
|
### Repository Management and Security
|
||||||
|
|
||||||
|
**Protected Directories:**
|
||||||
|
- Added `.gitignore` file to protect confidential `.claude` directory
|
||||||
|
- Directory contains AI assistant context, memory files, and user-specific configuration
|
||||||
|
- Prevents accidental commits of sensitive information to version control
|
||||||
|
- Protects user privacy and prevents repository pollution with local-only data
|
||||||
|
|
||||||
|
### Bug Fixes and Stability Improvements
|
||||||
|
|
||||||
|
**Backend Fixes:**
|
||||||
|
- Fixed missing import for `_log_admin_event` (now `_log_audit_event`) in routes_customers
|
||||||
|
- Resolved crash when performing customer operations with audit logging
|
||||||
|
- Enhanced `local_datetime` filter to handle string datetime values in addition to datetime objects
|
||||||
|
- Prevents template rendering errors when datetime is stored as string
|
||||||
|
- Automatically converts string to datetime before timezone conversion
|
||||||
|
|
||||||
|
**UI and CSS Fixes:**
|
||||||
|
- Fixed callout box formatting throughout documentation
|
||||||
|
- Corrected CSS specificity to only make first `<strong>` tag in callout boxes block-level
|
||||||
|
- Prevents nested strong tags from causing layout issues
|
||||||
|
- Applied fixes to Quick Start, First Login, and all other documentation pages
|
||||||
|
- Centered all documentation images horizontally (display: block, margin: 20px auto)
|
||||||
|
|
||||||
|
**Documentation Content Corrections:**
|
||||||
|
- Removed fabricated and non-existent features from documentation
|
||||||
|
- Removed "AI-powered parsing" future enhancement (not in planning, completely fabricated)
|
||||||
|
- Removed "Inbox Filters" section (not in planning)
|
||||||
|
- Removed "Enabling and Disabling Parsers" section (feature doesn't exist - users cannot manage parsers)
|
||||||
|
- Removed individual email re-parse from modal (only "Re-parse all" on inbox page)
|
||||||
|
- Corrected workflow descriptions to match actual UI implementation
|
||||||
|
- Fixed customer selection workflow (job name is read-only during approval, cannot be edited)
|
||||||
|
- Fixed email detail modal layout (two-column: left metadata/actions/objects, right email iframe)
|
||||||
|
- Fixed parser viewing location (separate Parsers page, Admin-only, not in Settings)
|
||||||
|
- Fixed Mail Import setup workflow (two-step save: credentials first, then complete config)
|
||||||
|
- Updated status badge colors and descriptions to match actual behavior
|
||||||
|
- Blue badges indicate override-applied runs (not green)
|
||||||
|
- Green badges only for genuine successes
|
||||||
|
- Expected status badge color corrections
|
||||||
|
- Fixed per-job review mechanism documentation
|
||||||
|
- Clarified marking one job as reviewed marks ALL runs for that job
|
||||||
|
- Corrected bulk review from "select run checkboxes" to "select job checkboxes"
|
||||||
|
- Updated "Unmark Reviewed" to reflect per-job unmarking
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## v0.1.22
|
## v0.1.22
|
||||||
|
|
||||||
This major release introduces comprehensive Autotask PSA integration, enabling seamless ticket management, customer company mapping, and automated ticket lifecycle handling directly from Backupchecks. The integration includes extensive settings configuration, robust API client implementation, intelligent ticket linking across job runs, and conditional ticket status updates based on time entries.
|
This major release introduces comprehensive Autotask PSA integration, enabling seamless ticket management, customer company mapping, and automated ticket lifecycle handling directly from Backupchecks. The integration includes extensive settings configuration, robust API client implementation, intelligent ticket linking across job runs, and conditional ticket status updates based on time entries.
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
v0.1.22
|
v0.1.23
|
||||||
|
|||||||