<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.8.1/font/bootstrap-icons.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://cdn.datatables.net/2.1.8/css/dataTables.dataTables.css" />
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.3/moment.min.js"></script>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/jquery/latest/jquery.min.js"></script>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.min.js"></script>
    <script src="https://cdn.datatables.net/2.1.8/js/dataTables.js"></script>
    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.css" />
    <title>{% block title %} {% endblock %} AIND Data Transfer Service Jobs</title>
    <style>
        body {
            margin: 20px;
            font-family: arial, sans-serif;
        }
        nav {
            height: 40px;
        }
        .modal-body {
            height: calc(100vh - 100px);
        }
    </style>
</head>
<body>
    <nav>
        <a href="/">Submit Jobs</a> |
        <a href="/jobs">Job Status</a> |
        <a href="/job_params">Job Parameters</a> |
        <a title="Download job template as .xslx" href= "/api/job_upload_template" download>Job Submit Template</a> |
        <a title="List of project names" href= "{{ project_names_url }}" target="_blank" >Project Names</a> |
        <a title="For more information click here" href="https://aind-data-transfer-service.readthedocs.io"  target="_blank" >Help</a>
    </nav>
    <div class="content">
        <!-- display total entries -->
        <h4 class="mb-2">Jobs Submitted: <span id="total-entries"></span></h4>
        <!-- filters for job status results-->
        <div class="card mb-4 small" style="width:400px">
            <div class="card-header py-1" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-filters" aria-expanded="false" aria-controls="collapse-filters">
                <i class="bi bi-filter"></i><span class="ms-2">Filter by</span>
                <i class="bi bi-chevron-expand float-end"></i>
            </div>
            <div id="collapse-filters" class="collapse card-body p-2">
                <!-- filter by job status -->
                <div class="input-group input-group-sm mb-1">
                    <span class="input-group-text" style="width:35%">Status</span>
                    <select class="form-select" onchange="filterJobsByColumn(4, this.value);this.blur();">
                        {% for s in [
                            {"label": "all", "value": "", "class": "text-dark"},
                            {"label": "queued", "value": "queued", "class": "text-secondary"},
                            {"label": "running", "value": "running", "class": "text-info"},
                            {"label": "failed", "value": "failed", "class": "text-danger"},
                            {"label": "success", "value": "success", "class": "text-success"},
                        ] %}
                        <option class="{{ s.class }}" value="{{ s.value }}">{{ s.label }}</option>
                        {% endfor %}
                    </select>
                </div>
                <!-- filter by job submitted date range -->
                <div class="input-group input-group-sm">
                    <span class="input-group-text" style="width:35%">Submit Time</span>
                    <input id="submit-date-range" class="form-select" type="text" />
                </div>
                <!-- filter by asset name -->
                <hr class="flex-grow-1 border-secondary">
                <div class="input-group input-group-sm mb-1">
                    <span class="input-group-text" style="width:35%">Asset Name</span>
                    <input id="filter-name-input" type="text" class="form-control" placeholder="asset name" oninput="filterJobsByColumn(0, this.value)">
                    <button id="filter-name-clear" class="btn btn-outline-secondary" type="button" title="Clear" onclick="clearFilterJobsByColumn(0, '#filter-name-input')">
                        <i class="bi bi-x-lg"></i>
                    </button>
                </div>
                <!-- filter by job id-->
                <div class="input-group input-group-sm mb-1">
                    <span class="input-group-text" style="width:35%">Job ID</span>
                    <input id="filter-id-input" type="text" class="form-control" placeholder="job id" oninput="filterJobsByColumn(1, this.value)">
                    <button id="filter-id-clear" class="btn btn-outline-secondary" type="button" title="Clear" onclick="clearFilterJobsByColumn(1, '#filter-id-input')">
                        <i class="bi bi-x-lg"></i>
                    </button>
                </div>
                <!-- filter by job type-->
                <div class="input-group input-group-sm mb-1">
                    <span class="input-group-text" style="width:35%">Job Type</span>
                    <input id="filter-job-type-input" type="text" class="form-control" placeholder="job type" oninput="filterJobsByColumn(2, this.value)">
                    <button id="filter-job-type-clear" class="btn btn-outline-secondary" type="button" title="Clear" onclick="clearFilterJobsByColumn(2, '#filter-job-type-input')">
                        <i class="bi bi-x-lg"></i>
                    </button>
                </div>
                <!-- filter by dag id -->
                <div class="input-group input-group-sm mb-1">
                    <span class="input-group-text" style="width:35%">Dag ID</span>
                    <select class="form-select" onchange="filterJobsByColumn(3, this.value);this.blur();">
                        <option class="text-dark" value="">all</option>
                        {% for dag_id in dag_ids %}
                        <option class="text-secondary" value="^{{ dag_id }}$">{{ dag_id }}</option>
                        {% endfor %}
                    </select>
                </div>
            </div>
        </div>
        <!-- job status table -->
        <div>
            <table id="searchJobsTable" class="display compact table table-bordered table-sm" style="font-size: small">
                <thead>
                    <tr>
                        <th>Asset Name</th>
                        <th>Job ID</th>
                        <th>Job Type</th>
                        <th>Dag ID</th>
                        <th>Status</th>
                        <th>Submit Time</th>
                        <th>Start Time</th>
                        <th>End Time</th>
                        <th>Comment</th>
                        <th>Tasks</th>
                    </tr>
                </thead>
            </table>
            <!-- modal for displaying tasks per job from full jobs table -->
            <div class="modal fade" id="tasks-modal-full" tabindex="-1" aria-labelledby="tasks-modal-full-label" aria-hidden="true">
                <div class="modal-dialog modal-xl">
                <div class="modal-content">
                    <div class="modal-header p-2">
                    <div class="modal-title fw-bold" id="tasks-modal-full-label" style="font-size: small">
                        <span id="modal-title-job-name" class="me-2"></span>
                        <span id="modal-title-job-id" class="me-2"></span>
                        <span id="modal-title-job-state" class="badge"></span>
                    </div>
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                    </div>
                    <div class="modal-body p-2">
                        <iframe id="tasks-iframe" class="w-100 h-100" src=""></iframe>
                    </div>
                </div>
                </div>
            </div>
        </div>
    </div>
    <script>
        // User can filter for jobs by status and submit time, and view results.
        // Full Jobs Table: Loads all jobs using DataTables library. Enables searching across all jobs.
        let tableUrl = new URL("{{ url_for('get_job_status_list') }}");
        $(document).ready(function() {
            const today = moment();
            const twoWeeksAgo = moment().subtract(13, 'days');
            
            // initialize daterangepicker for submit date filter
            $('#submit-date-range').daterangepicker({
                startDate: twoWeeksAgo,
                endDate: today,
                minDate: twoWeeksAgo,
                maxDate: today,
                ranges: {
                    'Today': [today, today],
                    'Last 3 Days': [moment().subtract(2, 'days'), today],
                    'Last 7 Days': [moment().subtract(6, 'days'), today],
                    'Last 14 Days': [twoWeeksAgo, today],
                }
            }, filterJobsBySubmitTimeRange);

            // initialize job status table with default values
            const initialParams = {
                // set default submit date range client-side for browser's local time
                execution_date_gte: twoWeeksAgo.startOf('day').toISOString(),
                execution_date_lte: today.endOf('day').toISOString(),
            };
            updateJobStatusTable(initialParams);

            // tasks modal for full jobs table
            var tasksModal = document.getElementById('tasks-modal-full');
            tasksModal.addEventListener('show.bs.modal', function (event) {
                var sourceData = event.relatedTarget?.dataset;
                updateJobTasksModal(sourceData?.dagId, sourceData?.jobId, sourceData?.jobName, sourceData?.jobState);
            })
            tasksModal.addEventListener('hidden.bs.modal', function (event) {
                updateJobTasksModal(null, null, null, null);
            })
        });
        // FULL JOBS TABLE -----------------------------------------------
        // Helper functions for custom column rendering
        function renderJobStatus(cell, cellData, rowData, rowIndex, colIndex) {
            let customClass = cellData === 'success' ? 'table-success'
                : cellData === 'failed' ? 'table-danger'
                : cellData === 'running' ? 'table-info'
                : cellData === 'queued' ? 'table-secondary'
                : null;
            if (customClass) {
                cell.classList.add(customClass);
            }
        }
        function renderDatetime(data, type, row) {
            return (type === 'display') ? moment.utc(data).local().format('YYYY-MM-DD h:mm:ss a') : data;
        }
        function renderTasksButton(data, type, row) {
            if (type == 'display') {
                return (`<button type="button" class="btn btn-outline-primary btn-sm" data-bs-toggle="modal" data-bs-target="#tasks-modal-full" data-dag-id="${row.dag_id}" data-job-id="${data}" data-job-name="${row.name}" data-job-state="${row.job_state}">
                    <i class="bi bi-box-arrow-up-right" title="View tasks and logs"></i>
                    </button>`);
            }
            return data;
        }
        // Create DataTable for full jobs table
        function createFullJobsTable() {
            $('#searchJobsTable').DataTable({
                ajax: {
                    url: tableUrl,
                    dataSrc: 'data.job_status_list'
                },
                processing: true,
                columns: [
                    { data: 'name', searchable: true },
                    { data: 'job_id', searchable: true },
                    { data: 'job_type', searchable: true },
                    { data: 'dag_id', searchable: true },
                    { data: 'job_state', searchable: true, createdCell: renderJobStatus },
                    { data: 'submit_time', searchable: false, render: renderDatetime },
                    { data: 'start_time', searchable: false, render: renderDatetime },
                    { data: 'end_time', searchable: false, render: renderDatetime },
                    { data: 'comment', searchable: false, defaultContent: 'None' },
                    { data: 'job_id', searchable: false, render: renderTasksButton },
                ],
                initComplete: (settings, json) => updateJobsCount(json),
                // layout options
                pageLength: 25,
                order: [5, 'desc'], // submit time descending
                layout: {
                    topStart: null,
                    topEnd: null,
                    bottomStart: null,
                    bottomEnd: null,
                    top: [
                        'pageLength',
                        'info',
                        { paging: { numbers: false } }
                    ],
                },
                language: {
                    info: "_START_ to _END_ of _TOTAL_",
                    infoEmpty: "0 to 0 of 0",
                    entries: { _: "jobs", 1: "job" },
                    paginate: {
                        first: '&laquo; First',
                        previous: '&lsaquo; Prev',
                        next: 'Next &rsaquo;',
                        last: 'Last &raquo;'
                    },
                },
            });
        }
        // Modal for tasks per job from full jobs table
        function updateJobTasksModal(dagId, jobId, jobName, jobState) {
            // Update the modal header with the job id and name
            document.getElementById('modal-title-job-id').textContent = `(${jobId})`;
            document.getElementById('modal-title-job-name').textContent = jobName;
            var modalTitleJobState = document.getElementById('modal-title-job-state');
            modalTitleJobState.textContent = jobState;
            if (jobState) {
                modalTitleJobState.classList.add(
                jobState === 'success' ? 'bg-success'
                : jobState === 'failed' ? 'bg-danger'
                : jobState === 'running' ? 'bg-info'
                : 'bg-secondary'
                );
            } else {
                modalTitleJobState.classList.value = 'badge';
            }
            // Update the iframe src with the job id
            var tasksIframe = document.getElementById('tasks-iframe');
            if (jobId) {
                var url = new URL("{{ url_for('job_tasks_table') }}");
                url.searchParams.append('dag_id', dagId);
                url.searchParams.append('dag_run_id', jobId);
                tasksIframe.src = url;
            } else {
                tasksIframe.src = "";
            }
        }
        // EVENT HANDLERS ------------------------------------------------
        function updateJobStatusTable(newParams) {
            Object.entries(newParams).forEach(([key, value]) => {
                tableUrl.searchParams.set(key, value);
            });
            if (DataTable.isDataTable('#searchJobsTable')) {
                // load the table with new params and update total entries
                $('#searchJobsTable').DataTable().ajax.url(tableUrl.toString()).load(
                    (data) => updateJobsCount(data)
                );
            } else {
                createFullJobsTable();
            }
        }
        function updateJobsCount(data) {
            // data is the response from the server after ajax call
            $('#total-entries').text(data?.data?.total_entries);
        }
        // Filters
        function filterJobsByColumn(columnIndex, value) {
            if (columnIndex == 3 || columnIndex == 4) {
                //exact match for dag_id and job_state dropdowns
                $('#searchJobsTable').DataTable().columns(columnIndex).search(value.trim(), true, false).draw();
            } else {
                // smart match
                $('#searchJobsTable').DataTable().columns(columnIndex).search(value.trim()).draw();
            }
        }
        function clearFilterJobsByColumn(columnIndex, inputElementId) {
            $(inputElementId).val('');
            filterJobsByColumn(columnIndex, '');
        }
        function filterJobsBySubmitTimeRange(start, end) {
            // This filter is the only one that sends a new ajax request to the server
            // NOTE: daterangepicker already has 00:00:00.000 and 23:59:59.999 for start and end
            updateJobStatusTable({
                execution_date_gte: start.toISOString(),
                execution_date_lte: end.toISOString(),
            });
        }
    </script>
    </body>
</html>
