simpler state change and action input

This commit is contained in:
Thomas Nahuel Rusiecki 2024-12-19 18:17:09 -03:00 committed by Cayo Puigdefabregas
parent 9484389a02
commit 3fbc505700
6 changed files with 115 additions and 124 deletions

View file

@ -2,21 +2,15 @@ from django import forms
from .models import State from .models import State
class AddStateForm(forms.Form): class ChangeStateForm(forms.Form):
add_note = forms.BooleanField(required=False) previous_state = forms.CharField(widget=forms.HiddenInput())
snapshot_uuid = forms.UUIDField(widget=forms.HiddenInput())
new_state = forms.CharField(widget=forms.HiddenInput())
class AddNoteForm(forms.Form):
snapshot_uuid = forms.UUIDField(widget=forms.HiddenInput())
note = forms.CharField( note = forms.CharField(
required=False, required=True,
widget=forms.Textarea(attrs={'rows': 4, 'maxlength': 200, 'placeholder': 'Max 200 characters'}), widget=forms.Textarea(attrs={'rows': 4, 'maxlength': 200, 'placeholder': 'Max 200 characters'}),
) )
state_id = forms.IntegerField(required=True, widget=forms.HiddenInput())
snapshot_uuid = forms.UUIDField(required=True, widget=forms.HiddenInput())
def clean(self):
cleaned_data = super().clean()
add_note = cleaned_data.get('add_note')
note = cleaned_data.get('note')
if add_note == True and not note:
self.add_error('note', 'Please enter a note if you checked "Add a note".')
return cleaned_data

View file

@ -58,7 +58,7 @@ class Note(models.Model):
institution = models.ForeignKey(Institution, on_delete=models.CASCADE) institution = models.ForeignKey(Institution, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True) user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
date = models.DateTimeField(auto_now_add=True) date = models.DateTimeField(auto_now_add=True)
description = models.TextField(max_lenght=250) description = models.TextField()
snapshot_uuid = models.UUIDField() snapshot_uuid = models.UUIDField()
class Meta: class Meta:

View file

@ -5,7 +5,8 @@ app_name = 'action'
urlpatterns = [ urlpatterns = [
path("new/", views.NewActionView.as_view(), name="new_action"), path("new/", views.ChangeStateView.as_view(), name="change_state"),
path('state/<int:pk>/undo/', views.ActionUndoView.as_view(), name='undo_action'), path('state/<int:pk>/undo/', views.UndoStateView.as_view(), name='undo_state'),
path('note/add/', views.AddNoteView.as_view(), name='add_note'),
] ]

View file

