feat(osint): display execution history
This commit is contained in:
parent
1cbbc6acb1
commit
bb8295af85
9 changed files with 473 additions and 13 deletions
|
|
@ -153,7 +153,9 @@ Le socle actuel comprend notamment :
|
|||
- création transactionnelle des relations DNS `resolves_to`, `aliases_to` et
|
||||
`uses_name_server` depuis l'entité interrogée ;
|
||||
- provenance OSINT SQLite V3 conservant les arguments, sorties brutes,
|
||||
empreinte SHA-256 et liaisons vers les entités et relations intégrées.
|
||||
empreinte SHA-256 et liaisons vers les entités et relations intégrées ;
|
||||
- historique OSINT contextuel en lecture seule avec détail des exécutions,
|
||||
sorties standard et d'erreur, et objets créés ou réutilisés.
|
||||
|
||||
Les outils actuellement présents dans le catalogue initial sont :
|
||||
|
||||
|
|
|
|||
|
|
@ -1125,6 +1125,11 @@ transaction que l'intégration DNS.
|
|||
Les descriptions métier restent présentes pour la lisibilité, mais les
|
||||
entités DNS demeurent des résultats OSINT à vérifier et non des faits établis.
|
||||
|
||||
Le menu contextuel du workspace permet de consulter cet historique pour
|
||||
l'entité ou la relation sélectionnée. La vue est strictement en lecture seule
|
||||
et expose les métadonnées, les sorties brutes rendues en UTF-8, l'empreinte et
|
||||
les objets liés avec leur disposition `created` ou `reused`.
|
||||
|
||||
---
|
||||
|
||||
# 6. Tables de liaison
|
||||
|
|
|
|||
|
|
@ -26,6 +26,25 @@ gboolean osint_execution_dao_insert(
|
|||
OsintExecutionRecord *osint_execution_dao_find_by_identifier(
|
||||
OsintExecutionDao *dao, const char *identifier, GError **error
|
||||
);
|
||||
/**
|
||||
* @brief Liste les exécutions d'une sélection, de la plus récente à l'ancienne.
|
||||
*
|
||||
* @return Tableau possédé de OsintExecutionRecord, ou NULL en cas d'erreur.
|
||||
*/
|
||||
GPtrArray *osint_execution_dao_list_by_selection(
|
||||
OsintExecutionDao *dao, const char *selection_kind,
|
||||
const char *selection_identifier, GError **error
|
||||
);
|
||||
/**
|
||||
* @brief Liste les objets liés à une exécution sous forme de libellés possédés.
|
||||
*
|
||||
* Chaque libellé indique la nature, l'UUID et la disposition created/reused.
|
||||
*
|
||||
* @return Tableau possédé de chaînes, ou NULL en cas d'erreur.
|
||||
*/
|
||||
GPtrArray *osint_execution_dao_list_linked_objects(
|
||||
OsintExecutionDao *dao, const char *execution_identifier, GError **error
|
||||
);
|
||||
/** @brief Lie une entité créée ou réutilisée à une exécution. */
|
||||
gboolean osint_execution_dao_link_entity(
|
||||
OsintExecutionDao *dao, const char *execution_identifier,
|
||||
|
|
|
|||
32
include/views/osint_execution_history_dialog.h
Normal file
32
include/views/osint_execution_history_dialog.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/******************************************************************************
|
||||
* @file osint_execution_history_dialog.h
|
||||
* @brief Consultation en lecture seule de l'historique OSINT.
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef LABFY_INVESTIGATION_OSINT_EXECUTION_HISTORY_DIALOG_H
|
||||
#define LABFY_INVESTIGATION_OSINT_EXECUTION_HISTORY_DIALOG_H
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/**
|
||||
* @brief Affiche les exécutions et leurs objets liés dans une fenêtre modale.
|
||||
*
|
||||
* La fonction conserve une référence sur les deux conteneurs jusqu'à la
|
||||
* fermeture. records contient des OsintExecutionRecord. linked_objects associe
|
||||
* chaque UUID d'exécution à un GPtrArray de chaînes.
|
||||
*
|
||||
* @param parent_window Fenêtre parente, ou NULL.
|
||||
* @param records Historique non vide, de la plus récente à l'ancienne.
|
||||
* @param linked_objects Table des objets liés par UUID d'exécution.
|
||||
*/
|
||||
void osint_execution_history_dialog_present(
|
||||
GtkWindow *parent_window,
|
||||
GPtrArray *records,
|
||||
GHashTable *linked_objects
|
||||
);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
||||
|
|
@ -34,6 +34,7 @@
|
|||
#include "views/evidence_import_dialog.h"
|
||||
#include "views/create_relation_dialog.h"
|
||||
#include "views/osint_dns_review_dialog.h"
|
||||
#include "views/osint_execution_history_dialog.h"
|
||||
#include "dao/evidence_dao.h"
|
||||
#include "dao/entity_dao.h"
|
||||
#include "dao/osint_execution_dao.h"
|
||||
|
|
@ -1476,6 +1477,61 @@ static void application_start_dns_lookup(
|
|||
tool_task_free(tool_task);
|
||||
}
|
||||
|
||||
/** @brief Charge et présente l'historique OSINT d'une sélection. */
|
||||
static void application_present_osint_history(
|
||||
Application *application,
|
||||
const char *selection_kind,
|
||||
const char *selection_identifier
|
||||
)
|
||||
{
|
||||
Database *database = NULL;
|
||||
OsintExecutionDao *dao = NULL;
|
||||
GPtrArray *records = NULL;
|
||||
GHashTable *linked_objects = NULL;
|
||||
GError *error = NULL;
|
||||
if (application == NULL || application->session == NULL ||
|
||||
application->main_window == NULL) return;
|
||||
database = investigation_session_get_database(application->session);
|
||||
dao = osint_execution_dao_new(database, &error);
|
||||
if (dao != NULL) records = osint_execution_dao_list_by_selection(
|
||||
dao, selection_kind, selection_identifier, &error);
|
||||
linked_objects = g_hash_table_new_full(
|
||||
g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_ptr_array_unref);
|
||||
for (guint index = 0U; records != NULL && index < records->len; index++)
|
||||
{
|
||||
const OsintExecutionRecord *record = g_ptr_array_index(records, index);
|
||||
GPtrArray *objects = osint_execution_dao_list_linked_objects(
|
||||
dao, osint_execution_record_get_identifier(record), &error);
|
||||
if (objects == NULL) break;
|
||||
g_hash_table_insert(linked_objects,
|
||||
g_strdup(osint_execution_record_get_identifier(record)), objects);
|
||||
}
|
||||
osint_execution_dao_free(dao);
|
||||
if (error != NULL)
|
||||
{
|
||||
application_present_error(application, "Historique OSINT indisponible",
|
||||
error->message);
|
||||
g_clear_error(&error);
|
||||
}
|
||||
else if (records == NULL || records->len == 0U)
|
||||
{
|
||||
application_message_dialog_present(
|
||||
main_window_get_window(application->main_window),
|
||||
APPLICATION_MESSAGE_DIALOG_INFORMATION,
|
||||
"Historique OSINT",
|
||||
"Aucune exécution OSINT n'est enregistrée pour cette sélection."
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
osint_execution_history_dialog_present(
|
||||
main_window_get_window(application->main_window),
|
||||
records, linked_objects);
|
||||
}
|
||||
g_clear_pointer(&records, g_ptr_array_unref);
|
||||
g_hash_table_unref(linked_objects);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Traite une action OSINT relayée par la fenêtre principale.
|
||||
*/
|
||||
|
|
@ -1488,6 +1544,18 @@ static void application_on_osint_action_requested(
|
|||
{
|
||||
Application *application = user_data;
|
||||
|
||||
if (g_strcmp0(action_identifier, "history-entity") == 0 ||
|
||||
g_strcmp0(action_identifier, "history-relation") == 0)
|
||||
{
|
||||
application_present_osint_history(
|
||||
application,
|
||||
g_strcmp0(action_identifier, "history-relation") == 0
|
||||
? "relation" : "entity",
|
||||
target_identifier
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_strcmp0(action_identifier, "dns-preview") == 0)
|
||||
{
|
||||
application_start_dns_lookup(
|
||||
|
|
|
|||
|
|
@ -20,6 +20,21 @@ static const char *const find_sql =
|
|||
"selection_kind,target_value,arguments,started_at,finished_at,exit_code,"
|
||||
"final_state,stdout_raw,stderr_raw,output_sha256 FROM osint_executions "
|
||||
"WHERE id=?;";
|
||||
static const char *const list_by_selection_sql =
|
||||
"SELECT id,tool_identifier,tool_version,action_identifier,selection_id,"
|
||||
"selection_kind,target_value,arguments,started_at,finished_at,exit_code,"
|
||||
"final_state,stdout_raw,stderr_raw,output_sha256 FROM osint_executions "
|
||||
"WHERE (selection_kind=? AND selection_id=?) "
|
||||
"OR (?='entity' AND id IN (SELECT execution_id "
|
||||
"FROM osint_execution_entities WHERE entity_id=?)) "
|
||||
"OR (?='relation' AND id IN (SELECT execution_id "
|
||||
"FROM osint_execution_relations WHERE relation_id=?)) "
|
||||
"ORDER BY finished_at DESC,id DESC;";
|
||||
static const char *const list_linked_objects_sql =
|
||||
"SELECT 'Entité',entity_id,disposition FROM osint_execution_entities "
|
||||
"WHERE execution_id=? UNION ALL "
|
||||
"SELECT 'Relation',relation_id,disposition FROM osint_execution_relations "
|
||||
"WHERE execution_id=? ORDER BY 1,2;";
|
||||
|
||||
static void osint_execution_dao_set_error(
|
||||
OsintExecutionDao *dao, GError **error, const char *context
|
||||
|
|
@ -101,22 +116,16 @@ cleanup:
|
|||
return success;
|
||||
}
|
||||
|
||||
OsintExecutionRecord *osint_execution_dao_find_by_identifier(
|
||||
OsintExecutionDao *dao, const char *identifier, GError **error
|
||||
static OsintExecutionRecord *osint_execution_dao_read_record(
|
||||
DatabaseStatement *statement, GError **error
|
||||
)
|
||||
{
|
||||
DatabaseStatement *statement = NULL;
|
||||
OsintExecutionRecord *record = NULL;
|
||||
char *values[12] = {0};
|
||||
GBytes *stdout_raw = NULL;
|
||||
GBytes *stderr_raw = NULL;
|
||||
int64_t exit_code = 0;
|
||||
bool exit_is_null = true;
|
||||
if (dao == NULL || identifier == NULL) return NULL;
|
||||
statement = database_statement_prepare(dao->database, find_sql);
|
||||
if (statement == NULL || !database_statement_bind_text(statement, 1, identifier) ||
|
||||
database_statement_step(statement) != DATABASE_STATEMENT_STEP_ROW)
|
||||
goto cleanup;
|
||||
if (!database_statement_column_text(statement, 0, &values[0]) ||
|
||||
!database_statement_column_text(statement, 1, &values[1]) ||
|
||||
!database_statement_column_text(statement, 2, &values[2]) ||
|
||||
|
|
@ -138,16 +147,108 @@ OsintExecutionRecord *osint_execution_dao_find_by_identifier(
|
|||
values[6], values[7], values[8], values[9], !exit_is_null,
|
||||
(gint) exit_code, values[10], stdout_raw, stderr_raw, values[11], error);
|
||||
cleanup:
|
||||
if (record == NULL && error != NULL && *error == NULL)
|
||||
osint_execution_dao_set_error(dao, error,
|
||||
"Impossible de lire l'exécution OSINT");
|
||||
for (guint index = 0; index < G_N_ELEMENTS(values); index++) g_free(values[index]);
|
||||
g_clear_pointer(&stdout_raw, g_bytes_unref);
|
||||
g_clear_pointer(&stderr_raw, g_bytes_unref);
|
||||
return record;
|
||||
}
|
||||
|
||||
OsintExecutionRecord *osint_execution_dao_find_by_identifier(
|
||||
OsintExecutionDao *dao, const char *identifier, GError **error
|
||||
)
|
||||
{
|
||||
DatabaseStatement *statement = NULL;
|
||||
OsintExecutionRecord *record = NULL;
|
||||
if (dao == NULL || identifier == NULL) return NULL;
|
||||
statement = database_statement_prepare(dao->database, find_sql);
|
||||
if (statement != NULL && database_statement_bind_text(statement, 1, identifier) &&
|
||||
database_statement_step(statement) == DATABASE_STATEMENT_STEP_ROW)
|
||||
record = osint_execution_dao_read_record(statement, error);
|
||||
if (record == NULL && error != NULL && *error == NULL)
|
||||
osint_execution_dao_set_error(dao, error,
|
||||
"Impossible de lire l'exécution OSINT");
|
||||
database_statement_finalize(statement);
|
||||
return record;
|
||||
}
|
||||
|
||||
GPtrArray *osint_execution_dao_list_by_selection(
|
||||
OsintExecutionDao *dao, const char *selection_kind,
|
||||
const char *selection_identifier, GError **error
|
||||
)
|
||||
{
|
||||
DatabaseStatement *statement = NULL;
|
||||
GPtrArray *records = NULL;
|
||||
DatabaseStatementStepResult step = DATABASE_STATEMENT_STEP_ERROR;
|
||||
if (dao == NULL || selection_identifier == NULL ||
|
||||
(g_strcmp0(selection_kind, "entity") != 0 &&
|
||||
g_strcmp0(selection_kind, "relation") != 0)) return NULL;
|
||||
statement = database_statement_prepare(dao->database, list_by_selection_sql);
|
||||
records = g_ptr_array_new_with_free_func(
|
||||
(GDestroyNotify) osint_execution_record_free);
|
||||
if (statement == NULL || records == NULL ||
|
||||
!database_statement_bind_text(statement, 1, selection_kind) ||
|
||||
!database_statement_bind_text(statement, 2, selection_identifier) ||
|
||||
!database_statement_bind_text(statement, 3, selection_kind) ||
|
||||
!database_statement_bind_text(statement, 4, selection_identifier) ||
|
||||
!database_statement_bind_text(statement, 5, selection_kind) ||
|
||||
!database_statement_bind_text(statement, 6, selection_identifier))
|
||||
goto failure;
|
||||
while ((step = database_statement_step(statement)) == DATABASE_STATEMENT_STEP_ROW)
|
||||
{
|
||||
OsintExecutionRecord *record = osint_execution_dao_read_record(statement, error);
|
||||
if (record == NULL) goto failure;
|
||||
g_ptr_array_add(records, record);
|
||||
}
|
||||
if (step != DATABASE_STATEMENT_STEP_DONE) goto failure;
|
||||
database_statement_finalize(statement);
|
||||
return records;
|
||||
failure:
|
||||
osint_execution_dao_set_error(dao, error,
|
||||
"Impossible de lister l'historique OSINT");
|
||||
database_statement_finalize(statement);
|
||||
g_clear_pointer(&records, g_ptr_array_unref);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GPtrArray *osint_execution_dao_list_linked_objects(
|
||||
OsintExecutionDao *dao, const char *execution_identifier, GError **error
|
||||
)
|
||||
{
|
||||
DatabaseStatement *statement = NULL;
|
||||
GPtrArray *objects = NULL;
|
||||
DatabaseStatementStepResult step = DATABASE_STATEMENT_STEP_ERROR;
|
||||
if (dao == NULL || execution_identifier == NULL) return NULL;
|
||||
statement = database_statement_prepare(dao->database, list_linked_objects_sql);
|
||||
objects = g_ptr_array_new_with_free_func(g_free);
|
||||
if (statement == NULL || objects == NULL ||
|
||||
!database_statement_bind_text(statement, 1, execution_identifier) ||
|
||||
!database_statement_bind_text(statement, 2, execution_identifier))
|
||||
goto failure;
|
||||
while ((step = database_statement_step(statement)) == DATABASE_STATEMENT_STEP_ROW)
|
||||
{
|
||||
char *kind = NULL; char *identifier = NULL; char *disposition = NULL;
|
||||
char *description = NULL;
|
||||
if (!database_statement_column_text(statement, 0, &kind) ||
|
||||
!database_statement_column_text(statement, 1, &identifier) ||
|
||||
!database_statement_column_text(statement, 2, &disposition))
|
||||
{ g_free(kind); g_free(identifier); g_free(disposition); goto failure; }
|
||||
description = g_strdup_printf("%s %s — %s", kind, identifier,
|
||||
g_strcmp0(disposition, "created") == 0 ? "créée" : "réutilisée");
|
||||
g_free(kind); g_free(identifier); g_free(disposition);
|
||||
if (description == NULL) goto failure;
|
||||
g_ptr_array_add(objects, description);
|
||||
}
|
||||
if (step != DATABASE_STATEMENT_STEP_DONE) goto failure;
|
||||
database_statement_finalize(statement);
|
||||
return objects;
|
||||
failure:
|
||||
osint_execution_dao_set_error(dao, error,
|
||||
"Impossible de lister les objets liés à l'exécution OSINT");
|
||||
database_statement_finalize(statement);
|
||||
g_clear_pointer(&objects, g_ptr_array_unref);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static gboolean osint_execution_dao_link(
|
||||
OsintExecutionDao *dao, const char *table, const char *object_column,
|
||||
const char *execution_identifier, const char *object_identifier,
|
||||
|
|
|
|||
173
src/views/osint_execution_history_dialog.c
Normal file
173
src/views/osint_execution_history_dialog.c
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
/******************************************************************************
|
||||
* @file osint_execution_history_dialog.c
|
||||
* @brief Consultation en lecture seule de l'historique OSINT.
|
||||
******************************************************************************/
|
||||
|
||||
#include "views/osint_execution_history_dialog.h"
|
||||
|
||||
#include "models/osint_execution_record.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GtkWindow *window;
|
||||
GtkTextBuffer *details_buffer;
|
||||
GPtrArray *records;
|
||||
GHashTable *linked_objects;
|
||||
} OsintExecutionHistoryDialogContext;
|
||||
|
||||
/** @brief Libère les données conservées par la fenêtre. */
|
||||
static void osint_execution_history_dialog_context_free(gpointer data)
|
||||
{
|
||||
OsintExecutionHistoryDialogContext *context = data;
|
||||
if (context == NULL) return;
|
||||
g_clear_pointer(&context->records, g_ptr_array_unref);
|
||||
g_clear_pointer(&context->linked_objects, g_hash_table_unref);
|
||||
g_free(context);
|
||||
}
|
||||
|
||||
/** @brief Convertit une sortie brute en texte UTF-8 affichable. */
|
||||
static char *osint_execution_history_dialog_output_to_text(GBytes *bytes)
|
||||
{
|
||||
gconstpointer data = NULL;
|
||||
gsize size = 0U;
|
||||
if (bytes == NULL) return g_strdup("");
|
||||
data = g_bytes_get_data(bytes, &size);
|
||||
return data != NULL && size > 0U
|
||||
? g_utf8_make_valid(data, (gssize) size) : g_strdup("");
|
||||
}
|
||||
|
||||
/** @brief Construit le détail textuel complet d'une exécution. */
|
||||
static char *osint_execution_history_dialog_build_details(
|
||||
const OsintExecutionRecord *record, GPtrArray *linked_objects
|
||||
)
|
||||
{
|
||||
GBytes *stdout_raw = osint_execution_record_ref_stdout(record);
|
||||
GBytes *stderr_raw = osint_execution_record_ref_stderr(record);
|
||||
char *stdout_text = osint_execution_history_dialog_output_to_text(stdout_raw);
|
||||
char *stderr_text = osint_execution_history_dialog_output_to_text(stderr_raw);
|
||||
GString *details = g_string_new(NULL);
|
||||
g_string_append_printf(details,
|
||||
"Exécution : %s\nOutil : %s%s%s\nAction : %s\nÉtat : %s\n"
|
||||
"Sélection : %s %s\nCible : %s\nDébut : %s\nFin : %s\n"
|
||||
"Code de sortie : ",
|
||||
osint_execution_record_get_identifier(record),
|
||||
osint_execution_record_get_tool_identifier(record),
|
||||
osint_execution_record_get_tool_version(record) != NULL ? " — " : "",
|
||||
osint_execution_record_get_tool_version(record) != NULL
|
||||
? osint_execution_record_get_tool_version(record) : "",
|
||||
osint_execution_record_get_action_identifier(record),
|
||||
osint_execution_record_get_final_state(record),
|
||||
osint_execution_record_get_selection_kind(record),
|
||||
osint_execution_record_get_selection_identifier(record),
|
||||
osint_execution_record_get_target_value(record),
|
||||
osint_execution_record_get_started_at(record),
|
||||
osint_execution_record_get_finished_at(record));
|
||||
if (osint_execution_record_has_exit_code(record))
|
||||
g_string_append_printf(details, "%d", osint_execution_record_get_exit_code(record));
|
||||
else g_string_append(details, "indisponible");
|
||||
g_string_append_printf(details,
|
||||
"\nArguments : %s\nSHA-256 : %s\n\nObjets liés :\n",
|
||||
osint_execution_record_get_arguments(record),
|
||||
osint_execution_record_get_output_sha256(record));
|
||||
if (linked_objects == NULL || linked_objects->len == 0U)
|
||||
g_string_append(details, "Aucun objet intégré.\n");
|
||||
else for (guint index = 0U; index < linked_objects->len; index++)
|
||||
g_string_append_printf(details, "- %s\n",
|
||||
(const char *) g_ptr_array_index(linked_objects, index));
|
||||
g_string_append_printf(details, "\nSortie standard :\n%s\n\nSortie d'erreur :\n%s",
|
||||
stdout_text[0] != '\0' ? stdout_text : "(vide)",
|
||||
stderr_text[0] != '\0' ? stderr_text : "(vide)");
|
||||
g_bytes_unref(stdout_raw); g_bytes_unref(stderr_raw);
|
||||
g_free(stdout_text); g_free(stderr_text);
|
||||
return g_string_free(details, FALSE);
|
||||
}
|
||||
|
||||
/** @brief Affiche le détail correspondant au bouton d'historique activé. */
|
||||
static void osint_execution_history_dialog_on_record_clicked(
|
||||
GtkButton *button, gpointer user_data
|
||||
)
|
||||
{
|
||||
OsintExecutionHistoryDialogContext *context = user_data;
|
||||
guint encoded_index = GPOINTER_TO_UINT(g_object_get_data(
|
||||
G_OBJECT(button), "osint-execution-index"));
|
||||
OsintExecutionRecord *record = NULL;
|
||||
GPtrArray *linked_objects = NULL;
|
||||
char *details = NULL;
|
||||
if (context == NULL || encoded_index == 0U ||
|
||||
encoded_index > context->records->len) return;
|
||||
record = g_ptr_array_index(context->records, encoded_index - 1U);
|
||||
linked_objects = g_hash_table_lookup(context->linked_objects,
|
||||
osint_execution_record_get_identifier(record));
|
||||
details = osint_execution_history_dialog_build_details(record, linked_objects);
|
||||
gtk_text_buffer_set_text(context->details_buffer, details, -1);
|
||||
g_free(details);
|
||||
}
|
||||
|
||||
void osint_execution_history_dialog_present(
|
||||
GtkWindow *parent_window, GPtrArray *records, GHashTable *linked_objects
|
||||
)
|
||||
{
|
||||
OsintExecutionHistoryDialogContext *context = NULL;
|
||||
GtkWidget *main_box = NULL; GtkWidget *content_box = NULL;
|
||||
GtkWidget *history_scroll = NULL; GtkWidget *history_box = NULL;
|
||||
GtkWidget *details_scroll = NULL; GtkWidget *details_view = NULL;
|
||||
GtkWidget *close_button = NULL;
|
||||
if (records == NULL || records->len == 0U || linked_objects == NULL) return;
|
||||
context = g_new0(OsintExecutionHistoryDialogContext, 1);
|
||||
context->window = GTK_WINDOW(gtk_window_new());
|
||||
context->records = g_ptr_array_ref(records);
|
||||
context->linked_objects = g_hash_table_ref(linked_objects);
|
||||
gtk_window_set_title(context->window, "Historique OSINT");
|
||||
gtk_window_set_default_size(context->window, 980, 650);
|
||||
gtk_window_set_modal(context->window, TRUE);
|
||||
gtk_window_set_destroy_with_parent(context->window, TRUE);
|
||||
if (parent_window != NULL) gtk_window_set_transient_for(context->window, parent_window);
|
||||
g_object_set_data_full(G_OBJECT(context->window), "osint-history-context",
|
||||
context, osint_execution_history_dialog_context_free);
|
||||
main_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
|
||||
gtk_widget_set_margin_start(main_box, 16); gtk_widget_set_margin_end(main_box, 16);
|
||||
gtk_widget_set_margin_top(main_box, 16); gtk_widget_set_margin_bottom(main_box, 16);
|
||||
content_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12);
|
||||
gtk_widget_set_vexpand(content_box, TRUE);
|
||||
history_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
|
||||
history_scroll = gtk_scrolled_window_new();
|
||||
gtk_widget_set_size_request(history_scroll, 300, -1);
|
||||
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(history_scroll), history_box);
|
||||
for (guint index = 0U; index < records->len; index++)
|
||||
{
|
||||
OsintExecutionRecord *record = g_ptr_array_index(records, index);
|
||||
char *label = g_strdup_printf("%s\n%s — %s",
|
||||
osint_execution_record_get_finished_at(record),
|
||||
osint_execution_record_get_tool_identifier(record),
|
||||
osint_execution_record_get_final_state(record));
|
||||
GtkWidget *button = gtk_button_new_with_label(label);
|
||||
gtk_widget_set_halign(button, GTK_ALIGN_FILL);
|
||||
g_object_set_data(G_OBJECT(button), "osint-execution-index",
|
||||
GUINT_TO_POINTER(index + 1U));
|
||||
g_signal_connect(button, "clicked",
|
||||
G_CALLBACK(osint_execution_history_dialog_on_record_clicked), context);
|
||||
gtk_box_append(GTK_BOX(history_box), button);
|
||||
g_free(label);
|
||||
}
|
||||
details_view = gtk_text_view_new();
|
||||
context->details_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(details_view));
|
||||
gtk_text_view_set_editable(GTK_TEXT_VIEW(details_view), FALSE);
|
||||
gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(details_view), TRUE);
|
||||
gtk_text_view_set_monospace(GTK_TEXT_VIEW(details_view), TRUE);
|
||||
gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(details_view), GTK_WRAP_WORD_CHAR);
|
||||
details_scroll = gtk_scrolled_window_new();
|
||||
gtk_widget_set_hexpand(details_scroll, TRUE);
|
||||
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(details_scroll), details_view);
|
||||
gtk_box_append(GTK_BOX(content_box), history_scroll);
|
||||
gtk_box_append(GTK_BOX(content_box), details_scroll);
|
||||
close_button = gtk_button_new_with_label("Fermer");
|
||||
gtk_widget_set_halign(close_button, GTK_ALIGN_END);
|
||||
g_signal_connect_swapped(close_button, "clicked",
|
||||
G_CALLBACK(gtk_window_destroy), context->window);
|
||||
gtk_box_append(GTK_BOX(main_box), content_box);
|
||||
gtk_box_append(GTK_BOX(main_box), close_button);
|
||||
gtk_window_set_child(context->window, main_box);
|
||||
osint_execution_history_dialog_on_record_clicked(
|
||||
GTK_BUTTON(gtk_widget_get_first_child(history_box)), context);
|
||||
gtk_window_present(context->window);
|
||||
}
|
||||
|
|
@ -183,7 +183,9 @@ static void workspace_on_osint_action_clicked(
|
|||
|
||||
gtk_label_set_text(
|
||||
GTK_LABEL(workspace->osint_tools_status_label),
|
||||
"Exécution lancée ; suivez sa progression dans les tâches."
|
||||
g_str_has_prefix(action_identifier, "history-")
|
||||
? "Ouverture de l'historique enregistré."
|
||||
: "Exécution lancée ; suivez sa progression dans les tâches."
|
||||
);
|
||||
|
||||
gtk_popover_popdown(
|
||||
|
|
@ -290,6 +292,31 @@ static void workspace_update_osint_tools_menu(
|
|||
context
|
||||
);
|
||||
|
||||
{
|
||||
GtkWidget *history_button = gtk_button_new_with_label(
|
||||
"Historique OSINT"
|
||||
);
|
||||
const char *history_action =
|
||||
osint_selection_context_get_kind(context) ==
|
||||
OSINT_SELECTION_CONTEXT_KIND_RELATION
|
||||
? "history-relation" : "history-entity";
|
||||
gtk_widget_set_tooltip_text(
|
||||
history_button,
|
||||
"Consulter les exécutions OSINT enregistrées pour cette sélection"
|
||||
);
|
||||
g_object_set_data_full(
|
||||
G_OBJECT(history_button), "osint-action-identifier",
|
||||
g_strdup(history_action), g_free
|
||||
);
|
||||
g_signal_connect(
|
||||
history_button, "clicked",
|
||||
G_CALLBACK(workspace_on_osint_action_clicked), workspace
|
||||
);
|
||||
gtk_box_append(
|
||||
GTK_BOX(workspace->osint_tools_actions_box), history_button
|
||||
);
|
||||
}
|
||||
|
||||
for (guint action_index = 0;
|
||||
compatible_actions != NULL &&
|
||||
action_index < compatible_actions->len;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include "dao/osint_execution_dao.h"
|
||||
#include "database/database.h"
|
||||
#include "database/statement.h"
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib/gstdio.h>
|
||||
|
|
@ -22,6 +23,9 @@ static void test_insert_and_read_raw_execution(void)
|
|||
GBytes *stdout_raw = g_bytes_new_static(raw_data, sizeof(raw_data));
|
||||
GBytes *stderr_raw = g_bytes_new_static("", 0U);
|
||||
GBytes *loaded_stdout = NULL;
|
||||
GPtrArray *history = NULL;
|
||||
GPtrArray *linked_objects = NULL;
|
||||
DatabaseStatement *statement = NULL;
|
||||
|
||||
g_assert_true(database_initialize(path, "OSINT", directory));
|
||||
database = database_open(path);
|
||||
|
|
@ -37,6 +41,18 @@ static void test_insert_and_read_raw_execution(void)
|
|||
);
|
||||
g_assert_nonnull(record);
|
||||
g_assert_true(osint_execution_dao_insert(dao, record, &error));
|
||||
statement = database_statement_prepare(database,
|
||||
"INSERT INTO entites(id,type_id,valeur,confiance,created_at,updated_at,status) "
|
||||
"VALUES('bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb',"
|
||||
"(SELECT id FROM types_entite LIMIT 1),'linked.example',50,"
|
||||
"'2026-01-01T00:00:00Z','2026-01-01T00:00:00Z','active');");
|
||||
g_assert_nonnull(statement);
|
||||
g_assert_cmpint(database_statement_step(statement), ==,
|
||||
DATABASE_STATEMENT_STEP_DONE);
|
||||
database_statement_finalize(statement);
|
||||
g_assert_true(osint_execution_dao_link_entity(
|
||||
dao, "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee",
|
||||
"bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", "created", &error));
|
||||
loaded = osint_execution_dao_find_by_identifier(
|
||||
dao, "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee", &error
|
||||
);
|
||||
|
|
@ -47,7 +63,24 @@ static void test_insert_and_read_raw_execution(void)
|
|||
g_assert_true(osint_execution_record_has_exit_code(loaded));
|
||||
loaded_stdout = osint_execution_record_ref_stdout(loaded);
|
||||
g_assert_true(g_bytes_equal(stdout_raw, loaded_stdout));
|
||||
history = osint_execution_dao_list_by_selection(
|
||||
dao, "entity", "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", &error);
|
||||
g_assert_nonnull(history);
|
||||
g_assert_cmpuint(history->len, ==, 1U);
|
||||
g_ptr_array_unref(history);
|
||||
history = osint_execution_dao_list_by_selection(
|
||||
dao, "entity", "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", &error);
|
||||
g_assert_nonnull(history);
|
||||
g_assert_cmpuint(history->len, ==, 1U);
|
||||
linked_objects = osint_execution_dao_list_linked_objects(
|
||||
dao, "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee", &error);
|
||||
g_assert_nonnull(linked_objects);
|
||||
g_assert_cmpuint(linked_objects->len, ==, 1U);
|
||||
g_assert_nonnull(g_strstr_len(
|
||||
g_ptr_array_index(linked_objects, 0U), -1, "créée"));
|
||||
|
||||
g_ptr_array_unref(linked_objects);
|
||||
g_ptr_array_unref(history);
|
||||
g_bytes_unref(loaded_stdout);
|
||||
osint_execution_record_free(loaded);
|
||||
osint_execution_record_free(record);
|
||||
|
|
|
|||
Loading…
Reference in a new issue