diff --git a/ghostwriter/oplog/forms.py b/ghostwriter/oplog/forms.py index d945e1664..21ced2994 100644 --- a/ghostwriter/oplog/forms.py +++ b/ghostwriter/oplog/forms.py @@ -134,9 +134,13 @@ def __init__(self, *args, **kwargs): self.helper.layout = Layout( Row( - Column(Field("start_date", step=1), css_class="form-group col-4 mb-0"), - Column(Field("end_date", step=1), css_class="form-group col-4 mb-0"), - Column("operator_name", css_class="form-group col-4 mb-0"), + Column(Field("start_date", step=1), css_class="form-group col-6 mb-0"), + Column(Field("end_date", step=1), css_class="form-group col-6 mb-0"), + css_class="form-row", + ), + Row( + Column("entry_identifier", css_class="form-group col-6 mb-0"), + Column("operator_name", css_class="form-group col-6 mb-0"), css_class="form-row", ), Row( diff --git a/ghostwriter/oplog/migrations/0012_auto_20231211_2154.py b/ghostwriter/oplog/migrations/0012_auto_20231211_2154.py new file mode 100644 index 000000000..59fb5a2e6 --- /dev/null +++ b/ghostwriter/oplog/migrations/0012_auto_20231211_2154.py @@ -0,0 +1,22 @@ +# Generated by Django 3.2.19 on 2023-12-11 21:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('oplog', '0011_auto_20230323_2248'), + ] + + operations = [ + migrations.AddField( + model_name='oplogentry', + name='entry_identifier', + field=models.CharField(blank=True, help_text='Integrations may use this to track log entries.', max_length=65535, null=True, verbose_name='Identifier'), + ), + migrations.AddIndex( + model_name='oplogentry', + index=models.Index(fields=['oplog_id', 'entry_identifier'], name='oplog_oplog_oplog_i_0e03f5_idx'), + ), + ] diff --git a/ghostwriter/oplog/models.py b/ghostwriter/oplog/models.py index d33f77880..93b92c91f 100644 --- a/ghostwriter/oplog/models.py +++ b/ghostwriter/oplog/models.py @@ -44,6 +44,13 @@ def __str__(self): class OplogEntry(models.Model): """Stores an individual log entry, related to :model:`oplog.Oplog`.""" + entry_identifier = models.CharField( + "Identifier", + null=True, + blank=True, + help_text="Integrations may use this to track log entries.", + max_length=65535, + ) start_date = models.DateTimeField( "Start Date", null=True, @@ -135,6 +142,9 @@ class Meta: ordering = ["-start_date", "-end_date", "oplog_id"] verbose_name = "Activity log entry" verbose_name_plural = "Activity log entries" + indexes = [ + models.Index(fields=["oplog_id", "entry_identifier"]), + ] def clean(self, *args, **kwargs): if isinstance(self.start_date, str): diff --git a/ghostwriter/oplog/tests/test_views.py b/ghostwriter/oplog/tests/test_views.py index f7baa5edd..fab05b980 100644 --- a/ghostwriter/oplog/tests/test_views.py +++ b/ghostwriter/oplog/tests/test_views.py @@ -157,6 +157,7 @@ def test_view_uses_correct_template(self): def test_post_data_and_permissions(self): filename = "oplog_import_test.csv" fieldnames = [ + "entry_identifier", "start_date", "end_date", "source_ip", @@ -207,6 +208,7 @@ def test_oplog_id_override(self): """Test that the ``oplog_id`` field is overridden when importing.""" filename = "oplog_import_test.csv" fieldnames = [ + "entry_identifier", "oplog_id", "start_date", "end_date", diff --git a/ghostwriter/static/js/oplog.js b/ghostwriter/static/js/oplog.js index e5bf9ad50..eb58040b3 100644 --- a/ghostwriter/static/js/oplog.js +++ b/ghostwriter/static/js/oplog.js @@ -6,8 +6,10 @@ let hiddenLogTblColumns = JSON.parse((localStorage.getItem('hiddenLogTblColumns' // Assemble the array of column information for the table let columnInfo = [] columnInfo = [ - ['startDateCheckBox', 'startDateColumn', 'Start Date', 'start_date'], - ['endDateCheckbox', 'endDateColumn', 'End Date', 'end_date'], + // [checkBoxID, columnClass, Pretty Name, name_in_json, toHtmlFunc (default `jsescape`), shownByDefault (default true)] + ['identifierCheckBox', 'identifierColumn', 'Identifier', 'entry_identifier', undefined, false], + ['startDateCheckBox', 'startDateColumn', 'Start Date', 'start_date', entry => jsEscape(entry).replace(/\.\d+/, "").replace("Z", "").replace("T", " ")], + ['endDateCheckbox', 'endDateColumn', 'End Date', 'end_date', entry => jsEscape(entry).replace(/\.\d+/, "").replace("Z", "").replace("T", " ")], ['sourceIPCheckbox', 'sourceIPColumn', 'Source', 'source_ip'], ['destIPCheckbox', 'destIPColumn', 'Destination', 'dest_ip'], ['toolNameCheckbox', 'toolNameColumn', 'Tool Name', 'tool'], @@ -17,25 +19,17 @@ columnInfo = [ ['outputCheckbox', 'outputColumn', 'Output', 'output'], ['commentsCheckbox', 'commentsColumn', 'Comments', 'comments'], ['operatorCheckbox', 'operatorColumn', 'Operator', 'operator_name'], - ['tagsCheckbox', 'tagsColumn', 'Tags', 'tags'], + ['tagsCheckbox', 'tagsColumn', 'Tags', 'tags', entry => stylizeTags(jsEscape(entry))], ['optionsCheckbox', 'optionsColumn', 'Options', ''], ] // Generate a table row based on a log entry function generateTableHeaders() { - return `