File indexing completed on 2026-06-13 08:42:18
0001 {% extends "base.html" %}
0002 {% block title %}Production Task Catalog{% endblock %}
0003
0004 {% block content %}
0005 <link rel="stylesheet" href="https://cdn.datatables.net/1.13.8/css/dataTables.bootstrap5.min.css">
0006
0007 <div class="container-fluid mt-3 pcs-catalog">
0008 <div class="d-flex align-items-center mb-3">
0009 <h2 class="me-3 mb-0">Production Task Catalog</h2>
0010 <a href="{% url 'pcs:pcs_hub' %}" class="btn btn-sm btn-outline-secondary">PCS Hub</a>
0011 <form method="post" action="{% url 'pcs:pcs_catalog_csv_update' %}" class="ms-2 d-inline">
0012 {% csrf_token %}
0013 <button type="submit" class="btn btn-sm btn-outline-primary"
0014 title="Re-import eic/epic-prod/docs/_data/datasets.csv into the catalog"
0015 onclick="return confirm('Re-import the default-datasets CSV into the catalog?');">
0016 Update from CSV
0017 </button>
0018 </form>
0019 <form method="post" action="{% url 'pcs:pcs_catalog_rucio_update' %}" class="ms-2 d-inline">
0020 {% csrf_token %}
0021 <button type="submit" class="btn btn-sm btn-outline-primary"
0022 title="Refresh the JLab Rucio output snapshot for the current campaign"
0023 onclick="return confirm('Refresh the JLab Rucio output snapshot for the current campaign?');">
0024 Update from Rucio
0025 </button>
0026 </form>
0027 </div>
0028
0029 {% if show_tabs %}
0030 <div class="pcs-tlf-tabs btn-group mb-2 flex-wrap" role="tablist" aria-label="Lifecycle tabs">
0031 {% for tab in lifecycle_tabs %}
0032 <a class="btn btn-sm btn-outline-{{ tab.color }}{% if active_lifecycle == tab.key %} active{% endif %}"
0033 href="?lifecycle={{ tab.key }}">
0034 {{ tab.label }}{% if tab.key != 'past' and tab.campaigns %} <span class="text-muted">·</span>
0035 {% for c in tab.campaigns %}{{ c.name }}{% if not forloop.last %}, {% endif %}{% endfor %}
0036 {% endif %}
0037 </a>
0038 {% endfor %}
0039 </div>
0040 {% endif %}
0041
0042 {% if rucio_detected and rucio_detected.0.version != rucio_current_name and active_lifecycle == 'current' %}
0043 {# Current tab and current is stale: selector to switch. #}
0044 <div class="border rounded p-2 mb-3 small bg-light">
0045 <div class="mb-1 text-muted">
0046 Detected newer 26.x release in JLab Rucio
0047 (PCS current: <strong>{{ rucio_current_name }}</strong>):
0048 </div>
0049 <div class="d-flex flex-wrap align-items-center gap-2">
0050 {% for r in rucio_detected %}
0051 <span>{{ r.version }} <span class="text-muted">({{ r.count }})</span>
0052 {% if r.version != rucio_current_name %}
0053 <form method="post" action="{% url 'pcs:pcs_catalog_set_current' %}" class="d-inline">
0054 {% csrf_token %}
0055 <input type="hidden" name="name" value="{{ r.version }}">
0056 <button type="submit" class="btn btn-sm btn-outline-primary py-0 px-2 ms-1"
0057 title="Rename PCS current campaign to {{ r.version }} and pull its Rucio snapshot in one click"
0058 onclick="return confirm('Switch PCS current to {{ r.version }} and refresh snapshot?');">Make current</button>
0059 </form>
0060 {% else %}
0061 <span class="badge bg-success">current</span>
0062 {% endif %}
0063 </span>
0064 {% endfor %}
0065 </div>
0066 </div>
0067 {% endif %}
0068
0069 <div id="pcs-rucio-timeline" class="mb-3" style="min-height:540px"></div>
0070
0071 {% if rucio_unmatched %}
0072 <details class="mb-3 small">
0073 <summary class="text-muted">
0074 <strong style="color:#dc3545">⚠ {{ rucio_unmatched|length }} unmatched Rucio output{{ rucio_unmatched|length|pluralize }}</strong>
0075 for campaign {{ rucio_unmatched_campaign }} (no request in this table matched these DIDs)
0076 </summary>
0077 <ul class="mt-2 mb-0" style="font-family:monospace; font-size:0.85em;">
0078 {% for u in rucio_unmatched %}
0079 <li>{{ u.did }} · {{ u.files }} files · {{ u.bytes|filesizeformat }}{% if u.incomplete %} <span style="color:#dc3545">(incomplete)</span>{% endif %}</li>
0080 {% endfor %}
0081 </ul>
0082 </details>
0083 {% endif %}
0084
0085 {% include "pcs/_task_list_filter.html" %}
0086 </div>
0087
0088 <script src="https://cdn.plot.ly/plotly-2.35.2.min.js"></script>
0089 <script>
0090 (function() {
0091 const data = {{ rucio_timeline_json|safe }};
0092 const el = document.getElementById('pcs-rucio-timeline');
0093 if (!el) return;
0094 if (!data || !data.dates || !data.dates.length) {
0095 el.innerHTML = '<div class="text-muted small">No Rucio arrivals timeline available — '
0096 + 'click <em>Update from Rucio</em> to populate the snapshot.</div>';
0097 return;
0098 }
0099 const TB = 1e12;
0100 const SIMU_COLOR = '#1f77b4';
0101 const RECO_COLOR = '#ff7f0e';
0102 const tb = bytes => bytes.map(b => b / TB);
0103 // Three stacked panes, sharing the time axis at the bottom:
0104 // top = cumulative datasets
0105 // middle = cumulative files (registered)
0106 // bottom = cumulative output (TB)
0107 // One legend entry per stage; toggling hides all three panes for
0108 // that stage via legendgroup.
0109 const traces = [
0110 // Top pane.
0111 {x: data.dates, y: data.simu.cum_datasets, name: 'Simu',
0112 legendgroup: 'simu', mode: 'lines', line: {shape: 'hv', color: SIMU_COLOR},
0113 xaxis: 'x', yaxis: 'y'},
0114 {x: data.dates, y: data.reco.cum_datasets, name: 'Reco',
0115 legendgroup: 'reco', mode: 'lines', line: {shape: 'hv', color: RECO_COLOR},
0116 xaxis: 'x', yaxis: 'y'},
0117 // Middle pane.
0118 {x: data.dates, y: data.simu.cum_files, legendgroup: 'simu',
0119 mode: 'lines', line: {shape: 'hv', color: SIMU_COLOR},
0120 xaxis: 'x', yaxis: 'y2', showlegend: false},
0121 {x: data.dates, y: data.reco.cum_files, legendgroup: 'reco',
0122 mode: 'lines', line: {shape: 'hv', color: RECO_COLOR},
0123 xaxis: 'x', yaxis: 'y2', showlegend: false},
0124 // Bottom pane.
0125 {x: data.dates, y: tb(data.simu.cum_bytes), legendgroup: 'simu',
0126 mode: 'lines', line: {shape: 'hv', color: SIMU_COLOR},
0127 xaxis: 'x', yaxis: 'y3', showlegend: false},
0128 {x: data.dates, y: tb(data.reco.cum_bytes), legendgroup: 'reco',
0129 mode: 'lines', line: {shape: 'hv', color: RECO_COLOR},
0130 xaxis: 'x', yaxis: 'y3', showlegend: false},
0131 ];
0132 const layout = {
0133 title: {text: 'Rucio arrivals — ' + (data.campaign_name || ''),
0134 font: {size: 14}},
0135 margin: {l: 70, r: 30, t: 40, b: 40},
0136 height: 540,
0137 // Single shared x axis, anchored to the bottom pane's y3.
0138 xaxis: {anchor: 'y3', type: 'date'},
0139 yaxis: {domain: [0.70, 1.00], title: 'datasets', rangemode: 'tozero'},
0140 yaxis2: {domain: [0.36, 0.66], title: 'files (registered)',
0141 rangemode: 'tozero'},
0142 yaxis3: {domain: [0.00, 0.32], title: 'output (TB)', rangemode: 'tozero'},
0143 legend: {orientation: 'h', y: -0.12},
0144 showlegend: true,
0145 };
0146 Plotly.newPlot(el, traces, layout, {displayModeBar: false, responsive: true});
0147 })();
0148 </script>
0149 {% endblock %}