UPDATED TO BOOTSTRAP 5

This commit is contained in:
Jeroen De Meerleer 2021-07-20 18:54:07 +02:00
parent f466f5510e
commit 169c40a38d
Signed by: JeroenED
GPG Key ID: 28CCCB8F62BFADD6
9 changed files with 105 additions and 117 deletions

View File

@ -1,5 +1,4 @@
import 'tempusdominus-bootstrap-4';
import bsCustomFileInput from 'bs-custom-file-input';
import 'bootstrap';
$(function() {
@ -10,7 +9,6 @@ $(function() {
initVarInputs();
initIntervalPattern();
initEternalCheckbox();
bsCustomFileInput.init()
});
function initDatePickers()

View File

@ -1,4 +1,4 @@
import 'bootstrap';
import { Modal } from 'bootstrap';
$(function() {
initDeleteButtons();
@ -48,7 +48,9 @@ function initRunNowButtons() {
let tr = me.parents('tr');
tr.addClass('running');
tr.addClass('text-success');
tr.removeClass('norun');
tr.removeClass('text-danger');
} else if (data.status == 'ran') {
let content = '<p>Cronjob ran in ' + data.runtime.toFixed(3) + ' seconds with exit code ' + data.exitcode +'</p>'
content += '<pre>' + data.output + '</pre>'
@ -56,7 +58,7 @@ function initRunNowButtons() {
modal.find('.modal-body').html(content);
}
modal.modal({show: true})
var bsModal = new Modal('#runnow_result').show();//modal.modal({show: true})
}
})
})

18
package-lock.json generated
View File

