Thursday, April 2, 2026

Build a Report of Redhat AAP or AWX

 Hello Guys,

I am recently working for a client in which he has a ask that can i export the report what he can see in the AAP/AWX dashboard in html or PDF format for compliance so i started researching a bit on it and shared so report which are available on internet bit he did not like it.

He wanted a tailer made report showing job name start and finish time and how much time it took and final status also if i can show the logs of that execution that will be great.

So i use the API which are available with AAP or AWX to fetch the report of last 30 days with these field 

below is the job template report.yml


---
- name: Generate Last 1 Month Job Report
  hosts: aux_server 
##can be replace with any server name you want localhost will not work as it will destroy the report once the job is finish execution become: true gather_facts: true vars: controller_url: "https://<FQDN_AAP or AWX server>" tower_username: "admin" tower_password: "primod123" # Calculate date 30 days ago in ISO format (YYYY-MM-DD) last_month: "{{ lookup('pipe', 'date -d \"30 days ago\" +%Y-%m-%d') }}" page_size: 200 is_pdf: false ##Remove the word controller from api call if you are using awx tasks: - name: Fetch first page of jobs ansible.builtin.uri: url: "{{ controller_url }}/api/controller/v2/jobs/?finished__gte={{ last_month }}&order_by=-finished&page_size={{ page_size }}" method: GET user: "{{ tower_username }}" password: "{{ tower_password }}" force_basic_auth: yes validate_certs: false register: first_page - name: Calculate total pages ansible.builtin.set_fact: total_pages: "{{ (first_page.json.count / page_size) | round(0, 'ceil') | int }}" - name: Fetch remaining pages ansible.builtin.uri: url: "{{ controller_url }}/api/controller/v2/jobs/?finished__gte={{ last_month }}&order_by=-finished&page_size={{ page_size }}&page={{ item }}" method: GET user: "{{ tower_username }}" password: "{{ tower_password }}" force_basic_auth: yes validate_certs: false # Loop from page 2 to the end loop: "{{ range(2, total_pages | int + 1) | list }}" register: remaining_pages when: total_pages | int > 1 - name: Consolidate all results ansible.builtin.set_fact: all_jobs: >- {{ first_page.json.results + (remaining_pages.results | default([]) | map(attribute='json.results') | flatten) }} - name: Generate HTML Report ansible.builtin.template: src: report_template.j2 dest: "/usr/share/nginx/html/job_report_{{ ansible_date_time.date }}.html" vars: jobs: "{{ all_jobs }}" report_title: "Monthly Automation Summary" report_range: "Last 30 Days (Since {{ last_month }})"
and jinja template : report_template.j2

<!DOCTYPE html>
<html>
<head>
    <style>
        body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 40px; color: #333; }
        .header { border-bottom: 2px solid #005596; padding-bottom: 10px; margin-bottom: 20px; }
        .logo {height: 40px; /* Fixed height for a cleaner look */
               width: auto;  /* Maintains aspect ratio */
               margin-right: 20px; }
        table { width: 100%; border-collapse: collapse; margin-top: 20px; }
        th, td { padding: 12px; border: 1px solid #ddd; text-align: left; }
        th { background-color: #f8f9fa; font-weight: bold; }
        .status-successful { color: #28a745; font-weight: bold; }
        .status-failed { color: #dc3545; font-weight: bold; }
        .status-canceled { color: #f5f503fc; font-weight: bold; }
        .summary-box { background: #e9ecef; padding: 15px; border-radius: 5px; margin-bottom: 30px; }
    </style>
</head>
<body>
    <div class="header">
        <h1>{{ report_title }}</h1>
        <img src="https://raw.githubusercontent.com/benc-uk/icon-collection/refs/heads/master/logos/ansible.svg" alt="Ansible" class="logo"> <p><strong>Report Genrated on and using ansible automation platform 2.6</strong></p>
        <p><strong>Period:</strong> {{ report_range }}</p>
        <p><strong>Generated On:</strong> {{ ansible_date_time.date }}</p>
    </div>

    <div class="summary-box">
        <strong>Total Jobs Executed:</strong> {{ jobs | length }}
    </div>

    <table>
        <thead>
            <tr>
                <th>Job ID</th>
                <th>Template Name</th>
                <th>Status</th>
                 <th>Started At</th>
                <th>Finished At</th>
                <th>Duration</th>
    
            </tr>
        </thead>
        <tbody>
            {% for job in jobs %}
            <tr>
                <td>{{ job.id }}</td>
                <td>{{ job.name }}</td>
                <td class="status-{{ job.status }}">{{ job.status | capitalize }}</td>
                <td>{{ job.started | default(job.created) }}</td>
                <td>{{ job.finished }}</td>
                <td>{{'%M:%S' |ansible.builtin.strftime(job.elapsed)}}</td>
            </tr>{% endfor %}
        </tbody>
    </table>
</body>
</html>

sample output



Let me know you feedback in comments