@ -1,10 +1,11 @@
from django.views import View from django.views import View
from django.shortcuts import redirect, get_object_or_404 from django.shortcuts import redirect, get_object_or_404
from django.contrib import messages from django.contrib import messages
from action.forms import AddStateForm from action.forms import ChangeStateForm, AddNoteForm
from django.views.generic.edit import DeleteView from django.views.generic.edit import DeleteView, CreateView, FormView
from django.urls import reverse_lazy from django.urls import reverse_lazy
from action.models import State, StateDefinition from django.utils.translation import gettext_lazy as _
from action.models import State, StateDefinition, Note
from device.models import Device from device.models import Device
import logging import logging
@ -12,36 +13,35 @@ import logging
device_logger = logging.getLogger('device_log') device_logger = logging.getLogger('device_log')
class NewActionView(View): class ChangeStateView(View):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
form = AddStateForm(request.POST) form = ChangeStateForm(request.POST)
if form.is_valid(): if form.is_valid():
state_definition_id = form.cleaned_data['state_id'] previous_state = form.cleaned_data['previous_state']
state_definition = get_object_or_404(StateDefinition, pk=state_definition_id) new_state = form.cleaned_data['new_state']
snapshot_uuid = form.cleaned_data['snapshot_uuid'] snapshot_uuid = form.cleaned_data['snapshot_uuid']
#TODO: implement notes
note = form.cleaned_data.get('note', '')
state = State.objects.create( State.objects.create(
snapshot_uuid=snapshot_uuid, snapshot_uuid=snapshot_uuid,
state=state_definition.state, state=new_state,
user=request.user, user=self.request.user,
institution=request.user.institution, institution=self.request.user.institution,
) )
#TODO: also change logger for full fledged table
device_logger.info(f"<Updated> State to '{state_definition.state}', for device '{snapshot_uuid}') by user {self.request.user}.")
messages.success(request, f"Action to '{state_definition.state}' has been added.") device_logger.info(f"<Updated> State to '{new_state}', from '{previous_state}' ) by user {self.request.user}.")
return redirect(request.META.get('HTTP_REFERER'))
message = _("State changed from '{}' to '{}'.".format(previous_state, new_state) )
messages.success(request,message)
else: else:
messages.error(request, "There was an error with your submission.") messages.error(request, "There was an error with your submission.")
return redirect(request.META.get('HTTP_REFERER'))
return redirect(request.META.get('HTTP_REFERER') )
class ActionUndoView(DeleteView): class UndoStateView(DeleteView):
model = State model = State
def delete(self, request, *args, **kwargs): def delete(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
@ -52,3 +52,24 @@ class ActionUndoView(DeleteView):
messages.info(self.request, f"Action to state: {self.object.state} has been deleted.") messages.info(self.request, f"Action to state: {self.object.state} has been deleted.")
device_logger.info(f"<Deleted> State '{self.object.state}', for device '{self.object.snapshot_uuid}') by user {self.request.user}.") device_logger.info(f"<Deleted> State '{self.object.state}', for device '{self.object.snapshot_uuid}') by user {self.request.user}.")
return self.request.META.get('HTTP_REFERER', reverse_lazy('device:details', args=[self.object.snapshot_uuid])) return self.request.META.get('HTTP_REFERER', reverse_lazy('device:details', args=[self.object.snapshot_uuid]))
class AddNoteView(View):
def post(self, request, *args, **kwargs):
form = AddNoteForm(request.POST)
if form.is_valid():
note = form.cleaned_data['note']
snapshot_uuid = form.cleaned_data['snapshot_uuid']
Note.objects.create(
snapshot_uuid=snapshot_uuid,
description=note,
user=self.request.user,
institution=self.request.user.institution,
)
messages.success(request, _("Note has been added"))
else:
messages.error(request, "There was an error with your submission.")
return redirect(request.META.get('HTTP_REFERER') )

View file

@ -5,36 +5,56 @@
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<h3>{{ object.shortid }} <h3>{{ object.shortid }}</h3>
</h3>
</div> </div>
<div class="col text-end"> <div class="col text-end">
{% if state_definitions %} <div class="btn-group" role="group" aria-label="Actions">
<div class="dropdown">
<button class="btn btn-green-admin dropdown-toggle" type="button" id="addStateDropdown" data-bs-toggle="dropdown" aria-expanded="false"> <!-- change state button -->
{% trans "Action" %} {% if state_definitions %}
</button> <div class="dropdown ms-2">
<ul class="dropdown-menu" aria-labelledby="addStateDropdown"> <a class="btn btn-green-admin dropdown-toggle" id="addStateDropdown" data-bs-toggle="dropdown" aria-expanded="false">
{% for state in state_definitions %} {% trans "Change state" %}
<li> {% if device_states %}
<a class="dropdown-item d-flex justify-content-between align-items-center" href="#" data-bs-toggle="modal" data-bs-target="#addStateModal{{ state.id }}"> ({{ device_states.0.state }})
<span class="badge bg-secondary rounded-pill-sm">{{ forloop.counter }} {% endif %}
</span> </a>
<span>{{ state.state }} <ul class="dropdown-menu" aria-labelledby="addStateDropdown" style="width: 100%;">
</span> {% for state in state_definitions %}
<li style="width: 100%;">
<form id="changeStateForm{{ state.id }}" method="post" action="{% url 'action:change_state' %}">
{% csrf_token %}
<input type="hidden" name="previous_state" value="{{ device_states.0.state }}">
<input type="hidden" name="snapshot_uuid" value="{{ object.last_uuid }}">
<input type="hidden" name="new_state" value="{{ state.state }}">
<a class="dropdown-item d-flex justify-content-between align-items-center" href="#" onclick="document.getElementById('changeStateForm{{ state.id }}').submit(); return false;">
<span>{{ state.state }}</span>
<span class="badge bg-secondary rounded-pill-sm">{{ forloop.counter }}</span>
</a> </a>
</li> </form>
{% endfor %} </li>
</ul> {% endfor %}
</div> </ul>
{% else %} </div>
<button class="btn btn-green-admin" type="button" disabled> {% else %}
{% trans "Action" %} <button class="btn btn-green-admin" type="button" disabled>
<i class="bi bi-plus"></i> {% trans "Change state" %}
{% if device_states %}
({{ device_states.0.state }})
{% endif %}
</button>
{% endif %}
<!-- Add note button -->
<button class="btn btn-warning ms-2" type="button" data-bs-toggle="modal" data-bs-target="#addNoteModal">
<i class="bi bi-sticky"></i> {% trans "Add a note" %}
</button> </button>
{% endif %}
</div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<ul class="nav nav-tabs nav-tabs-bordered"> <ul class="nav nav-tabs nav-tabs-bordered">
@ -45,7 +65,7 @@
<a href="#log" class="nav-link" data-bs-toggle="tab" data-bs-target="#log">{% trans 'Log' %}</a> <a href="#log" class="nav-link" data-bs-toggle="tab" data-bs-target="#log">{% trans 'Log' %}</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="#user_properties" class="nav-link" data-bs-toggle="tab" data-bs-target="#user_properties">{% trans 'User properties' %}</a> <a href="#user_prope rties" class="nav-link" data-bs-toggle="tab" data-bs-target="#user_properties">{% trans 'User properties' %}</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="#documents" class="nav-link" data-bs-toggle="tab" data-bs-target="#documents">{% trans 'Documents' %}</a> <a href="#documents" class="nav-link" data-bs-toggle="tab" data-bs-target="#documents">{% trans 'Documents' %}</a>
@ -84,75 +104,30 @@
{% include 'tabs/evidences.html' %} {% include 'tabs/evidences.html' %}
<div class="modal fade" id="addNoteModal" tabindex="-1" aria-labelledby="addNoteModalLabel" aria-hidden="true">
<!-- add state to device - popup modal--> <div class="modal-dialog">
{% if state_definitions %}
{% for state in state_definitions %}
<div class="modal fade" id="addStateModal{{ state.id }}" tabindex="-1" aria-labelledby="addStateModalLabel{{ state.id }}" aria-hidden="true">
<div class="modal-dialog">
<form method="post" action="{% url 'action:new_action'%}">
{% csrf_token %}
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="addStateModalLabel{{ state.id }}">{% trans "Summary of changes" %}</h5> <h5 class="modal-title" id="addNoteModalLabel">{% trans "Add a Note" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% trans 'Close' %}"></button>
</div> </div>
<div class="modal-body m-1"> <div class="modal-body">
{% if device_states and device_states.0.state == state.state %} <form method="post" action="{% url 'action:add_note' %}">
<div class="alert alert-warning d-flex align-items-center"> {% csrf_token %}
<i class="bi bi-exclamation-triangle-fill me-2"></i> <div class="mb-3">
<span>{% trans "The device is already in the selected state." %}</span> <input type="hidden" name="snapshot_uuid" value="{{ object.last_uuid }}">
</div> <label for="noteDescription" class="form-label">{% trans "Note" %}</label>
{% endif %} <textarea class="form-control" id="noteDescription" name="note" placeholder="Max 250 characters" name="note" rows="3" required></textarea>
<div class="mb-3">
<div class="d-flex align-items-center">
<i class="bi bi-arrow-right-circle text-danger me-2"></i>
<span class="text-danger fw-bold me-2">{% trans "From:" %}</span>
<span class="text-danger fw-italic">
{% if device_states %}
{{device_states.0.state}}
{% else %}
{% trans 'None' %}
{% endif %}
</span>
</div> </div>
<div class="d-flex align-items-center mt-2"> <div class="modal-footer">
<i class="bi bi-arrow-right-circle text-success me-2"></i> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
<span class="text-success fw-bold me-2">{% trans "To:" %}</span> <button type="submit" class="btn btn-green-admin">{% trans "Save Note" %}</button>
<span class="text-success fw-italic">{{ state.state }}</span>
</div> </div>
</div> </form>
<div class="form-check form-switch mt-3 d-flex justify-content-end">
<label class="form-check-label font-monospace" for="addNoteCheckbox{{ state.id }}">
{% trans "Add a note" %}
</label>
<input class="form-check-input ms-2" type="checkbox" id="addNoteCheckbox{{ state.id }}" data-bs-toggle="collapse" data-bs-target="#collapseInput{{ state.id }}" name="add_note">
</div>
<div class="mb-3 mt-2 collapse" id="collapseInput{{ state.id }}">
<textarea type="text" class="form-control" id="stateNote{{ state.id }}" name="note" rows="4" maxlength="200" placeholder="{% trans "Max 200 characters" %}"></textarea>
</div>
<input type="hidden" name="state_id" value="{{ state.id }}">
<input type="hidden" name="snapshot_uuid" value="{{ object.last_uuid }}">
</div> </div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
<button type="submit" class="btn btn-primary">{% trans "Add State" %}</button>
</div>
</div> </div>
</form> </div>
</div> </div>
</div>
{% endfor %}
{% endif %}
{% endblock %} {% endblock %}

View file

@ -8,7 +8,7 @@ from utils.device import create_property, create_doc, create_index
from utils.forms import MultipleFileField from utils.forms import MultipleFileField
from device.models import Device from device.models import Device
from evidence.parse import Build from evidence.parse import Build
from evidence.models import SystemProperty from evidence.models import SystemProperty, UserProperty
from utils.save_snapshots import move_json, save_in_disk from utils.save_snapshots import move_json, save_in_disk