@ -1163,6 +1163,11 @@
}
}
},
"@popperjs/core": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.9.2.tgz",
"integrity": "sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q=="
},
"@symfony/webpack-encore": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@symfony/webpack-encore/-/webpack-encore-1.5.0.tgz",
@ -1844,9 +1849,9 @@
"dev": true
},
"bootstrap": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz",
"integrity": "sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw=="
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.0.2.tgz",
"integrity": "sha512-1Ge963tyEQWJJ+8qtXFU6wgmAVj9gweEjibUdbmcCEYsn38tVwRk8107rk2vzt6cfQcRr3SlZ8aQBqaD8aqf+Q=="
},
"brace-expansion": {
"version": "1.1.11",
@ -5592,6 +5597,13 @@
"moment": "^2.29.0",
"moment-timezone": "^0.5.31",
"popper.js": "^1.16.1"
},
"dependencies": {
"bootstrap": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz",
"integrity": "sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw=="
}
}
},
"terser": {

View File

@ -7,8 +7,8 @@
"lib": "lib"
},
"dependencies": {
"bootstrap": "^4.6.0",
"bs-custom-file-input": "^1.3.4",
"bootstrap": "^5.0.2",
"@popperjs/core": "^2.9.2",
"font-awesome": "^4.7.0",
"jquery": "^3.6.0",
"moment": "^2.29.1",

View File

@ -2,8 +2,7 @@
{% for flash in flashes %}
<div class="alert alert-{{ flash.category }} alert-dismissible fade show" role="alert">
{{ flash.content }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
<button type="button" class="btn-close" data-dismiss="alert" aria-label="Close">
</button>
</div>
{% endfor %}

View File

@ -14,37 +14,33 @@
<div class="mb-3">
<label for="name">Interval (in seconds)</label>
<div class="input-group">
<div class="dropdown input-group-prepend">
<button class="btn btn-outline-primary dropdown-toggle" type="button" id="intervalButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Patterns
</button>
<div class="dropdown-menu" aria-labelledby="intervalButton">
<a class="dropdown-item intervalpattern-item" href="#" data-time="60">Every minute</a>
<a class="dropdown-item intervalpattern-item" href="#" data-time="3600">Every hour</a>
<a class="dropdown-item intervalpattern-item" href="#" data-time="86400">Every day</a>
<a class="dropdown-item intervalpattern-item" href="#" data-time="604800">Every week</a>
<a class="dropdown-item intervalpattern-item" href="#" data-time="2419200">Every 4 weeks</a>
</div>
</div>
<button class="btn btn-outline-primary dropdown-toggle" type="button" id="intervalButton" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Patterns
</button>
<ul class="dropdown-menu" aria-labelledby="intervalButton">
<li><a class="dropdown-item intervalpattern-item" href="#" data-time="60">Every minute</a></li>
<li><a class="dropdown-item intervalpattern-item" href="#" data-time="3600">Every hour</a></li>
<li><a class="dropdown-item intervalpattern-item" href="#" data-time="86400">Every day</a></li>
<li><a class="dropdown-item intervalpattern-item" href="#" data-time="604800">Every week</a></li>
<li><a class="dropdown-item intervalpattern-item" href="#" data-time="2419200">Every 4 weeks</a></li>
</ul>
<input type="number" class="form-control" id="interval" name="interval">
</div>
</div>
<div class="mb-3">
<label for="nextrun">Next run</label>
<input type="text" autocomplete="off" id="nextrunselector" class="form-control datetimepicker-input" data-target="#nextrunselector" data-toggle="datetimepicker" name="nextrun">
<input type="datetime-local" autocomplete="off" id="nextrunselector" class="form-control datetimepicker-input" data-target="#nextrunselector" data-bs-toggle="datetimepicker" name="nextrun">
</div>
<div class="mb-3">
<label for="lastrun">Last run</label>
<div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text border-right-0">
<input type="checkbox" name="lastrun-eternal" class="lastrun-eternal" placeholder="value" value="true" {% if attribute(data, 'lastrun-eternal') is not empty %} checked{% endif %}>
</div>
<span class="input-group-text border-left-0">Eternal</span>
<div class="input-group-text border-end-0">
<input type="checkbox" name="lastrun-eternal" class="lastrun-eternal" placeholder="value" value="true" {% if attribute(data, 'lastrun-eternal') is not empty %} checked{% endif %}>
</div>
<input type="text" autocomplete="off" id="lastrunselector" class="form-control datetimepicker-input" data-target="#lastrunselector" data-toggle="datetimepicker" name="lastrun">
<span class="input-group-text border-start-0">Eternal</span>
<input type="datetime-local" autocomplete="off" id="lastrunselector" class="form-control datetimepicker-input" data-target="#lastrunselector" data-bs-toggle="datetimepicker" name="lastrun">
</div>
</div>
@ -57,34 +53,34 @@
<h3>Job details</h3>
<div class="mb-3 btn-group croncategory-selector">
<div class="dropdown croncategory-group crontype-group">
<button class="btn btn-outline-primary dropdown-toggle" type="button" id="crontypeButton" data-default-text="" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button class="btn btn-outline-primary dropdown-toggle" type="button" id="crontypeButton" data-default-text="" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Job type
</button>
<div class="dropdown-menu" aria-labelledby="crontypeButton">
<a class="dropdown-item crontype-item" href="#" data-type="command">Command</a>
<a class="dropdown-item crontype-item" href="#" data-type="reboot">Reboot</a>
<a class="dropdown-item crontype-item" href="#" data-type="http">Http request</a>
</div>
<ul class="dropdown-menu" aria-labelledby="crontypeButton">
<li><a class="dropdown-item crontype-item" href="#" data-type="command">Command</a></li>
<li><a class="dropdown-item crontype-item" href="#" data-type="reboot">Reboot</a></li>
<li><a class="dropdown-item crontype-item" href="#" data-type="http">Http request</a></li>
</ul>
</div>
<div class="dropdown croncategory-group d-none hosttype-group">
<button class="btn btn-outline-primary dropdown-toggle" type="button" id="hosttypeButton" data-default-text="" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button class="btn btn-outline-primary dropdown-toggle" type="button" id="hosttypeButton" data-default-text="" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Host type
</button>
<div class="dropdown-menu" aria-labelledby="hosttypeButton">
<a class="dropdown-item hosttype-item" href="#" data-type="local">Local</a>
<a class="dropdown-item hosttype-item" href="#" data-type="ssh">SSH</a>
</div>
<ul class="dropdown-menu" aria-labelledby="hosttypeButton">
<li><a class="dropdown-item hosttype-item" href="#" data-type="local">Local</a></li>
<li><a class="dropdown-item hosttype-item" href="#" data-type="ssh">SSH</a></li>
</ul>
</div>
<div class="dropdown croncategory-group d-none containertype-group">
<button class="btn btn-outline-primary dropdown-toggle" type="button" id="containertypeButton" data-default-text="" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button class="btn btn-outline-primary dropdown-toggle" type="button" id="containertypeButton" data-default-text="" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Container
</button>
<div class="dropdown-menu" aria-labelledby="containertypeButton">
<a class="dropdown-item containertype-item" href="#" data-type="none">None</a>
<a class="dropdown-item containertype-item" href="#" data-type="docker">Docker</a>
</div>
<ul class="dropdown-menu" aria-labelledby="containertypeButton">
<li><a class="dropdown-item containertype-item" href="#" data-type="none">None</a></li>
<li><a class="dropdown-item containertype-item" href="#" data-type="docker">Docker</a></li>
</ul>
</div>
</div>
@ -173,9 +169,8 @@
<div class="mb-3">
<label for="privkey">Private key</label>
<div class="custom-file">
<input type="file" class="custom-file-input" id="privkey" name="privkey">
<label class="custom-file-label" for="privkey">Choose file</label>
<div class="input-group">
<input type="file" class="form-control" id="privkey" class="form-control" name="privkey">
</div>
<small id="custom-file-help" class="form-text text-muted">This file is being saved as a secret</small>
</div>
@ -208,12 +203,10 @@
<div class="vars mb-3">
<div class="input-group var-group d-none">
<div class="input-group-prepend">
<div class="input-group-text border-right-0">
<input type="checkbox" name="var-issecret[0]" class="var-issecret" placeholder="value" value="true">
</div>
<span class="input-group-text border-left-0">Secret</span>
</div>
<span class="input-group-text border-end-0">
<input type="checkbox" name="var-issecret[0]" class="var-issecret" placeholder="value" value="true">
</span>
<span class="input-group-text border-start-0">Secret</span>
<input type="text" name="var-id[0]" class="form-control var-id" placeholder="name">
<input type="text" name="var-value[0]" class="form-control var-value" placeholder="value">

View File

@ -12,35 +12,31 @@
<div class="mb-3">
<label for="name">Interval (in seconds)</label>
<div class="input-group">
<div class="dropdown input-group-prepend">
<button class="btn btn-outline-primary dropdown-toggle" type="button" id="intervalButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button class="btn btn-outline-primary dropdown-toggle" type="button" id="intervalButton" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Patterns
</button>
<div class="dropdown-menu" aria-labelledby="intervalButton">
<a class="dropdown-item intervalpattern-item" href="#" data-time="60">Every minute</a>
<a class="dropdown-item intervalpattern-item" href="#" data-time="3600">Every hour</a>
<a class="dropdown-item intervalpattern-item" href="#" data-time="86400">Every day</a>
<a class="dropdown-item intervalpattern-item" href="#" data-time="604800">Every week</a>
<a class="dropdown-item intervalpattern-item" href="#" data-time="2419200">Every 4 weeks</a>
</div>
</div>
</button>
<ul class="dropdown-menu" aria-labelledby="intervalButton">
<li><a class="dropdown-item intervalpattern-item" href="#" data-time="60">Every minute</a></li>
<li><a class="dropdown-item intervalpattern-item" href="#" data-time="3600">Every hour</a></li>
<li><a class="dropdown-item intervalpattern-item" href="#" data-time="86400">Every day</a></li>
<li><a class="dropdown-item intervalpattern-item" href="#" data-time="604800">Every week</a></li>
<li><a class="dropdown-item intervalpattern-item" href="#" data-time="2419200">Every 4 weeks</a></li>
</ul>
<input type="number" class="form-control" id="interval" name="interval" value="{{ interval }}">
</div>
</div>
<div class="mb-3">
<label for="nextrun">Next run</label>
<input type="text" autocomplete="off" id="nextrunselector" class="form-control datetimepicker-input" data-target="#nextrunselector" data-toggle="datetimepicker" name="nextrun" value="{{ nextrun | date("d/m/Y H:i:s")}}">
<input type="text" autocomplete="off" id="nextrunselector" class="form-control datetimepicker-input" data-target="#nextrunselector" data-bs-toggle="datetimepicker" name="nextrun" value="{{ nextrun | date("d/m/Y H:i:s")}}">
</div>
<div class="mb-3">
<label for="lastrun">Last run</label>
<div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text border-right-0">
<input type="checkbox" name="lastrun-eternal" class="lastrun-eternal" placeholder="value" value="true"{% if lastrun is empty %} checked{% endif %}>
</div>
<span class="input-group-text border-left-0">Eternal</span>
<div class="input-group-text border-end-0">
<input type="checkbox" name="lastrun-eternal" class="lastrun-eternal" placeholder="value" value="true"{% if lastrun is empty %} checked{% endif %}>
</div>
<input type="text" autocomplete="off" id="lastrunselector" class="form-control datetimepicker-input" data-target="#lastrunselector" data-toggle="datetimepicker" name="lastrun"{% if lastrun is empty %} disabled{% else %} value="{{ lastrun | date("d/m/Y H:i:s")}}"{% endif %}>
<span class="input-group-text border-start-0">Eternal</span>
<input type="text" autocomplete="off" id="lastrunselector" class="form-control datetimepicker-input" data-target="#lastrunselector" data-bs-toggle="datetimepicker" name="lastrun"{% if lastrun is empty %} disabled{% else %} value="{{ lastrun | date("d/m/Y H:i:s")}}"{% endif %}>
</div>
</div>
@ -53,7 +49,7 @@
<h3>Job details</h3>
<div class="mb-3 btn-group croncategory-selector">
<div class="dropdown croncategory-group crontype-group{% if data.crontype != 'http' %} btn-group{% endif %}">
<button class="btn btn-outline-primary dropdown-toggle" type="button" id="crontypeButton" data-default-text="Job type" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button class="btn btn-outline-primary dropdown-toggle" type="button" id="crontypeButton" data-default-text="Job type" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{% if data.crontype == 'command' %}
Command
{% elseif data.crontype == 'reboot' %}
@ -70,7 +66,7 @@
</div>
<div class="dropdown croncategory-group hosttype-group{% if data.crontype != 'http' %} btn-group{% else %} d-none{% endif %}">
<button class="btn btn-outline-primary dropdown-toggle" type="button" id="hosttypeButton" data-default-text="Host type" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button class="btn btn-outline-primary dropdown-toggle" type="button" id="hosttypeButton" data-default-text="Host type" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{% if data.hosttype == 'local' %}
Local
{% elseif data.hosttype == 'ssh' %}
@ -88,7 +84,7 @@
<div id="btn-group-discriminator" class="d-none">
{% endif %}
<div class="dropdown croncategory-group containertype-group{% if data.crontype != 'http' %} btn-group{% else %} d-none{% endif %}">
<button class="btn btn-outline-primary dropdown-toggle" type="button" id="containertypeButton" data-default-text="" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button class="btn btn-outline-primary dropdown-toggle" type="button" id="containertypeButton" data-default-text="" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{% if data.containertype == 'none' or data.containertype == '' %}
None
{% elseif data.containertype == 'docker' %}
@ -188,17 +184,12 @@
<div class="mb-3">
<label for="privkey">Private key</label>
<div class="input-group">
<div class="input-group-prepend">
<div class="input-group-text border-right-0">
<input type="checkbox" name="privkey-keep" class="privkey-keep" value="true" data-privkey="{% if attribute(data, 'ssh-privkey') is not empty %}{{ attribute(data, 'ssh-privkey') }}{% endif %}" checked>
<input type="hidden" name="privkey-orig" class="privkey-orig" value="{% if attribute(data, 'ssh-privkey') is not empty %}{{ attribute(data, 'ssh-privkey') }}{% endif %}">
</div>
<span class="input-group-text border-left-0">Keep</span>
</div>
<div class="custom-file input-group">
<input type="file" class="custom-file-input" id="privkey" name="privkey" disabled>
<label class="custom-file-label" for="privkey">Choose file</label>
</div>
<span class=" input-group-text border-end-0">
<input type="checkbox" name="privkey-keep" class="privkey-keep" value="true" data-privkey="{% if attribute(data, 'ssh-privkey') is not empty %}{{ attribute(data, 'ssh-privkey') }}{% endif %}" checked>
</span>
<input type="hidden" name="privkey-orig" class="privkey-orig" value="{% if attribute(data, 'ssh-privkey') is not empty %}{{ attribute(data, 'ssh-privkey') }}{% endif %}">
<span class="input-group-text border-start-0">Keep</span>
<input type="file" id="privkey" name="privkey" class="form-control " disabled>
</div>
<small id="custom-file-help" class="form-text text-muted">This file is being saved as a secret</small>
</div>
@ -231,24 +222,20 @@
<div class="vars mb-3">
<div class="input-group var-group d-none">
<div class="input-group-prepend">
<div class="input-group-text border-right-0">
<input type="checkbox" name="var-issecret[0]" class="var-issecret" placeholder="value" value="true">
</div>
<span class="input-group-text border-left-0">Secret</span>
<div class="input-group-text border-right-0">
<input type="checkbox" name="var-issecret[0]" class="var-issecret" placeholder="value" value="true">
</div>
<span class="input-group-text border-left-0">Secret</span>
<input type="text" name="var-id[0]" class="form-control var-id" placeholder="name">
<input type="text" name="var-value[0]" class="form-control var-value" placeholder="value">
</div>
{% set key = 1 %}
{% for id,var in data.vars %}
<div class="input-group var-group">
<div class="input-group-prepend">
<div class="input-group-text border-right-0">
<input type="checkbox" name="var-issecret[{{ key }}]" class="var-issecret" placeholder="value" value="true"{% if var.issecret %} checked{% endif %}>
</div>
<span class="input-group-text border-left-0">Secret</span>
<div class="input-group-text border-right-0">
<input type="checkbox" name="var-issecret[{{ key }}]" class="var-issecret" placeholder="value" value="true"{% if var.issecret %} checked{% endif %}>
</div>
<span class="input-group-text border-left-0">Secret</span>
<input type="text" name="var-id[{{ key }}]" class="form-control var-id" placeholder="name" value="{{ id }}">
<input type="{% if var.issecret %}password{% else %}text{% endif %}" name="var-value[{{ key }}]" class="form-control var-value" placeholder="value" value="{{ var.value }}">
</div>

View File

@ -20,7 +20,7 @@
<td class="d-block d-md-table-cell align-middle">{{ attribute(job, 'host-displayname') }}</td>
<td class="d-block d-md-table-cell align-middle">{{ job.interval | interval }}</td>
<td class="d-block d-md-table-cell align-middle">{{ job.nextrun | date("d/m/Y H:i:s") }}</td>
<td class="text-md-right d-block d-md-table-cell align-middle">
<td class="text-md-end d-block d-md-table-cell align-middle">
<a href="#" data-href="{{ path('job_runnow', {'id': job.id}) }}" class="runnow btn btn-outline-{% if job.running == true %}success{% elseif job.norun == true %}danger{% else %}primary{% endif %}{% if job.running == true %} disabled{% endif %}"><i class="fa fa-play" aria-hidden="true"></i></a>
<a href="{{ path('job_view', {'id': job.id}) }}" class="btn btn-outline-{% if job.running == true %}success{% elseif job.norun == true %}danger{% else %}primary{% endif %}"><i class="fa fa-search" aria-hidden="true"></i></a>
<a href="{{ path('job_edit', {'id': job.id}) }}" class="btn btn-outline-{% if job.running == true %}success{% elseif job.norun == true %}danger{% else %}primary{% endif %}"><i class="fa fa-pencil-square-o" aria-hidden="true"></i></a>
@ -35,15 +35,15 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel"></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close">
<span aria-hidden="true"></span>
</button>
</div>
<div class="modal-body">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-primary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-outline-primary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>

View File

@ -10,23 +10,20 @@
</p>
<div id="runs" class="accordion">
{% for run in runs %}
<div class="card">
<div class="card-header" id="run-{{ run.id }}-header">
<h2 class="mb-0">
<button class="btn btn-link btn-block d-flex justify-content-between align-items-center" type="button" data-toggle="collapse" data-target="#run-{{ run.id }}" aria-expanded="true" aria-controls="run-{{ run.id }}">
<div>
<div class="d-md-inline d-block text-left">{{ run.timestamp | date("d/m/Y H:i:s") }}</div>
<div class="d-md-inline d-block text-left">(runtime: {{ run.runtime | format_number({fraction_digit: 3}) }})</div>
{% if 'M' in run.flags %}
<div class="d-md-inline d-block text-left">Manual Run</div>
{% endif %}
</div>
<div>{{ run.exitcode }}</div>
</button>
</h2>
<div class="accordion-item">
<div class="accordion-header" id="run-{{ run.id }}-header">
<button class="accordion-button{% if loop.index != 1 %} collapsed{% endif %}" type="button" data-bs-toggle="collapse" data-bs-target="#run-{{ run.id }}" aria-expanded="true" aria-controls="run-{{ run.id }}">
<div>
<div class="d-md-inline d-block text-left">{{ run.timestamp | date("d/m/Y H:i:s") }}</div>
<div class="d-md-inline d-block text-left">(exit code: {{ run.exitcode }} | runtime: {{ run.runtime | format_number({fraction_digit: 3}) }})</div>
{% if 'M' in run.flags %}
<div class="d-md-inline d-block text-left">Manual Run</div>
{% endif %}
</div>
</button>
</div>
<div id="run-{{ run.id }}" class="collapse{% if loop.index == 1%} show{% endif %}" aria-labelledby="run-{{ run.id }}-header" data-parent="#runs">
<div class="card-body">
<div id="run-{{ run.id }}" class="accordion-collapse collapse{% if loop.index == 1%} show{% endif %}" aria-labelledby="run-{{ run.id }}-header" data-parent="#runs">
<div class="accordion-body">
<pre>{{ run.output }}</pre>
</div>
</div>