From 55e6deb02b89bf7d04c36f634e246a88961e36e5 Mon Sep 17 00:00:00 2001 From: mutantsan Date: Mon, 25 Nov 2024 12:17:00 +0200 Subject: [PATCH] Deployed 5209077 with MkDocs version: 1.6.1 --- 404.html | 29 +- assets/_mkdocstrings.css | 26 +- cli/index.html | 68 +- configure/admin_panel/index.html | 1135 +++++++++++++++++ configure/async/index.html | 29 +- configure/cloudwatch/index.html | 41 +- configure/ignore/index.html | 35 +- .../{active_repo => repository}/index.html | 131 +- configure/tracking/index.html | 29 +- exporters/basic/index.html | 46 +- exporters/csv/index.html | 54 +- exporters/json/index.html | 42 +- exporters/tsv/index.html | 38 +- exporters/xlsx/index.html | 46 +- img/ap_config.png | Bin 0 -> 52256 bytes img/ap_toolbar.png | Bin 0 -> 83980 bytes index.html | 29 +- install/index.html | 29 +- interfaces/index.html | 34 +- repositories/abstract/index.html | 66 +- repositories/basic/index.html | 29 +- repositories/cloudwatch/index.html | 58 +- repositories/custom/index.html | 29 +- repositories/postgres/index.html | 74 +- repositories/redis/index.html | 54 +- search/search_index.json | 2 +- sitemap.xml | 52 +- sitemap.xml.gz | Bin 364 -> 370 bytes usage/index.html | 29 +- utils/index.html | 42 +- validators/index.html | 37 +- 31 files changed, 1948 insertions(+), 365 deletions(-) create mode 100644 configure/admin_panel/index.html rename configure/{active_repo => repository}/index.html (88%) create mode 100644 img/ap_config.png create mode 100644 img/ap_toolbar.png diff --git a/404.html b/404.html index f4e979c..103f5d1 100644 --- a/404.html +++ b/404.html @@ -12,7 +12,7 @@ - + @@ -401,6 +401,8 @@ + + @@ -439,11 +441,11 @@
  • - + - Active repository + Repository @@ -480,6 +482,27 @@ +
  • + + + + + Admin panel + + + + +
  • + + + + + + + + + +
  • diff --git a/assets/_mkdocstrings.css b/assets/_mkdocstrings.css index b500381..85449ec 100644 --- a/assets/_mkdocstrings.css +++ b/assets/_mkdocstrings.css @@ -26,33 +26,20 @@ float: right; } -/* Parameter headings must be inline, not blocks. */ -.doc-heading-parameter { - display: inline; -} - -/* Prefer space on the right, not the left of parameter permalinks. */ -.doc-heading-parameter .headerlink { - margin-left: 0 !important; - margin-right: 0.2rem; -} - /* Backward-compatibility: docstring section titles in bold. */ .doc-section-title { font-weight: bold; } /* Symbols in Navigation and ToC. */ -:root, :host, +:root, [data-md-color-scheme="default"] { - --doc-symbol-parameter-fg-color: #df50af; --doc-symbol-attribute-fg-color: #953800; --doc-symbol-function-fg-color: #8250df; --doc-symbol-method-fg-color: #8250df; --doc-symbol-class-fg-color: #0550ae; --doc-symbol-module-fg-color: #5cad0f; - --doc-symbol-parameter-bg-color: #df50af1a; --doc-symbol-attribute-bg-color: #9538001a; --doc-symbol-function-bg-color: #8250df1a; --doc-symbol-method-bg-color: #8250df1a; @@ -61,14 +48,12 @@ } [data-md-color-scheme="slate"] { - --doc-symbol-parameter-fg-color: #ffa8cc; --doc-symbol-attribute-fg-color: #ffa657; --doc-symbol-function-fg-color: #d2a8ff; --doc-symbol-method-fg-color: #d2a8ff; --doc-symbol-class-fg-color: #79c0ff; --doc-symbol-module-fg-color: #baff79; - --doc-symbol-parameter-bg-color: #ffa8cc1a; --doc-symbol-attribute-bg-color: #ffa6571a; --doc-symbol-function-bg-color: #d2a8ff1a; --doc-symbol-method-bg-color: #d2a8ff1a; @@ -83,15 +68,6 @@ code.doc-symbol { font-weight: bold; } -code.doc-symbol-parameter { - color: var(--doc-symbol-parameter-fg-color); - background-color: var(--doc-symbol-parameter-bg-color); -} - -code.doc-symbol-parameter::after { - content: "param"; -} - code.doc-symbol-attribute { color: var(--doc-symbol-attribute-fg-color); background-color: var(--doc-symbol-attribute-bg-color); diff --git a/cli/index.html b/cli/index.html index 9ff36e8..e9e0fba 100644 --- a/cli/index.html +++ b/cli/index.html @@ -14,11 +14,11 @@ - + - + @@ -470,6 +470,8 @@ + + @@ -508,11 +510,11 @@
  • - + - Active repository + Repository @@ -549,6 +551,27 @@ +
  • + + + + + Admin panel + + + + +
  • + + + + + + + + + +
  • @@ -1028,11 +1051,6 @@

    CLI

    - - - - -
    @@ -1066,9 +1084,7 @@

    - - exporter_name - + exporter_name

    The name of the exporter.

    @@ -1082,9 +1098,7 @@

    - - start - + start

    The start date string in %Y-%m-%d format.

    @@ -1098,9 +1112,7 @@

    - - end - + end

    The end date string in %Y-%m-%d format.

    @@ -1114,9 +1126,7 @@

    - - config - + config

    The exporter config in JSON format. See the exporter's @@ -1306,9 +1316,7 @@

    - - repository - + repository

    The repository name. If not provided, the @@ -1323,9 +1331,7 @@

    - - start - + start

    The start date string in %Y-%m-%d format.

    @@ -1339,9 +1345,7 @@

    - - end - + end

    The end date string in %Y-%m-%d format.

    @@ -1538,13 +1542,13 @@

    - + @@ -944,18 +1035,34 @@ -

    Active repository

    -

    The event audit logs are stored in a configurable storages, we call them repositories.

    -

    The default repository is redis, but it can be changed to a different one. To do this, we have to set the following configuration options in the CKAN configuration file:

    -
    ckanext.event_audit.active_repo = postgres
    -
    +

    Repository

    + +

    The event audit logs are stored in a configurable storages, we call them repositories. To use an extension, you have to choose one of the available repositories.

    The following repositories are available:

    1. redis - the default repository, stores logs in Redis.
    2. postgres - stores logs in a PostgreSQL database.
    3. cloudwatch - stores logs in AWS CloudWatch.
    +
    +Note

    If the cloudwatch repository is used, the extension will automatically create a log group in CloudWatch. Also, check the CloudWatch repository documentation for additional configuration options.

    +
    +

    Active repository

    +

    The default repository is redis, but it can be changed to a different one. To do this, we have to set the following configuration options in the CKAN configuration file:

    +
    ckanext.event_audit.active_repo = postgres
    +
    +

    List of available repositories

    +

    You can restrict a list of available repositories by setting the following configuration option in the CKAN configuration file:

    +
    ckanext.event_audit.active_repo = cloudwatch
    +ckanext.event_audit.restrict_available_repos = cloudwatch
    +
    +
    +Note +

    By default, we're not restricting the list of available repositories. It means that all registered repositories are available for use.

    +
    +

    This could be useful if you want to limit the available repositories to a specific set of options due to some security concerns. +This config option won't be available in the admin interface and can't be changed in real time.

    diff --git a/configure/tracking/index.html b/configure/tracking/index.html index 5ff5c4d..fa9c38a 100644 --- a/configure/tracking/index.html +++ b/configure/tracking/index.html @@ -18,7 +18,7 @@ - + @@ -414,6 +414,8 @@ + + @@ -452,11 +454,11 @@
  • - + - Active repository + Repository @@ -493,6 +495,27 @@ +
  • + + + + + Admin panel + + + + +
  • + + + + + + + + + +
  • diff --git a/exporters/basic/index.html b/exporters/basic/index.html index b6fc540..be1e475 100644 --- a/exporters/basic/index.html +++ b/exporters/basic/index.html @@ -18,7 +18,7 @@ - + @@ -412,6 +412,8 @@ + + @@ -450,11 +452,11 @@
  • - + - Active repository + Repository @@ -491,6 +493,27 @@ +
  • + + + + + Admin panel + + + + +
  • + + + + + + + + + +
  • @@ -1058,11 +1081,6 @@

    Base exporter class

    - - - - -
    @@ -1102,9 +1120,7 @@

    - - events - + events

    events to export

    @@ -1176,9 +1192,7 @@

    - - filters - + filters

    search filters.

    @@ -1192,9 +1206,7 @@

    - - repo_name - + repo_name

    name of the repo to use. Defaults to None.

    diff --git a/exporters/csv/index.html b/exporters/csv/index.html index 8331edc..0a0b2ac 100644 --- a/exporters/csv/index.html +++ b/exporters/csv/index.html @@ -18,7 +18,7 @@ - + @@ -412,6 +412,8 @@ + + @@ -450,11 +452,11 @@
  • - + - Active repository + Repository @@ -491,6 +493,27 @@ +
  • + + + + + Admin panel + + + + +
  • + + + + + + + + + +
  • @@ -1032,11 +1055,6 @@

    CSV exporter

    - - - - -
    @@ -1070,9 +1088,7 @@

    - - delimiter - + delimiter

    delimiter. Defaults to ",".

    @@ -1090,9 +1106,7 @@

    - - quotechar - + quotechar

    quote character. Defaults to '"'.

    @@ -1110,9 +1124,7 @@

    - - quoting - + quoting

    quoting. Defaults to QUOTE_ALL.

    @@ -1130,9 +1142,7 @@

    - - ignore_fields - + ignore_fields

    fields to ignore. By @@ -1180,9 +1190,7 @@

    - - events - + events

    events to export.

    diff --git a/exporters/json/index.html b/exporters/json/index.html index f6ba974..ae4de50 100644 --- a/exporters/json/index.html +++ b/exporters/json/index.html @@ -18,7 +18,7 @@ - + @@ -412,6 +412,8 @@ + + @@ -450,11 +452,11 @@
  • - + - Active repository + Repository @@ -491,6 +493,27 @@ +
  • + + + + + Admin panel + + + + +
  • + + + + + + + + + +
  • @@ -1034,11 +1057,6 @@

    JSON exporter

    - - - - -
    @@ -1072,9 +1090,7 @@

    - - stringify - + stringify

    whether to return a string or a dict. By @@ -1122,9 +1138,7 @@

    - - events - + events

    events to export.

    diff --git a/exporters/tsv/index.html b/exporters/tsv/index.html index 36a4929..a91703b 100644 --- a/exporters/tsv/index.html +++ b/exporters/tsv/index.html @@ -18,7 +18,7 @@ - + @@ -412,6 +412,8 @@ + + @@ -450,11 +452,11 @@
  • - + - Active repository + Repository @@ -491,6 +493,27 @@ +
  • + + + + + Admin panel + + + + +
  • + + + + + + + + + +
  • @@ -1014,11 +1037,6 @@

    TSV exporter

    - - - - -
    @@ -1052,9 +1070,7 @@

    - - ignore_fields - + ignore_fields

    fields to ignore. By diff --git a/exporters/xlsx/index.html b/exporters/xlsx/index.html index 7363a4f..e857a34 100644 --- a/exporters/xlsx/index.html +++ b/exporters/xlsx/index.html @@ -18,7 +18,7 @@ - + @@ -412,6 +412,8 @@ + + @@ -450,11 +452,11 @@

  • - + - Active repository + Repository @@ -491,6 +493,27 @@ +
  • + + + + + Admin panel + + + + +
  • + + + + + + + + + +
  • @@ -1032,11 +1055,6 @@

    XLSX exporter

    - - - - -
    @@ -1070,9 +1088,7 @@

    - - file_path - + file_path

    path to the file or BytesIO object to @@ -1087,9 +1103,7 @@

    - - ignore_fields - + ignore_fields

    fields to ignore. By @@ -1137,9 +1151,7 @@

    - - events - + events

    events to export.

    diff --git a/img/ap_config.png b/img/ap_config.png new file mode 100644 index 0000000000000000000000000000000000000000..faad130db1a0efbd685be5b1f80b924103878c7b GIT binary patch literal 52256 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 literal 0 HcmV?d00001 diff --git a/img/ap_toolbar.png b/img/ap_toolbar.png new file mode 100644 index 0000000000000000000000000000000000000000..e31dd831083856e523818e4c646622e8b4cb89d5 GIT binary patch literal 83980 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 literal 0 HcmV?d00001 diff --git a/index.html b/index.html index 6cad978..d5c06ea 100644 --- a/index.html +++ b/index.html @@ -16,7 +16,7 @@ - + @@ -477,6 +477,8 @@ + + @@ -515,11 +517,11 @@

  • - + - Active repository + Repository @@ -556,6 +558,27 @@ +
  • + + + + + Admin panel + + + + +
  • + + + + + + + + + +
  • diff --git a/install/index.html b/install/index.html index b98b588..468fa3d 100644 --- a/install/index.html +++ b/install/index.html @@ -18,7 +18,7 @@ - + @@ -470,6 +470,8 @@ + + @@ -508,11 +510,11 @@
  • - + - Active repository + Repository @@ -549,6 +551,27 @@ +
  • + + + + + Admin panel + + + + +
  • + + + + + + + + + +
  • diff --git a/interfaces/index.html b/interfaces/index.html index cab4aa2..6b5e999 100644 --- a/interfaces/index.html +++ b/interfaces/index.html @@ -18,7 +18,7 @@ - + @@ -488,6 +488,8 @@ + + @@ -526,11 +528,11 @@
  • - + - Active repository + Repository @@ -567,6 +569,27 @@ +
  • + + + + + Admin panel + + + + +
  • + + + + + + + + + +
  • @@ -1098,11 +1121,6 @@

    IEventAudit

    - - - - -
    diff --git a/repositories/abstract/index.html b/repositories/abstract/index.html index 1522a0f..f18caa8 100644 --- a/repositories/abstract/index.html +++ b/repositories/abstract/index.html @@ -18,7 +18,7 @@ - + @@ -412,6 +412,8 @@ + + @@ -450,11 +452,11 @@
  • - + - Active repository + Repository @@ -491,6 +493,27 @@ +
  • + + + + + Admin panel + + + + +
  • + + + + + + + + + +
  • @@ -1205,11 +1228,6 @@

    Abstract

    - - - - -
    @@ -1260,9 +1278,7 @@

    - - event_data - + event_data

    event data.

    @@ -1329,9 +1345,7 @@

    - - event - + event

    event to write.

    @@ -1402,9 +1416,7 @@

    - - filters - + filters

    filters to apply.

    @@ -1451,9 +1463,7 @@

    - - event_id - + event_id

    event ID.

    @@ -1611,9 +1621,7 @@

    - - event_id - + event_id

    event ID.

    @@ -1680,9 +1688,7 @@

    - - filters - + filters

    filters to apply.

    @@ -1802,9 +1808,7 @@

    - - event - + event

    event to write.

    @@ -1871,9 +1875,7 @@

    - - events - + events

    events to write.

    diff --git a/repositories/basic/index.html b/repositories/basic/index.html index 8094c99..448d975 100644 --- a/repositories/basic/index.html +++ b/repositories/basic/index.html @@ -18,7 +18,7 @@ - + @@ -407,6 +407,8 @@ + + @@ -445,11 +447,11 @@
  • - + - Active repository + Repository @@ -486,6 +488,27 @@ +
  • + + + + + Admin panel + + + + +
  • + + + + + + + + + +
  • diff --git a/repositories/cloudwatch/index.html b/repositories/cloudwatch/index.html index 2b0880a..ae9d36f 100644 --- a/repositories/cloudwatch/index.html +++ b/repositories/cloudwatch/index.html @@ -18,7 +18,7 @@ - + @@ -412,6 +412,8 @@ + + @@ -450,11 +452,11 @@
  • - + - Active repository + Repository @@ -491,6 +493,27 @@ +
  • + + + + + Admin panel + + + + +
  • + + + + + + + + + +
  • @@ -1140,11 +1163,6 @@

    Cloudwatch repository

    - - - - -
    @@ -1178,9 +1196,7 @@

    - - credentials - + credentials

    AWS credentials. @@ -1199,9 +1215,7 @@

    - - log_group - + log_group

    CloudWatch log group name.

    @@ -1219,9 +1233,7 @@

    - - log_stream - + log_stream

    CloudWatch log stream name.

    @@ -1268,9 +1280,7 @@

    - - filters - + filters

    filters to apply.

    @@ -1313,9 +1323,7 @@

    - - event_id - + event_id

    event ID.

    @@ -1513,9 +1521,7 @@

    - - event - + event

    event to write.

    diff --git a/repositories/custom/index.html b/repositories/custom/index.html index 044e1ed..4012c40 100644 --- a/repositories/custom/index.html +++ b/repositories/custom/index.html @@ -16,7 +16,7 @@ - + @@ -405,6 +405,8 @@ + + @@ -443,11 +445,11 @@
  • - + - Active repository + Repository @@ -484,6 +486,27 @@ +
  • + + + + + Admin panel + + + + +
  • + + + + + + + + + +
  • diff --git a/repositories/postgres/index.html b/repositories/postgres/index.html index b176f5d..1ce3330 100644 --- a/repositories/postgres/index.html +++ b/repositories/postgres/index.html @@ -18,7 +18,7 @@ - + @@ -412,6 +412,8 @@ + + @@ -450,11 +452,11 @@
  • - + - Active repository + Repository @@ -491,6 +493,27 @@ +
  • + + + + + Admin panel + + + + +
  • + + + + + + + + + +
  • @@ -1140,11 +1163,6 @@

    Postgres repository

    - - - - -
    @@ -1178,9 +1196,7 @@

    - - filters - + filters

    filters to apply.

    @@ -1247,9 +1263,7 @@

    - - event_id - + event_id

    event ID.

    @@ -1357,9 +1371,7 @@

    - - event_id - + event_id

    event ID.

    @@ -1373,9 +1385,7 @@

    - - session - + session

    session to use.

    @@ -1393,9 +1403,7 @@

    - - defer_commit - + defer_commit

    whether to defer the commit.

    @@ -1466,9 +1474,7 @@

    - - filters - + filters

    filters to apply.

    @@ -1580,9 +1586,7 @@

    - - event - + event

    event to write.

    @@ -1596,9 +1600,7 @@

    - - session - + session

    session to use.

    @@ -1616,9 +1618,7 @@

    - - defer_commit - + defer_commit

    whether to defer the commit.

    @@ -1689,9 +1689,7 @@

    - - events - + events

    events to write.

    diff --git a/repositories/redis/index.html b/repositories/redis/index.html index 9f59eb0..f8865da 100644 --- a/repositories/redis/index.html +++ b/repositories/redis/index.html @@ -18,7 +18,7 @@ - + @@ -412,6 +412,8 @@ + + @@ -450,11 +452,11 @@
  • - + - Active repository + Repository @@ -491,6 +493,27 @@ +
  • + + + + + Admin panel + + + + +
  • + + + + + + + + + +
  • @@ -1122,11 +1145,6 @@

    Redis repository

    - - - - -
    @@ -1160,9 +1178,7 @@

    - - filters - + filters

    filters to apply.

    @@ -1205,9 +1221,7 @@

    - - event_id - + event_id

    event ID.

    @@ -1291,9 +1305,7 @@

    - - event_id - + event_id

    event ID.

    @@ -1360,9 +1372,7 @@

    - - filters - + filters

    filters to apply.

    @@ -1474,9 +1484,7 @@

    - - event - + event

    event to write.

    diff --git a/search/search_index.json b/search/search_index.json index 325e010..626d236 100644 --- a/search/search_index.json +++ b/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-\\.\\_]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Home","text":""},{"location":"#ckanext-event-audit","title":"ckanext-event-audit","text":"

    This extension will capture and retain a comprehensive record of all changes within a CKAN app.

    "},{"location":"#developer-installation","title":"Developer installation","text":"

    To install ckanext-event-audit for development, activate your CKAN virtualenv and do:

    git clone https://github.com/DataShades/ckanext-event-audit.git\ncd ckanext-event-audit\npip install -e .\npip install -r dev-requirements.txt\n
    "},{"location":"#tests","title":"Tests","text":"

    To run the tests, do:

    pytest --ckan-ini=test.ini\n
    "},{"location":"#license","title":"License","text":"

    AGPL

    "},{"location":"cli/","title":"CLI","text":""},{"location":"cli/#event_audit.cli.export_data","title":"export_data(exporter_name, start, end, config)","text":"

    Export data using the specified exporter.

    PARAMETER DESCRIPTION exporter_name

    The name of the exporter.

    TYPE: str

    start

    The start date string in %Y-%m-%d format.

    TYPE: str

    end

    The end date string in %Y-%m-%d format.

    TYPE: str | None

    config

    The exporter config in JSON format. See the exporter's documentation for args details.

    TYPE: str | None

    RETURNS DESCRIPTION str

    The exported data

    Example

    $ ckan event-audit export-data csv --start=2024-11-11 > report.csv

    $ ckan event-audit export-data json --start=2024-11-11 | jq '[.[] | {id, category, action}]'

    $ ckan event-audit export-data xlsx --start=2024-11-11 --config='{\"file_path\": \"/tmp/test.xlsx\"}'

    Source code in ckanext/event_audit/cli.py
    @event_audit.command()\n@click.argument(\"exporter_name\", type=str)\n@click.option(\n    \"--start\",\n    required=True,\n    type=click.DateTime(formats=[\"%Y-%m-%d\"]),\n    help=\"ISO format start date\",\n)\n@click.option(\n    \"--end\",\n    required=False,\n    type=click.DateTime(formats=[\"%Y-%m-%d\"]),\n    help=\"ISO format end date\",\n)\n@click.option(\"--config\", required=False, type=str, help=\"Custom config in JSON format\")\ndef export_data(exporter_name: str, start: dt, end: dt | None, config: str | None):\n    \"\"\"Export data using the specified exporter.\n\n    Args:\n        exporter_name (str): The name of the exporter.\n        start (str): The start date string in %Y-%m-%d format.\n        end (str | None): The end date string in %Y-%m-%d format.\n        config (str | None): The exporter config in JSON format. See the exporter's\n            documentation for args details.\n\n    Returns:\n        str : The exported data\n\n    Example:\n        $ ckan event-audit export-data csv --start=2024-11-11 > report.csv\n\n        $ ckan event-audit export-data json --start=2024-11-11 | jq '[.[] |\n            {id, category, action}]'\n\n        $ ckan event-audit export-data xlsx --start=2024-11-11\n            --config='{\"file_path\": \"/tmp/test.xlsx\"}'\n    \"\"\"\n    start = UTC.localize(start) if start else None\n    end = UTC.localize(end) if end else None\n\n    try:\n        config_dict = json.loads(config or \"{}\")\n    except json.JSONDecodeError:\n        return click.secho(\"Invalid JSON format for config.\")\n\n    try:\n        exporter = utils.get_exporter(exporter_name)(**config_dict)\n    except TypeError as e:\n        return click.secho(f\"Invalid exporter config: {config}. Error: {e}\", fg=\"red\")\n    except ValueError as e:\n        return click.secho(e, fg=\"red\")\n\n    if start and end and start > end:\n        return click.secho(\"Start date must be before the end date.\", fg=\"red\")\n\n    click.echo(exporter.from_filters(types.Filters(time_from=start, time_to=end)))\n
    "},{"location":"cli/#event_audit.cli.remove_events","title":"remove_events(repository, start, end)","text":"

    Remove events from the repository by time range.

    PARAMETER DESCRIPTION repository

    The repository name. If not provided, the active repository will be used.

    TYPE: str | None

    start

    The start date string in %Y-%m-%d format.

    TYPE: str

    end

    The end date string in %Y-%m-%d format.

    TYPE: str | None

    Example

    $ ckan event-audit remove-events --start=2024-11-11 --end=2024-11-12

    Source code in ckanext/event_audit/cli.py
    @event_audit.command()\n@click.option(\"--repository\", required=False, help=\"The repository name\")\n@click.option(\n    \"--start\",\n    required=False,\n    type=click.DateTime(formats=[\"%Y-%m-%d\"]),\n    help=\"ISO format start date\",\n)\n@click.option(\n    \"--end\",\n    required=False,\n    type=click.DateTime(formats=[\"%Y-%m-%d\"]),\n    help=\"ISO format end date\",\n)\ndef remove_events(repository: str | None, start: dt | None, end: dt | None):\n    \"\"\"Remove events from the repository by time range.\n\n    Args:\n        repository (str | None): The repository name. If not provided, the\n            active repository will be used.\n        start (str): The start date string in %Y-%m-%d format.\n        end (str | None): The end date string in %Y-%m-%d format.\n\n    Example:\n        $ ckan event-audit remove-events --start=2024-11-11 --end=2024-11-12\n    \"\"\"\n    start = UTC.localize(start) if start else None\n    end = UTC.localize(end) if end else None\n\n    if start and end and start > end:\n        return click.secho(\"Start date must be before the end date.\", fg=\"red\")\n\n    try:\n        repo = utils.get_repo(repository) if repository else utils.get_active_repo()\n    except ValueError:\n        return click.secho(f\"Unknown repository: {repository}\", fg=\"red\")\n\n    if not (start or end) and not isinstance(repo, repositories.RemoveAll):\n        return click.secho(\n            f\"Repository {repository} does not support removing events.\", fg=\"red\"\n        )\n\n    if not isinstance(repo, repositories.RemoveFiltered):\n        if start or end:\n            return click.secho(\n                (\n                    f\"Repository {repository} does not support removing events \"\n                    \"by time range. \"\n                    \"Please remove the --start and --end flags to delete all events.\"\n                ),\n                fg=\"red\",\n            )\n        return repo.remove_all_events()\n\n    return repo.remove_events(types.Filters(time_from=start, time_to=end))\n
    "},{"location":"install/","title":"Installation","text":""},{"location":"install/#requirements","title":"Requirements","text":"

    Compatibility with core CKAN versions:

    CKAN version Compatible? 2.9 no 2.10 yes 2.11 yes master yes"},{"location":"install/#installation_1","title":"Installation","text":"
    1. Install the extension from PyPI:

      pip install ckanext-event-audit\n

    2. Enable the plugin in your CKAN configuration file (e.g. ckan.ini or production.ini):

      ckan.plugins = ... event_audit ...\n

    3. Run DB migrations:

      ckan db upgrade -p event_audit\n

    4. Configure the extension up to your needs and you're ready to go. See the documentation for more details about the configuration options.

    "},{"location":"interfaces/","title":"Interfaces","text":""},{"location":"interfaces/#ieventaudit","title":"IEventAudit","text":"

    Extend functionality of ckanext-event-audit.

    Example:

    import ckan.plugins as p\n\nfrom ckanext.event_audit.interfaces import IEventAudit\nfrom ckanext.event_audit.repositories import AbstractRepository\nfrom ckanext.event_audit.exporters import AbstractExporter\n\nclass MyPlugin(p.SingletonPlugin):\n    p.implements(IEventAudit, inherit=True)\n\n    def register_repository(self) -> dict[str, type[AbstractRepository]]:\n        return {\n            \"my_repo\": MyRepository,\n        }\n\n    def register_exporter(self) -> dict[str, type[AbstractExporter]]:\n        return {\n            \"my_exporter\": MyExporter,\n        }\n\n    def skip_event(self, event: types.Event) -> bool:\n        if event.category == \"api\" and event.action == \"status_show\":\n            return True\n\n        if event.category == \"model\" and event.action_object == \"Dashboard\":\n            return True\n\n        return False\n

    "},{"location":"interfaces/#event_audit.interfaces.IEventAudit.register_exporter","title":"register_exporter()","text":"

    Return the exporters provided by this plugin.

    Example
    def register_exporter(self):\n    return {\n        \"csv\": CSVExporter,\n    }\n
    RETURNS DESCRIPTION dict[str, type[AbstractExporter]]

    mapping of exporter names to exporter classes

    "},{"location":"interfaces/#event_audit.interfaces.IEventAudit.register_repository","title":"register_repository()","text":"

    Return the repositories provided by this plugin.

    Example
    def register_repository(self):\n    return {\n        \"my_repo\": MyRepository,\n    }\n
    RETURNS DESCRIPTION dict[str, type[AbstractRepository]]

    mapping of repository names to repository classes

    "},{"location":"interfaces/#event_audit.interfaces.IEventAudit.skip_event","title":"skip_event(event)","text":"

    Skip an event.

    This method is called before writing the event to the repository.

    Example
    def skip_event(self, event: types.Event) -> bool:\n    if event.category == \"api\" and event.action == \"status_show\":\n        return True\n\n    if  event.category == \"model\"  and event.action_object == \"Dashboard\":\n        return True\n\n    return False\n
    RETURNS DESCRIPTION bool

    True if the event should be skipped, False otherwise

    "},{"location":"usage/","title":"Usage","text":"

    To use an event audit in your extension, you should get an instance of the repository class. There are two ways to do this:

    1. Using the get_repo function:

      import ckanext.event_audit.utils as utils\n\nrepo = utils.get_active_repo()\n\nevent = repo.build_event({\"category\": \"xxx\", \"action\": \"xxx\"})\n\nrepo.write_event(event)\n

      The get_active_repo function will return an instance of the active repository class that is configured in the CKAN configuration file.

    2. Using the get_repo function with a specific repository:

      import ckanext.event_audit.utils as utils\n\nrepo = utils.get_repo(\"file\")\n\nevent = repo.build_event({\"category\": \"xxx\", \"action\": \"xxx\"})\n\nrepo.write_event(event)\n

      The get_repo function will return an instance of the repository class that is specified in the argument. You can use this method to get a specific repository instance.

    "},{"location":"utils/","title":"Utility Functions","text":""},{"location":"utils/#event_audit.utils.get_active_repo","title":"get_active_repo()","text":"

    Get the active repository.

    The active repository is the one that is currently configured in the extension configuration.

    RETURNS DESCRIPTION AbstractRepository

    The active repository.

    "},{"location":"utils/#event_audit.utils.get_available_exporters","title":"get_available_exporters()","text":"

    Retrieve a dictionary of available exporters.

    This function collects and returns a dictionary where the keys are exporter names (as strings) and the values are the corresponding exporter classes.

    RETURNS DESCRIPTION dict[str, type[AbstractExporter]]

    A dictionary mapping exporter names to their respective exporter classes.

    "},{"location":"utils/#event_audit.utils.get_available_repos","title":"get_available_repos()","text":"

    Retrieve a dictionary of available repositories.

    This function collects and returns a dictionary where the keys are repository names (as strings) and the values are the corresponding repository classes.

    RETURNS DESCRIPTION dict[str, type[AbstractRepository]]

    A dictionary mapping repository names to their respective repository classes.

    "},{"location":"utils/#event_audit.utils.get_exporter","title":"get_exporter(exporter_name)","text":"

    Retrieve an exporter class by name.

    This function retrieves an exporter class by name. If the exporter is not found, a ValueError is raised.

    PARAMETER DESCRIPTION exporter_name

    The name of the exporter to retrieve.

    TYPE: str

    RETURNS DESCRIPTION type[AbstractExporter]

    The exporter class.

    "},{"location":"utils/#event_audit.utils.get_repo","title":"get_repo(repo_name)","text":"

    Retrieve a repository class by name.

    This function retrieves a repository class by name. If the repository is not found, a ValueError is raised.

    PARAMETER DESCRIPTION repo_name

    The name of the repository to retrieve.

    TYPE: str

    RETURNS DESCRIPTION AbstractRepository

    The repository class.

    "},{"location":"utils/#event_audit.utils.test_active_connection","title":"test_active_connection()","text":"

    Test the connection to the active repository.

    When we test the connection, we store the result in the repository object, so we can reuse it later.

    RETURNS DESCRIPTION bool

    whether the connection is active

    "},{"location":"validators/","title":"Validators","text":""},{"location":"validators/#event_audit.logic.validators.audit_repo_exists","title":"audit_repo_exists(value, context)","text":"

    Check if the repository with the given name is registered.

    PARAMETER DESCRIPTION value

    The repository name.

    TYPE: Any

    context

    The CKAN context.

    TYPE: Context

    RETURNS DESCRIPTION Any

    The repository name if it exists.

    TYPE: Any

    RAISES DESCRIPTION Invalid

    If the repository does not exist.

    Source code in ckanext/event_audit/logic/validators.py
    def audit_repo_exists(value: Any, context: Context) -> Any:\n    \"\"\"Check if the repository with the given name is registered.\n\n    Args:\n        value (Any): The repository name.\n        context (Context): The CKAN context.\n\n    Returns:\n        Any: The repository name if it exists.\n\n    Raises:\n        tk.Invalid: If the repository does not exist.\n    \"\"\"\n    if value not in utils.get_available_repos():\n        raise tk.Invalid(f\"Repository `{value}` is not registered\")\n\n    return value\n
    "},{"location":"configure/active_repo/","title":"Active repository","text":"

    The event audit logs are stored in a configurable storages, we call them repositories.

    The default repository is redis, but it can be changed to a different one. To do this, we have to set the following configuration options in the CKAN configuration file:

    ckanext.event_audit.active_repo = postgres\n

    The following repositories are available:

    1. redis - the default repository, stores logs in Redis.
    2. postgres - stores logs in a PostgreSQL database.
    3. cloudwatch - stores logs in AWS CloudWatch.

    If the cloudwatch repository is used, the extension will automatically create a log group in CloudWatch. Also, check the CloudWatch repository documentation for additional configuration options.

    "},{"location":"configure/async/","title":"Asynchronous processing","text":"

    To avoid blocking the main thread, the extension uses a separate thread to write the audit logs. A separate thread will be started automatically along with the CKAN application.

    The thread is responsible for storing the logs in the configured repository.

    By default, we're using the threaded mode. However, if you want to disable the threaded mode, you can do this by setting the following configuration option in the CKAN configuration file:

    ckanext.event_audit.threaded_mode = false\n

    Disabling the threaded mode will cause the extension to write the logs in the main thread. This can be useful for debugging purposes.

    Note, that pairing it with the Cloudwatch repository is not recommended, as it can block the main thread for a long time. Network operations can be slow and can cause the application to hang for a while.

    If your custom repository involves a network operations, it's recommended to keep the threaded mode enabled.

    "},{"location":"configure/async/#batch-size","title":"Batch size","text":"

    The extension writes the logs in batches. The batch size can be adjusted by setting the following configuration option in the CKAN configuration file:

    ckanext.event_audit.batch.size = 50\n

    By default, we're accumulating 50 events before writing them to the repository.

    "},{"location":"configure/async/#batch-timeout","title":"Batch timeout","text":"

    Force push the events to the repository after this time in seconds since the last push:

    ckanext.event_audit.batch.timeout = 3600\n

    The default value is 3600 seconds (1 hour). This options is required to ensure that the logs are written to the repository in case of low activity.

    "},{"location":"configure/cloudwatch/","title":"Cloudwatch","text":"

    Using Cloudwatch repository requires you to configure the following options in the CKAN configuration file:

    ckanext.event_audit.cloudwatch.access_key = YOUR_ACCESS_KEY\nckanext.event_audit.cloudwatch.secret_key = YOUR_SECRET_KEY\nckanext.event_audit.cloudwatch.region = YOUR_REGION\n

    See the AWS documentation for more information on how to obtain these values and configure the AWS Cloudwatch service.

    "},{"location":"configure/ignore/","title":"Ignore events","text":"

    The extension provides a various set of configuration options to adjust the behavior of the audit logs.

    Warning

    These config options are applicable only for built-in tracking methods (API, Database) and not related to client's usage of the extension.

    "},{"location":"configure/ignore/#ignoring-categories","title":"Ignoring categories","text":"

    The extension allows to ignore specific categories of events. To do this, we have to set the following configuration option in the CKAN configuration file:

    ckanext.event_audit.ignore.categories = test\n

    By default, we're not ignoring any categories. The categories option is a comma-separated list of categories that should be ignored.

    Categories are arbitrary strings that can be used to group events.

    "},{"location":"configure/ignore/#ignoring-actions","title":"Ignoring actions","text":"

    The extension allows to ignore specific actions. To do this, we have to set the following configuration option in the CKAN configuration file:

    ckanext.event_audit.ignore.actions = test\n

    Some actions might be called more frequently than others, and we might not be interested in storing them. The actions option is a comma-separated list of actions that should be ignored.

    By default, we're excluding next actions from being stored:

    • editable_config_list
    • editable_config_change
    • get_site_user
    • ckanext_pages_list
    • user_show
    "},{"location":"configure/ignore/#ignoring-models","title":"Ignoring models","text":"

    The extension allows to ignore specific models. To do this, we have to set the following configuration option in the CKAN configuration file:

    ckanext.event_audit.ignore.models = User Package Resource\n

    By default, we're excluding next models from being stored:

    • Option
    "},{"location":"configure/tracking/","title":"In-built tracking","text":"

    There are two built-in trackers in the extension that are enabled by default and work out of the box - API and Database trackers.

    "},{"location":"configure/tracking/#api-tracker","title":"API tracker","text":"

    Captures all events that are triggered by the CKAN API. Everything that is called via tk.get_action will be tracked by this tracker, unless it's explicitly ignored by the configuration. See the ignore section for more details.

    To disable the API tracker, specify this in the configuration file:

    ckanext.event_audit.track.api = false\n

    We can ignore specific actions from being tracked by setting the ckanext.event_audit.ignore.actions configuration option. See the ignore section for more details.

    "},{"location":"configure/tracking/#database-tracker","title":"Database tracker","text":"

    We're utilising the SQLAlchemy\u2019s event system for tracking database interactions. The audit event creation will be triggered when the model is created, updated, or deleted.

    To disable the Database tracker, specify this in the configuration file:

    ckanext.event_audit.track.model = false\n

    We can ignore specific models from being tracked by setting the ckanext.event_audit.ignore.models configuration option. See the ignore section for more details.

    "},{"location":"configure/tracking/#custom-trackers","title":"Custom trackers","text":"

    You can create and write an event anywhere in your codebase.

    TODO: add link to usage docs

    "},{"location":"exporters/basic/","title":"Basic","text":"

    The exporters allow you to export the event audit logs to a different file format.

    The following exporters are available:

    1. CSV Exporter: Exports the event audit logs to a CSV file.
    2. JSON Exporter: Exports the event audit logs to a JSON file.
    3. TSV Exporter: Exports the event audit logs to a TSV file.
    4. XLSX Exporter: Exports the event audit logs to an XLSX file.

    Each exporter has its own configuration options. The configuration options are described in the respective exporter's documentation section.

    "},{"location":"exporters/basic/#base-exporter-class","title":"Base exporter class","text":"

    Base class for all exporters.

    Exporters are used to export a lsit of events to a specific file format.

    "},{"location":"exporters/basic/#event_audit.exporters.base.AbstractExporter.export","title":"export(events) abstractmethod","text":"

    Export events to a specific format.

    We are not providing a specific return type, because it will depend on the specific exporter implementation.

    PARAMETER DESCRIPTION events

    events to export

    TYPE: Iterable[Event]

    RETURNS DESCRIPTION Any

    exported data.

    TYPE: Any

    "},{"location":"exporters/basic/#event_audit.exporters.base.AbstractExporter.from_filters","title":"from_filters(filters, repo_name=None)","text":"

    Export events from a repo using the given filters.

    If repo_name is not provided, the active repo is used.

    PARAMETER DESCRIPTION filters

    search filters.

    TYPE: Filters

    repo_name

    name of the repo to use. Defaults to None.

    TYPE: str | None DEFAULT: None

    RETURNS DESCRIPTION Any

    exported data.

    TYPE: Any

    "},{"location":"exporters/csv/","title":"CSV exporter","text":""},{"location":"exporters/csv/#event_audit.exporters.csv.CSVExporter.__init__","title":"__init__(delimiter=',', quotechar='\"', quoting=QUOTE_ALL, ignore_fields=None)","text":"

    CSV exporter.

    PARAMETER DESCRIPTION delimiter

    delimiter. Defaults to \",\".

    TYPE: str DEFAULT: ','

    quotechar

    quote character. Defaults to '\"'.

    TYPE: str DEFAULT: '\"'

    quoting

    quoting. Defaults to QUOTE_ALL.

    TYPE: int DEFAULT: QUOTE_ALL

    ignore_fields

    fields to ignore. By default we ignore the \"result\" and \"payload\" fields.

    TYPE: list[str] | None DEFAULT: None

    "},{"location":"exporters/csv/#event_audit.exporters.csv.CSVExporter.export","title":"export(events)","text":"

    Export events to CSV format.

    PARAMETER DESCRIPTION events

    events to export.

    TYPE: Iterable[Event]

    RETURNS DESCRIPTION str | None

    str | None: CSV data.

    "},{"location":"exporters/json/","title":"JSON exporter","text":"

    Bases: AbstractExporter

    "},{"location":"exporters/json/#event_audit.exporters.json.JSONExporter.__init__","title":"__init__(stringify=True)","text":"

    JSON exporter.

    PARAMETER DESCRIPTION stringify

    whether to return a string or a dict. By default we return a string.

    TYPE: bool DEFAULT: True

    "},{"location":"exporters/json/#event_audit.exporters.json.JSONExporter.export","title":"export(events)","text":"

    Export events to JSON format.

    PARAMETER DESCRIPTION events

    events to export.

    TYPE: Iterable[Event]

    RETURNS DESCRIPTION list[dict[str, Any]] | str | None

    str | None: JSON data.

    "},{"location":"exporters/tsv/","title":"TSV exporter","text":""},{"location":"exporters/tsv/#event_audit.exporters.tsv.TSVExporter.__init__","title":"__init__(ignore_fields=None)","text":"

    TSV exporter.

    PARAMETER DESCRIPTION ignore_fields

    fields to ignore. By default we ignore the \"result\" and \"payload\" fields.

    TYPE: list[str] | None DEFAULT: None

    "},{"location":"exporters/xlsx/","title":"XLSX exporter","text":""},{"location":"exporters/xlsx/#event_audit.exporters.xlsx.XLSXExporter.__init__","title":"__init__(file_path, ignore_fields=None)","text":"

    XLSX exporter.

    PARAMETER DESCRIPTION file_path

    path to the file or BytesIO object to write the XLSX data.

    TYPE: str | BytesIO

    ignore_fields

    fields to ignore. By default we ignore the \"result\" and \"payload\" fields.

    TYPE: list[str] | None DEFAULT: None

    "},{"location":"exporters/xlsx/#event_audit.exporters.xlsx.XLSXExporter.export","title":"export(events)","text":"

    Export events to a XLSX file.

    PARAMETER DESCRIPTION events

    events to export.

    TYPE: Iterable[Event]

    RETURNS DESCRIPTION str | BytesIO | None

    str | BytesIO | None: path to the file or BytesIO object if the

    str | BytesIO | None

    export was successful, None otherwise.

    "},{"location":"repositories/abstract/","title":"Abstract","text":""},{"location":"repositories/abstract/#event_audit.repositories.base.AbstractRepository.__new__","title":"__new__(*args, **kwargs)","text":"

    Singleton pattern implementation.

    "},{"location":"repositories/abstract/#event_audit.repositories.base.AbstractRepository.build_event","title":"build_event(event_data)","text":"

    Build an event object from the provided data.

    PARAMETER DESCRIPTION event_data

    event data.

    TYPE: EventData

    RETURNS DESCRIPTION Event

    types.Event: event object.

    "},{"location":"repositories/abstract/#event_audit.repositories.base.AbstractRepository.enqueue_event","title":"enqueue_event(event)","text":"

    Enqueue an event to be written to the repository.

    PARAMETER DESCRIPTION event

    event to write.

    TYPE: Event

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/abstract/#event_audit.repositories.base.AbstractRepository.filter_events","title":"filter_events(filters) abstractmethod","text":"

    Filters events based on provided filter criteria.

    PARAMETER DESCRIPTION filters

    filters to apply.

    TYPE: Filters

    "},{"location":"repositories/abstract/#event_audit.repositories.base.AbstractRepository.get_event","title":"get_event(event_id) abstractmethod","text":"

    Retrieves a single event from the repository.

    PARAMETER DESCRIPTION event_id

    event ID.

    TYPE: str

    RETURNS DESCRIPTION Event | None

    types.Event | None: event object or None if not found.

    "},{"location":"repositories/abstract/#event_audit.repositories.base.AbstractRepository.get_name","title":"get_name() abstractmethod classmethod","text":"

    Return the name of the repository.

    RETURNS DESCRIPTION str

    name of the repository.

    TYPE: str

    "},{"location":"repositories/abstract/#event_audit.repositories.base.AbstractRepository.remove_all_events","title":"remove_all_events()","text":"

    Removes all events from the repository.

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/abstract/#event_audit.repositories.base.AbstractRepository.remove_event","title":"remove_event(event_id)","text":"

    Removes a single event from the repository.

    PARAMETER DESCRIPTION event_id

    event ID.

    TYPE: Any

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/abstract/#event_audit.repositories.base.AbstractRepository.remove_events","title":"remove_events(filters)","text":"

    Removes a filtered set of events from the repository.

    PARAMETER DESCRIPTION filters

    filters to apply.

    TYPE: Filters

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/abstract/#event_audit.repositories.base.AbstractRepository.test_connection","title":"test_connection() abstractmethod","text":"

    Test the connection to the repository.

    RETURNS DESCRIPTION bool

    whether the connection was successful.

    TYPE: bool

    "},{"location":"repositories/abstract/#event_audit.repositories.base.AbstractRepository.write_event","title":"write_event(event) abstractmethod","text":"

    Writes a single event to the repository.

    PARAMETER DESCRIPTION event

    event to write.

    TYPE: Event

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/abstract/#event_audit.repositories.base.AbstractRepository.write_events","title":"write_events(events)","text":"

    Write multiple events to the repository.

    PARAMETER DESCRIPTION events

    events to write.

    TYPE: Iterable[Event]

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/basic/","title":"Basic","text":"

    Repositories are the storages where the event audit logs are stored. There are a few basic repositories, that you can use out of the box:

    1. redis - the default repository, stores logs in Redis.
    2. postgres - stores logs in a PostgreSQL database.
    3. cloudwatch - stores logs in AWS CloudWatch.

    You can also implement your own repository. To do this, you need to create a new class that inherits from the AbstractRepository class and implement all the required methods.

    See the abstract repository documentation and custom repository documentation for more information.

    "},{"location":"repositories/cloudwatch/","title":"Cloudwatch repository","text":""},{"location":"repositories/cloudwatch/#event_audit.repositories.cloudwatch.CloudWatchRepository.__init__","title":"__init__(credentials=None, log_group='/ckan/event-audit', log_stream='event-audit-stream')","text":"

    CloudWatch repository.

    PARAMETER DESCRIPTION credentials

    AWS credentials. If not provided, the extension configuration will be used.

    TYPE: AWSCredentials | None DEFAULT: None

    log_group

    CloudWatch log group name.

    TYPE: str DEFAULT: '/ckan/event-audit'

    log_stream

    CloudWatch log stream name.

    TYPE: str DEFAULT: 'event-audit-stream'

    "},{"location":"repositories/cloudwatch/#event_audit.repositories.cloudwatch.CloudWatchRepository.filter_events","title":"filter_events(filters)","text":"

    Filters events based on provided filter criteria.

    PARAMETER DESCRIPTION filters

    filters to apply.

    TYPE: Filters

    "},{"location":"repositories/cloudwatch/#event_audit.repositories.cloudwatch.CloudWatchRepository.get_event","title":"get_event(event_id)","text":"

    Retrieves a single event from the repository.

    PARAMETER DESCRIPTION event_id

    event ID.

    TYPE: str

    RETURNS DESCRIPTION Optional[Event]

    types.Event | None: event object or None if not found.

    "},{"location":"repositories/cloudwatch/#event_audit.repositories.cloudwatch.CloudWatchRepository.remove_all_events","title":"remove_all_events()","text":"

    Removes all events from the repository.

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/cloudwatch/#event_audit.repositories.cloudwatch.CloudWatchRepository.remove_event","title":"remove_event(event_id)","text":"

    Remove operation is not supported for CloudWatch logs.

    As of today, you cannot delete a single log event from CloudWatch log stream, the alternative will be using Lambda functions: set a Lambda function trigger, filter all logs, then write the remaining logs to a new log group/stream, then delete the original log stream.

    It's potentially too expensive to do this operation, so it's not implemented.

    Note

    The remove single event operation is not supported

    "},{"location":"repositories/cloudwatch/#event_audit.repositories.cloudwatch.CloudWatchRepository.remove_events","title":"remove_events(filters)","text":"

    See remove_event method docstring.

    "},{"location":"repositories/cloudwatch/#event_audit.repositories.cloudwatch.CloudWatchRepository.test_connection","title":"test_connection()","text":"

    Tests the connection to the repository.

    RETURNS DESCRIPTION bool

    whether the connection was successful.

    TYPE: bool

    "},{"location":"repositories/cloudwatch/#event_audit.repositories.cloudwatch.CloudWatchRepository.write_event","title":"write_event(event)","text":"

    Writes a single event to the repository.

    PARAMETER DESCRIPTION event

    event to write.

    TYPE: Event

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/custom/","title":"Custom","text":"

    Here you can see a naive example of how to implement a custom repository, that stores logs in a json file.

    from ckanext.event_audit.repositories import AbstractRepository\n\nclass FileRepository(AbstractRepository):\n    def __init__(self, file_path: str | None = None):\n        self.file_path = file_path or '/tmp/event_audit.json'\n\n    def log(self) -> str:\n        return \"file\"\n\n    def write_event(self, event: types.Event) -> types.Result:\n        with open(self.file_path, 'a') as f:\n            data = json.load(f)\n            data[event.id] = event.model_dump()\n\n            f.write(json.dumps(data))\n\n        return types.Result(success=True)\n\n    def get_event(self, event_id: Any) -> types.Event | None:\n        with open(self.file_path, 'r') as f:\n            data = json.load(f)\n\n            if event_id in data:\n                return types.Event.model_validate(data[event_id])\n\n        return None\n\n    def filter_events(self, filters: types.Filters) -> list[types.Event]:\n        with open(self.file_path, 'r') as f:\n            data = json.load(f)\n\n            result = []\n\n            for event in data.values():\n                if _match_filters(event, filters):\n                    result.append(types.Event.model_validate(event))\n\n            return result\n\n    def _match_filters(self, event: types.EventData, filters: types.Filters) -> bool:\n        ...\n\n    def test_connection(self) -> types.Result:\n        return types.Result(success=True)\n

    In this version, it doesn't implement the remove_event, remove_events and remove_all_events methods, but you can implement them in the same way as the other methods. If the repository able to remove one or multiple events, it must inherits from the respective class - RemoveSingle or RemoveAll. For example:

    import os\n\nfrom ckanext.event_audit.repositories import (\n    AbstractRepository,\n    RemoveSingle,\n    RemoveAll,\n    RemoveFiltered,\n)\n\n\nclass FileRepository(AbstractRepository, RemoveSingle, RemoveAll, RemoveFiltered):\n    ...\n\n    def remove_event(self, event_id: Any) -> types.Result:\n        with open(self.file_path, \"w\") as f:\n            data = json.load(f)\n\n            if event_id in data:\n                del data[event_id]\n                f.write(json.dumps(data))\n\n                return types.Result(success=True)\n\n        return types.Result(success=False, message=\"Event not found\")\n\n    def remove_events(self, filters: types.Filters) -> types.Result:\n        with open(self.file_path, \"w\") as f:\n            data = json.load(f)\n\n            for event_id, event in data.items():\n                if _match_filters(event, filters):\n                    del data[event_id]\n\n            f.write(json.dumps(data))\n\n        return types.Result(success=True)\n\n    def _match_filters(\n        self, event: types.EventData, filters: types.Filters\n    ) -> bool: ...\n\n    def remove_all_events(self, filters: types.Filters) -> types.Result:\n        \"\"\"Removes the file if exists.\"\"\"\n\n        if os.path.exists(self.file_path):\n            os.remove(self.file_path)\n\n        return types.Result(success=True)\n
    "},{"location":"repositories/postgres/","title":"Postgres repository","text":""},{"location":"repositories/postgres/#event_audit.repositories.postgres.PostgresRepository.filter_events","title":"filter_events(filters)","text":"

    Filters events based on provided filter criteria.

    PARAMETER DESCRIPTION filters

    filters to apply.

    TYPE: Filters

    RETURNS DESCRIPTION List[Event]

    List[types.Event]: list of events.

    "},{"location":"repositories/postgres/#event_audit.repositories.postgres.PostgresRepository.get_event","title":"get_event(event_id)","text":"

    Retrieves a single event from the repository.

    PARAMETER DESCRIPTION event_id

    event ID.

    TYPE: str

    RETURNS DESCRIPTION Event | None

    types.Event | None: event object or None if not found.

    "},{"location":"repositories/postgres/#event_audit.repositories.postgres.PostgresRepository.remove_all_events","title":"remove_all_events()","text":"

    Removes all events from the repository.

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/postgres/#event_audit.repositories.postgres.PostgresRepository.remove_event","title":"remove_event(event_id, session=None, defer_commit=False)","text":"

    Removes a single event from the repository.

    PARAMETER DESCRIPTION event_id

    event ID.

    TYPE: str

    session

    session to use.

    TYPE: Session | None DEFAULT: None

    defer_commit

    whether to defer the commit.

    TYPE: bool DEFAULT: False

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/postgres/#event_audit.repositories.postgres.PostgresRepository.remove_events","title":"remove_events(filters)","text":"

    Removes a filtered set of events from the repository.

    PARAMETER DESCRIPTION filters

    filters to apply.

    TYPE: Filters

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/postgres/#event_audit.repositories.postgres.PostgresRepository.test_connection","title":"test_connection()","text":"

    Tests the connection to the repository.

    RETURNS DESCRIPTION bool

    whether the connection was successful.

    TYPE: bool

    "},{"location":"repositories/postgres/#event_audit.repositories.postgres.PostgresRepository.write_event","title":"write_event(event, session=None, defer_commit=False)","text":"

    Writes a single event to the repository.

    PARAMETER DESCRIPTION event

    event to write.

    TYPE: Event

    session

    session to use.

    TYPE: Session | None DEFAULT: None

    defer_commit

    whether to defer the commit.

    TYPE: bool DEFAULT: False

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/postgres/#event_audit.repositories.postgres.PostgresRepository.write_events","title":"write_events(events)","text":"

    Write multiple events to the repository.

    PARAMETER DESCRIPTION events

    events to write.

    TYPE: Iterable[Event]

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/redis/","title":"Redis repository","text":""},{"location":"repositories/redis/#event_audit.repositories.redis.RedisRepository.filter_events","title":"filter_events(filters)","text":"

    Filters events based on patterns generated from the provided filters.

    PARAMETER DESCRIPTION filters

    filters to apply.

    TYPE: Filters

    "},{"location":"repositories/redis/#event_audit.repositories.redis.RedisRepository.get_event","title":"get_event(event_id)","text":"

    Get an event by its ID.

    PARAMETER DESCRIPTION event_id

    event ID.

    TYPE: float

    "},{"location":"repositories/redis/#event_audit.repositories.redis.RedisRepository.remove_all_events","title":"remove_all_events()","text":"

    Removes all events from the repository.

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/redis/#event_audit.repositories.redis.RedisRepository.remove_event","title":"remove_event(event_id)","text":"

    Removes an event by its ID.

    PARAMETER DESCRIPTION event_id

    event ID.

    TYPE: float

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/redis/#event_audit.repositories.redis.RedisRepository.remove_events","title":"remove_events(filters)","text":"

    Removes a filtered set of events from the repository.

    PARAMETER DESCRIPTION filters

    filters to apply.

    TYPE: Filters

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/redis/#event_audit.repositories.redis.RedisRepository.test_connection","title":"test_connection()","text":"

    Tests the connection to the repository.

    RETURNS DESCRIPTION bool

    whether the connection was successful.

    TYPE: bool

    "},{"location":"repositories/redis/#event_audit.repositories.redis.RedisRepository.write_event","title":"write_event(event)","text":"

    Writes an event to Redis.

    PARAMETER DESCRIPTION event

    event to write.

    TYPE: Event

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-\\.\\_]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Home","text":""},{"location":"#ckanext-event-audit","title":"ckanext-event-audit","text":"

    This extension will capture and retain a comprehensive record of all changes within a CKAN app.

    "},{"location":"#developer-installation","title":"Developer installation","text":"

    To install ckanext-event-audit for development, activate your CKAN virtualenv and do:

    git clone https://github.com/DataShades/ckanext-event-audit.git\ncd ckanext-event-audit\npip install -e .\npip install -r dev-requirements.txt\n
    "},{"location":"#tests","title":"Tests","text":"

    To run the tests, do:

    pytest --ckan-ini=test.ini\n
    "},{"location":"#license","title":"License","text":"

    AGPL

    "},{"location":"cli/","title":"CLI","text":""},{"location":"cli/#event_audit.cli.export_data","title":"export_data(exporter_name, start, end, config)","text":"

    Export data using the specified exporter.

    PARAMETER DESCRIPTION exporter_name

    The name of the exporter.

    TYPE: str

    start

    The start date string in %Y-%m-%d format.

    TYPE: str

    end

    The end date string in %Y-%m-%d format.

    TYPE: str | None

    config

    The exporter config in JSON format. See the exporter's documentation for args details.

    TYPE: str | None

    RETURNS DESCRIPTION str

    The exported data

    Example

    $ ckan event-audit export-data csv --start=2024-11-11 > report.csv

    $ ckan event-audit export-data json --start=2024-11-11 | jq '[.[] | {id, category, action}]'

    $ ckan event-audit export-data xlsx --start=2024-11-11 --config='{\"file_path\": \"/tmp/test.xlsx\"}'

    Source code in ckanext/event_audit/cli.py
    @event_audit.command()\n@click.argument(\"exporter_name\", type=str)\n@click.option(\n    \"--start\",\n    required=True,\n    type=click.DateTime(formats=[\"%Y-%m-%d\"]),\n    help=\"ISO format start date\",\n)\n@click.option(\n    \"--end\",\n    required=False,\n    type=click.DateTime(formats=[\"%Y-%m-%d\"]),\n    help=\"ISO format end date\",\n)\n@click.option(\"--config\", required=False, type=str, help=\"Custom config in JSON format\")\ndef export_data(exporter_name: str, start: dt, end: dt | None, config: str | None):\n    \"\"\"Export data using the specified exporter.\n\n    Args:\n        exporter_name (str): The name of the exporter.\n        start (str): The start date string in %Y-%m-%d format.\n        end (str | None): The end date string in %Y-%m-%d format.\n        config (str | None): The exporter config in JSON format. See the exporter's\n            documentation for args details.\n\n    Returns:\n        str : The exported data\n\n    Example:\n        $ ckan event-audit export-data csv --start=2024-11-11 > report.csv\n\n        $ ckan event-audit export-data json --start=2024-11-11 | jq '[.[] |\n            {id, category, action}]'\n\n        $ ckan event-audit export-data xlsx --start=2024-11-11\n            --config='{\"file_path\": \"/tmp/test.xlsx\"}'\n    \"\"\"\n    start = UTC.localize(start) if start else None\n    end = UTC.localize(end) if end else None\n\n    try:\n        config_dict = json.loads(config or \"{}\")\n    except json.JSONDecodeError:\n        return click.secho(\"Invalid JSON format for config.\")\n\n    try:\n        exporter = utils.get_exporter(exporter_name)(**config_dict)\n    except TypeError as e:\n        return click.secho(f\"Invalid exporter config: {config}. Error: {e}\", fg=\"red\")\n    except ValueError as e:\n        return click.secho(e, fg=\"red\")\n\n    if start and end and start > end:\n        return click.secho(\"Start date must be before the end date.\", fg=\"red\")\n\n    click.echo(exporter.from_filters(types.Filters(time_from=start, time_to=end)))\n
    "},{"location":"cli/#event_audit.cli.remove_events","title":"remove_events(repository, start, end)","text":"

    Remove events from the repository by time range.

    PARAMETER DESCRIPTION repository

    The repository name. If not provided, the active repository will be used.

    TYPE: str | None

    start

    The start date string in %Y-%m-%d format.

    TYPE: str

    end

    The end date string in %Y-%m-%d format.

    TYPE: str | None

    Example

    $ ckan event-audit remove-events --start=2024-11-11 --end=2024-11-12

    Source code in ckanext/event_audit/cli.py
    @event_audit.command()\n@click.option(\"--repository\", required=False, help=\"The repository name\")\n@click.option(\n    \"--start\",\n    required=False,\n    type=click.DateTime(formats=[\"%Y-%m-%d\"]),\n    help=\"ISO format start date\",\n)\n@click.option(\n    \"--end\",\n    required=False,\n    type=click.DateTime(formats=[\"%Y-%m-%d\"]),\n    help=\"ISO format end date\",\n)\ndef remove_events(repository: str | None, start: dt | None, end: dt | None):\n    \"\"\"Remove events from the repository by time range.\n\n    Args:\n        repository (str | None): The repository name. If not provided, the\n            active repository will be used.\n        start (str): The start date string in %Y-%m-%d format.\n        end (str | None): The end date string in %Y-%m-%d format.\n\n    Example:\n        $ ckan event-audit remove-events --start=2024-11-11 --end=2024-11-12\n    \"\"\"\n    start = UTC.localize(start) if start else None\n    end = UTC.localize(end) if end else None\n\n    if start and end and start > end:\n        return click.secho(\"Start date must be before the end date.\", fg=\"red\")\n\n    try:\n        repo = utils.get_repo(repository) if repository else utils.get_active_repo()\n    except ValueError:\n        return click.secho(f\"Unknown repository: {repository}\", fg=\"red\")\n\n    if not (start or end) and not isinstance(repo, repositories.RemoveAll):\n        return click.secho(\n            f\"Repository {repository} does not support removing events.\", fg=\"red\"\n        )\n\n    if not isinstance(repo, repositories.RemoveFiltered):\n        if start or end:\n            return click.secho(\n                (\n                    f\"Repository {repository} does not support removing events \"\n                    \"by time range. \"\n                    \"Please remove the --start and --end flags to delete all events.\"\n                ),\n                fg=\"red\",\n            )\n        return repo.remove_all_events()\n\n    return repo.remove_events(types.Filters(time_from=start, time_to=end))\n
    "},{"location":"install/","title":"Installation","text":""},{"location":"install/#requirements","title":"Requirements","text":"

    Compatibility with core CKAN versions:

    CKAN version Compatible? 2.9 no 2.10 yes 2.11 yes master yes"},{"location":"install/#installation_1","title":"Installation","text":"
    1. Install the extension from PyPI:

      pip install ckanext-event-audit\n

    2. Enable the plugin in your CKAN configuration file (e.g. ckan.ini or production.ini):

      ckan.plugins = ... event_audit ...\n

    3. Run DB migrations:

      ckan db upgrade -p event_audit\n

    4. Configure the extension up to your needs and you're ready to go. See the documentation for more details about the configuration options.

    "},{"location":"interfaces/","title":"Interfaces","text":""},{"location":"interfaces/#ieventaudit","title":"IEventAudit","text":"

    Extend functionality of ckanext-event-audit.

    Example:

    import ckan.plugins as p\n\nfrom ckanext.event_audit.interfaces import IEventAudit\nfrom ckanext.event_audit.repositories import AbstractRepository\nfrom ckanext.event_audit.exporters import AbstractExporter\n\nclass MyPlugin(p.SingletonPlugin):\n    p.implements(IEventAudit, inherit=True)\n\n    def register_repository(self) -> dict[str, type[AbstractRepository]]:\n        return {\n            \"my_repo\": MyRepository,\n        }\n\n    def register_exporter(self) -> dict[str, type[AbstractExporter]]:\n        return {\n            \"my_exporter\": MyExporter,\n        }\n\n    def skip_event(self, event: types.Event) -> bool:\n        if event.category == \"api\" and event.action == \"status_show\":\n            return True\n\n        if event.category == \"model\" and event.action_object == \"Dashboard\":\n            return True\n\n        return False\n

    "},{"location":"interfaces/#event_audit.interfaces.IEventAudit.register_exporter","title":"register_exporter()","text":"

    Return the exporters provided by this plugin.

    Example
    def register_exporter(self):\n    return {\n        \"csv\": CSVExporter,\n    }\n
    RETURNS DESCRIPTION dict[str, type[AbstractExporter]]

    mapping of exporter names to exporter classes

    "},{"location":"interfaces/#event_audit.interfaces.IEventAudit.register_repository","title":"register_repository()","text":"

    Return the repositories provided by this plugin.

    Example
    def register_repository(self):\n    return {\n        \"my_repo\": MyRepository,\n    }\n
    RETURNS DESCRIPTION dict[str, type[AbstractRepository]]

    mapping of repository names to repository classes

    "},{"location":"interfaces/#event_audit.interfaces.IEventAudit.skip_event","title":"skip_event(event)","text":"

    Skip an event.

    This method is called before writing the event to the repository.

    Example
    def skip_event(self, event: types.Event) -> bool:\n    if event.category == \"api\" and event.action == \"status_show\":\n        return True\n\n    if  event.category == \"model\"  and event.action_object == \"Dashboard\":\n        return True\n\n    return False\n
    RETURNS DESCRIPTION bool

    True if the event should be skipped, False otherwise

    "},{"location":"usage/","title":"Usage","text":"

    To use an event audit in your extension, you should get an instance of the repository class. There are two ways to do this:

    1. Using the get_repo function:

      import ckanext.event_audit.utils as utils\n\nrepo = utils.get_active_repo()\n\nevent = repo.build_event({\"category\": \"xxx\", \"action\": \"xxx\"})\n\nrepo.write_event(event)\n

      The get_active_repo function will return an instance of the active repository class that is configured in the CKAN configuration file.

    2. Using the get_repo function with a specific repository:

      import ckanext.event_audit.utils as utils\n\nrepo = utils.get_repo(\"file\")\n\nevent = repo.build_event({\"category\": \"xxx\", \"action\": \"xxx\"})\n\nrepo.write_event(event)\n

      The get_repo function will return an instance of the repository class that is specified in the argument. You can use this method to get a specific repository instance.

    "},{"location":"utils/","title":"Utility Functions","text":""},{"location":"utils/#event_audit.utils.get_active_repo","title":"get_active_repo()","text":"

    Get the active repository.

    The active repository is the one that is currently configured in the extension configuration.

    RETURNS DESCRIPTION AbstractRepository

    The active repository.

    "},{"location":"utils/#event_audit.utils.get_available_exporters","title":"get_available_exporters()","text":"

    Retrieve a dictionary of available exporters.

    This function collects and returns a dictionary where the keys are exporter names (as strings) and the values are the corresponding exporter classes.

    RETURNS DESCRIPTION dict[str, type[AbstractExporter]]

    A dictionary mapping exporter names to their respective exporter classes.

    "},{"location":"utils/#event_audit.utils.get_available_repos","title":"get_available_repos()","text":"

    Retrieve a dictionary of available repositories.

    This function collects and returns a dictionary where the keys are repository names (as strings) and the values are the corresponding repository classes.

    RETURNS DESCRIPTION dict[str, type[AbstractRepository]]

    A dictionary mapping repository names to their respective repository classes.

    "},{"location":"utils/#event_audit.utils.get_exporter","title":"get_exporter(exporter_name)","text":"

    Retrieve an exporter class by name.

    This function retrieves an exporter class by name. If the exporter is not found, a ValueError is raised.

    PARAMETER DESCRIPTION exporter_name

    The name of the exporter to retrieve.

    TYPE: str

    RETURNS DESCRIPTION type[AbstractExporter]

    The exporter class.

    "},{"location":"utils/#event_audit.utils.get_repo","title":"get_repo(repo_name)","text":"

    Retrieve a repository class by name.

    This function retrieves a repository class by name. If the repository is not found, a ValueError is raised.

    PARAMETER DESCRIPTION repo_name

    The name of the repository to retrieve.

    TYPE: str

    RETURNS DESCRIPTION AbstractRepository

    The repository class.

    "},{"location":"utils/#event_audit.utils.test_active_connection","title":"test_active_connection()","text":"

    Test the connection to the active repository.

    When we test the connection, we store the result in the repository object, so we can reuse it later.

    RETURNS DESCRIPTION bool

    whether the connection is active

    "},{"location":"validators/","title":"Validators","text":""},{"location":"validators/#event_audit.logic.validators.audit_repo_exists","title":"audit_repo_exists(value, context)","text":"

    Check if the repository with the given name is registered.

    PARAMETER DESCRIPTION value

    The repository name.

    TYPE: Any

    context

    The CKAN context.

    TYPE: Context

    RETURNS DESCRIPTION Any

    The repository name if it exists.

    TYPE: Any

    RAISES DESCRIPTION Invalid

    If the repository does not exist.

    Source code in ckanext/event_audit/logic/validators.py
    def audit_repo_exists(value: Any, context: Context) -> Any:\n    \"\"\"Check if the repository with the given name is registered.\n\n    Args:\n        value (Any): The repository name.\n        context (Context): The CKAN context.\n\n    Returns:\n        Any: The repository name if it exists.\n\n    Raises:\n        tk.Invalid: If the repository does not exist.\n    \"\"\"\n    if value not in utils.get_available_repos():\n        raise tk.Invalid(f\"Repository `{value}` is not registered\")\n\n    return value\n
    "},{"location":"configure/admin_panel/","title":"Admin panel","text":"

    We have an integration with the ckanext-admin-panel extension, which allows you to manage the CKAN configuration from the web interface. To enable this integration, you need to install the ckanext-admin-panel extension and configure it as described in the ckanext-admin-panel documentation.

    Note

    The admin panel is available only for sysadmin users.

    By default, the admin pages are not being registered. But if you want to enable it, you can set the respective option to true in your CKAN configuration file.

    ckanext.event_audit.enable_admin_panel = true\n
    "},{"location":"configure/admin_panel/#configuration-with-ckanext-admin-panel","title":"Configuration with ckanext-admin-panel","text":"

    The ckanext-admin-panel allows you to configure the extension in real time from the web interface.

    "},{"location":"configure/async/","title":"Asynchronous processing","text":"

    To avoid blocking the main thread, the extension uses a separate thread to write the audit logs. A separate thread will be started automatically along with the CKAN application.

    The thread is responsible for storing the logs in the configured repository.

    By default, we're using the threaded mode. However, if you want to disable the threaded mode, you can do this by setting the following configuration option in the CKAN configuration file:

    ckanext.event_audit.threaded_mode = false\n

    Disabling the threaded mode will cause the extension to write the logs in the main thread. This can be useful for debugging purposes.

    Note, that pairing it with the Cloudwatch repository is not recommended, as it can block the main thread for a long time. Network operations can be slow and can cause the application to hang for a while.

    If your custom repository involves a network operations, it's recommended to keep the threaded mode enabled.

    "},{"location":"configure/async/#batch-size","title":"Batch size","text":"

    The extension writes the logs in batches. The batch size can be adjusted by setting the following configuration option in the CKAN configuration file:

    ckanext.event_audit.batch.size = 50\n

    By default, we're accumulating 50 events before writing them to the repository.

    "},{"location":"configure/async/#batch-timeout","title":"Batch timeout","text":"

    Force push the events to the repository after this time in seconds since the last push:

    ckanext.event_audit.batch.timeout = 3600\n

    The default value is 3600 seconds (1 hour). This options is required to ensure that the logs are written to the repository in case of low activity.

    "},{"location":"configure/cloudwatch/","title":"Cloudwatch","text":"

    Using Cloudwatch repository requires you to configure the following options in the CKAN configuration file:

    ckanext.event_audit.cloudwatch.access_key = YOUR_ACCESS_KEY\nckanext.event_audit.cloudwatch.secret_key = YOUR_SECRET_KEY\nckanext.event_audit.cloudwatch.region = YOUR_REGION\n

    See the AWS documentation for more information on how to obtain these values and configure the AWS Cloudwatch service.

    "},{"location":"configure/ignore/","title":"Ignore events","text":"

    The extension provides a various set of configuration options to adjust the behavior of the audit logs.

    Warning

    These config options are applicable only for built-in tracking methods (API, Database) and not related to client's usage of the extension.

    "},{"location":"configure/ignore/#ignoring-categories","title":"Ignoring categories","text":"

    The extension allows to ignore specific categories of events. To do this, we have to set the following configuration option in the CKAN configuration file:

    ckanext.event_audit.ignore.categories = test\n

    By default, we're not ignoring any categories. The categories option is a comma-separated list of categories that should be ignored.

    Categories are arbitrary strings that can be used to group events.

    "},{"location":"configure/ignore/#ignoring-actions","title":"Ignoring actions","text":"

    The extension allows to ignore specific actions. To do this, we have to set the following configuration option in the CKAN configuration file:

    ckanext.event_audit.ignore.actions = test\n

    Some actions might be called more frequently than others, and we might not be interested in storing them. The actions option is a comma-separated list of actions that should be ignored.

    By default, we're excluding next actions from being stored:

    • editable_config_list
    • editable_config_change
    • get_site_user
    • ckanext_pages_list
    • user_show
    "},{"location":"configure/ignore/#ignoring-models","title":"Ignoring models","text":"

    The extension allows to ignore specific models. To do this, we have to set the following configuration option in the CKAN configuration file:

    ckanext.event_audit.ignore.models = User Package Resource\n

    By default, we're excluding next models from being stored:

    • Option
    "},{"location":"configure/repository/","title":"Repository","text":"

    The event audit logs are stored in a configurable storages, we call them repositories. To use an extension, you have to choose one of the available repositories.

    The following repositories are available:

    1. redis - the default repository, stores logs in Redis.
    2. postgres - stores logs in a PostgreSQL database.
    3. cloudwatch - stores logs in AWS CloudWatch.
    Note

    If the cloudwatch repository is used, the extension will automatically create a log group in CloudWatch. Also, check the CloudWatch repository documentation for additional configuration options.

    "},{"location":"configure/repository/#active-repository","title":"Active repository","text":"

    The default repository is redis, but it can be changed to a different one. To do this, we have to set the following configuration options in the CKAN configuration file:

    ckanext.event_audit.active_repo = postgres\n
    "},{"location":"configure/repository/#list-of-available-repositories","title":"List of available repositories","text":"

    You can restrict a list of available repositories by setting the following configuration option in the CKAN configuration file:

    ckanext.event_audit.active_repo = cloudwatch\nckanext.event_audit.restrict_available_repos = cloudwatch\n
    Note

    By default, we're not restricting the list of available repositories. It means that all registered repositories are available for use.

    This could be useful if you want to limit the available repositories to a specific set of options due to some security concerns. This config option won't be available in the admin interface and can't be changed in real time.

    "},{"location":"configure/tracking/","title":"In-built tracking","text":"

    There are two built-in trackers in the extension that are enabled by default and work out of the box - API and Database trackers.

    "},{"location":"configure/tracking/#api-tracker","title":"API tracker","text":"

    Captures all events that are triggered by the CKAN API. Everything that is called via tk.get_action will be tracked by this tracker, unless it's explicitly ignored by the configuration. See the ignore section for more details.

    To disable the API tracker, specify this in the configuration file:

    ckanext.event_audit.track.api = false\n

    We can ignore specific actions from being tracked by setting the ckanext.event_audit.ignore.actions configuration option. See the ignore section for more details.

    "},{"location":"configure/tracking/#database-tracker","title":"Database tracker","text":"

    We're utilising the SQLAlchemy\u2019s event system for tracking database interactions. The audit event creation will be triggered when the model is created, updated, or deleted.

    To disable the Database tracker, specify this in the configuration file:

    ckanext.event_audit.track.model = false\n

    We can ignore specific models from being tracked by setting the ckanext.event_audit.ignore.models configuration option. See the ignore section for more details.

    "},{"location":"configure/tracking/#custom-trackers","title":"Custom trackers","text":"

    You can create and write an event anywhere in your codebase.

    TODO: add link to usage docs

    "},{"location":"exporters/basic/","title":"Basic","text":"

    The exporters allow you to export the event audit logs to a different file format.

    The following exporters are available:

    1. CSV Exporter: Exports the event audit logs to a CSV file.
    2. JSON Exporter: Exports the event audit logs to a JSON file.
    3. TSV Exporter: Exports the event audit logs to a TSV file.
    4. XLSX Exporter: Exports the event audit logs to an XLSX file.

    Each exporter has its own configuration options. The configuration options are described in the respective exporter's documentation section.

    "},{"location":"exporters/basic/#base-exporter-class","title":"Base exporter class","text":"

    Base class for all exporters.

    Exporters are used to export a lsit of events to a specific file format.

    "},{"location":"exporters/basic/#event_audit.exporters.base.AbstractExporter.export","title":"export(events) abstractmethod","text":"

    Export events to a specific format.

    We are not providing a specific return type, because it will depend on the specific exporter implementation.

    PARAMETER DESCRIPTION events

    events to export

    TYPE: Iterable[Event]

    RETURNS DESCRIPTION Any

    exported data.

    TYPE: Any

    "},{"location":"exporters/basic/#event_audit.exporters.base.AbstractExporter.from_filters","title":"from_filters(filters, repo_name=None)","text":"

    Export events from a repo using the given filters.

    If repo_name is not provided, the active repo is used.

    PARAMETER DESCRIPTION filters

    search filters.

    TYPE: Filters

    repo_name

    name of the repo to use. Defaults to None.

    TYPE: str | None DEFAULT: None

    RETURNS DESCRIPTION Any

    exported data.

    TYPE: Any

    "},{"location":"exporters/csv/","title":"CSV exporter","text":""},{"location":"exporters/csv/#event_audit.exporters.csv.CSVExporter.__init__","title":"__init__(delimiter=',', quotechar='\"', quoting=QUOTE_ALL, ignore_fields=None)","text":"

    CSV exporter.

    PARAMETER DESCRIPTION delimiter

    delimiter. Defaults to \",\".

    TYPE: str DEFAULT: ','

    quotechar

    quote character. Defaults to '\"'.

    TYPE: str DEFAULT: '\"'

    quoting

    quoting. Defaults to QUOTE_ALL.

    TYPE: int DEFAULT: QUOTE_ALL

    ignore_fields

    fields to ignore. By default we ignore the \"result\" and \"payload\" fields.

    TYPE: list[str] | None DEFAULT: None

    "},{"location":"exporters/csv/#event_audit.exporters.csv.CSVExporter.export","title":"export(events)","text":"

    Export events to CSV format.

    PARAMETER DESCRIPTION events

    events to export.

    TYPE: Iterable[Event]

    RETURNS DESCRIPTION str | None

    str | None: CSV data.

    "},{"location":"exporters/json/","title":"JSON exporter","text":"

    Bases: AbstractExporter

    "},{"location":"exporters/json/#event_audit.exporters.json.JSONExporter.__init__","title":"__init__(stringify=True)","text":"

    JSON exporter.

    PARAMETER DESCRIPTION stringify

    whether to return a string or a dict. By default we return a string.

    TYPE: bool DEFAULT: True

    "},{"location":"exporters/json/#event_audit.exporters.json.JSONExporter.export","title":"export(events)","text":"

    Export events to JSON format.

    PARAMETER DESCRIPTION events

    events to export.

    TYPE: Iterable[Event]

    RETURNS DESCRIPTION list[dict[str, Any]] | str | None

    str | None: JSON data.

    "},{"location":"exporters/tsv/","title":"TSV exporter","text":""},{"location":"exporters/tsv/#event_audit.exporters.tsv.TSVExporter.__init__","title":"__init__(ignore_fields=None)","text":"

    TSV exporter.

    PARAMETER DESCRIPTION ignore_fields

    fields to ignore. By default we ignore the \"result\" and \"payload\" fields.

    TYPE: list[str] | None DEFAULT: None

    "},{"location":"exporters/xlsx/","title":"XLSX exporter","text":""},{"location":"exporters/xlsx/#event_audit.exporters.xlsx.XLSXExporter.__init__","title":"__init__(file_path, ignore_fields=None)","text":"

    XLSX exporter.

    PARAMETER DESCRIPTION file_path

    path to the file or BytesIO object to write the XLSX data.

    TYPE: str | BytesIO

    ignore_fields

    fields to ignore. By default we ignore the \"result\" and \"payload\" fields.

    TYPE: list[str] | None DEFAULT: None

    "},{"location":"exporters/xlsx/#event_audit.exporters.xlsx.XLSXExporter.export","title":"export(events)","text":"

    Export events to a XLSX file.

    PARAMETER DESCRIPTION events

    events to export.

    TYPE: Iterable[Event]

    RETURNS DESCRIPTION str | BytesIO | None

    str | BytesIO | None: path to the file or BytesIO object if the

    str | BytesIO | None

    export was successful, None otherwise.

    "},{"location":"repositories/abstract/","title":"Abstract","text":""},{"location":"repositories/abstract/#event_audit.repositories.base.AbstractRepository.__new__","title":"__new__(*args, **kwargs)","text":"

    Singleton pattern implementation.

    "},{"location":"repositories/abstract/#event_audit.repositories.base.AbstractRepository.build_event","title":"build_event(event_data)","text":"

    Build an event object from the provided data.

    PARAMETER DESCRIPTION event_data

    event data.

    TYPE: EventData

    RETURNS DESCRIPTION Event

    types.Event: event object.

    "},{"location":"repositories/abstract/#event_audit.repositories.base.AbstractRepository.enqueue_event","title":"enqueue_event(event)","text":"

    Enqueue an event to be written to the repository.

    PARAMETER DESCRIPTION event

    event to write.

    TYPE: Event

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/abstract/#event_audit.repositories.base.AbstractRepository.filter_events","title":"filter_events(filters) abstractmethod","text":"

    Filters events based on provided filter criteria.

    PARAMETER DESCRIPTION filters

    filters to apply.

    TYPE: Filters

    "},{"location":"repositories/abstract/#event_audit.repositories.base.AbstractRepository.get_event","title":"get_event(event_id) abstractmethod","text":"

    Retrieves a single event from the repository.

    PARAMETER DESCRIPTION event_id

    event ID.

    TYPE: str

    RETURNS DESCRIPTION Event | None

    types.Event | None: event object or None if not found.

    "},{"location":"repositories/abstract/#event_audit.repositories.base.AbstractRepository.get_name","title":"get_name() abstractmethod classmethod","text":"

    Return the name of the repository.

    RETURNS DESCRIPTION str

    name of the repository.

    TYPE: str

    "},{"location":"repositories/abstract/#event_audit.repositories.base.AbstractRepository.remove_all_events","title":"remove_all_events()","text":"

    Removes all events from the repository.

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/abstract/#event_audit.repositories.base.AbstractRepository.remove_event","title":"remove_event(event_id)","text":"

    Removes a single event from the repository.

    PARAMETER DESCRIPTION event_id

    event ID.

    TYPE: Any

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/abstract/#event_audit.repositories.base.AbstractRepository.remove_events","title":"remove_events(filters)","text":"

    Removes a filtered set of events from the repository.

    PARAMETER DESCRIPTION filters

    filters to apply.

    TYPE: Filters

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/abstract/#event_audit.repositories.base.AbstractRepository.test_connection","title":"test_connection() abstractmethod","text":"

    Test the connection to the repository.

    RETURNS DESCRIPTION bool

    whether the connection was successful.

    TYPE: bool

    "},{"location":"repositories/abstract/#event_audit.repositories.base.AbstractRepository.write_event","title":"write_event(event) abstractmethod","text":"

    Writes a single event to the repository.

    PARAMETER DESCRIPTION event

    event to write.

    TYPE: Event

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/abstract/#event_audit.repositories.base.AbstractRepository.write_events","title":"write_events(events)","text":"

    Write multiple events to the repository.

    PARAMETER DESCRIPTION events

    events to write.

    TYPE: Iterable[Event]

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/basic/","title":"Basic","text":"

    Repositories are the storages where the event audit logs are stored. There are a few basic repositories, that you can use out of the box:

    1. redis - the default repository, stores logs in Redis.
    2. postgres - stores logs in a PostgreSQL database.
    3. cloudwatch - stores logs in AWS CloudWatch.

    You can also implement your own repository. To do this, you need to create a new class that inherits from the AbstractRepository class and implement all the required methods.

    See the abstract repository documentation and custom repository documentation for more information.

    "},{"location":"repositories/cloudwatch/","title":"Cloudwatch repository","text":""},{"location":"repositories/cloudwatch/#event_audit.repositories.cloudwatch.CloudWatchRepository.__init__","title":"__init__(credentials=None, log_group='/ckan/event-audit', log_stream='event-audit-stream')","text":"

    CloudWatch repository.

    PARAMETER DESCRIPTION credentials

    AWS credentials. If not provided, the extension configuration will be used.

    TYPE: AWSCredentials | None DEFAULT: None

    log_group

    CloudWatch log group name.

    TYPE: str DEFAULT: '/ckan/event-audit'

    log_stream

    CloudWatch log stream name.

    TYPE: str DEFAULT: 'event-audit-stream'

    "},{"location":"repositories/cloudwatch/#event_audit.repositories.cloudwatch.CloudWatchRepository.filter_events","title":"filter_events(filters)","text":"

    Filters events based on provided filter criteria.

    PARAMETER DESCRIPTION filters

    filters to apply.

    TYPE: Filters

    "},{"location":"repositories/cloudwatch/#event_audit.repositories.cloudwatch.CloudWatchRepository.get_event","title":"get_event(event_id)","text":"

    Retrieves a single event from the repository.

    PARAMETER DESCRIPTION event_id

    event ID.

    TYPE: str

    RETURNS DESCRIPTION Optional[Event]

    types.Event | None: event object or None if not found.

    "},{"location":"repositories/cloudwatch/#event_audit.repositories.cloudwatch.CloudWatchRepository.remove_all_events","title":"remove_all_events()","text":"

    Removes all events from the repository.

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/cloudwatch/#event_audit.repositories.cloudwatch.CloudWatchRepository.remove_event","title":"remove_event(event_id)","text":"

    Remove operation is not supported for CloudWatch logs.

    As of today, you cannot delete a single log event from CloudWatch log stream, the alternative will be using Lambda functions: set a Lambda function trigger, filter all logs, then write the remaining logs to a new log group/stream, then delete the original log stream.

    It's potentially too expensive to do this operation, so it's not implemented.

    Note

    The remove single event operation is not supported

    "},{"location":"repositories/cloudwatch/#event_audit.repositories.cloudwatch.CloudWatchRepository.remove_events","title":"remove_events(filters)","text":"

    See remove_event method docstring.

    "},{"location":"repositories/cloudwatch/#event_audit.repositories.cloudwatch.CloudWatchRepository.test_connection","title":"test_connection()","text":"

    Tests the connection to the repository.

    RETURNS DESCRIPTION bool

    whether the connection was successful.

    TYPE: bool

    "},{"location":"repositories/cloudwatch/#event_audit.repositories.cloudwatch.CloudWatchRepository.write_event","title":"write_event(event)","text":"

    Writes a single event to the repository.

    PARAMETER DESCRIPTION event

    event to write.

    TYPE: Event

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/custom/","title":"Custom","text":"

    Here you can see a naive example of how to implement a custom repository, that stores logs in a json file.

    from ckanext.event_audit.repositories import AbstractRepository\n\nclass FileRepository(AbstractRepository):\n    def __init__(self, file_path: str | None = None):\n        self.file_path = file_path or '/tmp/event_audit.json'\n\n    def log(self) -> str:\n        return \"file\"\n\n    def write_event(self, event: types.Event) -> types.Result:\n        with open(self.file_path, 'a') as f:\n            data = json.load(f)\n            data[event.id] = event.model_dump()\n\n            f.write(json.dumps(data))\n\n        return types.Result(success=True)\n\n    def get_event(self, event_id: Any) -> types.Event | None:\n        with open(self.file_path, 'r') as f:\n            data = json.load(f)\n\n            if event_id in data:\n                return types.Event.model_validate(data[event_id])\n\n        return None\n\n    def filter_events(self, filters: types.Filters) -> list[types.Event]:\n        with open(self.file_path, 'r') as f:\n            data = json.load(f)\n\n            result = []\n\n            for event in data.values():\n                if _match_filters(event, filters):\n                    result.append(types.Event.model_validate(event))\n\n            return result\n\n    def _match_filters(self, event: types.EventData, filters: types.Filters) -> bool:\n        ...\n\n    def test_connection(self) -> types.Result:\n        return types.Result(success=True)\n

    In this version, it doesn't implement the remove_event, remove_events and remove_all_events methods, but you can implement them in the same way as the other methods. If the repository able to remove one or multiple events, it must inherits from the respective class - RemoveSingle or RemoveAll. For example:

    import os\n\nfrom ckanext.event_audit.repositories import (\n    AbstractRepository,\n    RemoveSingle,\n    RemoveAll,\n    RemoveFiltered,\n)\n\n\nclass FileRepository(AbstractRepository, RemoveSingle, RemoveAll, RemoveFiltered):\n    ...\n\n    def remove_event(self, event_id: Any) -> types.Result:\n        with open(self.file_path, \"w\") as f:\n            data = json.load(f)\n\n            if event_id in data:\n                del data[event_id]\n                f.write(json.dumps(data))\n\n                return types.Result(success=True)\n\n        return types.Result(success=False, message=\"Event not found\")\n\n    def remove_events(self, filters: types.Filters) -> types.Result:\n        with open(self.file_path, \"w\") as f:\n            data = json.load(f)\n\n            for event_id, event in data.items():\n                if _match_filters(event, filters):\n                    del data[event_id]\n\n            f.write(json.dumps(data))\n\n        return types.Result(success=True)\n\n    def _match_filters(\n        self, event: types.EventData, filters: types.Filters\n    ) -> bool: ...\n\n    def remove_all_events(self, filters: types.Filters) -> types.Result:\n        \"\"\"Removes the file if exists.\"\"\"\n\n        if os.path.exists(self.file_path):\n            os.remove(self.file_path)\n\n        return types.Result(success=True)\n
    "},{"location":"repositories/postgres/","title":"Postgres repository","text":""},{"location":"repositories/postgres/#event_audit.repositories.postgres.PostgresRepository.filter_events","title":"filter_events(filters)","text":"

    Filters events based on provided filter criteria.

    PARAMETER DESCRIPTION filters

    filters to apply.

    TYPE: Filters

    RETURNS DESCRIPTION List[Event]

    List[types.Event]: list of events.

    "},{"location":"repositories/postgres/#event_audit.repositories.postgres.PostgresRepository.get_event","title":"get_event(event_id)","text":"

    Retrieves a single event from the repository.

    PARAMETER DESCRIPTION event_id

    event ID.

    TYPE: str

    RETURNS DESCRIPTION Event | None

    types.Event | None: event object or None if not found.

    "},{"location":"repositories/postgres/#event_audit.repositories.postgres.PostgresRepository.remove_all_events","title":"remove_all_events()","text":"

    Removes all events from the repository.

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/postgres/#event_audit.repositories.postgres.PostgresRepository.remove_event","title":"remove_event(event_id, session=None, defer_commit=False)","text":"

    Removes a single event from the repository.

    PARAMETER DESCRIPTION event_id

    event ID.

    TYPE: str

    session

    session to use.

    TYPE: Session | None DEFAULT: None

    defer_commit

    whether to defer the commit.

    TYPE: bool DEFAULT: False

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/postgres/#event_audit.repositories.postgres.PostgresRepository.remove_events","title":"remove_events(filters)","text":"

    Removes a filtered set of events from the repository.

    PARAMETER DESCRIPTION filters

    filters to apply.

    TYPE: Filters

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/postgres/#event_audit.repositories.postgres.PostgresRepository.test_connection","title":"test_connection()","text":"

    Tests the connection to the repository.

    RETURNS DESCRIPTION bool

    whether the connection was successful.

    TYPE: bool

    "},{"location":"repositories/postgres/#event_audit.repositories.postgres.PostgresRepository.write_event","title":"write_event(event, session=None, defer_commit=False)","text":"

    Writes a single event to the repository.

    PARAMETER DESCRIPTION event

    event to write.

    TYPE: Event

    session

    session to use.

    TYPE: Session | None DEFAULT: None

    defer_commit

    whether to defer the commit.

    TYPE: bool DEFAULT: False

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/postgres/#event_audit.repositories.postgres.PostgresRepository.write_events","title":"write_events(events)","text":"

    Write multiple events to the repository.

    PARAMETER DESCRIPTION events

    events to write.

    TYPE: Iterable[Event]

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/redis/","title":"Redis repository","text":""},{"location":"repositories/redis/#event_audit.repositories.redis.RedisRepository.filter_events","title":"filter_events(filters)","text":"

    Filters events based on patterns generated from the provided filters.

    PARAMETER DESCRIPTION filters

    filters to apply.

    TYPE: Filters

    "},{"location":"repositories/redis/#event_audit.repositories.redis.RedisRepository.get_event","title":"get_event(event_id)","text":"

    Get an event by its ID.

    PARAMETER DESCRIPTION event_id

    event ID.

    TYPE: float

    "},{"location":"repositories/redis/#event_audit.repositories.redis.RedisRepository.remove_all_events","title":"remove_all_events()","text":"

    Removes all events from the repository.

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/redis/#event_audit.repositories.redis.RedisRepository.remove_event","title":"remove_event(event_id)","text":"

    Removes an event by its ID.

    PARAMETER DESCRIPTION event_id

    event ID.

    TYPE: float

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/redis/#event_audit.repositories.redis.RedisRepository.remove_events","title":"remove_events(filters)","text":"

    Removes a filtered set of events from the repository.

    PARAMETER DESCRIPTION filters

    filters to apply.

    TYPE: Filters

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "},{"location":"repositories/redis/#event_audit.repositories.redis.RedisRepository.test_connection","title":"test_connection()","text":"

    Tests the connection to the repository.

    RETURNS DESCRIPTION bool

    whether the connection was successful.

    TYPE: bool

    "},{"location":"repositories/redis/#event_audit.repositories.redis.RedisRepository.write_event","title":"write_event(event)","text":"

    Writes an event to Redis.

    PARAMETER DESCRIPTION event

    event to write.

    TYPE: Event

    RETURNS DESCRIPTION Result

    types.Result: result of the operation.

    "}]} \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml index 0dc3a42..8990eee 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -2,94 +2,98 @@ https://.github.io/ckanext-event-audit/ - 2024-11-20 + 2024-11-25 https://.github.io/ckanext-event-audit/cli/ - 2024-11-20 + 2024-11-25 https://.github.io/ckanext-event-audit/install/ - 2024-11-20 + 2024-11-25 https://.github.io/ckanext-event-audit/interfaces/ - 2024-11-20 + 2024-11-25 https://.github.io/ckanext-event-audit/usage/ - 2024-11-20 + 2024-11-25 https://.github.io/ckanext-event-audit/utils/ - 2024-11-20 + 2024-11-25 https://.github.io/ckanext-event-audit/validators/ - 2024-11-20 + 2024-11-25 - https://.github.io/ckanext-event-audit/configure/active_repo/ - 2024-11-20 + https://.github.io/ckanext-event-audit/configure/admin_panel/ + 2024-11-25 https://.github.io/ckanext-event-audit/configure/async/ - 2024-11-20 + 2024-11-25 https://.github.io/ckanext-event-audit/configure/cloudwatch/ - 2024-11-20 + 2024-11-25 https://.github.io/ckanext-event-audit/configure/ignore/ - 2024-11-20 + 2024-11-25 + + + https://.github.io/ckanext-event-audit/configure/repository/ + 2024-11-25 https://.github.io/ckanext-event-audit/configure/tracking/ - 2024-11-20 + 2024-11-25 https://.github.io/ckanext-event-audit/exporters/basic/ - 2024-11-20 + 2024-11-25 https://.github.io/ckanext-event-audit/exporters/csv/ - 2024-11-20 + 2024-11-25 https://.github.io/ckanext-event-audit/exporters/json/ - 2024-11-20 + 2024-11-25 https://.github.io/ckanext-event-audit/exporters/tsv/ - 2024-11-20 + 2024-11-25 https://.github.io/ckanext-event-audit/exporters/xlsx/ - 2024-11-20 + 2024-11-25 https://.github.io/ckanext-event-audit/repositories/abstract/ - 2024-11-20 + 2024-11-25 https://.github.io/ckanext-event-audit/repositories/basic/ - 2024-11-20 + 2024-11-25 https://.github.io/ckanext-event-audit/repositories/cloudwatch/ - 2024-11-20 + 2024-11-25 https://.github.io/ckanext-event-audit/repositories/custom/ - 2024-11-20 + 2024-11-25 https://.github.io/ckanext-event-audit/repositories/postgres/ - 2024-11-20 + 2024-11-25 https://.github.io/ckanext-event-audit/repositories/redis/ - 2024-11-20 + 2024-11-25 \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz index 438b76e642db119dfafe5691532ded3a98e0fdd7..8e4c4672a0487525c1666a499dc51529fb44a062 100644 GIT binary patch delta 350 zcmV-k0ipiv0`dX}ABzYG0KP+!2Ooc=ZA#PIo?tsq6x_rsfe1SvefyENs@8K4zy+|3 z|38pypxV!!$wu&m&hGL}xz01N%4xLCF8}`eEZ*{cy{ZOp2sjJQmUel|+kdA-U)TLN51msT04>Lm(=FlB?>4u=w*P$y|&<^q34uw2MN zBa2Eb&i~g+;?C)MyWYMOn@zEO4K6p^RZQH~p3qeWqiLfhmc~TXXYfaK8eUt{ZWar}{C})q@3?78k9onBhxR?}Z=xkL29Ll&sPm)!e01P(Pc}M`nL+?WI zcmA9J411|g;U0^I0(0+tD5Op#4w6tMz@cbNfZ~^&O#p+FIE>lQ91Y{1uNZE6d7vd_ wI1QS(Df57mK&^k|8Hin%21^)n<0HNkA80&id;;atg?~={2UVQ+?iURJ06@*8Hvj+t delta 344 zcmV-e0jK`*0_*|@ABzYGfF?bW2Ooc7o6_{QC)mytC0^o{;0QY(d;5{Ln$~j<;0qrz z{{KL-f$F&QHk-gRk~`$Pa+_!13^nN5L;mspRy^m&dQ*+wGDsGj9Ut;eWO!9-p68NL zpw|PJfTJOdtXqhu zE@iE#%`N7;<0OjvkaYf)jjt7$^lA{&6M&4r6I-W-X>m00vt}9ufc%ugy7J z9nnx=8OVp)#pbfxtXpJsP79vXZA<
  • diff --git a/utils/index.html b/utils/index.html index 3876c3e..40e9b0c 100644 --- a/utils/index.html +++ b/utils/index.html @@ -18,7 +18,7 @@ - + @@ -506,6 +506,8 @@ + + @@ -544,11 +546,11 @@
  • - + - Active repository + Repository @@ -585,6 +587,27 @@ +
  • + + + + + Admin panel + + + + +
  • + + + + + + + + + +
  • @@ -1100,11 +1123,6 @@

    Utility Functions

    - - - - -