mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-11 04:36:48 +00:00
Deleting or detaching a disk, deleting a network card, forcing a VM off, reverting or deleting a snapshot, stopping or deleting a pool, a network, an interface, a filter or a secret, and the other actions that ask first opened the browser's yes/no box (confirm()), and the ISO upload told its mistakes in the browser's alert(). One dialog in the base page now asks for all of them: its title and confirming button carry the button's own words, its body the button's question, the confirming button red for a destructive action. Confirmed, the button is clicked again, so its form sends what it did before. A button inside another dialog (the flavors) hides that one while asking and shows it again after; Cancel gives the focus back, and the confirming button acts once. The ISO upload's mistakes show in its dialog's error box, announced.
210 lines
13 KiB
HTML
210 lines
13 KiB
HTML
{% extends "base.html" %}
|
||
{% load i18n %}
|
||
{% load static %}
|
||
{% load bootstrap_icons %}
|
||
|
||
{% block title %}{% trans "Network filter" %} - {{ name }}{% endblock %}
|
||
|
||
{% block page_heading %}{{ compute.name }} - {% trans "Network filter" %}: {{ name }}{% endblock page_heading %}
|
||
|
||
{% block content %}
|
||
{% include "computes/host_nav.html" with section="nwfilters" detail=True %}
|
||
|
||
{% if uuid %}
|
||
<div class="card mb-3">
|
||
<div class="card-header">{% trans "Filter" %}</div>
|
||
<ul class="list-group list-group-flush">
|
||
<li class="list-group-item py-1 d-flex justify-content-between align-items-baseline gap-3">
|
||
<span class="text-body-secondary">{% trans "UUID" %}</span><span class="font-monospace text-break text-end">{{ uuid }}</span>
|
||
</li>
|
||
<li class="list-group-item py-1 d-flex justify-content-between align-items-baseline gap-3">
|
||
<span class="text-body-secondary">{% trans "Chain" %}</span><span class="font-monospace">{{ chain|default:"–" }}</span>
|
||
</li>
|
||
<li class="list-group-item py-1 d-flex justify-content-between align-items-baseline gap-3">
|
||
<span class="text-body-secondary">{% trans "Priority" %}</span><span>{% if priority is not None %}{{ priority }}{% else %}–{% endif %}</span>
|
||
</li>
|
||
<li class="list-group-item py-1 d-flex justify-content-between align-items-baseline gap-3">
|
||
<span class="text-body-secondary">{% trans "Directly used by" %}</span>
|
||
<span class="text-end">{% for vm in vms %}{% if vm.id %}<a href="{% url 'instances:instance' vm.id %}">{{ vm.name }}</a>{% else %}{{ vm.name }}{% endif %}{% if not forloop.last %}, {% endif %}{% empty %}–{% endfor %}</span>
|
||
</li>
|
||
<li class="list-group-item py-1 d-flex justify-content-between align-items-baseline gap-3">
|
||
<span class="text-body-secondary">{% trans "Referenced by" %}</span>
|
||
<span class="text-end">{% for other in referenced_by %}<a href="{% url 'nwfilter' compute.id other %}">{{ other }}</a>{% if not forloop.last %}, {% endif %}{% empty %}–{% endfor %}</span>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="card mb-3">
|
||
<div class="card-header d-flex flex-wrap align-items-center gap-2">
|
||
<span class="me-auto">{% trans "References" %}</span>
|
||
{% if ref_choices %}
|
||
<form method="post" class="d-flex gap-2">{% csrf_token %}
|
||
<select name="nwfilters_select" class="form-select form-select-sm w-auto" aria-label="{% trans "Filter to reference" %}" required>
|
||
{% for choice in ref_choices %}<option value="{{ choice }}">{{ choice }}</option>{% endfor %}
|
||
</select>
|
||
<button type="submit" class="btn btn-sm btn-primary text-nowrap" name="add_nwfilter_ref">{% bs_icon 'plus-lg' %} {% trans "Add" %}</button>
|
||
</form>
|
||
{% endif %}
|
||
</div>
|
||
{% if refs %}
|
||
<div class="table-responsive position-relative">
|
||
<table class="table table-hover align-middle mb-0">
|
||
<thead>
|
||
<tr>
|
||
<th scope="col">{% trans "Filter" %}</th>
|
||
<th scope="col">{% trans "Parameters" %}</th>
|
||
<th scope="col"><span class="visually-hidden">{% trans "Actions" %}</span></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for ref in refs %}
|
||
<tr>
|
||
<td class="text-nowrap">
|
||
{% if ref.filter in filter_names %}<a href="{% url 'nwfilter' compute.id ref.filter %}">{{ ref.filter }}</a>
|
||
{% else %}{{ ref.filter }} <span class="badge bg-danger-subtle text-danger-emphasis">{% trans "Missing" %}</span>{% endif %}
|
||
</td>
|
||
<td class="font-monospace small">{% for param, value in ref.parameters %}<div>{{ param }}={{ value }}</div>{% empty %}–{% endfor %}</td>
|
||
<td class="text-end">
|
||
<form method="post">{% csrf_token %}
|
||
<input type="hidden" name="index" value="{{ forloop.counter0 }}">
|
||
<input type="hidden" name="ref" value="{{ ref.text }}">
|
||
<button type="submit" class="btn btn-sm btn-outline-danger" name="del_nwfilter_ref" aria-label="{% blocktrans with ref=ref.filter %}Remove the reference to {{ ref }}{% endblocktrans %}"
|
||
data-confirm="{% blocktrans with ref=ref.filter %}Remove the reference to {{ ref }}? Its rules no longer apply through this filter.{% endblocktrans %}">{% bs_icon 'trash' %}</button>
|
||
</form>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{% else %}
|
||
<div class="card-body small text-body-secondary">{% trans "It references no other filter." %}</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<div class="card mb-3">
|
||
<div class="card-header d-flex flex-wrap align-items-center gap-2">
|
||
<span class="me-auto">{% trans "Rules" %}</span>
|
||
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#AddNWFilterRule">{% bs_icon 'plus-lg' %} {% trans "Add rule" %}</button>
|
||
</div>
|
||
{% if rules %}
|
||
<div class="table-responsive position-relative">
|
||
<table class="table table-hover align-middle mb-0">
|
||
<thead>
|
||
<tr>
|
||
<th scope="col">#</th>
|
||
<th scope="col">{% trans "Action" %}</th>
|
||
<th scope="col">{% trans "Direction" %}</th>
|
||
<th scope="col">{% trans "Priority" %}</th>
|
||
<th scope="col">{% trans "Matches" %}</th>
|
||
<th scope="col"><span class="visually-hidden">{% trans "Actions" %}</span></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for rule in rules %}
|
||
<tr>
|
||
<th scope="row">{{ forloop.counter }}</th>
|
||
<td>
|
||
{% if rule.action == "accept" %}<span class="badge bg-success-subtle text-success-emphasis">{% trans "Accept" %}</span>
|
||
{% elif rule.action == "drop" or rule.action == "reject" %}<span class="badge bg-danger-subtle text-danger-emphasis">{{ rule.action|capfirst }}</span>
|
||
{% else %}<span class="badge text-bg-light border">{{ rule.action|capfirst }}</span>{% endif %}
|
||
</td>
|
||
<td class="text-nowrap">{% if rule.direction == "in" %}{% trans "To the VM" %}{% elif rule.direction == "out" %}{% trans "From the VM" %}{% elif rule.direction == "inout" %}{% trans "Both ways" %}{% else %}{{ rule.direction }}{% endif %}</td>
|
||
<td>{% if rule.priority is not None %}{{ rule.priority }}{% else %}500{% endif %}</td>
|
||
<td class="small">
|
||
{% for protocol in rule.protocols %}
|
||
<div><span class="badge text-bg-light border">{{ protocol.name }}</span>{% for attribute, value in protocol.attributes %} <span class="font-monospace">{{ attribute }}={{ value }}</span>{% endfor %}</div>
|
||
{% empty %}
|
||
<span class="text-body-secondary">{% trans "Any traffic of the chain" %}</span>
|
||
{% endfor %}
|
||
{% if rule.statematch == "false" or rule.statematch == "0" %}<div class="text-body-secondary">{% trans "Without connection tracking" %}</div>{% endif %}
|
||
</td>
|
||
<td class="text-end">
|
||
<form method="post">{% csrf_token %}
|
||
<input type="hidden" name="index" value="{{ forloop.counter0 }}">
|
||
<input type="hidden" name="rule" value="{{ rule.text }}">
|
||
<button type="submit" class="btn btn-sm btn-outline-danger" name="del_nwfilter_rule" aria-label="{% blocktrans with number=forloop.counter %}Delete rule {{ number }}{% endblocktrans %}"
|
||
data-confirm="{% trans "Delete this rule? Interfaces that use the filter no longer get it." %}">{% bs_icon 'trash' %}</button>
|
||
</form>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{% else %}
|
||
<div class="card-body small text-body-secondary">{% trans "The filter has no rules of its own." %}</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<section class="accordion mb-3" aria-labelledby="xml-heading">
|
||
<div class="accordion-item">
|
||
<h2 class="accordion-header" id="xml-heading">
|
||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#nwfilter-xml" aria-expanded="false" aria-controls="nwfilter-xml">
|
||
{% trans "XML definition" %}
|
||
</button>
|
||
</h2>
|
||
<div class="accordion-collapse collapse" id="nwfilter-xml">
|
||
<form method="post" class="accordion-body" aria-label="{% trans "Change the filter's XML" %}">{% csrf_token %}
|
||
<p class="small text-body-secondary">{% trans "A change applies at once, also to the running VMs that use the filter; it keeps the filter's name and UUID." %}</p>
|
||
<input type="hidden" name="edit_xml">
|
||
<div class="xmlheight mb-3"><textarea id="edit_editor" aria-label="{% trans "Filter XML" %}">{{ xml }}</textarea></div>
|
||
<div class="d-flex justify-content-end">
|
||
<button type="submit" class="btn btn-primary" name="edit_nwfilter">{% trans "Save" %}</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<div class="modal fade" id="AddNWFilterRule" tabindex="-1" aria-labelledby="AddNWFilterRuleLabel" aria-hidden="true">
|
||
<div class="modal-dialog modal-lg modal-fullscreen-sm-down">
|
||
<form method="post" class="modal-content">{% csrf_token %}
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="AddNWFilterRuleLabel">{% trans "Add rule" %}</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% trans "Close" %}"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p class="small text-body-secondary" id="rule_editor_help">
|
||
{% trans "One rule: in is traffic to the VM, out from it; a lower priority applies first (500 if none is given)." %}
|
||
<a href="https://libvirt.org/formatnwfilter.html#supported-protocols" target="_blank" rel="noopener">{% trans "The protocols and their attributes" %}</a>
|
||
</p>
|
||
<input type="hidden" name="nwfilterrule_xml">
|
||
<div class="xmlheight"><textarea id="rule_editor" aria-label="{% trans "Rule XML" %}" aria-describedby="rule_editor_help"><rule action='accept' direction='in' priority='500'>
|
||
<tcp dstportstart='443'/>
|
||
</rule></textarea></div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
|
||
<button type="submit" class="btn btn-primary" name="add_nwfilter_rule">{% trans "Add rule" %}</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
{% endblock %}
|
||
|
||
{% block script %}
|
||
{% if uuid %}
|
||
<script src="{% static 'js/ace/ace.js' %}"></script>
|
||
<script>
|
||
// each editor's text goes in its form's hidden field, the starter too if sent as it is,
|
||
// and it measures itself when its section or dialog is shown
|
||
function xmlEditor(textareaId, fieldName, shownBy, shownEvent) {
|
||
var textarea = document.getElementById(textareaId);
|
||
var form = textarea.closest("form");
|
||
var editor = ace.edit(textareaId);
|
||
editor.getSession().setMode("ace/mode/xml");
|
||
editor.setOption("textInputAriaLabel", textarea.getAttribute("aria-label"));
|
||
var field = form.elements[fieldName];
|
||
field.value = editor.getSession().getValue();
|
||
editor.getSession().on("change", function () {
|
||
field.value = editor.getSession().getValue();
|
||
});
|
||
document.getElementById(shownBy).addEventListener(shownEvent, () => editor.resize());
|
||
}
|
||
xmlEditor("edit_editor", "edit_xml", "nwfilter-xml", "shown.bs.collapse");
|
||
xmlEditor("rule_editor", "nwfilterrule_xml", "AddNWFilterRule", "shown.bs.modal");
|
||
</script>
|
||
{% endif %}
|
||
{% endblock %}
|