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 63b8815d1b
commit 070004f9ea
6 changed files with 115 additions and 124 deletions

View file

@ -2,21 +2,15 @@ from django import forms
from .models import State
class AddStateForm(forms.Form):
add_note = forms.BooleanField(required=False)
class ChangeStateForm(forms.Form):
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(
required=False,
required=True,
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)
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
date = models.DateTimeField(auto_now_add=True)
description = models.TextField(max_lenght=250)
description = models.TextField()
snapshot_uuid = models.UUIDField()
class Meta:

View file

@ -5,7 +5,8 @@ app_name = 'action'
urlpatterns = [
path("new/", views.NewActionView.as_view(), name="new_action"),
path('state/<int:pk>/undo/', views.ActionUndoView.as_view(), name='undo_action'),
path("new/", views.ChangeStateView.as_view(), name="change_state"),
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.shortcuts import redirect, get_object_or_404
from django.contrib import messages
from action.forms import AddStateForm
from django.views.generic.edit import DeleteView
from action.forms import ChangeStateForm, AddNoteForm
from django.views.generic.edit import DeleteView, CreateView, FormView
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
import logging
@ -12,36 +13,35 @@ import logging
device_logger = logging.getLogger('device_log')
class NewActionView(View):
class ChangeStateView(View):
def post(self, request, *args, **kwargs):
form = AddStateForm(request.POST)
form = ChangeStateForm(request.POST)
if form.is_valid():
state_definition_id = form.cleaned_data['state_id']
state_definition = get_object_or_404(StateDefinition, pk=state_definition_id)
previous_state = form.cleaned_data['previous_state']
new_state = form.cleaned_data['new_state']
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,
state=state_definition.state,
user=request.user,
institution=request.user.institution,
state=new_state,
user=self.request.user,
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.")
return redirect(request.META.get('HTTP_REFERER'))
device_logger.info(f"<Updated> State to '{new_state}', from '{previous_state}' ) by user {self.request.user}.")
message = _("State changed from '{}' to '{}'.".format(previous_state, new_state) )
messages.success(request,message)
else:
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):
model = State
class UndoStateView(DeleteView):
model = State
def delete(self, request, *args, **kwargs):
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.")
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]))
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="col">
<h3>{{ object.shortid }}
</h3>
<h3>{{ object.shortid }}</h3>
</div>
<div class="col text-end">
{% if state_definitions %}
<div class="dropdown">
<button class="btn btn-green-admin dropdown-toggle" type="button" id="addStateDropdown" data-bs-toggle="dropdown" aria-expanded="false">
{% trans "Action" %}
</button>
<ul class="dropdown-menu" aria-labelledby="addStateDropdown">
{% for state in state_definitions %}
<li>
<a class="dropdown-item d-flex justify-content-between align-items-center" href="#" data-bs-toggle="modal" data-bs-target="#addStateModal{{ state.id }}">
<span class="badge bg-secondary rounded-pill-sm">{{ forloop.counter }}
</span>
<span>{{ state.state }}
</span>
<div class="btn-group" role="group" aria-label="Actions">
<!-- change state button -->
{% if state_definitions %}
<div class="dropdown ms-2">
<a class="btn btn-green-admin dropdown-toggle" id="addStateDropdown" data-bs-toggle="dropdown" aria-expanded="false">
{% trans "Change state" %}
{% if device_states %}
({{ device_states.0.state }})
{% endif %}
</a>
<ul class="dropdown-menu" aria-labelledby="addStateDropdown" style="width: 100%;">
{% 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>
</li>
{% endfor %}
</ul>
</div>
{% else %}
<button class="btn btn-green-admin" type="button" disabled>
{% trans "Action" %}
</form>
</li>
{% endfor %}
</ul>
</div>
{% else %}
<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>
{% endif %}
</div>
</div>
</div>
<div class="row">
<div class="col">
<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>
</li>
<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 class="nav-item">
<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' %}
<!-- add state to device - popup modal-->
{% 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 fade" id="addNoteModal" tabindex="-1" aria-labelledby="addNoteModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="addStateModalLabel{{ state.id }}">{% trans "Summary of changes" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<h5 class="modal-title" id="addNoteModalLabel">{% trans "Add a Note" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% trans 'Close' %}"></button>
</div>
<div class="modal-body m-1">
{% if device_states and device_states.0.state == state.state %}
<div class="alert alert-warning d-flex align-items-center">
<i class="bi bi-exclamation-triangle-fill me-2"></i>
<span>{% trans "The device is already in the selected state." %}</span>
</div>
{% endif %}
<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 class="modal-body">
<form method="post" action="{% url 'action:add_note' %}">
{% csrf_token %}
<div class="mb-3">
<input type="hidden" name="snapshot_uuid" value="{{ object.last_uuid }}">
<label for="noteDescription" class="form-label">{% trans "Note" %}</label>
<textarea class="form-control" id="noteDescription" name="note" placeholder="Max 250 characters" name="note" rows="3" required></textarea>
</div>
<div class="d-flex align-items-center mt-2">
<i class="bi bi-arrow-right-circle text-success me-2"></i>
<span class="text-success fw-bold me-2">{% trans "To:" %}</span>
<span class="text-success fw-italic">{{ state.state }}</span>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
<button type="submit" class="btn btn-green-admin">{% trans "Save Note" %}</button>
</div>
</div>
<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 }}">
</form>
</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>
</form>
</div>
</div>
</div>
{% endfor %}
{% endif %}
{% endblock %}

View file

@ -8,7 +8,7 @@ from utils.device import create_property, create_doc, create_index
from utils.forms import MultipleFileField
from device.models import Device
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