diff --git a/.github/workflows/push-tag-rc.yml b/.github/workflows/push-tag-rc.yml
index 729275b1..4ee10317 100644
--- a/.github/workflows/push-tag-rc.yml
+++ b/.github/workflows/push-tag-rc.yml
@@ -21,9 +21,17 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- - name: Extract tag version
+ - name: Extract metadata and set variables
id: vars
- run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
+ run: |
+ RAW_TAG=${GITHUB_REF#refs/tags/}
+ CLEAN_TAG=${RAW_TAG#v}
+ COMMIT=$(git rev-parse --short HEAD)
+ BRANCH=$(git branch -r --contains "$GITHUB_SHA" | grep -v '\->' | head -n 1 | sed 's|origin/||')
+ echo "TAG=$CLEAN_TAG" >> $GITHUB_OUTPUT
+ echo "RAW_TAG=$RAW_TAG" >> $GITHUB_OUTPUT
+ echo "COMMIT=$COMMIT" >> $GITHUB_OUTPUT
+ echo "BRANCH=$BRANCH" >> $GITHUB_OUTPUT
- name: Build and push backend image for ngen-django release candidate
uses: docker/build-push-action@v5
@@ -32,6 +40,13 @@ jobs:
file: docker/Dockerfile.prod
push: true
tags: certunlp/ngen-django:${{ steps.vars.outputs.TAG }}
+ build-args: |
+ APP_VERSION_TAG=${{ steps.vars.outputs.RAW_TAG }}
+ APP_COMMIT=${{ steps.vars.outputs.COMMIT }}
+ APP_BRANCH=${{ steps.vars.outputs.BRANCH }}
+ labels: |
+ org.opencontainers.image.version=${{ steps.vars.outputs.RAW_TAG }}
+ org.opencontainers.image.revision=${{ steps.vars.outputs.COMMIT }}
- name: Build and push backend image for ngen release candidate
uses: docker/build-push-action@v5
@@ -40,6 +55,13 @@ jobs:
file: docker/Dockerfile.prod
push: true
tags: certunlp/ngen:${{ steps.vars.outputs.TAG }}
+ build-args: |
+ APP_VERSION_TAG=${{ steps.vars.outputs.RAW_TAG }}
+ APP_COMMIT=${{ steps.vars.outputs.COMMIT }}
+ APP_BRANCH=${{ steps.vars.outputs.BRANCH }}
+ labels: |
+ org.opencontainers.image.version=${{ steps.vars.outputs.RAW_TAG }}
+ org.opencontainers.image.revision=${{ steps.vars.outputs.COMMIT }}
- name: Build and push frontend image release candidate
uses: docker/build-push-action@v5
@@ -48,3 +70,10 @@ jobs:
file: frontend/Dockerfile.prod
push: true
tags: certunlp/ngen-frontend:${{ steps.vars.outputs.TAG }}
+ build-args: |
+ APP_VERSION_TAG=${{ steps.vars.outputs.RAW_TAG }}
+ APP_COMMIT=${{ steps.vars.outputs.COMMIT }}
+ APP_BRANCH=${{ steps.vars.outputs.BRANCH }}
+ labels: |
+ org.opencontainers.image.version=${{ steps.vars.outputs.RAW_TAG }}
+ org.opencontainers.image.revision=${{ steps.vars.outputs.COMMIT }}
diff --git a/.github/workflows/push-tag.yml b/.github/workflows/push-tag.yml
index 97ba7e35..02c24dad 100644
--- a/.github/workflows/push-tag.yml
+++ b/.github/workflows/push-tag.yml
@@ -21,12 +21,17 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- - name: Extract tag version
+ - name: Extract metadata and set variables
id: vars
run: |
RAW_TAG=${GITHUB_REF#refs/tags/}
- CLEAN_TAG=${RAW_TAG#v} # Elimina la v si está al principio
+ CLEAN_TAG=${RAW_TAG#v}
+ COMMIT=$(git rev-parse --short HEAD)
+ BRANCH=$(git branch -r --contains "$GITHUB_SHA" | grep -v '\->' | head -n 1 | sed 's|origin/||')
echo "TAG=$CLEAN_TAG" >> $GITHUB_OUTPUT
+ echo "RAW_TAG=$RAW_TAG" >> $GITHUB_OUTPUT
+ echo "COMMIT=$COMMIT" >> $GITHUB_OUTPUT
+ echo "BRANCH=$BRANCH" >> $GITHUB_OUTPUT
- name: Build and push backend image for ngen-django
uses: docker/build-push-action@v5
@@ -35,6 +40,13 @@ jobs:
file: docker/Dockerfile.prod
push: true
tags: certunlp/ngen-django:latest,certunlp/ngen-django:${{ steps.vars.outputs.TAG }}
+ build-args: |
+ APP_VERSION_TAG=${{ steps.vars.outputs.RAW_TAG }}
+ APP_COMMIT=${{ steps.vars.outputs.COMMIT }}
+ APP_BRANCH=${{ steps.vars.outputs.BRANCH }}
+ labels: |
+ org.opencontainers.image.version=${{ steps.vars.outputs.RAW_TAG }}
+ org.opencontainers.image.revision=${{ steps.vars.outputs.COMMIT }}
- name: Build and push backend image for ngen
uses: docker/build-push-action@v5
@@ -43,6 +55,13 @@ jobs:
file: docker/Dockerfile.prod
push: true
tags: certunlp/ngen:latest,certunlp/ngen:${{ steps.vars.outputs.TAG }}
+ build-args: |
+ APP_VERSION_TAG=${{ steps.vars.outputs.RAW_TAG }}
+ APP_COMMIT=${{ steps.vars.outputs.COMMIT }}
+ APP_BRANCH=${{ steps.vars.outputs.BRANCH }}
+ labels: |
+ org.opencontainers.image.version=${{ steps.vars.outputs.RAW_TAG }}
+ org.opencontainers.image.revision=${{ steps.vars.outputs.COMMIT }}
- name: Build and push frontend image
uses: docker/build-push-action@v5
@@ -51,3 +70,10 @@ jobs:
file: frontend/Dockerfile.prod
push: true
tags: certunlp/ngen-frontend:latest,certunlp/ngen-frontend:${{ steps.vars.outputs.TAG }}
+ build-args: |
+ APP_VERSION_TAG=${{ steps.vars.outputs.RAW_TAG }}
+ APP_COMMIT=${{ steps.vars.outputs.COMMIT }}
+ APP_BRANCH=${{ steps.vars.outputs.BRANCH }}
+ labels: |
+ org.opencontainers.image.version=${{ steps.vars.outputs.RAW_TAG }}
+ org.opencontainers.image.revision=${{ steps.vars.outputs.COMMIT }}
diff --git a/docker/.env/ngen.base.env b/docker/.env/ngen.base.env
index 53518a07..36985ec2 100644
--- a/docker/.env/ngen.base.env
+++ b/docker/.env/ngen.base.env
@@ -52,11 +52,11 @@ NGEN_LANG_EXTERNAL=en
PAGE_SIZE=10
PAGE_SIZE_MAX=100
-ALLOWED_FIELDS_BLOCKED_CASE=parent,state,tlp
-ALLOWED_FIELDS_BLOCKED_EVENT=parent,case,tlp
+BLOCKED_FIELDS_CASE=
+BLOCKED_FIELDS_EVENT=
ALLOWED_FIELDS_MERGED_CASE=parent,state,tlp
ALLOWED_FIELDS_MERGED_EVENT=parent,case,tlp,network,sid
-ALLOWED_FIELDS_BLOCKED_EXCEPTION=True
+BLOCKED_FIELDS_EXCEPTION=True
PRIORITY_ATTEND_TIME_DEFAULT=10080
PRIORITY_SOLVE_TIME_DEFAULT=10080
diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev
index 98184d85..18680fdc 100644
--- a/docker/Dockerfile.dev
+++ b/docker/Dockerfile.dev
@@ -9,5 +9,15 @@ RUN apk add gettext libmagic gcc musl-dev python3-dev
COPY . /code/
+# Add version information
+ARG APP_VERSION_TAG
+ARG APP_COMMIT
+ARG APP_BRANCH
+ENV APP_VERSION_TAG=$APP_VERSION_TAG
+ENV APP_COMMIT=$APP_COMMIT
+ENV APP_BRANCH=$APP_BRANCH
+ENV APP_BUILD_FILE="dev"
+RUN echo "{\"tag\": \"$APP_VERSION_TAG\", \"commit\": \"$APP_COMMIT\", \"branch\": \"$APP_BRANCH\", \"build_file\": \"$APP_BUILD_FILE\"}" > /code/version.json
+
# Must be built from the project root directory
RUN pip install -r requirements.txt
diff --git a/docker/Dockerfile.prod b/docker/Dockerfile.prod
index c6fe8e66..2eaca93b 100644
--- a/docker/Dockerfile.prod
+++ b/docker/Dockerfile.prod
@@ -1,6 +1,15 @@
# Base stage for dependency installation
FROM python:3.11-alpine3.20 AS base
+ARG APP_VERSION_TAG
+ARG APP_COMMIT
+ARG APP_BRANCH
+
+ENV APP_VERSION_TAG=$APP_VERSION_TAG
+ENV APP_COMMIT=$APP_COMMIT
+ENV APP_BRANCH=$APP_BRANCH
+ENV APP_BUILD_FILE="prod"
+
# Configure environment variables for pip
ENV PIP_NO_CACHE_DIR=true \
PYTHONDONTWRITEBYTECODE=1 \
@@ -37,6 +46,9 @@ COPY --from=base /install /usr/local
# Copy the application code
COPY . /code/
+# Add version information
+RUN echo "{\"tag\": \"$APP_VERSION_TAG\", \"commit\": \"$APP_COMMIT\", \"branch\": \"$APP_BRANCH\", \"build_file\": \"$APP_BUILD_FILE\"}" > /code/version.json
+
# Expose port 8000 but we will use a reverse proxy to serve the app anyway
EXPOSE 8000
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index 0e8c069b..9ef9b6a9 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -1,6 +1,6 @@
services:
ngen-frontend:
- image: certunlp/ngen-frontend:rc-2.3.2
+ image: certunlp/ngen-frontend:rc-2.3.8.2
restart: always
volumes:
- ./data_static:/app/staticfiles
@@ -11,7 +11,7 @@ services:
- "80:80"
ngen-django:
- image: certunlp/ngen-django:rc-2.3.2
+ image: certunlp/ngen-django:rc-2.3.8.2
restart: always
entrypoint: ./docker/entrypoint.sh
command: gunicorn project.wsgi:application --bind 0.0.0.0:8000
@@ -27,7 +27,7 @@ services:
- ngen-redis
ngen-celery-worker:
- image: certunlp/ngen-django:rc-2.3.2
+ image: certunlp/ngen-django:rc-2.3.8.2
restart: always
command: celery -A project worker -l warning
env_file:
@@ -39,7 +39,7 @@ services:
- ngen-redis
ngen-celery-beat:
- image: certunlp/ngen-django:rc-2.3.2
+ image: certunlp/ngen-django:rc-2.3.8.2
restart: always
command: celery -A project beat -l warning
env_file:
diff --git a/frontend/Dockerfile.dev b/frontend/Dockerfile.dev
index 070cd27a..e75b05d6 100644
--- a/frontend/Dockerfile.dev
+++ b/frontend/Dockerfile.dev
@@ -22,5 +22,15 @@ COPY . ./
# Expose port
EXPOSE 3000
+# Add version information
+ARG APP_VERSION_TAG
+ARG APP_COMMIT
+ARG APP_BRANCH
+ENV VITE_APP_VERSION_TAG=$APP_VERSION_TAG
+ENV VITE_APP_COMMIT=$APP_COMMIT
+ENV VITE_APP_BRANCH=$APP_BRANCH
+ENV VITE_APP_BUILD_FILE="dev"
+RUN echo "{\"tag\": \"$VITE_APP_VERSION_TAG\", \"commit\": \"$VITE_APP_COMMIT\", \"branch\": \"$VITE_APP_BRANCH\", \"build_file\": \"$VITE_APP_BUILD_FILE\"}" > /app/public/version.json
+
# start app
CMD ["npm", "run","start"]
diff --git a/frontend/Dockerfile.prod b/frontend/Dockerfile.prod
index d793fc48..71b9c193 100644
--- a/frontend/Dockerfile.prod
+++ b/frontend/Dockerfile.prod
@@ -4,6 +4,14 @@ FROM node:18-alpine AS build
# set working directory
WORKDIR /app
+ARG APP_VERSION_TAG
+ARG APP_COMMIT
+ARG APP_BRANCH
+
+ENV VITE_APP_VERSION_TAG=$APP_VERSION_TAG
+ENV VITE_APP_COMMIT=$APP_COMMIT
+ENV VITE_APP_BRANCH=$APP_BRANCH
+ENV VITE_APP_BUILD_FILE="prod"
# add `/app/node_modules/.bin` to $PATH
ENV PATH=/app/node_modules/.bin:$PATH
@@ -37,6 +45,9 @@ COPY nginx-prod/nginx.conf.template.ssl /etc/nginx/nginx.conf.template.ssl
COPY nginx-prod/nginx.conf.template.no-ssl /etc/nginx/nginx.conf.template.no-ssl
COPY nginx-prod/docker-entrypoint.sh /docker-entrypoint.sh
+# Add version information
+RUN echo "{\"tag\": \"$VITE_APP_VERSION_TAG\", \"commit\": \"$VITE_APP_COMMIT\", \"branch\": \"$VITE_APP_BRANCH\", \"build_file\": \"$VITE_APP_BUILD_FILE\"}" > /usr/share/nginx/html/version.json
+
# Give permissions to the entry script
RUN chmod +x /docker-entrypoint.sh
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 7294b303..5b7d14be 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -101,13 +101,14 @@
}
},
"node_modules/@babel/code-frame": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
- "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
"license": "MIT",
"dependencies": {
- "@babel/highlight": "^7.24.7",
- "picocolors": "^1.0.0"
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
},
"engines": {
"node": ">=6.9.0"
@@ -447,18 +448,18 @@
}
},
"node_modules/@babel/helper-string-parser": {
- "version": "7.24.8",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz",
- "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
- "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
+ "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -487,87 +488,25 @@
}
},
"node_modules/@babel/helpers": {
- "version": "7.25.0",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz",
- "integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz",
+ "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==",
"license": "MIT",
"dependencies": {
- "@babel/template": "^7.25.0",
- "@babel/types": "^7.25.0"
+ "@babel/template": "^7.27.1",
+ "@babel/types": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
}
},
- "node_modules/@babel/highlight": {
- "version": "7.24.7",
- "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz",
- "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==",
- "license": "MIT",
- "dependencies": {
- "@babel/helper-validator-identifier": "^7.24.7",
- "chalk": "^2.4.2",
- "js-tokens": "^4.0.0",
- "picocolors": "^1.0.0"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/highlight/node_modules/ansi-styles": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
- "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
- "license": "MIT",
- "dependencies": {
- "color-convert": "^1.9.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/@babel/highlight/node_modules/chalk": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
- "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^3.2.1",
- "escape-string-regexp": "^1.0.5",
- "supports-color": "^5.3.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/@babel/highlight/node_modules/escape-string-regexp": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
- "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
- "license": "MIT",
- "engines": {
- "node": ">=0.8.0"
- }
- },
- "node_modules/@babel/highlight/node_modules/supports-color": {
- "version": "5.5.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
- "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
- "license": "MIT",
- "dependencies": {
- "has-flag": "^3.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/@babel/parser": {
- "version": "7.25.3",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz",
- "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.1.tgz",
+ "integrity": "sha512-I0dZ3ZpCrJ1c04OqlNsQcKiZlsrXf/kkE4FXzID9rIOYICsAbA8mMDzhW/luRNAHdCNt7os/u8wenklZDlUVUQ==",
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.25.2"
+ "@babel/types": "^7.27.1"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -2051,26 +1990,23 @@
"license": "MIT"
},
"node_modules/@babel/runtime": {
- "version": "7.25.0",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz",
- "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz",
+ "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==",
"license": "MIT",
- "dependencies": {
- "regenerator-runtime": "^0.14.0"
- },
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/template": {
- "version": "7.25.0",
- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz",
- "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.1.tgz",
+ "integrity": "sha512-Fyo3ghWMqkHHpHQCoBs2VnYjR4iWFFjguTDEqA5WgZDOrFesVjMhMM2FSqTKSoUSDO1VQtavj8NFpdRBEvJTtg==",
"license": "MIT",
"dependencies": {
- "@babel/code-frame": "^7.24.7",
- "@babel/parser": "^7.25.0",
- "@babel/types": "^7.25.0"
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.1",
+ "@babel/types": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
@@ -2095,14 +2031,13 @@
}
},
"node_modules/@babel/types": {
- "version": "7.25.2",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz",
- "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==",
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz",
+ "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==",
"license": "MIT",
"dependencies": {
- "@babel/helper-string-parser": "^7.24.8",
- "@babel/helper-validator-identifier": "^7.24.7",
- "to-fast-properties": "^2.0.0"
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
@@ -4404,9 +4339,9 @@
}
},
"node_modules/axios": {
- "version": "1.7.4",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz",
- "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==",
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
+ "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
@@ -4714,21 +4649,6 @@
"node": ">=6"
}
},
- "node_modules/color-convert": {
- "version": "1.9.3",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
- "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
- "license": "MIT",
- "dependencies": {
- "color-name": "1.1.3"
- }
- },
- "node_modules/color-name": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
- "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
- "license": "MIT"
- },
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -6873,15 +6793,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/has-flag": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
- "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/has-property-descriptors": {
"version": "1.0.2",
"dev": true,
@@ -8120,9 +8031,9 @@
"license": "MIT"
},
"node_modules/picocolors": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
- "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"license": "ISC"
},
"node_modules/picomatch": {
@@ -8798,10 +8709,6 @@
"node": ">=4"
}
},
- "node_modules/regenerator-runtime": {
- "version": "0.14.1",
- "license": "MIT"
- },
"node_modules/regenerator-transform": {
"version": "0.15.2",
"dev": true,
@@ -9513,13 +9420,6 @@
"version": "1.0.3",
"license": "MIT"
},
- "node_modules/to-fast-properties": {
- "version": "2.0.0",
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/to-regex-range": {
"version": "5.0.1",
"devOptional": true,
@@ -9815,9 +9715,9 @@
}
},
"node_modules/vite": {
- "version": "5.4.14",
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz",
- "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==",
+ "version": "5.4.19",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz",
+ "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==",
"license": "MIT",
"dependencies": {
"esbuild": "^0.21.3",
diff --git a/frontend/public/locales/en/translation.json b/frontend/public/locales/en/translation.json
index 2b8a5153..c654a906 100644
--- a/frontend/public/locales/en/translation.json
+++ b/frontend/public/locales/en/translation.json
@@ -53,6 +53,7 @@
"filter.cidr_domain": "cidr or domain",
"info.related": "Related information",
"login.do_not_have_an_account": "Don't have an account?",
+ "menu.about": "About",
"menu.cases": "Cases",
"menu.config": "Configuration",
"menu.constituency": "Constituencies",
@@ -81,6 +82,7 @@
"menu.states": "States",
"menu.taxonomies": "Taxonomies",
"menu.taxonomygroups": "Taxonomy Groups",
+ "menu.analyzermappings": "Analyzer Mappings",
"menu.templates": "Templates",
"menu.tags": "Tags",
"menu.tlp": "TLP",
@@ -424,6 +426,7 @@
"validate.username": "Only letters, numbers and '@', '.' , '+', '-', '_' special characters are allowed",
"validation.password": "A password is required",
"validation.username": "An username is required",
+ "w.about": "About",
"w.active": "Active",
"w.add": "Add",
"w.assigned": "Assigned",
@@ -457,6 +460,7 @@
"w.modify": "Modify",
"w.nextState": "Next State",
"w.no": "No",
+ "w.not_available": "Not available",
"w.not_assigned": "Not assigned",
"w.permissions": "Permissions",
"w.posteriorState": "Posterior State",
@@ -509,5 +513,10 @@
"ngen.retest.refresh": "Refresh",
"ngen.retest.no_analyzer_mapping": "There is no analyzer mapping related to the event's taxonomy.",
"ngen.retest.refresh.success": "Retests table refreshed successfully!",
- "ngen.retest.refresh.error": "Failed to refresh retests table."
+ "ngen.retest.refresh.error": "Failed to refresh retests table.",
+ "ngen.analyzer_mapping.mapping_from": "Taxonomy",
+ "ngen.analyzer_mapping.mapping_to": "Mapping to",
+ "ngen.analyzer_mapping.analyzer_type": "Analyzer",
+ "ngen.analyzer_mapping.details": "Details",
+ "ngen.analyzer_mapping": "Analyzer mapping"
}
diff --git a/frontend/public/locales/es/translation.json b/frontend/public/locales/es/translation.json
index b80e6704..cc41d1e5 100644
--- a/frontend/public/locales/es/translation.json
+++ b/frontend/public/locales/es/translation.json
@@ -53,6 +53,7 @@
"filter.cidr_domain": "CIDR o dominio",
"info.related": "Información relacionada",
"login.do_not_have_an_account": "¿No tienes una cuenta?",
+ "menu.about": "Acerca de",
"menu.cases": "Casos",
"menu.config": "Configuración",
"menu.constituency": "Comunidades",
@@ -81,6 +82,7 @@
"menu.states": "Estados",
"menu.taxonomies": "Taxonomías",
"menu.taxonomygroups": "Grupos de Taxonomías",
+ "menu.analyzermappings": "Mapeo de Analizadores",
"menu.templates": "Plantillas",
"menu.tags": "Etiquetas",
"menu.tlp": "TLP",
@@ -423,6 +425,7 @@
"validate.username": "Solo se permiten letras, números y los caracteres especiales '@', '.', '+', '-', '_'",
"validation.password": "Se requiere una contraseña",
"validation.username": "Se requiere un nombre de usuario",
+ "w.about": "Acerca de",
"w.active": "Activo",
"w.add": "Agregar",
"w.assigned": "Asignado",
@@ -456,6 +459,7 @@
"w.modify": "Modificar",
"w.nextState": "Siguiente estado",
"w.no": "No",
+ "w.not_available": "No disponible",
"w.not_assigned": "No asignado",
"w.permissions": "Permisos",
"w.posteriorState": "Estado posterior",
@@ -505,8 +509,13 @@
"ngen.retest.create": "Retest",
"ngen.retest.success": "Retest realizado satisfactoriamente!",
"ngen.retest.error": "Hubo un error al realizar el retest",
- "ngen.retest.refresh": "Actualizar",
- "ngen.retest.no_analyzer_mapping": "No hay ningun analizador mapeado para la taxonomia del evento",
+ "ngen.retest.refresh": "Actualizar",
+ "ngen.retest.no_analyzer_mapping": "No hay ningun analizador mapeado para la taxonomia del evento",
"ngen.retest.refresh.success": "Tabla de retests actualizada con éxito.",
- "ngen.retest.refresh.error": "No se pudo actualizar la tabla de retests."
+ "ngen.retest.refresh.error": "No se pudo actualizar la tabla de retests.",
+ "ngen.analyzer_mapping.mapping_from": "Taxonomia",
+ "ngen.analyzer_mapping.mapping_to": "Mapeado a",
+ "ngen.analyzer_mapping.analyzer_type": "Analizador",
+ "ngen.analyzer_mapping.details": "Detalles",
+ "ngen.analyzer_mapping": "Mapeo de analizador"
}
diff --git a/frontend/public/version.json b/frontend/public/version.json
new file mode 100644
index 00000000..7a5adfc6
--- /dev/null
+++ b/frontend/public/version.json
@@ -0,0 +1 @@
+{"tag": "rc-2.3.8.2", "commit": "cf3e0dc", "branch": "develop", "build_file": "dev"}
diff --git a/frontend/src/api/services/about.jsx b/frontend/src/api/services/about.jsx
new file mode 100644
index 00000000..d7a18b00
--- /dev/null
+++ b/frontend/src/api/services/about.jsx
@@ -0,0 +1,17 @@
+import apiInstance from "../api";
+import { COMPONENT_URL } from "config/constant";
+import setAlert from "utils/setAlert";
+
+const getVersion = async () => {
+ try {
+ const response = await apiInstance.get(COMPONENT_URL.version);
+ return response.data;
+ } catch (error) {
+ const message = error?.response?.data?.detail || "No se pudo recuperar la información de la aplicación";
+ setAlert(message, "error");
+ console.error("Error al obtener la versión:", error);
+ throw error;
+ }
+};
+
+export { getVersion };
diff --git a/frontend/src/api/services/analyzerMapping.jsx b/frontend/src/api/services/analyzerMapping.jsx
index a77b7c6f..e53c62fe 100644
--- a/frontend/src/api/services/analyzerMapping.jsx
+++ b/frontend/src/api/services/analyzerMapping.jsx
@@ -1,17 +1,133 @@
import apiInstance from "../api";
-import { COMPONENT_URL } from "../../config/constant";
+import { COMPONENT_URL, PAGE } from "../../config/constant";
+import setAlert from "../../utils/setAlert";
-const getAnalyzerMappings = () => {
- return apiInstance
- .get(COMPONENT_URL.analyzerMapping)
- .then((response) => {
- return response.data;
- })
- .catch((error) => {
- return Promise.reject(error);
- });
- };
+const getAllAnalyzerMappings = () => {
+ return apiInstance
+ .get(COMPONENT_URL.analyzerMapping)
+ .then((response) => {
+ return response.data;
+ })
+ .catch((error) => {
+ return Promise.reject(error);
+ });
+};
+
+
+const getAnalyzerMappings = (currentPage, filters, order) => {
+ let messageError = `No se pudo recuperar la informacion de los mapeos`;
+ return apiInstance
+ .get(COMPONENT_URL.analyzerMapping + PAGE + currentPage + "&ordering=" + order + "&" + filters)
+ .then((response) => {
+ return response;
+ })
+ .catch((error) => {
+ setAlert(messageError, "error", "analyzermapping");
+ return Promise.reject(error);
+ });
+};
+
+
+const postAnalyzerMapping = (data) => {
+ const messageError = `Ya existe un mapeo con los mismos valores.`;
+ const messageSuccess = `El mapeo ha sido creado correctamente.`;
+
+ const filters = `mapping_to__icontains=${data.mapping_to}&mapping_from__name__icontains=${data.mapping_from_name}&analyzer_type=${data.analyzer_type}`;
+ return getAnalyzerMappings(1, filters, "date")
+ .then((response) => {
+ if (response.data.results.length > 0) {
+ setAlert(messageError, "error", "analyzermapping");
+ return Promise.reject(new Error(messageError));
+ }
+
+ return apiInstance
+ .post(COMPONENT_URL.analyzerMapping, {
+ mapping_to: data.mapping_to,
+ mapping_from: data.mapping_from,
+ analyzer_type: data.analyzer_type,
+ })
+ .then((response) => {
+ setAlert(messageSuccess, "success", "analyzermapping");
+ return response;
+ });
+ })
+ .catch((error) => {
+ return Promise.reject(error);
+ });
+};
+
+
+const getAnalyzerMapping = (url) => {
+ let messageError = `No se pudo recuperar la informacion del mapeo`;
+ return apiInstance
+ .get(url)
+ .then((response) => {
+ return apiInstance
+ .get(response.data.mapping_from)
+ .then((mappingResponse) => {
+ response.data.mapping_from_name = mappingResponse.data.name;
+ return response;
+ });
+ })
+ .catch((error) => {
+ setAlert(messageError, "error", "analyzermapping");
+ return Promise.reject(error);
+ });
+};
+
+const putAnalyzerMapping = (url, data) => {
+ const messageSuccess = `El mapeo ha sido editado correctamente.`;
+ const messageError = `Ya existe un mapeo con los mismos valores.`;
+ const notFoundError = `El mapeo no se ha encontrado.`;
+
+ const filters = `mapping_to__icontains=${data.mapping_to}&mapping_from__name__icontains=${data.mapping_from_name}&analyzer_type=${data.analyzer_type}`;
+ return getAnalyzerMappings(1, filters, "date")
+ .then((response) => {
+ if (response.data.results.length > 0) {
+ setAlert(messageError, "error", "analyzermapping");
+ return Promise.reject(new Error(messageError));
+ }
+
+ return apiInstance
+ .put(url, {
+ mapping_to: data.mapping_to,
+ mapping_from: data.mapping_from,
+ analyzer_type: data.analyzer_type,
+ })
+ .then((response) => {
+ setAlert(messageSuccess, "success", "analyzermapping");
+ return response;
+ });
+ })
+ .catch((error) => {
+ if (error.response?.detail === "Not found") {
+ setAlert(notFoundError, "error", "analyzermapping");
+ } else {
+ setAlert(messageError, "error", "analyzermapping");
+ }
+ return Promise.reject(error);
+ });
+};
+
+
+const deleteAnalyzerMapping = (url, name_from, name_to, analyzer) => {
+ let messageSuccess = `El mapeo de ${name_from} a ${name_to} del analizador ${analyzer} ha sido eliminado.`;
+ let messageError = `El mapeo de ${name_from} a ${name_to} del analizador ${analyzer} no se ha encontrado.`;
+ return apiInstance
+ .delete(url)
+ .then((response) => {
+ setAlert(messageSuccess, "success", "analyzerMapping");
+ return response;
+ })
+ .catch((error) => {
+ if (error.response.detail && error.response.detail === "Not found") {
+ messageError = `El mapeo de ${name_from} a ${name_to} del analizador ${analyzer} no se ha encontrado.`;
+ }
+ setAlert(messageError, "error", "analyzerMapping");
+ return Promise.reject(error);
+ });
+};
-export { getAnalyzerMappings };
\ No newline at end of file
+export { getAllAnalyzerMappings, getAnalyzerMappings, postAnalyzerMapping, getAnalyzerMapping, putAnalyzerMapping, deleteAnalyzerMapping };
\ No newline at end of file
diff --git a/frontend/src/api/services/eventAnalysis.jsx b/frontend/src/api/services/eventAnalysis.jsx
index 13891dbe..98a18282 100644
--- a/frontend/src/api/services/eventAnalysis.jsx
+++ b/frontend/src/api/services/eventAnalysis.jsx
@@ -27,7 +27,6 @@ const getRetests = (eventUrl, suppressAlert) => {
const postRetest = (eventId) => {
const messageSuccess = i18next.t("ngen.retest.success");
const messageError = i18next.t("ngen.retest.error");
-
return apiInstance
.post(`${COMPONENT_URL.event}${eventId}/retest/`, {})
.then((response) => {
diff --git a/frontend/src/config/constant.jsx b/frontend/src/config/constant.jsx
index a59f027e..3c3a2be3 100644
--- a/frontend/src/config/constant.jsx
+++ b/frontend/src/config/constant.jsx
@@ -1,5 +1,10 @@
export const BASE_URL = import.meta.env.VITE_APP_BASE_URL || "/home";
export const BASENAME = import.meta.env.VITE_APP_BASENAME || "";
+export const MODE = import.meta.env.MODE || "development";
+export const APP_VERSION_TAG = import.meta.env.VITE_APP_VERSION_TAG || 'unknown';
+export const APP_COMMIT = import.meta.env.VITE_APP_COMMIT || 'unknown';
+export const APP_BRANCH = import.meta.env.VITE_APP_BRANCH || 'unknown';
+export const APP_BUILD_FILE = import.meta.env.VITE_APP_BUILD_FILE || "unknown";
export const BASE_TITLE = " | ngen ";
export const PAGE = "?page=";
@@ -96,4 +101,5 @@ export const COMPONENT_URL = {
tag: "tag/",
eventAnalysis: "eventanalysis/",
analyzerMapping: "analyzermapping/",
+ version: "version/",
};
diff --git a/frontend/src/menu-items.jsx b/frontend/src/menu-items.jsx
index 4455491b..f937253b 100644
--- a/frontend/src/menu-items.jsx
+++ b/frontend/src/menu-items.jsx
@@ -201,6 +201,14 @@ const menuItems = {
icon: "",
breadcrumbs: true
},
+ {
+ id: "analyzerMapping",
+ title: "menu.analyzermappings",
+ type: "item",
+ url: "/analyzermappings",
+ icon: "",
+ breadcrumbs: true
+ },
{
id: "state",
title: "menu.states",
@@ -236,6 +244,15 @@ const menuItems = {
classes: "",
icon: "",
breadcrumbs: true
+ },
+ {
+ id: "about",
+ title: "menu.about",
+ type: "item",
+ url: "/about",
+ classes: "",
+ icon: "",
+ breadcrumbs: true
}
]
}
diff --git a/frontend/src/routes.jsx b/frontend/src/routes.jsx
index 844ca622..bdf73c99 100644
--- a/frontend/src/routes.jsx
+++ b/frontend/src/routes.jsx
@@ -150,6 +150,30 @@ const routes = [
permissions: ["change_taxonomygroup"],
element: lazy(() => import("./views/taxonomyGroup/EditTaxonomyGroup"))
},
+ {
+ exact: "true",
+ path: "/analyzermappings",
+ layout: AdminLayout,
+ guard: PermissionGuard,
+ permissions: ["view_analyzermapping"],
+ element: lazy(() => import("./views/analyzerMapping/ListAnalyzerMappings"))
+ },
+ {
+ exact: "true",
+ path: "/analyzermappings/create",
+ layout: AdminLayout,
+ guard: PermissionGuard,
+ permissions: ["add_analyzermapping"],
+ element: lazy(() => import("./views/analyzerMapping/CreateAnalyzerMapping"))
+ },
+ {
+ exact: "true",
+ path: "/analyzermappings/edit/:id",
+ layout: AdminLayout,
+ guard: PermissionGuard,
+ permissions: ["change_analyzermapping"],
+ element: lazy(() => import("./views/analyzerMapping/EditAnalyzerMapping"))
+ },
{
exact: "true",
path: "/tlp",
@@ -624,6 +648,13 @@ const routes = [
element: lazy(() => import("./views/network/EditNetwork")),
routeParams: { asNetworkAdmin: true, basePath: "/networkadmin" }
},
+ {
+ exact: "true",
+ path: "/about",
+ layout: AdminLayout,
+ guard: AuthGuard,
+ element: lazy(() => import("./views/home/About.jsx")),
+ },
{
path: "*",
exact: "true",
diff --git a/frontend/src/views/analyzerMapping/CreateAnalyzerMapping.jsx b/frontend/src/views/analyzerMapping/CreateAnalyzerMapping.jsx
new file mode 100644
index 00000000..aaf9a81f
--- /dev/null
+++ b/frontend/src/views/analyzerMapping/CreateAnalyzerMapping.jsx
@@ -0,0 +1,131 @@
+import React, { useEffect, useState } from "react";
+import { Button, Card, Col, Form, Row } from "react-bootstrap";
+import { getMinifiedTaxonomy } from "../../api/services/taxonomies";
+import { postAnalyzerMapping } from "../../api/services/analyzerMapping";
+import SelectLabel from "../../components/Select/SelectLabel";
+import { useTranslation } from "react-i18next";
+import CrudButton from "../../components/Button/CrudButton";
+
+const CreateAnalyzerMapping = () => {
+ const [mappingFrom, setMappingFrom] = useState("");
+ const [mappingTo, setMappingTo] = useState("");
+ const [analyzerType, setAnalyzerType] = useState("");
+ const [taxonomies, setTaxonomies] = useState([]);
+ const [selectedMappingFrom, setSelectedMappingFrom] = useState(null);
+ const [showAlert, setShowAlert] = useState(false);
+ const { t } = useTranslation();
+
+ useEffect(() => {
+ getMinifiedTaxonomy()
+ .then((response) => {
+ const listTaxonomies = response.map((taxonomy) => ({
+ value: taxonomy.url,
+ label: taxonomy.name,
+ }));
+ setTaxonomies(listTaxonomies);
+ })
+ .catch((error) => {
+ console.error("Error fetching taxonomies:", error);
+ });
+ }, []);
+
+ const createAnalyzerMapping = () => {
+ let data = {
+ mapping_from: mappingFrom,
+ mapping_from_name: selectedMappingFrom.label,
+ mapping_to: mappingTo,
+ analyzer_type: analyzerType,
+ };
+ postAnalyzerMapping(data)
+ .then(() => {
+ window.location.href = "/analyzermappings";
+ })
+ .catch((error) => {
+
+ console.error("Error creating analyzer mapping:", error);
+ setShowAlert(true);
+ });
+ };
+
+ const resetShowAlert = () => {
+ setShowAlert(false);
+ };
+
+ return (
+
+ {field.replace("_", " ").replace(/\b\w/g, (l) => l.toUpperCase())}: {data[field] || t("w.not_available")}
+
+
+
+
+ {/* Modal for Viewing Details */}
+
+
+
+
+ {list.map((mapping, index) => {
+ const parts = mapping.url.split("/");
+ let itemNumber = parts[parts.length - 2];
+ return (
+ {t("ngen.analyzer_mapping.mapping_from")}
+ {t("ngen.analyzer_mapping.mapping_to")}
+ {t("ngen.analyzer_mapping.analyzer_type")}
+ {t("ngen.date.created")}
+ {t("ngen.options")}
+
+
+ );
+ })}
+
+ {taxonomyNames[mapping.mapping_from]}
+ {mapping.mapping_to}
+ {mapping.analyzer_type}
+
+
+
+
+
+
+
+
+
+ {t("ngen.analyzer_mapping.mapping_from")}
+ {selectedMapping?.mapping_from_name}
+
+
+ {t("ngen.analyzer_mapping.mapping_to")}
+ {selectedMapping?.mapping_to}
+
+
+ {t("ngen.analyzer_mapping.analyzer_type")}
+ {selectedMapping?.analyzer_type}
+
+
+ {t("ngen.date.created")}
+ {selectedMapping?.created}
+
+
+
+ {t("ngen.date.modified")}
+ {selectedMapping?.modified}
+
Loading...
} + {error &&Error: {error.message}
} +