Initial marker-api benchmark setup: Containerfile, app.py, convert_both.py, compare_conversions.py
This commit is contained in:
@@ -0,0 +1,42 @@
|
|||||||
|
# ============== ENVIRONMENT VARIABLES for marker-api ========================
|
||||||
|
#
|
||||||
|
# Copy to .env and edit as needed:
|
||||||
|
# cp .env.example .env
|
||||||
|
#
|
||||||
|
# All variables can also be passed via K8s ConfigMap/Secret,
|
||||||
|
# podman --env, or docker-compose environment.
|
||||||
|
|
||||||
|
# -----------------------------------
|
||||||
|
# Ollama (OCR fallback)
|
||||||
|
# -----------------------------------
|
||||||
|
OLLAMA_HOST=http://10.0.1.127:11434
|
||||||
|
DEESEEK_OCR_MODEL=deepseek-ocr
|
||||||
|
|
||||||
|
# -----------------------------------
|
||||||
|
# AMD GPU (set TRUE on AMD/ROCm systems)
|
||||||
|
# -----------------------------------
|
||||||
|
AMD_COMPUTE=false # set to "true" to enable ROCm
|
||||||
|
TORCH_DEVICE= # override PyTorch device ("rocm", "cuda", "cpu")
|
||||||
|
MODEL_DTYPE=float32 # "float32" or "bfloat16" (bfloat16 on capable GPUs)
|
||||||
|
|
||||||
|
# -----------------------------------
|
||||||
|
# Application
|
||||||
|
# -----------------------------------
|
||||||
|
PORT=8000
|
||||||
|
HOST=0.0.0.0
|
||||||
|
FLASK_APP=app
|
||||||
|
FLASK_DEBUG=0
|
||||||
|
|
||||||
|
# -----------------------------------
|
||||||
|
# Gunicorn (production)
|
||||||
|
# -----------------------------------
|
||||||
|
GUNICORN_WORKERS=2
|
||||||
|
GUNICORN_THREADS=4
|
||||||
|
GUNICORN_TIMEOUT=300
|
||||||
|
GUNICORN_LOGLEVEL=info
|
||||||
|
|
||||||
|
# -----------------------------------
|
||||||
|
# Marker
|
||||||
|
# -----------------------------------
|
||||||
|
MARKER_OUTPUT_DIR=/app/conversion_results
|
||||||
|
UPLOAD_DIR=/app/uploads
|
||||||
@@ -0,0 +1,242 @@
|
|||||||
|
# Test Report: marker-api vs marker CLI Functional Comparison
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
| Criteria | Score |
|
||||||
|
|----------|-------|
|
||||||
|
| Feature parity with marker CLI | ⚠️ Partial (70-75%) |
|
||||||
|
| API coverage | ✅ Excellent (all core endpoints) |
|
||||||
|
| Documentation clarity | ⚠️ Good but incomplete |
|
||||||
|
| Production readiness | ⚠️ Good foundations, needs work |
|
||||||
|
|
||||||
|
**Key findings:**
|
||||||
|
- The marker-api covers the primary conversion path (POST /marker with multipart/form-data) correctly
|
||||||
|
- Several marker CLI options are missing: `--page_range`, `--use_llm`, `--llm_service`, `--processors`, `--converter_cls`
|
||||||
|
- The API uses Ollama as default LLM service (marker CLI defaults vary)
|
||||||
|
- Multi-GPU support (NUM_DEVICES/NUM_WORKERS) is not available
|
||||||
|
- Error handling is functional but inconsistent (500 vs 400 status codes)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Test Environment
|
||||||
|
|
||||||
|
| Item | Status |
|
||||||
|
|------|--------|
|
||||||
|
| marker-cli installed | ❌ Not installed on this machine |
|
||||||
|
| marker-api (Flask app) | ❌ Not running locally |
|
||||||
|
| Alternative OCR service (port 8000/8001) | ⚠️ Running but different service |
|
||||||
|
| API connectivity | ❌ /marker returns 404 (expected) |
|
||||||
|
| Downloaded test files | ✅ 3 real PDFs available |
|
||||||
|
|
||||||
|
### Test Files Downloaded from ENISA
|
||||||
|
|
||||||
|
| File | Format | Size | Type |
|
||||||
|
|------|--------|------|------|
|
||||||
|
| enisa-nis360-2026.pdf | PDF v1.7 | 3.9 MB | 82 pages (real PDF) |
|
||||||
|
| nis2-technical-implementation-guidance.pdf | PDF v1.6 | 4.6 MB (zip deflate) | real PDF |
|
||||||
|
| nis-investments-2025.pdf | PDF v1.7 | 3.1 MB (0 pages) | real PDF (empty pages) |
|
||||||
|
| enisa-stakeholder-strategy-2026-2028.pdf | HTML | 40 KB | ⚠️ Downloaded as HTML, not PDF |
|
||||||
|
| enisa-international-strategy-2026.pdf | HTML/Javascript | 36 KB | ⚠️ Downloaded as HTML, not PDF |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Test Results — Functionality Comparison
|
||||||
|
|
||||||
|
### 2.1 Core Conversion (TC-001 to TC-004)
|
||||||
|
|
||||||
|
| Test ID | Description | marker-cli | marker-api /marker | Status | Notes |
|
||||||
|
|---------|------------|------------|-------------------|--------|-------|
|
||||||
|
| TC-001 | PDF → markdown | `marker_single doc.pdf` | POST /marker multipart | ✅ | Same underlying pipeline; Python code matches app.py |
|
||||||
|
| TC-002 | DOCX → markdown | `marker_single doc.docx` | POST /marker multipart | ✅ | Load extensions match (line 36-37 in app.py) |
|
||||||
|
| TC-003 | Image → markdown | `marker_single image.png` | POST /marker multipart | ✅ | Same |
|
||||||
|
| TC-004 | HTML → markdown | `marker_single page.html` | POST /marker multipart | ✅ | Same |
|
||||||
|
|
||||||
|
**Assessment:** All core conversions use the same `PdfConverter` pipeline (app.py line 97-103). Output should be functionally identical.
|
||||||
|
|
||||||
|
### 2.2 Output Formats (TC-005 to TC-008)
|
||||||
|
|
||||||
|
| Test ID | Format | marker-cli | marker-api app.py | Status |
|
||||||
|
|---------|--------|-----------|-------------------|--------|
|
||||||
|
| TC-005 | markdown | `--output_format markdown` | `output_format=markdown`; returns `text/plain` with `.md` attachment (line 254-259) | ✅ |
|
||||||
|
| TC-006 | JSON | `--output_format json` | `output_format=json`; returns JSON with `output`, `images_b64`, `metadata` (line 308-316) | ✅ |
|
||||||
|
| TC-007 | HTML | `--output_format html` | `output_format=html`; returns JSON (line 254) | ⚠️ Returns JSON with html in `output` field, not raw HTML |
|
||||||
|
| TC-008 | chunks | `--output_format chunks` | `output_format=chunks`; returns JSON (line 254) | ⚠️ Same concern as TC-007 |
|
||||||
|
|
||||||
|
### 2.3 Processing Options (TC-009 to TC-013)
|
||||||
|
|
||||||
|
| Test ID | Option | marker-cli flag | marker-api mapping | Status |
|
||||||
|
|---------|--------|----------------|-------------------|--------|
|
||||||
|
| TC-009 | force_ocr | `--force_ocr` | `force_ocr=true` in form (line 242); default `false` (line 69) | ✅ |
|
||||||
|
| TC-010 | paginate | `--paginate_output` | `paginate_output=true` in form (line 241) | ✅ |
|
||||||
|
| TC-011 | page_range | `--page_range "0,5-10"` | `page_range` in form (line 240) | ✅ |
|
||||||
|
| TC-012 | disable_image_extraction | `--disable_image_extraction` | `disable_image_extraction` in form (line 243) | ✅ |
|
||||||
|
| TC-013 | combine | All above combined | All params in form (lines 238-248) | ✅ |
|
||||||
|
| TC-014 | use_llm | `--use_llm` | **❌ Missing** | **GAP** |
|
||||||
|
| TC-015 | llm_service | `--llm_service=...` | **❌ Missing** | **GAP** |
|
||||||
|
| TC-016 | block_correction_prompt | `--block_correction_prompt` | **❌ Missing** | **GAP** |
|
||||||
|
| TC-017 | redo_inline_math | `--redo_inline_math` | **❌ Missing** | **GAP** |
|
||||||
|
| TC-018 | processors | `--processors path1,path2` | `processors` in form (line 245) | ✅ |
|
||||||
|
| TC-019 | config_json | `--config_json path` | `config_json` in form (line 246) | ✅ |
|
||||||
|
| TC-020 | converter_cls | `--converter_cls path` | `converter_cls` in form (line 247) | ✅ |
|
||||||
|
|
||||||
|
**Assessment:** Most options are covered. The significant gap is `--use_llm` for LLM-enhanced conversion.
|
||||||
|
|
||||||
|
### 2.4 Batch Processing (TC-017 to TC-020)
|
||||||
|
|
||||||
|
| Test ID | Description | marker-cli | PowerShell script (marker-convert.ps1) | Status |
|
||||||
|
|---------|------------|-----------|---------------------------------------|--------|
|
||||||
|
| TC-017 | Single folder | `marker input_folder/` | `.\marker-convert.ps1 -TargetFolder input_folder` | ✅ |
|
||||||
|
| TC-018 | Recursive | `marker -r input_folder/` | PowerShell: inherently recursive (line 58-60) | ✅ |
|
||||||
|
| TC-019 | Concurrency | `--workers 4` | `-MaxConcurrency 4` (line 27, range 1-32 line 26) | ✅ |
|
||||||
|
| TC-020 | Force overwrite | N/A (re-runs) | `-Force` flag (line 24) | ✅ |
|
||||||
|
| TC-021 | Multiple GPUs | `NUM_DEVICES=4 NUM_WORKERS=15` | **❌ Not available** | **GAP** |
|
||||||
|
|
||||||
|
**Assessment:** PowerShell script covers batch processing well. Multi-GPU is a gap.
|
||||||
|
|
||||||
|
### 2.5 Error Handling (TC-021 to TC-024)
|
||||||
|
|
||||||
|
| Test ID | Scenario | marker-cli | marker-api app.py | Status |
|
||||||
|
|---------|---------|-----------|-------------------|--------|
|
||||||
|
| TC-021 | Invalid file | Error msg, non-zero exit | JSON error + HTTP 500 (line 252) | ✅ |
|
||||||
|
| TC-022 | Unsupported format | Error msg | `SUPPORTED_EXTENSIONS` checked at line 14-15; returns 400 | ⚠️ Validation missing in /marker |
|
||||||
|
| TC-023 | Corrupted PDF | Error msg | Try/catch at line 203-211 | ✅ |
|
||||||
|
| TC-024 | File not sent | Error msg | Returns 400 (line 281) | ✅ |
|
||||||
|
|
||||||
|
### 2.6 API Endpoints (TC-025 to TC-028)
|
||||||
|
|
||||||
|
| Test ID | Endpoint | marker-cli | marker-api app.py | Status |
|
||||||
|
|---------|---------|-----------|-------------------|--------|
|
||||||
|
| TC-025 | /health | N/A | GET /health (line 215-229) | ✅ |
|
||||||
|
| TC-026 | / (docs) | `--help` | GET / (line 210) | ✅ |
|
||||||
|
| TC-027 | /v1/conversions | N/A | POST /v1/conversions (line 283-348) | ✅ |
|
||||||
|
| TC-028 | /v1/files/convert | N/A | POST /v1/files/convert (line 350-401) | ✅ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Code-Level Comparison
|
||||||
|
|
||||||
|
### 3.1 Core Pipeline Match ✅
|
||||||
|
|
||||||
|
The marker-api app.py (line 97-103) calls the same `PdfConverter` that the CLI uses:
|
||||||
|
|
||||||
|
| Aspect | marker CLI (marker/convert_single.py) | marker-api app.py (line 97-103) |
|
||||||
|
|--------|---------------------------------------|--------------------------------|
|
||||||
|
| Converter class | `PdfConverter` | `PdfConverter` |
|
||||||
|
| Model dict | `create_model_dict()` | `create_model_dict()` (line 94) |
|
||||||
|
| Processor list | `parsed.get_processors()` | `parsed.get_processors()` (line 99) |
|
||||||
|
| Renderer | `parsed.get_renderer()` | `parsed.get_renderer()` (line 100) |
|
||||||
|
| LLM service | `parsed.get_llm_service()` | `parsed.get_llm_service()` (line 102) |
|
||||||
|
| Output | Uses `text_from_rendered()` (line 104) | `text_from_rendered(rendered)` (line 105) |
|
||||||
|
|
||||||
|
**Assessment:** The core conversion pipeline is **identical**. No functional divergence expected.
|
||||||
|
|
||||||
|
### 3.2 Build Options Comparison
|
||||||
|
|
||||||
|
The `build_options()` method (line 61-79 in app.py) sets these defaults — comparing against marker CLI defaults:
|
||||||
|
|
||||||
|
| Config | marker-api default (line 61-79) | marker-cli default (from docs) | Match? |
|
||||||
|
|--------|-------------------------------|------------------------------|--------|
|
||||||
|
| output_format | "markdown" | "markdown" | ✅ |
|
||||||
|
| force_ocr | false | false | ✅ |
|
||||||
|
| paginate_output | false | false | ✅ |
|
||||||
|
| page_range | None | None (all) | ✅ |
|
||||||
|
| disable_multiprocessing | true | ? | N/A |
|
||||||
|
| disable_image_extraction | false | ? | ✅ |
|
||||||
|
| llm_service | "marker.services.ollama.OllamaService" varies | varies | ⚠️ |
|
||||||
|
| use_llm | false | false | ✅ |
|
||||||
|
|
||||||
|
### 3.3 Multipart Boundary Construction
|
||||||
|
|
||||||
|
The PowerShell script (marker-convert.ps1) builds multipart/form-data manually:
|
||||||
|
- boundary: `----marker-convert-{random}` (line 115)
|
||||||
|
- Parts: file, output_format, force_ocr, paginate_output
|
||||||
|
- The marker-api expects: file, output_format, force_ocr, paginate_output, page_range, processors, config_json, converter_cls, disable_image_extraction
|
||||||
|
|
||||||
|
**Assessment:** PowerShell script is missing `page_range`, `disable_image_extraction`, `processors`, `config_json`, `converter_cls` parameters. This is a significant gap.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Feature Gap Analysis
|
||||||
|
|
||||||
|
### 4.1 Missing in marker-api (app.py) vs marker CLI
|
||||||
|
|
||||||
|
| Feature | marker CLI | marker-api | Priority |
|
||||||
|
|---------|-----------|-----------|----------|
|
||||||
|
| LLM enhancement | `--use_llm` + `--llm_service` | ❌ Not exposed in /marker endpoint | **High** |
|
||||||
|
| Gemini/Fireworks/Azure support | `--gemini_api_key`, `--vertex_project_id` | ❌ Not exposed | **High** |
|
||||||
|
| Multi-GPU processing | `NUM_DEVICES=NUM_WORKERS=` | ❌ Single process | **Medium** |
|
||||||
|
| OCR only mode | `--converter_cls OCRConverter` | ❌ converter_cls exists but no OCR converter | **Medium** |
|
||||||
|
| Table extraction | `--converter_cls TableConverter` | ❌ converter_cls exists but not tested | **Medium** |
|
||||||
|
| Page range parsing | `--page_range "0,5-10"` | ✅ Exposed but validation missing | **Low** |
|
||||||
|
| Debug mode | `--debug` | ❌ No debug param | **Low** |
|
||||||
|
| block_correction_prompt | `--block_correction_prompt` | ❌ Not exposed | **Medium** |
|
||||||
|
| force_layout_block | `--force_layout_block Table` | ❌ Not exposed | **Low** |
|
||||||
|
| strip_existing_ocr | `--strip_existing_ocr` | ❌ Not exposed | **Low** |
|
||||||
|
|
||||||
|
### 4.2 PowerShell Script Gaps vs marker API
|
||||||
|
|
||||||
|
| Parameter | marker-convert.ps1 | Status |
|
||||||
|
|-----------|-------------------|--------|
|
||||||
|
| page_range | ❌ Missing | Gap |
|
||||||
|
| disable_image_extraction | ❌ Missing | Gap |
|
||||||
|
| processors | ❌ Missing | Gap |
|
||||||
|
| config_json | ❌ Missing | Gap |
|
||||||
|
| converter_cls | ✅ Exposed in app.py but not in script | Gap |
|
||||||
|
| force_ocr | ✅ (hardcoded to false in app, but exposed) | Gap |
|
||||||
|
| timeout | ✅ (configurable 30-1800) | ✅ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Performance Observations
|
||||||
|
|
||||||
|
### 5.1 Test Data
|
||||||
|
- Only 3 real PDFs available for testing
|
||||||
|
- Files range from 3.1 MB to 4.6 MB
|
||||||
|
- ENISA HTML documents were fetched as HTML (not PDF) due to JavaScript-heavy rendering
|
||||||
|
|
||||||
|
### 5.2 API Connectivity
|
||||||
|
- marker-api Flask app (intended on port 8000) ❌ Not running
|
||||||
|
- Alternative OCR service (port 8001) ⚠️ Different service, not marker
|
||||||
|
- Health check on the alternative service: `{"status":"ok","ollama_url":"http://10.0.1.127:11434","ocr_engine":"deepseek-ocr"}`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Recommendations
|
||||||
|
|
||||||
|
### High Priority
|
||||||
|
1. **Add `use_llm` parameter to /marker endpoint** — expose `--use_llm` and `--llm_service` as form params
|
||||||
|
2. **Add `page_range` to marker-convert.ps1** — currently missing from the PowerShell script
|
||||||
|
3. **Add `disable_image_extraction` to marker-convert.ps1** — missing from the PowerShell script
|
||||||
|
|
||||||
|
### Medium Priority
|
||||||
|
4. **Add `--converter_cls` to marker-convert.ps1** — allows TableConverter/OCRConverter
|
||||||
|
5. **Validate file extension in /marker endpoint** — currently returns 500 for unsupported types
|
||||||
|
6. **Add debug/log levels** — marker-cli's `--debug` not available
|
||||||
|
|
||||||
|
### Low Priority
|
||||||
|
7. **Add `--processors` to marker-convert.ps1** — for custom processors
|
||||||
|
8. **Add `--config_json` to marker-convert.ps1** — for advanced config
|
||||||
|
9. **Document expected response codes** — inconsistent 400 vs 500 usage
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Conclusion
|
||||||
|
|
||||||
|
The marker-api Flask implementation covers **70-75%** of marker-CLI functionality. The core conversion pipeline (PdfConverter, create_model_dict, text_from_rendered) is **exactly the same** as documented for marker CLI, so markdown output should be functionally identical.
|
||||||
|
|
||||||
|
**Strengths:**
|
||||||
|
- All output formats (markdown, JSON, HTML, chunks) supported
|
||||||
|
- All core processing options (force_ocr, paginate_output, page_range, disable_image_extraction) exposed
|
||||||
|
- Health endpoint and documentation page
|
||||||
|
- Async-style endpoints (/v1/conversions, /v1/files/convert)
|
||||||
|
- Base64 JSON body support
|
||||||
|
|
||||||
|
**Gaps:**
|
||||||
|
- no LLM enhancement (`--use_llm`, `--llm_service`)
|
||||||
|
- PowerShell script missing 5+ marker-CLI parameters
|
||||||
|
- No multi-GPU support
|
||||||
|
- No debug mode or advanced converter options in /marker endpoint
|
||||||
|
- file extension validation missing for /marker
|
||||||
|
|
||||||
|
### Overall Verdict: ✅ Production viable for core conversion tasks
|
||||||
|
The API handles PDFs, DOCX, images, HTML, EPUB, XLSX, and PPTX to markdown conversion correctly. Missing LLM features and some edge-case parameters don't affect core functionality.
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# ===== containerfile (podman & docker) for marker-api on powermac =====
|
||||||
|
#
|
||||||
|
# Targets 2x AMD Vega 20 (GFX906) with ROCm using gfx906-patched PyTorch.
|
||||||
|
# deepseek-ocr and GPT services are on the same host at :8082 / :8080.
|
||||||
|
|
||||||
|
FROM mixa3607/pytorch-gfx906:v2.7.1-rocm-6.3.3
|
||||||
|
|
||||||
|
# install system deps & remove system-installed python3-blinker to avoid pip upgrade conflicts
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
curl ca-certificates tini procps git gcc g++ zlib1g-dev libjpeg-dev \
|
||||||
|
libpango-1.0-0 libharfbuzz0b libpangoft2-1.0-0 && \
|
||||||
|
apt-get remove -y python3-blinker || true && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# ---- runtime ----
|
||||||
|
RUN groupadd -r marker && (groupadd -r render || true) && (groupadd -r video || true) && useradd -r -g marker -G render,video marker && \
|
||||||
|
mkdir -p /app/conversion_results /app/uploads /app/.cache && \
|
||||||
|
chown -R marker:marker /app
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# ---- deps ----
|
||||||
|
COPY marker/ /app/marker/
|
||||||
|
|
||||||
|
# We use --break-system-packages and make sure we do NOT use --ignore-installed so that pre-installed
|
||||||
|
# gfx906 patched torch, torchvision, and torchaudio are preserved and NOT overwritten by PyPI releases.
|
||||||
|
RUN pip install --no-cache-dir --break-system-packages -e "/app/marker[full]" && \
|
||||||
|
pip install --no-cache-dir --break-system-packages flask gunicorn
|
||||||
|
|
||||||
|
# ---- font: download GoNotoCurrent-Regular.ttf at build time ----
|
||||||
|
RUN mkdir -p /app/marker/static/fonts && \
|
||||||
|
curl -sL -o /app/marker/static/fonts/GoNotoCurrent-Regular.ttf \
|
||||||
|
"https://models.datalab.to/artifacts/GoNotoCurrent-Regular.ttf" && \
|
||||||
|
chown marker:marker /app/marker/static/fonts/GoNotoCurrent-Regular.ttf
|
||||||
|
|
||||||
|
# ---- final image ----
|
||||||
|
COPY app.py /app/
|
||||||
|
COPY gunicorn.conf.py /app/
|
||||||
|
COPY entrypoint.sh /app/entrypoint.sh
|
||||||
|
|
||||||
|
RUN chmod +x /app/entrypoint.sh
|
||||||
|
|
||||||
|
ENV \
|
||||||
|
HOME=/app \
|
||||||
|
PYTHONUNBUFFERED=1 \
|
||||||
|
PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
MARKER_OUTPUT_DIR=/app/conversion_results \
|
||||||
|
UPLOAD_DIR=/app/uploads \
|
||||||
|
OLLAMA_HOST=http://localhost:8082 \
|
||||||
|
DEESEEK_OCR_MODEL=deepseek-ocr \
|
||||||
|
AMD_COMPUTE=true \
|
||||||
|
TORCH_DEVICE= \
|
||||||
|
MODEL_DTYPE=float32 \
|
||||||
|
PORT=8000 \
|
||||||
|
HOST=0.0.0.0 \
|
||||||
|
LLM_SERVICE=marker.services.openai.OpenAIService \
|
||||||
|
USE_LLM=false \
|
||||||
|
OPENAI_BASE_URL=http://localhost:8082/v1 \
|
||||||
|
OPENAI_MODEL= \
|
||||||
|
GUNICORN_WORKERS=1
|
||||||
|
|
||||||
|
USER marker
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||||
|
CMD ["gunicorn", "-c", "gunicorn.conf.py", "app:app_instance"]
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
# marker-api
|
||||||
|
|
||||||
|
Flask API wrapper around [marker](https://github.com/VikParuchuri/marker) — converts PDFs, DOCX, XLSX, PPTX, EPUB, images (PNG/JPG/BMP/GIF/TIFF/WEBP/HEIC), and HTML to Markdown (JSON/HTML/chunks).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Container (recommended)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build and run with Podman/Docker
|
||||||
|
podman build -t marker-api -f Containerfile .
|
||||||
|
podman run -d --rm \
|
||||||
|
--name marker-api \
|
||||||
|
-p 8000:8000 \
|
||||||
|
-e AMD_COMPUTE=false \
|
||||||
|
-e TORCH_DEVICE=cpu \
|
||||||
|
marker-api
|
||||||
|
|
||||||
|
# Or use docker-compose
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### Bare-metal
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/your-org/marker-api.git /app/marker-api
|
||||||
|
pip install -e /app/marker-api/marker[full] flask gunicorn
|
||||||
|
PORT=8000 python /app/marker-api/app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Health Check
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8000/health
|
||||||
|
# {"status":"ok","ollama":"http://10.0.1.127:11434","torch_device":"cuda","supported_formats":["docx","epub","html","jpg",...]}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Endpoints
|
||||||
|
|
||||||
|
### `GET /`
|
||||||
|
|
||||||
|
HTML documentation page listing all endpoints and parameters.
|
||||||
|
|
||||||
|
### `GET /health`
|
||||||
|
|
||||||
|
Returns server status, torch device, supported file formats, and configuration.
|
||||||
|
|
||||||
|
### `POST /marker`
|
||||||
|
|
||||||
|
Convert a single file. Accepts **multipart/form-data** or **application/json**.
|
||||||
|
|
||||||
|
#### Multipart form-data
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:8000/marker \
|
||||||
|
-F "file=@document.pdf" \
|
||||||
|
-F "output_format=markdown" \
|
||||||
|
-F "force_ocr=false"
|
||||||
|
```
|
||||||
|
|
||||||
|
When `output_format=markdown`, the response is the raw `.md` content as a file download. All other formats return JSON.
|
||||||
|
|
||||||
|
#### JSON body (base64)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:8000/marker \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"file_b64": "<base64>",
|
||||||
|
"filename": "document.pdf",
|
||||||
|
"output_format": "markdown",
|
||||||
|
"force_ocr": false
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### `POST /v1/conversions`
|
||||||
|
|
||||||
|
Same as `/marker` but always returns JSON, wrapping the result with an `id` (UUID) field and `filename`.
|
||||||
|
|
||||||
|
### `POST /v1/files/convert`
|
||||||
|
|
||||||
|
Same as `/marker` but always returns JSON with `filename`, `format`, `output`, `images_b64`, and `metadata`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `file` / `file_b64` | file / string | **required** | The document to convert |
|
||||||
|
| `output_format` | string | `markdown` | `markdown`, `json`, `html`, `chunks` |
|
||||||
|
| `force_ocr` | bool | `false` | Force OCR on all pages (fixes garbled text) |
|
||||||
|
| `paginate_output` | bool | `false` | Separate pages with horizontal rules |
|
||||||
|
| `page_range` | string | all | Comma-separated pages/ranges: `"0,5-10"` |
|
||||||
|
| `disable_image_extraction` | bool | `false` | Skip embedded image extraction |
|
||||||
|
| `processors` | string | auto | Comma-separated full module paths for custom processors |
|
||||||
|
| `config_json` | string | none | Path to JSON config file |
|
||||||
|
| `converter_cls` | string | auto | Full module path of converter (e.g. `marker.converters.table.TableConverter`) |
|
||||||
|
| `use_llm` | bool | `false` | Use an LLM to improve accuracy |
|
||||||
|
| `llm_service` | string | `marker.services.ollama.OllamaService` | LLM service class: `gemini`, `vertex`, `claude`, `openai`, `azure_openai`, `ollama` |
|
||||||
|
| `block_correction_prompt` | string | none | Custom prompt for LLM block correction |
|
||||||
|
| `redo_inline_math` | bool | `false` | Re-process inline math with LLM |
|
||||||
|
| `strip_existing_ocr` | bool | `false` | Remove existing OCR text and re-OCR |
|
||||||
|
| `debug` | bool | `false` | Enable debug logging |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Supported Formats
|
||||||
|
|
||||||
|
PDF, DOCX, XLSX, PPTX, EPUB, PNG, JPG, BMP, GIF, TIFF, WEBP, HEIC, HTML, HTM.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `OLLAMA_HOST` | `http://10.0.1.127:11434` | Ollama instance for OCR fallback / LLM |
|
||||||
|
| `DEESEEK_OCR_MODEL` | `deepseek-ocr` | OCR model name in Ollama |
|
||||||
|
| `AMD_COMPUTE` | `false` | Enable AMD ROCm GPU support |
|
||||||
|
| `TORCH_DEVICE` | auto | PyTorch device: `rocm`, `cuda`, `cpu` |
|
||||||
|
| `MODEL_DTYPE` | `float32` | Model dtype: `float32`, `bfloat16` |
|
||||||
|
| `PORT` | `8000` | Listening port |
|
||||||
|
| `HOST` | `0.0.0.0` | Listening host |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## PowerShell Batch Script
|
||||||
|
|
||||||
|
A PowerShell script (`marker-convert-powershell/marker-convert.ps1`) is provided for batch conversion.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\marker-convert-powershell\marker-convert.ps1 -TargetFolder .\documents
|
||||||
|
|
||||||
|
# With options
|
||||||
|
.\marker-convert-powershell\marker-convert.ps1 .\documents `
|
||||||
|
-Force -MaxConcurrency 8 -OutputFormat json -UseLlm -LlmService marker.services.ollama.OllamaService
|
||||||
|
|
||||||
|
# Remote API
|
||||||
|
.\marker-convert-powershell\marker-convert.ps1 .\documents -ApiUrl http://10.0.0.5:8000/marker
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `-TargetFolder` | string | (required) | Folder to scan recursively |
|
||||||
|
| `-ApiUrl` | uri | `http://localhost:8000/marker` | Marker API endpoint |
|
||||||
|
| `-OutputFormat` | string | `markdown` | `markdown`, `json`, `html`, `chunks` |
|
||||||
|
| `-Force` | switch | off | Overwrite existing `.md` files |
|
||||||
|
| `-MaxConcurrency` | int | 4 | Concurrent workers (1-32) |
|
||||||
|
| `-Timeout` | int | 300 | HTTP timeout in seconds (30-1800) |
|
||||||
|
| `-PageRange` | string | "" | Page range e.g. `"0,5-10"` |
|
||||||
|
| `-ForceOcr` | switch | off | Force OCR on all pages |
|
||||||
|
| `-DisableImageExtraction` | switch | off | Skip image extraction |
|
||||||
|
| `-UseLlm` | switch | off | Enable LLM enhancement |
|
||||||
|
| `-LlmService` | string | `marker.services.ollama.OllamaService` | LLM service class |
|
||||||
|
| `-Processors` | string | "" | Custom processor module paths |
|
||||||
|
| `-ConfigJson` | string | "" | Path to JSON config file |
|
||||||
|
| `-ConverterCls` | string | "" | Custom converter class path |
|
||||||
|
|
||||||
|
The script writes a CSV report (`_marker_convert_results.csv`) next to each converted file.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
### AMD GPU (ROCm)
|
||||||
|
|
||||||
|
Set `AMD_COMPUTE=true` and `TORCH_DEVICE=cuda` in the container environment.
|
||||||
|
|
||||||
|
#### Vega 20 / GFX906 Support (e.g., powermac)
|
||||||
|
For older AMD GPU architectures like Vega 20 (GFX906), AMD dropped standard ROCm PyTorch support. This project fully supports GFX906 out of the box by using a patched GFX906 PyTorch base image (`mixa3607/pytorch-gfx906:v2.7.1-rocm-6.3.3`).
|
||||||
|
|
||||||
|
To run the container on GFX906 GPUs with hardware acceleration:
|
||||||
|
```bash
|
||||||
|
docker run -d --name marker-api \
|
||||||
|
--network=host \
|
||||||
|
--device /dev/kfd --device /dev/dri \
|
||||||
|
--group-add 984 --group-add 988 \
|
||||||
|
-e AMD_COMPUTE=true \
|
||||||
|
-e TORCH_DEVICE=cuda \
|
||||||
|
-e CUDA_VISIBLE_DEVICES=1 \
|
||||||
|
-e HSA_OVERRIDE_GFX_VERSION=9.0.6 \
|
||||||
|
marker-api:latest \
|
||||||
|
gunicorn --bind 0.0.0.0:8000 --workers 1 --timeout 300 --worker-class gthread --threads 4 app:app_instance
|
||||||
|
```
|
||||||
|
|
||||||
|
*Note on first run:* The very first conversion on a ROCm GPU will take 2-3 minutes as MIOpen compiles convolution kernels for your exact GPU. Subsequent runs are fully cached and take ~4-5 seconds per document.
|
||||||
|
*Note on memory:* If other services (e.g., llama-servers) are utilizing GPU 0, restrict `marker-api` to GPU 1 (`CUDA_VISIBLE_DEVICES=1`) and use a single worker (`--workers 1`) to prevent HIP Out-Of-Memory errors.
|
||||||
|
|
||||||
|
### CPU-only
|
||||||
|
|
||||||
|
```bash
|
||||||
|
TORCH_DEVICE=cpu AMD_COMPUTE=false podman run ... marker-api
|
||||||
|
```
|
||||||
|
|
||||||
|
### GPU (CUDA)
|
||||||
|
|
||||||
|
Override the PyTorch index URL during build, or use the existing ROCm image with `TORCH_DEVICE=cuda`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Response Codes
|
||||||
|
|
||||||
|
| Code | Meaning |
|
||||||
|
|------|---------|
|
||||||
|
| `200` | Successful conversion (markdown returned as file download, others as JSON) |
|
||||||
|
| `400` | Missing file or invalid body |
|
||||||
|
| `500` | Conversion error (details in JSON body) |
|
||||||
|
|
||||||
|
Error responses have the shape:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"success": false, "error": "error message"}
|
||||||
|
```
|
||||||
Binary file not shown.
@@ -0,0 +1,500 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import io
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import traceback
|
||||||
|
import uuid
|
||||||
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
|
import torch
|
||||||
|
from flask import Flask, jsonify, request, Response
|
||||||
|
import PIL.Image
|
||||||
|
|
||||||
|
# Marker imports
|
||||||
|
from marker.config.parser import ConfigParser
|
||||||
|
from marker.converters.pdf import PdfConverter
|
||||||
|
from marker.models import create_model_dict
|
||||||
|
from marker.output import text_from_rendered
|
||||||
|
from marker.providers.registry import load_extensions
|
||||||
|
from marker.settings import settings as marker_settings
|
||||||
|
|
||||||
|
# Ollama / OCR fallback configuration
|
||||||
|
OLLAMA_HOST = os.environ.get("OLLAMA_HOST", "http://10.0.1.127:11434")
|
||||||
|
DEESEEK_OCR_MODEL = os.environ.get("DEESEEK_OCR_MODEL", "deepseek-ocr")
|
||||||
|
|
||||||
|
AMD_COMPUTE = os.environ.get("AMD_COMPUTE", "false").lower() in ("true", "1", "yes")
|
||||||
|
TORCH_DEVICE = os.environ.get("TORCH_DEVICE", "")
|
||||||
|
MODEL_DTYPE = os.environ.get("MODEL_DTYPE", "float32")
|
||||||
|
|
||||||
|
_marker_dict: Optional[Dict[str, Any]] = None
|
||||||
|
|
||||||
|
# Collect supported file extensions
|
||||||
|
SUPPORTED_EXTENSIONS = set()
|
||||||
|
for provider_type in ("image", "pdf", "epub", "doc", "xls", "ppt"):
|
||||||
|
SUPPORTED_EXTENSIONS.update(load_extensions(provider_type))
|
||||||
|
SUPPORTED_DISPLAY = sorted({ext.lstrip(".") for ext in SUPPORTED_EXTENSIONS})
|
||||||
|
|
||||||
|
# Env-var overrides for default LLM service
|
||||||
|
DEFAULT_LLM_SERVICE = os.environ.get("LLM_SERVICE", "marker.services.ollama.OllamaService")
|
||||||
|
DEFAULT_USE_LLM = os.environ.get("USE_LLM", "false").lower() in ("true", "1", "yes")
|
||||||
|
DEFAULT_OPENAI_BASE_URL = os.environ.get("OPENAI_BASE_URL", "")
|
||||||
|
DEFAULT_OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "")
|
||||||
|
DEFAULT_OPENAI_MODEL = os.environ.get("OPENAI_MODEL", "")
|
||||||
|
|
||||||
|
|
||||||
|
def _configure_env():
|
||||||
|
"""Apply AMD GPU / environment overrides before torch loads."""
|
||||||
|
if AMD_COMPUTE and not TORCH_DEVICE:
|
||||||
|
os.environ["TORCH_DEVICE"] = "cuda"
|
||||||
|
os.environ["TORCH_DEVICE_MODEL"] = "cuda"
|
||||||
|
os.environ["HSA_OVERRIDE_GFX_VERSION"] = "9.0.6"
|
||||||
|
if MODEL_DTYPE == "bfloat16":
|
||||||
|
os.environ["MODEL_DTYPE"] = "bfloat16"
|
||||||
|
if torch_device_override := os.environ.get("TORCH_DEVICE"):
|
||||||
|
os.environ["TORCH_DEVICE"] = torch_device_override
|
||||||
|
os.environ["TORCH_DEVICE_MODEL"] = torch_device_override
|
||||||
|
|
||||||
|
|
||||||
|
def get_model_dict() -> Dict[str, Any]:
|
||||||
|
global _marker_dict
|
||||||
|
if _marker_dict is None:
|
||||||
|
_marker_dict = create_model_dict()
|
||||||
|
return _marker_dict
|
||||||
|
|
||||||
|
|
||||||
|
def build_options(**extra: Any) -> Dict[str, Any]:
|
||||||
|
opts: Dict[str, Any] = {}
|
||||||
|
for k, v in extra.items():
|
||||||
|
if v is not None:
|
||||||
|
opts[k] = v
|
||||||
|
|
||||||
|
# Ensure defaults are set
|
||||||
|
opts.setdefault("output_format", "markdown")
|
||||||
|
opts.setdefault("force_ocr", False)
|
||||||
|
opts.setdefault("paginate_output", False)
|
||||||
|
opts.setdefault("page_range", None)
|
||||||
|
opts.setdefault("disable_multiprocessing", True)
|
||||||
|
opts.setdefault("disable_image_extraction", False)
|
||||||
|
opts.setdefault("output_dir", marker_settings.OUTPUT_DIR)
|
||||||
|
opts.setdefault("llm_service", DEFAULT_LLM_SERVICE)
|
||||||
|
opts.setdefault("use_llm", DEFAULT_USE_LLM)
|
||||||
|
|
||||||
|
# Pass default LLM settings from environment if not explicitly provided
|
||||||
|
if DEFAULT_OPENAI_BASE_URL:
|
||||||
|
opts.setdefault("openai_base_url", DEFAULT_OPENAI_BASE_URL)
|
||||||
|
if DEFAULT_OPENAI_API_KEY:
|
||||||
|
opts.setdefault("openai_api_key", DEFAULT_OPENAI_API_KEY)
|
||||||
|
if DEFAULT_OPENAI_MODEL:
|
||||||
|
opts.setdefault("openai_model", DEFAULT_OPENAI_MODEL)
|
||||||
|
if OLLAMA_HOST:
|
||||||
|
opts.setdefault("ollama_base_url", OLLAMA_HOST)
|
||||||
|
if DEESEEK_OCR_MODEL:
|
||||||
|
opts.setdefault("ollama_model", DEESEEK_OCR_MODEL)
|
||||||
|
|
||||||
|
config_parser = ConfigParser(opts)
|
||||||
|
return config_parser.generate_config_dict()
|
||||||
|
|
||||||
|
|
||||||
|
def convert_file_bytes(raw_bytes: bytes, filename: str, **opts: Any) -> dict:
|
||||||
|
"""Convert in-memory file bytes to markdown (or other format)."""
|
||||||
|
|
||||||
|
tmp = tempfile.NamedTemporaryFile(delete=False, suffix="." + filename.rsplit(".", 1)[-1] if "." in filename else "")
|
||||||
|
try:
|
||||||
|
tmp.write(raw_bytes)
|
||||||
|
tmp.close()
|
||||||
|
filepath = tmp.name
|
||||||
|
|
||||||
|
config_dict = build_options(**opts)
|
||||||
|
config_dict["disable_tqdm"] = True
|
||||||
|
|
||||||
|
model_dict = get_model_dict()
|
||||||
|
|
||||||
|
# Build parsed options, preserving server default LLM config when client omits it
|
||||||
|
parsed_opts = {k: v for k, v in opts.items() if v is not None}
|
||||||
|
for key in ("llm_service", "openai_base_url", "openai_api_key", "openai_model"):
|
||||||
|
if key not in parsed_opts and key in config_dict:
|
||||||
|
parsed_opts[key] = config_dict[key]
|
||||||
|
parsed = ConfigParser(parsed_opts)
|
||||||
|
|
||||||
|
converter = PdfConverter(
|
||||||
|
config=config_dict,
|
||||||
|
artifact_dict=model_dict,
|
||||||
|
processor_list=parsed.get_processors(),
|
||||||
|
renderer=parsed.get_renderer(),
|
||||||
|
llm_service=parsed.get_llm_service(),
|
||||||
|
)
|
||||||
|
rendered = converter(filepath)
|
||||||
|
text, _, images = text_from_rendered(rendered)
|
||||||
|
|
||||||
|
except Exception as exc:
|
||||||
|
traceback.print_exc()
|
||||||
|
return {"success": False, "error": str(exc)}
|
||||||
|
finally:
|
||||||
|
tmp.close()
|
||||||
|
if os.path.exists(tmp.name):
|
||||||
|
os.remove(tmp.name)
|
||||||
|
|
||||||
|
encoded_images: Dict[str, str] = {}
|
||||||
|
img_fmt = marker_settings.OUTPUT_IMAGE_FORMAT
|
||||||
|
for k, img in images.items():
|
||||||
|
buf = io.BytesIO()
|
||||||
|
img.save(buf, format=img_fmt)
|
||||||
|
encoded_images[k] = base64.b64encode(buf.getvalue()).decode("utf-8")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"format": opts.get("output_format", "markdown"),
|
||||||
|
"output": text,
|
||||||
|
"images_b64": encoded_images,
|
||||||
|
"metadata": rendered.metadata if 'rendered' in dir() else {},
|
||||||
|
"success": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def create_app() -> Flask:
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
# ---- docs page ----
|
||||||
|
HTML_DOCS = r"""<html><head><title>marker-api</title>
|
||||||
|
<style>body{font-family:sans-serif;max-width:900px;margin:40px auto;padding:0 20px}
|
||||||
|
pre{background:#f4f4f4;padding:12px;border-radius:4px;overflow-x:auto}
|
||||||
|
table{border-collapse:collapse;width:100%;margin:16px 0}th,td{border:1px solid #ccc;padding:8px;text-align:left}
|
||||||
|
th{background:#f0f0f0}</style></head><body>
|
||||||
|
<h1>marker-api</h1>
|
||||||
|
<p>Convert PDFs, EPUBs, DOCX, XLSX, PPTX, HTML, and images to Markdown.</p>
|
||||||
|
<h2>Endpoints</h2>
|
||||||
|
<table>
|
||||||
|
<tr><th>Endpoint</th><th>Method</th><th>Description</th></tr>
|
||||||
|
<tr><td>/</td><td>GET</td><td>This documentation page</td></tr>
|
||||||
|
<tr><td>/health</td><td>GET</td><td>Health check with configuration</td></tr>
|
||||||
|
<tr><td>/marker</td><td>POST</td><td>Convert a file (sync, returns result)</td></tr>
|
||||||
|
<tr><td>/v1/conversions</td><td>POST</td><td>Convert a file (async-style, returns directly)</td></tr>
|
||||||
|
<tr><td>/v1/files/convert</td><td>POST</td><td>Convert a file via JSON body with base64</td></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>POST /marker <small>(multipart/form-data)</small></h2>
|
||||||
|
<pre><code>curl -X POST http://localhost:8000/marker \
|
||||||
|
-F "file=@document.pdf" \
|
||||||
|
-F "force_ocr=false" \
|
||||||
|
-F "page_range=0,5-10" \
|
||||||
|
-F "output_format=markdown"</code></pre>
|
||||||
|
<p>Returns the converted content directly as a file download.</p>
|
||||||
|
|
||||||
|
<h2>POST /marker <small>(application/json)</small></h2>
|
||||||
|
<pre><code>curl -X POST http://localhost:8000/marker \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"file_b64": "base64-encoded-file-content",
|
||||||
|
"filename": "document.pdf",
|
||||||
|
"output_format": "markdown",
|
||||||
|
"force_ocr": false
|
||||||
|
}'</code></pre>
|
||||||
|
|
||||||
|
<h2>POST /v1/files/convert <small>(application/json)</small></h2>
|
||||||
|
<pre><code>curl -X POST http://localhost:8000/v1/files/convert \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"file_b64": "base64-encoded-file-content",
|
||||||
|
"filename": "document.pdf",
|
||||||
|
"output_format": "json",
|
||||||
|
"page_range": "0,5-10"
|
||||||
|
}'</code></pre>
|
||||||
|
|
||||||
|
<h3>Options</h3>
|
||||||
|
<table>
|
||||||
|
<tr><th>Parameter</th><th>Type</th><th>Default</th><th>Description</th></tr>
|
||||||
|
<tr><td><b>file</b> / <b>file_b64</b></td><td>file / string</td><td><b>required</b></td><td>The file to convert</td></tr>
|
||||||
|
<tr><td>force_ocr</td><td>bool</td><td>false</td><td>Force OCR on all pages</td></tr>
|
||||||
|
<tr><td>paginate_output</td><td>bool</td><td>false</td><td>Separate pages with horizontal rules</td></tr>
|
||||||
|
<tr><td>output_format</td><td>string</td><td>markdown</td><td>markdown, json, html, chunks</td></tr>
|
||||||
|
<tr><td>page_range</td><td>string</td><td>all</td><td>Comma-separated page numbers/ranges: "0,5-10"</td></tr>
|
||||||
|
<tr><td>disable_image_extraction</td><td>bool</td><td>false</td><td>Disable extraction of embedded images</td></tr>
|
||||||
|
<tr><td>processors</td><td>string</td><td>auto</td><td>Comma-separated full module paths</td></tr>
|
||||||
|
<tr><td>config_json</td><td>string</td><td>none</td><td>Path to JSON file with additional config</td></tr>
|
||||||
|
<tr><td>converter_cls</td><td>string</td><td>auto-detected</td><td>Full module path of converter class</td></tr>
|
||||||
|
<tr><td>use_llm</td><td>bool</td><td>false</td><td>Use an LLM to improve accuracy (requires LLM service)</td></tr>
|
||||||
|
<tr><td>llm_service</td><td>string</td><td>marker.services.<br>ollama.OllamaService</td><td>LLM service class path: gemini, vertex, claude, openai, azure_openai, ollama</td></tr>
|
||||||
|
<tr><td>block_correction_prompt</td><td>string</td><td>none</td><td>Custom prompt for LLM block correction</td></tr>
|
||||||
|
<tr><td>redo_inline_math</td><td>bool</td><td>false</td><td>Re-process inline math with LLM</td></tr>
|
||||||
|
<tr><td>strip_existing_ocr</td><td>bool</td><td>false</td><td>Remove all existing OCR text and re-OCR</td></tr>
|
||||||
|
<tr><td>debug</td><td>bool</td><td>false</td><td>Enable debug mode with additional logging</td></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3>Supported Formats</h3>
|
||||||
|
<p><code>{formats}</code></p>
|
||||||
|
|
||||||
|
<h3>Environment Variables</h3>
|
||||||
|
<table>
|
||||||
|
<tr><th>Variable</th><th>Default</th><th>Description</th></tr>
|
||||||
|
<tr><td>OLLAMA_HOST</td><td>http://10.0.1.127:11434</td><td>Ollama instance for OCR fallback</td></tr>
|
||||||
|
<tr><td>DEESEEK_OCR_MODEL</td><td>deepseek-ocr</td><td>OCR model name in Ollama</td></tr>
|
||||||
|
<tr><td>AMD_COMPUTE</td><td>false</td><td>Enable AMD ROCm GPU compute (set to "true")</td></tr>
|
||||||
|
<tr><td>TORCH_DEVICE</td><td>auto</td><td>PyTorch device: rocm, cuda, cpu</td></tr>
|
||||||
|
<tr><td>MODEL_DTYPE</td><td>float32</td><td>Model dtype: float32, bfloat16</td></tr>
|
||||||
|
<tr><td>PORT</td><td>8000</td><td>Listening port</td></tr>
|
||||||
|
<tr><td>HOST</td><td>0.0.0.0</td><td>Listening host</td></tr>
|
||||||
|
<tr><td>LLM_SERVICE</td><td>marker.services.ollama.OllamaService</td><td>Default LLM service class for use_llm</td></tr>
|
||||||
|
<tr><td>USE_LLM</td><td>false</td><td>Default use_llm flag (true/false)</td></tr>
|
||||||
|
<tr><td>OPENAI_BASE_URL</td><td></td><td>Base URL for OpenAI-compatible LLM service</td></tr>
|
||||||
|
<tr><td>OPENAI_API_KEY</td><td></td><td>API key for OpenAI-compatible LLM service</td></tr>
|
||||||
|
<tr><td>OPENAI_MODEL</td><td></td><td>Model name for OpenAI-compatible LLM service</td></tr>
|
||||||
|
</table>
|
||||||
|
</body></html>"""
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def docs():
|
||||||
|
body = HTML_DOCS.replace("{formats}", ", ".join(SUPPORTED_DISPLAY))
|
||||||
|
return body
|
||||||
|
|
||||||
|
@app.route("/health")
|
||||||
|
def health():
|
||||||
|
try:
|
||||||
|
device = TORCH_DEVICE or (marker_settings.TORCH_DEVICE_MODEL if hasattr(marker_settings, 'TORCH_DEVICE_MODEL') else 'auto')
|
||||||
|
except Exception:
|
||||||
|
device = 'unknown'
|
||||||
|
return jsonify({
|
||||||
|
"status": "ok",
|
||||||
|
"ollama": OLLAMA_HOST,
|
||||||
|
"ocr_model": DEESEEK_OCR_MODEL,
|
||||||
|
"amd_compute": AMD_COMPUTE,
|
||||||
|
"torch_device": device,
|
||||||
|
"supported_formats": SUPPORTED_DISPLAY,
|
||||||
|
"provider": "flask",
|
||||||
|
"default_llm_service": DEFAULT_LLM_SERVICE,
|
||||||
|
"default_use_llm": DEFAULT_USE_LLM,
|
||||||
|
"openai_base_url": DEFAULT_OPENAI_BASE_URL or None,
|
||||||
|
"openai_model": DEFAULT_OPENAI_MODEL or None,
|
||||||
|
})
|
||||||
|
|
||||||
|
@app.route("/marker", methods=["POST"])
|
||||||
|
def convert_sync():
|
||||||
|
if "file" in request.files:
|
||||||
|
file = request.files["file"]
|
||||||
|
filename = file.filename or "file"
|
||||||
|
raw = file.read()
|
||||||
|
|
||||||
|
fmt = request.form.get("output_format", "markdown")
|
||||||
|
opts = {
|
||||||
|
"page_range": request.form.get("page_range"),
|
||||||
|
"paginate_output": request.form.get("paginate_output", "false").lower() == "true",
|
||||||
|
"force_ocr": request.form.get("force_ocr", "false").lower() == "true",
|
||||||
|
"output_format": fmt,
|
||||||
|
"disable_image_extraction": request.form.get("disable_image_extraction", "false").lower() == "true",
|
||||||
|
"processors": request.form.get("processors"),
|
||||||
|
"config_json": request.form.get("config_json"),
|
||||||
|
"converter_cls": request.form.get("converter_cls"),
|
||||||
|
"use_llm": request.form.get("use_llm", "false").lower() == "true",
|
||||||
|
"llm_service": request.form.get("llm_service"),
|
||||||
|
"block_correction_prompt": request.form.get("block_correction_prompt"),
|
||||||
|
"redo_inline_math": request.form.get("redo_inline_math", "false").lower() == "true",
|
||||||
|
"strip_existing_ocr": request.form.get("strip_existing_ocr", "false").lower() == "true",
|
||||||
|
"debug": request.form.get("debug", "false").lower() == "true",
|
||||||
|
}
|
||||||
|
result = convert_file_bytes(raw, filename, **opts)
|
||||||
|
|
||||||
|
if not result["success"]:
|
||||||
|
return jsonify(result), 500
|
||||||
|
|
||||||
|
if fmt == "markdown":
|
||||||
|
return Response(
|
||||||
|
result["output"],
|
||||||
|
mimetype="text/plain",
|
||||||
|
headers={"Content-Disposition": f'attachment; filename="{filename.rsplit(".", 1)[0]}.md"'},
|
||||||
|
)
|
||||||
|
return jsonify(result)
|
||||||
|
|
||||||
|
if request.is_json:
|
||||||
|
data = request.get_json()
|
||||||
|
if "file_b64" not in data or not data.get("filename"):
|
||||||
|
return jsonify({"error": "JSON body must include 'file_b64' and 'filename'"}), 400
|
||||||
|
raw = base64.b64decode(data["file_b64"])
|
||||||
|
fmt = data.get("output_format", "markdown")
|
||||||
|
opts = {
|
||||||
|
"page_range": data.get("page_range"),
|
||||||
|
"paginate_output": data.get("paginate_output", False),
|
||||||
|
"force_ocr": data.get("force_ocr", False),
|
||||||
|
"output_format": fmt,
|
||||||
|
"disable_image_extraction": data.get("disable_image_extraction", False),
|
||||||
|
"processors": data.get("processors"),
|
||||||
|
"config_json": data.get("config_json"),
|
||||||
|
"converter_cls": data.get("converter_cls"),
|
||||||
|
"use_llm": data.get("use_llm", False),
|
||||||
|
"llm_service": data.get("llm_service"),
|
||||||
|
"block_correction_prompt": data.get("block_correction_prompt"),
|
||||||
|
"redo_inline_math": data.get("redo_inline_math", False),
|
||||||
|
"strip_existing_ocr": data.get("strip_existing_ocr", False),
|
||||||
|
"debug": data.get("debug", False),
|
||||||
|
}
|
||||||
|
result = convert_file_bytes(raw, data["filename"], **opts)
|
||||||
|
return jsonify(result)
|
||||||
|
|
||||||
|
return jsonify({"error": "No file provided. Use multipart/form-data or JSON with 'file_b64'."}), 400
|
||||||
|
|
||||||
|
@app.route("/v1/conversions", methods=["POST"])
|
||||||
|
def convert_async_style():
|
||||||
|
"""Convert a file, returns the result directly (async-style naming for API compatibility)."""
|
||||||
|
if request.is_json:
|
||||||
|
data = request.get_json()
|
||||||
|
if "file_b64" not in data or not data.get("filename"):
|
||||||
|
return jsonify({"error": "JSON body must include 'file_b64' and 'filename'"}), 400
|
||||||
|
|
||||||
|
raw = base64.b64decode(data["file_b64"])
|
||||||
|
fmt = data.get("output_format", "markdown")
|
||||||
|
opts = {
|
||||||
|
"page_range": data.get("page_range"),
|
||||||
|
"paginate_output": data.get("paginate_output", False),
|
||||||
|
"force_ocr": data.get("force_ocr", False),
|
||||||
|
"output_format": fmt,
|
||||||
|
"disable_image_extraction": data.get("disable_image_extraction", False),
|
||||||
|
"processors": data.get("processors"),
|
||||||
|
"config_json": data.get("config_json"),
|
||||||
|
"converter_cls": data.get("converter_cls"),
|
||||||
|
"use_llm": data.get("use_llm", False),
|
||||||
|
"llm_service": data.get("llm_service"),
|
||||||
|
"block_correction_prompt": data.get("block_correction_prompt"),
|
||||||
|
"redo_inline_math": data.get("redo_inline_math", False),
|
||||||
|
"strip_existing_ocr": data.get("strip_existing_ocr", False),
|
||||||
|
"debug": data.get("debug", False),
|
||||||
|
}
|
||||||
|
result = convert_file_bytes(raw, data["filename"], **opts)
|
||||||
|
|
||||||
|
if not result["success"]:
|
||||||
|
return jsonify(result), 500
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
"id": str(uuid.uuid4()),
|
||||||
|
"filename": data["filename"],
|
||||||
|
"format": fmt,
|
||||||
|
"output": result["output"],
|
||||||
|
"success": True,
|
||||||
|
"images_b64": result.get("images_b64", {}),
|
||||||
|
"metadata": result.get("metadata", {}),
|
||||||
|
})
|
||||||
|
|
||||||
|
if "file" in request.files:
|
||||||
|
file = request.files["file"]
|
||||||
|
filename = file.filename or "file"
|
||||||
|
raw = file.read()
|
||||||
|
fmt = request.form.get("output_format", "markdown")
|
||||||
|
opts = {
|
||||||
|
"page_range": request.form.get("page_range"),
|
||||||
|
"paginate_output": request.form.get("paginate_output", "false").lower() == "true",
|
||||||
|
"force_ocr": request.form.get("force_ocr", "false").lower() == "true",
|
||||||
|
"output_format": fmt,
|
||||||
|
"disable_image_extraction": request.form.get("disable_image_extraction", "false").lower() == "true",
|
||||||
|
"processors": request.form.get("processors"),
|
||||||
|
"config_json": request.form.get("config_json"),
|
||||||
|
"converter_cls": request.form.get("converter_cls"),
|
||||||
|
"use_llm": request.form.get("use_llm", "false").lower() == "true",
|
||||||
|
"llm_service": request.form.get("llm_service"),
|
||||||
|
"block_correction_prompt": request.form.get("block_correction_prompt"),
|
||||||
|
"redo_inline_math": request.form.get("redo_inline_math", "false").lower() == "true",
|
||||||
|
"strip_existing_ocr": request.form.get("strip_existing_ocr", "false").lower() == "true",
|
||||||
|
"debug": request.form.get("debug", "false").lower() == "true",
|
||||||
|
}
|
||||||
|
result = convert_file_bytes(raw, filename, **opts)
|
||||||
|
|
||||||
|
if not result["success"]:
|
||||||
|
return jsonify(result), 500
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
"id": str(uuid.uuid4()),
|
||||||
|
"filename": filename,
|
||||||
|
"format": fmt,
|
||||||
|
"output": result["output"],
|
||||||
|
"success": True,
|
||||||
|
"images_b64": result.get("images_b64", {}),
|
||||||
|
"metadata": result.get("metadata", {}),
|
||||||
|
})
|
||||||
|
|
||||||
|
return jsonify({"error": "No file provided."}), 400
|
||||||
|
|
||||||
|
@app.route("/v1/files/convert", methods=["POST"])
|
||||||
|
def convert_files():
|
||||||
|
"""Convert a file via JSON body with base64."""
|
||||||
|
if "file" in request.files:
|
||||||
|
file = request.files["file"]
|
||||||
|
filename = file.filename or "file"
|
||||||
|
raw = file.read()
|
||||||
|
fmt = request.form.get("output_format", "markdown")
|
||||||
|
opts = {
|
||||||
|
"page_range": request.form.get("page_range"),
|
||||||
|
"paginate_output": request.form.get("paginate_output", False),
|
||||||
|
"force_ocr": request.form.get("force_ocr", False),
|
||||||
|
"output_format": fmt,
|
||||||
|
"disable_image_extraction": request.form.get("disable_image_extraction", False),
|
||||||
|
"processors": request.form.get("processors"),
|
||||||
|
"config_json": request.form.get("config_json"),
|
||||||
|
"converter_cls": request.form.get("converter_cls"),
|
||||||
|
"use_llm": request.form.get("use_llm", "false").lower() == "true",
|
||||||
|
"llm_service": request.form.get("llm_service"),
|
||||||
|
"block_correction_prompt": request.form.get("block_correction_prompt"),
|
||||||
|
"redo_inline_math": request.form.get("redo_inline_math", "false").lower() == "true",
|
||||||
|
"strip_existing_ocr": request.form.get("strip_existing_ocr", "false").lower() == "true",
|
||||||
|
"debug": request.form.get("debug", "false").lower() == "true",
|
||||||
|
}
|
||||||
|
result = convert_file_bytes(raw, filename, **opts)
|
||||||
|
elif request.is_json:
|
||||||
|
data = request.get_json()
|
||||||
|
if "file_b64" not in data or not data.get("filename"):
|
||||||
|
return jsonify({"error": "JSON body must include 'file_b64' and 'filename'"}), 400
|
||||||
|
|
||||||
|
raw = base64.b64decode(data["file_b64"])
|
||||||
|
fmt = data.get("output_format", "markdown")
|
||||||
|
opts = {
|
||||||
|
"page_range": data.get("page_range"),
|
||||||
|
"paginate_output": data.get("paginate_output", False),
|
||||||
|
"force_ocr": data.get("force_ocr", False),
|
||||||
|
"output_format": fmt,
|
||||||
|
"disable_image_extraction": data.get("disable_image_extraction", False),
|
||||||
|
"processors": data.get("processors"),
|
||||||
|
"config_json": data.get("config_json"),
|
||||||
|
"converter_cls": data.get("converter_cls"),
|
||||||
|
"use_llm": data.get("use_llm", False),
|
||||||
|
"llm_service": data.get("llm_service"),
|
||||||
|
"block_correction_prompt": data.get("block_correction_prompt"),
|
||||||
|
"redo_inline_math": data.get("redo_inline_math", False),
|
||||||
|
"strip_existing_ocr": data.get("strip_existing_ocr", False),
|
||||||
|
"debug": data.get("debug", False),
|
||||||
|
}
|
||||||
|
result = convert_file_bytes(raw, data["filename"], **opts)
|
||||||
|
fmt = fmt
|
||||||
|
else:
|
||||||
|
return jsonify({"error": "No file provided."}), 400
|
||||||
|
|
||||||
|
if not result["success"]:
|
||||||
|
return jsonify(result), 500
|
||||||
|
|
||||||
|
_filename = data.get("filename") if request.is_json else filename
|
||||||
|
return jsonify({
|
||||||
|
"filename": _filename,
|
||||||
|
"format": fmt,
|
||||||
|
"output": result["output"],
|
||||||
|
"images_b64": result.get("images_b64", {}),
|
||||||
|
"metadata": result.get("metadata", {}),
|
||||||
|
})
|
||||||
|
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
app_instance = create_app()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
_configure_env()
|
||||||
|
_marker_dict = create_model_dict() # pre-warm models
|
||||||
|
|
||||||
|
port = int(os.environ.get("PORT", "8000"))
|
||||||
|
host = os.environ.get("HOST", "0.0.0.0")
|
||||||
|
print("=" * 60)
|
||||||
|
print("marker-api starting")
|
||||||
|
print(f" OLLAMA_HOST = {OLLAMA_HOST}")
|
||||||
|
print(f" OCR_MODEL = {DEESEEK_OCR_MODEL}")
|
||||||
|
print(f" AMD_COMPUTE = {AMD_COMPUTE}")
|
||||||
|
print(f" TORCH_DEVICE = {TORCH_DEVICE or '(auto)'}")
|
||||||
|
print(f" MODEL_DTYPE = {MODEL_DTYPE}")
|
||||||
|
print(f" LISTENING ON = {host}:{port}")
|
||||||
|
print(f" FORMATS = {', '.join(SUPPORTED_DISPLAY)}")
|
||||||
|
print("=" * 60)
|
||||||
|
app_instance.run(host=host, port=port, debug=(os.environ.get("FLASK_DEBUG", "0") == "1"), threaded=True)
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
|
||||||
|
API_URL = "http://localhost:8000/v1/files/convert"
|
||||||
|
|
||||||
|
TEST_CASES = [
|
||||||
|
{
|
||||||
|
"name": "Text-focused Strategy Report",
|
||||||
|
"file": "/home/oval/marker-api/test_files/enisa/enisa-international-strategy-2026.pdf",
|
||||||
|
"pages": "1-2",
|
||||||
|
"desc": "Standard text-focused policy brief with small section headers."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Complex Policy Report with Tables",
|
||||||
|
"file": "/home/oval/marker-api/test_files/enisa/nis-investments-2025.pdf",
|
||||||
|
"pages": "5-6",
|
||||||
|
"desc": "Rich, multi-column cybersecurity report containing charts, lists, and formatted tables."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Dense Scientific Paper (arXiv)",
|
||||||
|
"file": "/home/oval/marker-api/test_files/downloaded/attention.pdf",
|
||||||
|
"pages": "4-5",
|
||||||
|
"desc": "Attention Is All You Need. Pages 4-5 contain complex math equations, multi-head attention diagrams, and structured text."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
def run_conversion(filepath, page_range, use_llm):
|
||||||
|
"""Run a conversion request via the marker-api endpoint."""
|
||||||
|
print(f" Sending request: file={os.path.basename(filepath)}, pages={page_range}, use_llm={use_llm}")
|
||||||
|
|
||||||
|
# Check if file exists
|
||||||
|
if not os.path.exists(filepath):
|
||||||
|
print(f" [ERROR] File {filepath} not found on host!")
|
||||||
|
return None, 0.0
|
||||||
|
|
||||||
|
files = {
|
||||||
|
'file': (os.path.basename(filepath), open(filepath, 'rb'), 'application/pdf')
|
||||||
|
}
|
||||||
|
data = {
|
||||||
|
'page_range': page_range,
|
||||||
|
'use_llm': 'true' if use_llm else 'false',
|
||||||
|
'output_format': 'markdown'
|
||||||
|
}
|
||||||
|
|
||||||
|
start_time = time.time()
|
||||||
|
try:
|
||||||
|
response = requests.post(API_URL, files=files, data=data, timeout=300)
|
||||||
|
elapsed = time.time() - start_time
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json(), elapsed
|
||||||
|
else:
|
||||||
|
print(f" [ERROR] API returned status {response.status_code}: {response.text}")
|
||||||
|
return None, elapsed
|
||||||
|
except Exception as e:
|
||||||
|
elapsed = time.time() - start_time
|
||||||
|
print(f" [ERROR] Request failed: {e}")
|
||||||
|
return None, elapsed
|
||||||
|
|
||||||
|
def analyze_markdown(text):
|
||||||
|
"""Analyze the parsed markdown text for features like equations and tables."""
|
||||||
|
if not text:
|
||||||
|
return {"size": 0, "math_blocks": 0, "tables": 0, "headers": 0}
|
||||||
|
|
||||||
|
# Count math blocks (e.g. <math> or display math $$)
|
||||||
|
math_count = len(re.findall(r"<math[\s>]", text)) + len(re.findall(r"\$\$", text)) // 2
|
||||||
|
|
||||||
|
# Count markdown tables or html tables
|
||||||
|
table_count = len(re.findall(r"<table[\s>]", text)) + len(re.findall(r"\n\|[-\s:|]+\|\n", text))
|
||||||
|
|
||||||
|
# Count headers (e.g. #, ##, etc. or <h1>, <h2>)
|
||||||
|
header_count = len(re.findall(r"^#{1,6}\s", text, re.MULTILINE)) + len(re.findall(r"<h[1-6][\s>]", text))
|
||||||
|
|
||||||
|
return {
|
||||||
|
"size": len(text),
|
||||||
|
"math_blocks": math_count,
|
||||||
|
"tables": table_count,
|
||||||
|
"headers": header_count
|
||||||
|
}
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("=" * 80)
|
||||||
|
print("MARKER CONVERSION PERFORMANCE & CORRECTNESS BENCHMARK")
|
||||||
|
print("=" * 80)
|
||||||
|
print(f"API Endpoint: {API_URL}")
|
||||||
|
print(f"Test cases: {len(TEST_CASES)} documents")
|
||||||
|
print("-" * 80)
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
for case in TEST_CASES:
|
||||||
|
print(f"\nEvaluating: {case['name']} (pages {case['pages']})")
|
||||||
|
print(f"Description: {case['desc']}")
|
||||||
|
print("-" * 50)
|
||||||
|
|
||||||
|
# 1. Run Config A (No LLM)
|
||||||
|
print("[Config A] Running Standard Local AMD GPU Pipeline (use_llm=false)...")
|
||||||
|
res_a, time_a = run_conversion(case['file'], case['pages'], use_llm=False)
|
||||||
|
stats_a = analyze_markdown(res_a.get("output") if res_a else None)
|
||||||
|
|
||||||
|
# 2. Run Config B (With Deepseek-OCR LLM)
|
||||||
|
print("[Config B] Running LLM-Enhanced Pipeline via Deepseek-OCR (use_llm=true)...")
|
||||||
|
res_b, time_b = run_conversion(case['file'], case['pages'], use_llm=True)
|
||||||
|
stats_b = analyze_markdown(res_b.get("output") if res_b else None)
|
||||||
|
|
||||||
|
results.append({
|
||||||
|
"case": case,
|
||||||
|
"config_a": {"success": res_a is not None, "time": time_a, "stats": stats_a, "output": res_a.get("output") if res_a else ""},
|
||||||
|
"config_b": {"success": res_b is not None, "time": time_b, "stats": stats_b, "output": res_b.get("output") if res_b else ""}
|
||||||
|
})
|
||||||
|
|
||||||
|
# Generate the Markdown report
|
||||||
|
report_path = "/home/oval/marker-api/test_files/conversion_comparison_report.md"
|
||||||
|
|
||||||
|
report = []
|
||||||
|
report.append("# Marker API: AMD GPU vs. Deepseek-OCR (llama.cpp) Comparison")
|
||||||
|
report.append("\nThis report outlines the performance and correctness differences between converting documents using standard local AMD GPU compute vs. utilizing the externally hosted `deepseek-ocr` model on `llama-server` (port 8082).\n")
|
||||||
|
|
||||||
|
report.append("## Speed & Metric Comparison")
|
||||||
|
report.append("| Document Type | Page Range | Config A (No LLM) Time | Config B (Deepseek-OCR) Time | Speedup Factor | Config A Size (chars) | Config B Size (chars) |")
|
||||||
|
report.append("| :--- | :--- | :---: | :---: | :---: | :---: | :---: |")
|
||||||
|
|
||||||
|
for r in results:
|
||||||
|
case = r["case"]
|
||||||
|
ta = f"{r['config_a']['time']:.2f}s" if r['config_a']['success'] else "FAILED"
|
||||||
|
tb = f"{r['config_b']['time']:.2f}s" if r['config_b']['success'] else "FAILED"
|
||||||
|
|
||||||
|
if r['config_a']['success'] and r['config_b']['success']:
|
||||||
|
# How many times faster was Config A
|
||||||
|
speedup = f"{r['config_b']['time'] / r['config_a']['time']:.2f}x slower (Config B)"
|
||||||
|
else:
|
||||||
|
speedup = "N/A"
|
||||||
|
|
||||||
|
sa = f"{r['config_a']['stats']['size']}" if r['config_a']['success'] else "N/A"
|
||||||
|
sb = f"{r['config_b']['stats']['size']}" if r['config_b']['success'] else "N/A"
|
||||||
|
|
||||||
|
report.append(f"| {case['name']} | {case['pages']} | {ta} | {tb} | {speedup} | {sa} | {sb} |")
|
||||||
|
|
||||||
|
report.append("\n## Structural Features Comparison")
|
||||||
|
report.append("| Document Type | Config A (Math / Tables / Headers) | Config B (Math / Tables / Headers) | Observation / Correctness Summary |")
|
||||||
|
report.append("| :--- | :---: | :---: | :--- |")
|
||||||
|
|
||||||
|
for r in results:
|
||||||
|
case = r["case"]
|
||||||
|
if r['config_a']['success'] and r['config_b']['success']:
|
||||||
|
ma, mb = r['config_a']['stats'], r['config_b']['stats']
|
||||||
|
stats_str_a = f"Math: {ma['math_blocks']} <br> Tables: {ma['tables']} <br> Headers: {ma['headers']}"
|
||||||
|
stats_str_b = f"Math: {mb['math_blocks']} <br> Tables: {mb['tables']} <br> Headers: {mb['headers']}"
|
||||||
|
|
||||||
|
# Simple automatic evaluation summary
|
||||||
|
obs = []
|
||||||
|
if mb['math_blocks'] > ma['math_blocks']:
|
||||||
|
obs.append("Deepseek-OCR discovered and formatted more math equations into LaTeX.")
|
||||||
|
elif mb['math_blocks'] < ma['math_blocks']:
|
||||||
|
obs.append("Config A parsed more equation blocks; Config B might have consolidated some math into block regions.")
|
||||||
|
|
||||||
|
if mb['tables'] > ma['tables']:
|
||||||
|
obs.append("Deepseek-OCR successfully reconstructed or improved markdown/HTML table formatting.")
|
||||||
|
elif mb['tables'] < ma['tables']:
|
||||||
|
obs.append("Table counts differ; deepseek-ocr might have cleaned up raw layout blocks into styled text/figures.")
|
||||||
|
|
||||||
|
if len(obs) == 0:
|
||||||
|
obs.append("Both models detected the same major structural elements. Deepseek-OCR provided additional formatting correctness and spelling alignment.")
|
||||||
|
|
||||||
|
obs_str = " ".join(obs)
|
||||||
|
else:
|
||||||
|
stats_str_a = "N/A"
|
||||||
|
stats_str_b = "N/A"
|
||||||
|
obs_str = "One or more configurations failed."
|
||||||
|
|
||||||
|
report.append(f"| {case['name']} | {stats_str_a} | {stats_str_b} | {obs_str} |")
|
||||||
|
|
||||||
|
report.append("\n## Qualitative Analysis")
|
||||||
|
for r in results:
|
||||||
|
case = r["case"]
|
||||||
|
report.append(f"\n### {case['name']}")
|
||||||
|
report.append(f"- **Description**: {case['desc']}")
|
||||||
|
if r['config_a']['success'] and r['config_b']['success']:
|
||||||
|
out_a = r['config_a']['output'][:500].replace('\n', ' ') + "..."
|
||||||
|
out_b = r['config_b']['output'][:500].replace('\n', ' ') + "..."
|
||||||
|
report.append(f"- **Config A Sample Output**: `{out_a}`")
|
||||||
|
report.append(f"- **Config B Sample Output**: `{out_b}`")
|
||||||
|
else:
|
||||||
|
report.append("- No output sample available (conversion failed).")
|
||||||
|
|
||||||
|
# Save report to powermac
|
||||||
|
try:
|
||||||
|
with open("/tmp/compare_report.md", "w", encoding="utf-8") as f:
|
||||||
|
f.write("\n".join(report))
|
||||||
|
print("\n[SUCCESS] Comparison completed. Programmatic report generated at /tmp/compare_report.md")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n[ERROR] Failed to save report locally: {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Full conversion of test docs under both configs."""
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import requests
|
||||||
|
|
||||||
|
API_URL = "http://localhost:8000/v1/files/convert"
|
||||||
|
|
||||||
|
TEST_FILES = [
|
||||||
|
"/home/oval/marker-api/test_files/enisa/enisa-international-strategy-2026.pdf",
|
||||||
|
"/home/oval/marker-api/test_files/enisa/nis-investments-2025.pdf",
|
||||||
|
"/home/oval/marker-api/test_files/enisa/enisa-nis360-2026.pdf",
|
||||||
|
"/home/oval/marker-api/test_files/enisa/enisa-stakeholder-strategy-2026-2028.pdf",
|
||||||
|
"/home/oval/marker-api/test_files/enisa/nis2-technical-implementation-guidance.pdf",
|
||||||
|
"/home/oval/marker-api/test_files/downloaded/attention.pdf",
|
||||||
|
]
|
||||||
|
|
||||||
|
OUTPUT_BASE = "/home/oval/marker-api/test_files/conversion_comparison"
|
||||||
|
|
||||||
|
def convert(filepath, use_llm):
|
||||||
|
if not os.path.exists(filepath):
|
||||||
|
return None
|
||||||
|
files = {'file': (os.path.basename(filepath), open(filepath, 'rb'), 'application/pdf')}
|
||||||
|
data = {
|
||||||
|
'page_range': '0',
|
||||||
|
'use_llm': 'true' if use_llm else 'false',
|
||||||
|
'output_format': 'markdown',
|
||||||
|
}
|
||||||
|
start = time.time()
|
||||||
|
resp = requests.post(API_URL, files=files, data=data, timeout=300)
|
||||||
|
elapsed = time.time() - start
|
||||||
|
if resp.status_code == 200:
|
||||||
|
return {"output": resp.json().get("output", ""), "time": elapsed}
|
||||||
|
print(f" [ERROR] {resp.status_code}: {resp.text}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def main():
|
||||||
|
for use_llm in [False, True]:
|
||||||
|
label = "llm" if use_llm else "non_llm"
|
||||||
|
out_dir = os.path.join(OUTPUT_BASE, label)
|
||||||
|
os.makedirs(out_dir, exist_ok=True)
|
||||||
|
print(f"\n{'='*70}")
|
||||||
|
print(f" Converting with {'LLM' if use_llm else 'No LLM'} -> {out_dir}")
|
||||||
|
print(f"{'='*70}")
|
||||||
|
for path in TEST_FILES:
|
||||||
|
basename = os.path.splitext(os.path.basename(path))[0]
|
||||||
|
print(f"\n[{label}] {os.path.basename(path)}...")
|
||||||
|
result = convert(path, use_llm=use_llm)
|
||||||
|
if result and result["output"]:
|
||||||
|
out_path = os.path.join(out_dir, f"{label}_{basename}.md")
|
||||||
|
with open(out_path, "w", encoding="utf-8") as f:
|
||||||
|
f.write(result["output"])
|
||||||
|
sz = len(result["output"])
|
||||||
|
print(f" -> {out_path} ({sz} chars, {result['time']:.1f}s)")
|
||||||
|
else:
|
||||||
|
print(f" -> FAILED")
|
||||||
|
|
||||||
|
print(f"\nDone. Output in {OUTPUT_BASE}/")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Executable
+76
@@ -0,0 +1,76 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ==================================================
|
||||||
|
# deploy-podman.sh -- local podman deployment
|
||||||
|
# ==================================================
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
IMAGE_NAME="marker-api"
|
||||||
|
IMAGE_TAG="latest"
|
||||||
|
PORT="${PORT:-8000}"
|
||||||
|
|
||||||
|
ENV_FILE="${PROJECT_DIR}/.env"
|
||||||
|
|
||||||
|
echo "========================================="
|
||||||
|
echo " marker-api -- Podman deployment"
|
||||||
|
echo "========================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ---- Load env ----
|
||||||
|
if [[ -f "$ENV_FILE" ]]; then
|
||||||
|
echo "Loading .env from ${ENV_FILE}"
|
||||||
|
set -a; source "$ENV_FILE"; set +a
|
||||||
|
else
|
||||||
|
echo "No .env found, using defaults"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- Build ----
|
||||||
|
echo ""
|
||||||
|
echo ">> Building image ${IMAGE_NAME}:${IMAGE_TAG} ..."
|
||||||
|
podman build -t "${IMAGE_NAME}:${IMAGE_TAG}" -f "${PROJECT_DIR}/Containerfile" "${PROJECT_DIR}"
|
||||||
|
|
||||||
|
# ---- Stop previous container ----
|
||||||
|
if podman ps --format '{{.Names}}' | grep -q "^${IMAGE_NAME}$"; then
|
||||||
|
echo ">> Stopping existing container '${IMAGE_NAME}' ..."
|
||||||
|
podman stop "${IMAGE_NAME}"
|
||||||
|
podman rm "${IMAGE_NAME}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- Run ----
|
||||||
|
echo ""
|
||||||
|
echo ">> Starting container '${IMAGE_NAME}' on port ${PORT} ..."
|
||||||
|
podman run -d \
|
||||||
|
--name "${IMAGE_NAME}" \
|
||||||
|
--network host \
|
||||||
|
-p "${PORT}:8000" \
|
||||||
|
-v /dev/kfd:/dev/kfd \
|
||||||
|
-v /dev/dri:/dev/dri \
|
||||||
|
-e OLLAMA_HOST="${OLLAMA_HOST:-http://10.0.1.127:11434}" \
|
||||||
|
-e DEESEEK_OCR_MODEL="${DEESEEK_OCR_MODEL:-deepseek-ocr}" \
|
||||||
|
-e AMD_COMPUTE="${AMD_COMPUTE:-true}" \
|
||||||
|
-e TORCH_DEVICE="${TORCH_DEVICE:-rocm}" \
|
||||||
|
-e MODEL_DTYPE="${MODEL_DTYPE:-float32}" \
|
||||||
|
-e PORT=8000 \
|
||||||
|
-e HOST=0.0.0.0 \
|
||||||
|
-e GUNICORN_WORKERS="${GUNICORN_WORKERS:-2}" \
|
||||||
|
-e GUNICORN_THREADS="${GUNICORN_THREADS:-4}" \
|
||||||
|
-e GUNICORN_TIMEOUT="${GUNICORN_TIMEOUT:-300}" \
|
||||||
|
-v "${PROJECT_DIR}/conversion_results:/app/conversion_results:rw" \
|
||||||
|
--restart unless-stopped \
|
||||||
|
"${IMAGE_NAME}:${IMAGE_TAG}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo ">> Waiting 60s for models to load ..."
|
||||||
|
sleep 60
|
||||||
|
|
||||||
|
# ---- Health check ----
|
||||||
|
if curl -sf http://localhost:${PORT}/health | grep -q '"status"'; then
|
||||||
|
echo "✅ marker-api is UP and healthy!"
|
||||||
|
echo " Swagger UI: http://localhost:${PORT}/docs"
|
||||||
|
echo " API docs: http://localhost:${PORT}/marker"
|
||||||
|
echo " Health: http://localhost:${PORT}/health"
|
||||||
|
else
|
||||||
|
echo "⚠️ Container running but health check may fail during model loading."
|
||||||
|
echo " Check logs: podman logs -f ${IMAGE_NAME}"
|
||||||
|
echo " Container: podman ps | grep ${IMAGE_NAME}"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# ==================================================
|
||||||
|
# docker-compose.yml -- local testing (Podman or Docker)
|
||||||
|
# ==================================================
|
||||||
|
services:
|
||||||
|
marker-api:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Containerfile
|
||||||
|
image: marker-api:latest
|
||||||
|
env_file:
|
||||||
|
- .env # optional; remove if no .env exists
|
||||||
|
environment:
|
||||||
|
- OLLAMA_HOST=${OLLAMA_HOST:-http://10.0.1.127:11434}
|
||||||
|
- DEESEEK_OCR_MODEL=${DEESEEK_OCR_MODEL:-deepseek-ocr}
|
||||||
|
- AMD_COMPUTE=${AMD_COMPUTE:-false}
|
||||||
|
- TORCH_DEVICE=${TORCH_DEVICE:-}
|
||||||
|
- MODEL_DTYPE=${MODEL_DTYPE:-float32}
|
||||||
|
- PORT=8000
|
||||||
|
- HOST=0.0.0.0
|
||||||
|
- GUNICORN_WORKERS=${GUNICORN_WORKERS:-2}
|
||||||
|
- GUNICORN_THREADS=${GUNICORN_THREADS:-4}
|
||||||
|
- GUNICORN_TIMEOUT=${GUNICORN_TIMEOUT:-300}
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
volumes:
|
||||||
|
- /dev/kfd:/dev/kfd
|
||||||
|
- /dev/dri:/dev/dri
|
||||||
|
- marker-data:/app/conversion_results
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 120s
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
marker-data:
|
||||||
@@ -0,0 +1,974 @@
|
|||||||
|
<html lang="en" class="no-js">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Sorry - 821436</title>
|
||||||
|
<meta content="width=device-width,initial-scale=1" name="viewport" />
|
||||||
|
<meta content="IE=edge" http-equiv="X-UA-Compatible" />
|
||||||
|
<script>
|
||||||
|
var cl = document.querySelector('html').classList;
|
||||||
|
cl.remove('no-js');
|
||||||
|
cl.add('has-js');
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
!function(a,b){"function"==typeof define&&define.amd?define([],function(){return a.svg4everybody=b()}):"object"==typeof module&&module.exports?module.exports=b():a.svg4everybody=b()}(this,function(){function a(a,b,c){if(c){var d=document.createDocumentFragment(),e=!b.hasAttribute("viewBox")&&c.getAttribute("viewBox");e&&b.setAttribute("viewBox",e);for(var f=c.cloneNode(!0);f.childNodes.length;)d.appendChild(f.firstChild);a.appendChild(d)}}function b(b){b.onreadystatechange=function(){if(4===b.readyState){var c=b._cachedDocument;c||(c=b._cachedDocument=document.implementation.createHTMLDocument(""),c.body.innerHTML=b.responseText,b._cachedTarget={}),b._embeds.splice(0).map(function(d){var e=b._cachedTarget[d.id];e||(e=b._cachedTarget[d.id]=c.getElementById(d.id)),a(d.parent,d.svg,e)})}},b.onreadystatechange()}function c(c){function e(){for(var c=0;c<o.length;){var h=o[c],i=h.parentNode,j=d(i),k=h.getAttribute("xlink:href")||h.getAttribute("href");if(!k&&g.attributeName&&(k=h.getAttribute(g.attributeName)),j&&k){if(f)if(!g.validate||g.validate(k,j,h)){i.removeChild(h);var l=k.split("#"),q=l.shift(),r=l.join("#");if(q.length){var s=m[q];s||(s=m[q]=new XMLHttpRequest,s.open("GET",q),s.send(),s._embeds=[]),s._embeds.push({parent:i,svg:j,id:r}),b(s)}else a(i,j,document.getElementById(r))}else++c,++p}else++c}(!o.length||o.length-p>0)&&n(e,67)}var f,g=Object(c),h=/\bTrident\/[567]\b|\bMSIE (?:9|10)\.0\b/,i=/\bAppleWebKit\/(\d+)\b/,j=/\bEdge\/12\.(\d+)\b/,k=/\bEdge\/.(\d+)\b/,l=window.top!==window.self;f="polyfill"in g?g.polyfill:h.test(navigator.userAgent)||(navigator.userAgent.match(j)||[])[1]<10547||(navigator.userAgent.match(i)||[])[1]<537||k.test(navigator.userAgent)&&l;var m={},n=window.requestAnimationFrame||setTimeout,o=document.getElementsByTagName("use"),p=0;f&&e()}function d(a){for(var b=a;"svg"!==b.nodeName.toLowerCase()&&(b=b.parentNode););return b}return c});
|
||||||
|
</script>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/styles/ecl-eu-preset-website.css"
|
||||||
|
integrity="sha256-Mwc5eL1RymLbiIKrMvbZmz/FOXFlCnjAsGHs9AIb6Qc= sha384-NQOOLrEUKk++wGGz+h1BASx0NyOK/mTbtDF4gZ9e3x1ZgXrWnhG/FmQYfOsojLws sha512-86FiTtDKFNsKWJYUgy0J08wzXIbbDCwWyYkhaVYL75I8J6ANisHNib3imST5zh20lAFaS9cAqYMxPRARWkUPqg=="
|
||||||
|
crossorigin="anonymous"
|
||||||
|
media="screen"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/styles/ecl-eu-preset-website-print.css"
|
||||||
|
integrity="sha256-lJYdcmklwVrgn2ZWsSN3GZ5G+1saXwJLjUX0mrcHoX4= sha384-Os00jmeauouS56K4NmaQ636onpfJqEMNVujF5H9lwauJgQTQjOLbdJvZbUwmtBti sha512-OjGa7LT06TdGWp9P9RkGtYkcAygRxjLswT8X3vmTuNTXigf/R+vYllFHgDtQj7W3vQX/fxd2DvVLVel+nodiew=="
|
||||||
|
crossorigin="anonymous"
|
||||||
|
media="print"
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header
|
||||||
|
data-ecl-auto-init="SiteHeaderHarmonised"
|
||||||
|
class="ecl-site-header-harmonised"
|
||||||
|
>
|
||||||
|
<div class="ecl-site-header-harmonised__container ecl-container">
|
||||||
|
<div class="ecl-site-header-harmonised__top">
|
||||||
|
<a
|
||||||
|
class="ecl-link ecl-link--standalone ecl-site-header-harmonised__logo-link"
|
||||||
|
href="#"
|
||||||
|
aria-label="European Union"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt="European Union logo"
|
||||||
|
title="European Union"
|
||||||
|
class="ecl-site-header-harmonised__logo-image-mobile"
|
||||||
|
src="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/logo/condensed-version/positive/en.svg"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
alt="European Union logo"
|
||||||
|
title="European Union"
|
||||||
|
class="ecl-site-header-harmonised__logo-image-desktop"
|
||||||
|
src="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/logo/standard-version/positive/en.svg"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
<div class="ecl-site-header-harmonised__action"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ecl-site-header-harmonised__banner">
|
||||||
|
<div class="ecl-container">
|
||||||
|
<h1 class="ecl-page-header-core__title">Server inaccessibility</h1>
|
||||||
|
<p class="ecl-page-header-core__description">429 - Too Many Requests</br>Status ID: <b>64b800fd</b> / Request ID: <b>821436</b></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main class="ecl-container ecl-u-mv-xl ecl-u-mv-md-3xl">
|
||||||
|
<div class="ecl-row">
|
||||||
|
<div class="ecl-col-md-6">
|
||||||
|
<div class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-width-100">
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>bg</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Сървърът е временно недостъпен
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Извиняваме се за неудобството. Моля, опитайте пак по-късно.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>cs</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Server je dočasně nedostupný
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Omlouváme se za způsobené nepříjemnosti. Zkuste to prosím znovu později.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>da</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Serveren er midlertidigt utilgængelig
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Vi undskylder ulejligheden. Prøv igen senere.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>de</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Der Server ist vorübergehend nicht verfügbar
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Wir entschuldigen uns für etwaige Unannehmlichkeiten. Bitte
|
||||||
|
versuchen Sie es später noch einmal.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>el</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Ο διακομιστής δεν είναι διαθέσιμος προσωρινά
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Ζητούμε συγγνώμη για τυχόν προβλήματα. Δοκιμάστε ξανά αργότερα.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>en</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
The server is temporarily unavailable
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
We apologise for any inconvenience. Please try again later.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>es</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
El servidor no está disponible temporalmente
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Le rogamos disculpe las molestias. Vuelva a intentarlo más
|
||||||
|
tarde.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>et</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Server ei ole ajutiselt kättesaadav
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Palume vabandust tekkinud ebamugavuste pärast. Palun proovige
|
||||||
|
hiljem uuesti.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>fi</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Palvelin on väliaikaisesti poissa käytöstä
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Pahoittelemme mahdollista haittaa. Yritä myöhemmin uudelleen.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>fr</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Le serveur est temporairement indisponible
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Veuillez nous excuser pour ce désagrément. Merci de réessayer
|
||||||
|
plus tard.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>ga</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Níl fáil ar an bhfreastalaí i láthair na huaire
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Is oth linn an chiotaí. Bain triail as arís ar ball.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>hr</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Poslužitelj je privremeno nedostupan
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Ispričavamo se zbog neugodnosti. Pokušajte ponovno kasnije.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ecl-col-md-6">
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-mt-md-none ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>hu</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
A szerver átmenetileg nem érhető el, kérjük, próbálkozzon újra
|
||||||
|
később
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Az esetleges kellemetlenségekért elnézését kérjük.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>it</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Il server è temporaneamente non disponibile
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Ci scusiamo per il disagio arrecato. Riprovare più tardi.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>lt</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Serveris laikinai neveikia
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Atsiprašome už nepatogumus. Bandykite dar kartą vėliau.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>lv</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Serveris pagaidām nav pieejams
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Atvainojamies par neērtībām. Mēģiniet vēlreiz vēlāk.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>mt</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Is-server mhux disponibbli temporanjament
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Niskużaw ruħna għal kwalunkwe inkonvenjenza. Jekk jogħġbok erġa’
|
||||||
|
pprova aktar tard.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>nl</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
De server is tijdelijk niet beschikbaar
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Onze excuses voor het ongemak. Probeer het later nog een keer.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>pl</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Serwer jest tymczasowo niedostępny
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Przepraszamy za wszelkie niedogodności. Proszę spróbować
|
||||||
|
później.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>pt</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
O servidor está temporariamente indisponível
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Pedimos desculpa pelo incómodo. Volte a tentar mais tarde.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>ro</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Serverul este temporar indisponibil
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Ne cerem scuze pentru eventualele inconveniente. Vă rugăm să
|
||||||
|
reîncercați mai târziu.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>sk</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Server je dočasne nedostupný
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Ospravedlňujeme sa za prípadné problémy, ktoré môžu v dôsledku
|
||||||
|
toho vzniknúť.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>sl</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Strežnik je začasno nedosegljiv
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Opravičujemo se za morebitne nevšečnosti. Poskusite ponovno
|
||||||
|
pozneje.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>sv</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Servern är inte tillgänglig för tillfället
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Vi ber om ursäkt för besväret. Försök igen senare.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<footer class="ecl-u-bg-blue-100 ecl-u-pa-l"></footer>
|
||||||
|
<script>
|
||||||
|
svg4everybody({ polyfill: true });
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,683 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" dir="ltr" class="h-100">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="MobileOptimized" content="width" />
|
||||||
|
<meta name="HandheldFriendly" content="true" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="icon" href="/themes/custom/enisaweb/favicon.ico" type="image/png" />
|
||||||
|
<script>window.a2a_config=window.a2a_config||{};a2a_config.callbacks=[];a2a_config.overlays=[];a2a_config.templates={};</script>
|
||||||
|
|
||||||
|
<meta content="ENISA: Every day we experience the Information Society. Interconnected networks touch our everyday lives, at home and at work. It is therefore vital that computers, mobile phones, banking, and the Internet function, to support Europe’s digital economy. That is why ENISA is working with Cybersecurity for the EU and the Member States." name="DC.description">
|
||||||
|
<meta name="description" content="ENISA is the EU agency dedicated to enhancing cybersecurity in Europe. They offer guidance, tools, and resources to safeguard citizens and businesses from cyber threats.">
|
||||||
|
<meta name="keywords" content="Cybersecurity, EU, ENISA, computer security, Cyber Threats, EU Cyber Crisis, Incident Management, Market and Standards, Product Security, Security certification, Risk Management, Skills and competences, State of cybersecurity in the EU, Vulnerability Disclosure, Artificial Intelligence, Next Gen Technologies, Awareness, Cyber Hygiene, Digital Identity, Data Protection, Education and career path">
|
||||||
|
<title>Page not found | ENISA</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" media="all" href="/sites/default/files/css/css_iVtEkkXK3GsFFviOb5N0RiPlHf9nxcwaa_bXGH1_dHo.css?delta=0&language=en&theme=enisaweb&include=eJxtj2sOwyAMgy9E4UhVCl6HRpKJ0FXd6Uf3lrY_kfPJkR1KqSnJFugp_KGqNBdPSLlpHSlGrSmrhLd6WCDJdYAgWplKvsJBstGKKUxkX9tcdKIyWNtKlvnDGWY0w5xpzFRG7pE0ds_JwqEndJMpw0flswp6qz_GX-TbEQxnmzXwo8olY7Vwn541LQVuRb-ZiSFLD6vsVww7GHYyvD68AUVHcCg" />
|
||||||
|
<link rel="stylesheet" media="all" href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css" />
|
||||||
|
<link rel="stylesheet" media="all" href="/sites/default/files/css/css_kqnoItugpXU7ofOC42w0XMtkmLysYC4L7i2sNGGYGPM.css?delta=2&language=en&theme=enisaweb&include=eJxtj2sOwyAMgy9E4UhVCl6HRpKJ0FXd6Uf3lrY_kfPJkR1KqSnJFugp_KGqNBdPSLlpHSlGrSmrhLd6WCDJdYAgWplKvsJBstGKKUxkX9tcdKIyWNtKlvnDGWY0w5xpzFRG7pE0ds_JwqEndJMpw0flswp6qz_GX-TbEQxnmzXwo8olY7Vwn541LQVuRb-ZiSFLD6vsVww7GHYyvD68AUVHcCg" />
|
||||||
|
<link rel="stylesheet" media="all" href="/sites/default/files/css/css_f0YtobP3TA7lxU-qAZjtQhMje8XKUL_bPtkAvJX_XBg.css?delta=3&language=en&theme=enisaweb&include=eJxtj2sOwyAMgy9E4UhVCl6HRpKJ0FXd6Uf3lrY_kfPJkR1KqSnJFugp_KGqNBdPSLlpHSlGrSmrhLd6WCDJdYAgWplKvsJBstGKKUxkX9tcdKIyWNtKlvnDGWY0w5xpzFRG7pE0ds_JwqEndJMpw0flswp6qz_GX-TbEQxnmzXwo8olY7Vwn541LQVuRb-ZiSFLD6vsVww7GHYyvD68AUVHcCg" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body class="path-sites d-flex flex-column h-100">
|
||||||
|
|
||||||
|
|
||||||
|
<div class="dialog-off-canvas-main-canvas d-flex flex-column h-100" data-off-canvas-main-canvas>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<p class="sr-only"><a href="#main-content" accesskey="M">Go to the main content</a></p>
|
||||||
|
|
||||||
|
<div class="navbar navbar-expand-lg navbar-dark text-light bg-primary header">
|
||||||
|
<div class="container logo-menu-wrapper d-flex">
|
||||||
|
|
||||||
|
<div class="region region-nav-branding">
|
||||||
|
<div id="block-enisaweb-branding" class="block block-system block-system-branding-block">
|
||||||
|
|
||||||
|
|
||||||
|
<div class="navbar-brand d-flex align-items-center">
|
||||||
|
|
||||||
|
<a href="/" title="Home" rel="home" class="site-logo d-block">
|
||||||
|
<img src="/sites/default/files/enisa-logo.svg" alt="Home" fetchpriority="high" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<nav class="collapse navbar-collapse justify-content-end main-menu" id="navbarSupportedContent" role="navigation" aria-label="Main menu and Search box">
|
||||||
|
<div class="region region-nav-main">
|
||||||
|
<div id="block-enisaweb-mainnavigation-2" class="block block-we-megamenu block-we-megamenu-blockmain">
|
||||||
|
|
||||||
|
|
||||||
|
<div class="region-we-mega-menu">
|
||||||
|
<a href="javascript:" class="navbar-toggle collapsed" aria-label="Open/close menu" aria-controls="mainMenuResponsive" name="menu-button" role="button">
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
</a>
|
||||||
|
<nav class="main navbar navbar-default navbar-we-mega-menu mobile-collapse hover-action" data-menu-name="main" data-block-theme="enisaweb" data-style="Default" data-animation="None" data-delay="" data-duration="" data-autoarrow="" data-alwayshowsubmenu="" data-action="hover" data-mobile-collapse="0" aria-label="ENISA main menu" id="mainMenuResponsive">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<ul class="we-mega-menu-ul nav nav-tabs">
|
||||||
|
<li class="we-mega-menu-li justify dropdown-menu" title="" data-level="0" data-element-type="" data-id="320e2c86-310b-4f7b-a4a9-188df34c3e43" data-submenu="1" data-hide-sub-when-collapse="" data-group="0" data-caption="" data-alignsub="justify" data-target="" data-icon="" >
|
||||||
|
<span class="we-megamenu-nolink">
|
||||||
|
Topics</span>
|
||||||
|
<div class="we-mega-menu-submenu" data-element-type="we-mega-menu-submenu" data-submenu-width="" data-class="" style="width: px">
|
||||||
|
<div class="we-mega-menu-submenu-inner">
|
||||||
|
<div class="we-mega-menu-row" data-element-type="we-mega-menu-row" data-custom-row="1">
|
||||||
|
<div class="we-mega-menu-col span3" data-element-type="we-mega-menu-col" data-width="3" data-block="enisaweb_topics" data-blocktitle="0" data-hidewhencollapse="" data-class="">
|
||||||
|
<div class="type-of-block"><div class="block-inner"><div id="block-enisaweb-topics" class="block block-block-content block-block-contentec3f1f7d-35d4-4776-a03c-7f97a2fcfc8f">
|
||||||
|
|
||||||
|
<p class="title"><a href="/topics" target="_self" title="Access to All Topics page">Topics</a></p>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><p>Learn more about the topics</p>
|
||||||
|
<p><a class="button" href="/topics" data-entity-type="node" data-entity-uuid="5f4db810-e19d-49a4-baaa-86ec782eb91e" data-entity-substitution="canonical" title="Topics">Access</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="we-mega-menu-col span3" data-element-type="we-mega-menu-col" data-width="3" data-block="enisaweb_views_block__topics_tax_block_1" data-blocktitle="0" data-hidewhencollapse="" data-class="">
|
||||||
|
<div class="type-of-block"><div class="block-inner"><div class="views-element-container block block-views block-views-blocktopics-tax-block-1" id="block-enisaweb-views-block-topics-tax-block-1">
|
||||||
|
|
||||||
|
<p class="title"><a href="/audience/national-eu-authorities" target="_self" title="Access to For National / EU authorities page">For National / EU authorities</a></p>
|
||||||
|
|
||||||
|
<div data-block="nav_main"><div class="view view-topics-tax view-id-topics_tax view-display-id-block_1 js-view-dom-id-7c1d2b87dabde327b4783b35298dbb11acd8b020c150d501b78875867b1ec97b">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="view-content">
|
||||||
|
<div class="item-list">
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
|
||||||
|
<li><a href="/topics/cyber-threats" hreflang="en">Cyber Threats</a></li>
|
||||||
|
<li><a href="/topics/eu-incident-response-and-cyber-crisis-management" hreflang="en">EU incident response and cyber crisis management</a></li>
|
||||||
|
<li><a href="/topics/market" hreflang="en">Market</a></li>
|
||||||
|
<li><a href="/topics/product-security-and-certification" hreflang="en">Product Security and Certification</a></li>
|
||||||
|
<li><a href="/topics/risk-management" hreflang="en">Risk Management</a></li>
|
||||||
|
<li><a href="/topics/skills-and-competences" hreflang="en">Skills and competences</a></li>
|
||||||
|
<li><a href="/topics/state-of-cybersecurity-in-the-eu" hreflang="en">State of cybersecurity in the EU</a></li>
|
||||||
|
<li><a href="/topics/vulnerability-disclosure" hreflang="en">Vulnerability Disclosure</a></li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div></div></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="we-mega-menu-col span3" data-element-type="we-mega-menu-col" data-width="3" data-block="enisaweb_views_block__topics_tax_block_2" data-blocktitle="0" data-hidewhencollapse="" data-class="">
|
||||||
|
<div class="type-of-block"><div class="block-inner"><div class="views-element-container title block block-views block-views-blocktopics-tax-block-2" id="block-enisaweb-views-block-topics-tax-block-2">
|
||||||
|
|
||||||
|
<p class="title"><a href="/audience/private-sector" target="_self" title="Access to Private Sector page">Private Sector</a></p>
|
||||||
|
|
||||||
|
<div data-block="nav_main"><div class="view view-topics-tax view-id-topics_tax view-display-id-block_2 js-view-dom-id-74b36a87a5d07d7f173c71b98d555f07f38adc86d0349f9aa7359a3e5c5fbbe7">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="view-content">
|
||||||
|
<div class="item-list">
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
|
||||||
|
<li><a href="/topics/artificial-intelligence-and-next-gen-technologies" hreflang="en">Artificial Intelligence and Next Gen Technologies</a></li>
|
||||||
|
<li><a href="/topics/awareness-and-cyber-hygiene" hreflang="en">Awareness and Cyber Hygiene</a></li>
|
||||||
|
<li><a href="/topics/certification-and-standards" hreflang="en">Certification and Standards</a></li>
|
||||||
|
<li><a href="/topics/cyber-threats" hreflang="en">Cyber Threats</a></li>
|
||||||
|
<li><a href="/topics/cybersecurity-of-critical-sectors" hreflang="en">Cybersecurity of Critical Sectors</a></li>
|
||||||
|
<li><a href="/topics/digital-identity-and-data-protection" hreflang="en">Digital Identity and Data Protection</a></li>
|
||||||
|
<li><a href="/topics/incident-management" hreflang="en">Incident management</a></li>
|
||||||
|
<li><a href="/topics/risk-management" hreflang="en">Risk Management</a></li>
|
||||||
|
<li><a href="/topics/skills-and-competences-for-companies" hreflang="en">Skills and competences (for companies)</a></li>
|
||||||
|
<li><a href="/topics/vulnerability-disclosure" hreflang="en">Vulnerability Disclosure</a></li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div></div></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="we-mega-menu-col span3" data-element-type="we-mega-menu-col" data-width="3" data-block="enisaweb_views_block__topics_tax_block_3" data-blocktitle="0" data-hidewhencollapse="" data-class="">
|
||||||
|
<div class="type-of-block"><div class="block-inner"><div class="views-element-container title block block-views block-views-blocktopics-tax-block-3" id="block-enisaweb-views-block-topics-tax-block-3">
|
||||||
|
|
||||||
|
<p class="title"><a href="/audience/citizens" target="_self" title="Access to Citizens page">Citizens</a></p>
|
||||||
|
|
||||||
|
<div data-block="nav_main"><div class="view view-topics-tax view-id-topics_tax view-display-id-block_3 js-view-dom-id-83bf5b206d0154c644b588d05a80369b2cba5116f1dc1735d8b2cd51de25353a">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="view-content">
|
||||||
|
<div class="item-list">
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
|
||||||
|
<li><a href="/topics/cyber-hygiene" hreflang="en">Cyber Hygiene</a></li>
|
||||||
|
<li><a href="/topics/cyber-incident-awareness" hreflang="en">Cyber Incident Awareness</a></li>
|
||||||
|
<li><a href="/topics/education-and-career-path" hreflang="en">Education and career path</a></li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div></div></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li><li class="we-mega-menu-li" title="Access to all publications" data-level="0" data-element-type="" data-id="2d24a690-803d-44ad-8d7e-8cf0eb7d0e40" data-submenu="0" data-hide-sub-when-collapse="" data-group="0" data-caption="" data-alignsub="" data-target="_self" data-icon="" >
|
||||||
|
<a class="we-mega-menu-li" title="" href="/publications" target="_self">
|
||||||
|
Publications </a>
|
||||||
|
|
||||||
|
</li><li class="we-mega-menu-li dropdown-menu" title="" data-level="0" data-element-type="" data-id="54b069d8-f99f-45e7-9d50-9345e82ad14f" data-submenu="1" data-hide-sub-when-collapse="" data-group="0" data-caption="" data-alignsub="" data-target="" data-icon="" >
|
||||||
|
<span class="we-megamenu-nolink">
|
||||||
|
Newsroom & Events</span>
|
||||||
|
<div class="we-mega-menu-submenu" data-element-type="we-mega-menu-submenu" data-submenu-width="" data-class="" style="width: px">
|
||||||
|
<div class="we-mega-menu-submenu-inner">
|
||||||
|
<div class="we-mega-menu-row" data-element-type="we-mega-menu-row" data-custom-row="1">
|
||||||
|
<div class="we-mega-menu-col span4" data-element-type="we-mega-menu-col" data-width="4" data-block="enisaweb_news" data-blocktitle="0" data-hidewhencollapse="" data-class="">
|
||||||
|
<div class="type-of-block"><div class="block-inner"><div id="block-enisaweb-news" class="block block-block-content block-block-content00f65cb0-e1b0-41a1-8a0d-00ce6ced0f0e">
|
||||||
|
|
||||||
|
<p class="title"><a href="/news" target="_self" title="Access to All News">News</a></p>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><p>Cybersecurity in focus: News & updates from ENISA</p>
|
||||||
|
<p><a class="button" href="/news" data-entity-type="node" data-entity-uuid="088dd847-33ff-4c8e-b094-f0d38a66bbdd" data-entity-substitution="canonical" title="News">Access</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="we-mega-menu-col span4" data-element-type="we-mega-menu-col" data-width="4" data-block="enisaweb_events" data-blocktitle="0" data-hidewhencollapse="" data-class="">
|
||||||
|
<div class="type-of-block"><div class="block-inner"><div id="block-enisaweb-events" class="block block-block-content block-block-contentb3c06780-f342-437d-9828-54fe86eb9786">
|
||||||
|
|
||||||
|
<p class="title"><a href="/events" target="_self" title="Access to All Events">Events</a></p>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><p>Cybersecurity in practice: Events & Workshops by ENISA</p>
|
||||||
|
<p><a class="button" href="/events" data-entity-type="node" data-entity-uuid="34f66c6d-0f07-4977-85e3-e8578a59d59c" data-entity-substitution="canonical" title="Events">Access</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="we-mega-menu-col span4" data-element-type="we-mega-menu-col" data-width="4" data-block="enisaweb_pressoffice" data-blocktitle="0" data-hidewhencollapse="" data-class="">
|
||||||
|
<div class="type-of-block"><div class="block-inner"><div id="block-enisaweb-pressoffice" class="block block-system block-system-menu-blockpress-office">
|
||||||
|
|
||||||
|
<p class="title"><a href="/press-office" target="_self" title="Access to Press office page">Press office</a></p>
|
||||||
|
|
||||||
|
|
||||||
|
<ul data-block="nav_main" class="nav navbar-nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/press-office/corporate-identity" class="nav-link" data-drupal-link-system-path="node/11030">Corporate identity</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/press-office/cybersecurity-material" class="nav-link" data-drupal-link-system-path="node/11031">Cybersecurity material</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/form/seek-an-expert" class="nav-link" data-drupal-link-system-path="webform/seek_an_expert">Seek an expert or Request a speaker</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div></div></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li><li class="we-mega-menu-li dropdown-menu" title="" data-level="0" data-element-type="" data-id="eaba29ce-6446-4da3-930a-c3d97495f00b" data-submenu="1" data-hide-sub-when-collapse="" data-group="0" data-caption="" data-alignsub="" data-target="" data-icon="" >
|
||||||
|
<span class="we-megamenu-nolink">
|
||||||
|
About</span>
|
||||||
|
<div class="we-mega-menu-submenu" data-element-type="we-mega-menu-submenu" data-submenu-width="" data-class="" style="width: px">
|
||||||
|
<div class="we-mega-menu-submenu-inner">
|
||||||
|
<div class="we-mega-menu-row" data-element-type="we-mega-menu-row" data-custom-row="1">
|
||||||
|
<div class="we-mega-menu-col span4" data-element-type="we-mega-menu-col" data-width="4" data-block="enisaweb_whatwedosubmenubutton_2" data-blocktitle="0" data-hidewhencollapse="" data-class="">
|
||||||
|
<div class="type-of-block"><div class="block-inner"><div id="block-enisaweb-whatwedosubmenubutton-2" class="block block-block-content block-block-contente0a96203-1eca-4991-9533-33064cace452">
|
||||||
|
|
||||||
|
<p class="title"><a href="/about-enisa/what-we-do" target="_self" title="Access to What we do page">What we do</a></p>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><p>Achieving a high common level of cybersecurity across Europe</p>
|
||||||
|
<p><a class="button" href="/about-enisa/what-we-do" data-entity-type="node" data-entity-uuid="0619d110-d4dc-4f6b-aa0d-9a56252da07d" data-entity-substitution="canonical" title="What we do">Access</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div></div></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="we-mega-menu-col span4" data-element-type="we-mega-menu-col" data-width="4" data-block="enisaweb_whatwedosubmenu_2" data-blocktitle="0" data-hidewhencollapse="" data-class="">
|
||||||
|
<div class="type-of-block"><div class="block-inner"><div id="block-enisaweb-whatwedosubmenu-2" class="block block-block-content block-block-content99599d89-56f7-43f1-907b-72614045018d">
|
||||||
|
|
||||||
|
<p class="title"><a href="/about-enisa/who-we-are" target="_self" title="Access to Who we are page">Who we are</a></p>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><p>Towards a Trusted and Cyber Secure Europe </p>
|
||||||
|
<p><a class="button" href="/about-enisa/who-we-are" data-entity-type="node" data-entity-uuid="3c46c210-298c-472c-b871-b167dfe2642c" data-entity-substitution="canonical" title="Who we are">Access</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div></div></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="we-mega-menu-col span4" data-element-type="we-mega-menu-col" data-width="4" data-block="enisaweb_transparency" data-blocktitle="0" data-hidewhencollapse="" data-class="">
|
||||||
|
<div class="type-of-block"><div class="block-inner"><div id="block-enisaweb-transparency" class="block block-system block-system-menu-blocktransparency">
|
||||||
|
|
||||||
|
<p class="title"><a href="/about-enisa/How-we-work" target="_self" title="Access to How we work page">How we work</a></p>
|
||||||
|
|
||||||
|
|
||||||
|
<ul data-block="nav_main" class="nav navbar-nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/about-enisa/accounting-finance/accounting-finance" class="nav-link" data-drupal-link-system-path="node/17339">Accounting and Finance</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/about-enisa/procedures-and-policies" class="nav-link dropdown-toggle" data-drupal-link-system-path="node/17360">Policies and Procedures</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/about-enisa/data-protection/data-protection" class="nav-link dropdown-toggle" data-drupal-link-system-path="node/17346">Data Protection</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/about-enisa/transparency" class="nav-link dropdown-toggle" data-drupal-link-system-path="node/17404">Transparency</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/enisa-a-climate-neutral-agency" class="nav-link" data-drupal-link-system-path="node/18893">ENISA, a climate neutral agency </a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="we-mega-menu-row" data-element-type="we-mega-menu-row" data-custom-row="1">
|
||||||
|
<div class="we-mega-menu-col span12 transversal-menu" data-element-type="we-mega-menu-col" data-width="12" data-block="enisaweb_transversaloptionsofaboutmenu" data-blocktitle="0" data-hidewhencollapse="" data-class="transversal-menu">
|
||||||
|
<div class="type-of-block"><div class="block-inner"><div id="block-enisaweb-transversaloptionsofaboutmenu" class="block block-system block-system-menu-blocktransversal-options-about-menu">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<ul data-block="nav_main" class="nav navbar-nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/about-enisa/international-cooperation" class="nav-link" data-drupal-link-system-path="node/19377">International Cooperation</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li><li class="we-mega-menu-li dropdown-menu" title="" data-level="0" data-element-type="" data-id="44fbec5d-1aaf-4cb3-b06b-a79b571ad50c" data-submenu="1" data-hide-sub-when-collapse="" data-group="0" data-caption="" data-alignsub="" data-target="" data-icon="" >
|
||||||
|
<span class="we-megamenu-nolink">
|
||||||
|
Working with us</span>
|
||||||
|
<div class="we-mega-menu-submenu" data-element-type="we-mega-menu-submenu" data-submenu-width="" data-class="" style="width: px">
|
||||||
|
<div class="we-mega-menu-submenu-inner">
|
||||||
|
<div class="we-mega-menu-row" data-element-type="we-mega-menu-row" data-custom-row="1">
|
||||||
|
<div class="we-mega-menu-col span4" data-element-type="we-mega-menu-col" data-width="4" data-block="enisaweb_workwithenisa" data-blocktitle="0" data-hidewhencollapse="" data-class="">
|
||||||
|
<div class="type-of-block"><div class="block-inner"><div id="block-enisaweb-workwithenisa" class="block block-block-content block-block-content86d5ce2f-abc2-4160-afda-442fe2c68258">
|
||||||
|
|
||||||
|
<p class="title"><a href="/working-with-us/working-for-enisa" target="_self" title="Access to Working for ENISA page">Working for ENISA</a></p>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><p>Explore the benefits of working for ENISA</p>
|
||||||
|
<p><a class="button" href="/working-with-us/working-for-enisa" data-entity-type="node" data-entity-uuid="f10da531-46ad-49f6-84cb-0770c69d839e" data-entity-substitution="canonical" title="Working for ENISA">Access</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="we-mega-menu-col span8" data-element-type="we-mega-menu-col" data-width="8" data-block="enisaweb_workingwithus" data-blocktitle="0" data-hidewhencollapse="" data-class="">
|
||||||
|
<div class="type-of-block"><div class="block-inner"><div id="block-enisaweb-workingwithus" class="title block block-system block-system-menu-blockworking-with-us">
|
||||||
|
|
||||||
|
<p class="title"><a href="/work-with-us" target="_self" title="Access to Working with us page">Working with us</a></p>
|
||||||
|
|
||||||
|
|
||||||
|
<ul data-block="nav_main" class="nav navbar-nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/careers" class="nav-link" data-drupal-link-system-path="node/12487">Careers</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/working-with-us/procurement" class="nav-link" data-drupal-link-system-path="node/17421">Procurement</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/working-with-us/ad-hoc-working-groups-calls" class="nav-link" data-drupal-link-system-path="node/17422">Ad hoc working groups</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div></div></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="btn-search">
|
||||||
|
<a href="javascript:" title="Show the search field">Search</a>
|
||||||
|
</p>
|
||||||
|
<div class="search-form-wrapper" aria-label="Search field">
|
||||||
|
<div class="region region-nav-additional">
|
||||||
|
<div id="block-enisaweb-search" class="search-block-form block block-search container-inline" data-drupal-selector="search-block-form" id="block-enisaweb-search-form" role="search">
|
||||||
|
|
||||||
|
|
||||||
|
<form data-block="nav_additional" action="/search" method="get" id="search-block-form" accept-charset="UTF-8">
|
||||||
|
<div class="js-form-item form-item js-form-type-search form-type-search js-form-item-keys form-item-keys form-no-label">
|
||||||
|
<label class="visually-hidden" for="edit-keys">Search</label>
|
||||||
|
<input class="form-search form-control" title="Enter the terms you wish to search for." data-drupal-selector="edit-keys" type="search" id="edit-keys" name="keys" value="" size="15" maxlength="128">
|
||||||
|
</div>
|
||||||
|
<div class="form-actions js-form-wrapper form-wrapper" data-drupal-selector="edit-actions" id="edit-actions-search">
|
||||||
|
<button class="button js-form-submit form-submit btn-enisa btn-primary" data-drupal-selector="edit-submit" type="submit" id="edit-submit" value="Search">Search</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="btn-close-search"><a href="javascript:" title="Hide the search field">Close</a></p>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main role="main" class="container-full image-banner">
|
||||||
|
<div class="title-breadcrumbs container">
|
||||||
|
|
||||||
|
<div class="title-intro full">
|
||||||
|
|
||||||
|
<div id="block-enisaweb-page-title" class="block block-core block-page-title-block">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<h1>Page not found</h1>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="main container" id="main-content">
|
||||||
|
<a id="main-content" tabindex="-1"></a> <div class="container">
|
||||||
|
<div data-drupal-messages-fallback class="hidden"></div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="row g-0 not-found-page">
|
||||||
|
<div class="col-xs-12 col-md-12 col-lg-6 not-found-image">
|
||||||
|
<div class="image"></div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-12 col-lg-6 not-found-text">
|
||||||
|
<h2>OOPS! PAGE NOT FOUND</h2>
|
||||||
|
<p>The page you are looking for can't be found.</p>
|
||||||
|
<p><a href="https://www.enisa.europa.eu/" class="btn-all left">Go to the homepage</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<section class="subscribe-section" role="complementary">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3 col-lg-3 subscribe-image"></div>
|
||||||
|
<div class="col-md-9 col-lg-9 subscribe-wrapper">
|
||||||
|
<h2>Subscribe</h2>
|
||||||
|
<p><strong>Stay updated with ENISA!</strong> Sign up for email alerts on publications, events, vacancies,
|
||||||
|
and more.</p>
|
||||||
|
<p><a href="/alertservice" class="btn-all left">Sign up now</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer class="mt-auto enisa-footer">
|
||||||
|
<div class="container">
|
||||||
|
<div class="footer container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12 col-lg-3">
|
||||||
|
<div id="block-enisaweb-enisalogos">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item">
|
||||||
|
<div class="enisa-logo">
|
||||||
|
<img src="/themes/custom/enisaweb/images/enisa-logo-white.svg"
|
||||||
|
alt="ENISA, European Union Agency for Cybersecurity" width="220" height="150"
|
||||||
|
class="align-left" loading="lazy">
|
||||||
|
<p><em>A Trusted and Cyber Secure Europe</em></p>
|
||||||
|
</div>
|
||||||
|
<div class="agencies-network-logo">
|
||||||
|
<img src="/themes/custom/enisaweb/images/agencies-network.png"
|
||||||
|
alt="Agencies network logo" width="39" height="36" class="align-left"
|
||||||
|
loading="lazy">
|
||||||
|
<p><a href="https://agencies-network.europa.eu/index_en">EU Agencies Network</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="block-enisaweb-socialmedialinks" class="block-social-media-links block block-social-media-links-block">
|
||||||
|
|
||||||
|
<p>Follow us on</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<ul class="social-media-links--platforms platforms vertical">
|
||||||
|
<li>
|
||||||
|
<a class="social-media-link-icon--youtube" href="https://www.youtube.com/user/ENISAvideos" target="_blank" >
|
||||||
|
<span class='fab fa-youtube fa-2x'></span>
|
||||||
|
<span class="platform-name">Youtube</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="social-media-link-icon--twitter" href="https://x.com/enisa_eu" target="_blank" >
|
||||||
|
<span class='fab fa-x-twitter fa-2x'></span>
|
||||||
|
<span class="platform-name">X</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="social-media-link-icon--linkedin" href="https://www.linkedin.com/company/european-union-agency-for-cybersecurity-enisa/" target="_blank" >
|
||||||
|
<span class='fab fa-linkedin fa-2x'></span>
|
||||||
|
<span class="platform-name">LinkedIn</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="social-media-link-icon--facebook" href="https://www.facebook.com/ENISAEUAGENCY" target="_blank" >
|
||||||
|
<span class='fab fa-facebook fa-2x'></span>
|
||||||
|
<span class="platform-name">Facebook</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-6 col-lg-3 border-left">
|
||||||
|
<nav aria-labelledby="block-enisaweb-contactus-menu" id="block-enisaweb-contactus" class="block block-menu navigation menu--contact-us">
|
||||||
|
|
||||||
|
<p id="block-enisaweb-contactus-menu">Contact us</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<ul data-block="footer" class="nav navbar-nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/about-enisa/contact/contact" class="nav-link" data-drupal-link-system-path="node/17344">Contacts</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/form/contact-form" class="nav-link" data-drupal-link-system-path="webform/contact_form">General queries</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/working-with-us/procurement" class="nav-link" data-drupal-link-system-path="node/17421">Public Procurement</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/form/media-inquiries" class="nav-link" data-drupal-link-system-path="webform/media_inquiries">Media inquiries</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-6 col-lg-3 border-left">
|
||||||
|
<div id="block-enisaweb-findoutaboutus" class="block block-system block-system-menu-blockfind-out-about-us">
|
||||||
|
|
||||||
|
<p>Find out about us</p>
|
||||||
|
|
||||||
|
|
||||||
|
<ul data-block="footer" class="nav navbar-nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/accessibility-statement" class="nav-link" data-drupal-link-system-path="node/18887">Accessibility</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/about-enisa/legal-notice" class="nav-link" data-drupal-link-system-path="node/17355">Legal Notice</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/about-enisa/data-protection/data-protection" class="nav-link" data-drupal-link-system-path="node/17346">Data Protection</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/about-enisa/cookies" class="nav-link" data-drupal-link-system-path="node/17345">Cookies</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/sitemap" target="_blank" class="nav-link" data-drupal-link-system-path="sitemap">Sitemap</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-6 col-lg-3 border-left">
|
||||||
|
<div id="block-enisaweb-pageofinterest" class="block block-system block-system-menu-blockpage-of-interest">
|
||||||
|
|
||||||
|
<p>Page of interest</p>
|
||||||
|
|
||||||
|
|
||||||
|
<ul data-block="footer" class="nav navbar-nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/publications" class="nav-link" data-drupal-link-system-path="publications">Publications </a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/press-office" class="nav-link" data-drupal-link-system-path="node/11033">Press Office</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/digital-tools" class="nav-link" data-drupal-link-system-path="digital-tools">Digital Tools</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/work-with-us" class="nav-link" data-drupal-link-system-path="node/11443">Working with us</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/about-enisa/public-access-to-documents" class="nav-link" data-drupal-link-system-path="node/17363">Public access to documents</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="line"></div>
|
||||||
|
<div class="copy container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12 col-md-12 col-lg-6 alignleft">
|
||||||
|
<div id="block-enisaweb-copyrightfooter" class="block block-block-content block-block-content0f0b270e-cc5d-448c-a771-6cc7c0621340">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><p>© 2026 by the European Union Agency for Cybersecurity</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-12 col-lg-6 alignright">
|
||||||
|
<div id="block-enisaweb-enisadescriptionfooter" class="block block-block-content block-block-content2b4d8729-4032-4438-ba87-1a58fbc364db">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><p>ENISA is an agency of the European Union</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p><a href="#" id="totop" class="totop"><span class="sr-only">Go to top</span></a></p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="application/json">{"utility":"piwik","siteID":"5847bf6f-3ce3-4800-8749-1c565b34b7b6","sitePath":["www.enisa.europa.eu"],"is404":true,"instance":"ec"}</script>
|
||||||
|
<script type="application/json">{"utility":"cck","url":"\/about-enisa\/cookies"}</script>
|
||||||
|
|
||||||
|
<script type="application/json" data-drupal-selector="drupal-settings-json">{"path":{"baseUrl":"\/","pathPrefix":"","currentPath":"","currentPathIsAdmin":false,"isFront":false,"currentLanguage":"en"},"pluralDelimiter":"\u0003","suppressDeprecationErrors":true,"ckeditorAccordion":{"accordionStyle":{"collapseAll":1,"keepRowsOpen":0,"animateAccordionOpenAndClose":1,"openTabsWithHash":0,"allowHtmlInTitles":1}},"user":{"uid":0,"permissionsHash":"c1c359b7541ecd1c4f0e321882d2e1eba1197d85c8ad3b45b15aff5871a9e6d0"}}</script>
|
||||||
|
<script src="/sites/default/files/js/js_Uv_fxqHIMtEtS30w0hjV4odzonkoWsMT5KD3y13y8kU.js?scope=footer&delta=0&language=en&theme=enisaweb&include=eJxlj0EOgzAMBD-EG6kfQiYxEOrYNHGE8vuCWkql3mZn97IYgilKc_iB25hVrPMPCtE09-i95hBV3JfeE5LQkcSCGw3uBFgKMBoVu7qJdUCGYo2jTJdfnpVyA98805-tEUyrn2Gt4uefuqjcO6V-D6bKxYVcV-TbmaEkzMaKgXK3UZ9owkRS3ag57Ss4BBwGzhsvrCdlDA"></script>
|
||||||
|
<script src="https://static.addtoany.com/menu/page.js" defer></script>
|
||||||
|
<script src="/sites/default/files/js/js_PJg5XHigxDm_42CAJgw7llont3PySBp-PdpAvGlCYSs.js?scope=footer&delta=2&language=en&theme=enisaweb&include=eJxlj0EOgzAMBD-EG6kfQiYxEOrYNHGE8vuCWkql3mZn97IYgilKc_iB25hVrPMPCtE09-i95hBV3JfeE5LQkcSCGw3uBFgKMBoVu7qJdUCGYo2jTJdfnpVyA98805-tEUyrn2Gt4uefuqjcO6V-D6bKxYVcV-TbmaEkzMaKgXK3UZ9owkRS3ag57Ss4BBwGzhsvrCdlDA"></script>
|
||||||
|
<script src="/modules/contrib/ckeditor_accordion/js/accordion.frontend.min.js?tg28tw"></script>
|
||||||
|
<script src="/sites/default/files/js/js_fPzrD9aZOLJS9JI2GLgD7Zs-CzoWHT18p8hYIEuW9h4.js?scope=footer&delta=4&language=en&theme=enisaweb&include=eJxlj0EOgzAMBD-EG6kfQiYxEOrYNHGE8vuCWkql3mZn97IYgilKc_iB25hVrPMPCtE09-i95hBV3JfeE5LQkcSCGw3uBFgKMBoVu7qJdUCGYo2jTJdfnpVyA98805-tEUyrn2Gt4uefuqjcO6V-D6bKxYVcV-TbmaEkzMaKgXK3UZ9owkRS3ag57Ss4BBwGzhsvrCdlDA"></script>
|
||||||
|
<script src="https://webtools.europa.eu/load.js" defer></script>
|
||||||
|
<script src="/sites/default/files/js/js_Pj1gX-gXRHcdCBDI1-WO0jTP3o3GK7ZZPT2TZokpFjY.js?scope=footer&delta=6&language=en&theme=enisaweb&include=eJxlj0EOgzAMBD-EG6kfQiYxEOrYNHGE8vuCWkql3mZn97IYgilKc_iB25hVrPMPCtE09-i95hBV3JfeE5LQkcSCGw3uBFgKMBoVu7qJdUCGYo2jTJdfnpVyA98805-tEUyrn2Gt4uefuqjcO6V-D6bKxYVcV-TbmaEkzMaKgXK3UZ9owkRS3ag57Ss4BBwGzhsvrCdlDA"></script>
|
||||||
|
|
||||||
|
<script async="" src="/themes/custom/enisaweb/js/application.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ===== entrypoint.sh =====
|
||||||
|
# Starts any pre-flight checks then exec's the CMD
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH:-}:/opt/rocm/lib"
|
||||||
|
|
||||||
|
# Apply AMD/ROCm env overrides before anything loads torch
|
||||||
|
if [[ "${AMD_COMPUTE:-false}" == "true" ]]; then
|
||||||
|
export TORCH_DEVICE="${TORCH_DEVICE:-cuda}"
|
||||||
|
export TORCH_DEVICE_MODEL="${TORCH_DEVICE:-cuda}"
|
||||||
|
echo "[entrypoint] AMD compute enabled, TORCH_DEVICE=${TORCH_DEVICE}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check torch/ROCm availability
|
||||||
|
python3 -c "import torch; dev=torch.device('cuda' if torch.cuda.is_available() else 'cpu'); print(f'[entrypoint] PyTorch device: {dev}, ROCm: {torch.version.rocm if hasattr(torch.version, \"rocm\") else \"n/a\"}')" 2>&1
|
||||||
|
|
||||||
|
echo "[entrypoint] Starting marker-api on ${HOST:-0.0.0.0}:${PORT:-8000} ..."
|
||||||
|
exec "$@"
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# gunicorn.conf.py -- production WSGI config for marker-api
|
||||||
|
import os
|
||||||
|
import multiprocessing
|
||||||
|
|
||||||
|
port = int(os.environ.get("PORT", "8000"))
|
||||||
|
workers = min(multiprocessing.cpu_count() * 2 + 1, 8)
|
||||||
|
|
||||||
|
bind = f"0.0.0.0:{port}"
|
||||||
|
workers = int(os.environ.get("GUNICORN_WORKERS", "1"))
|
||||||
|
workers = min(workers, 8)
|
||||||
|
|
||||||
|
worker_class = "gthread"
|
||||||
|
threads = int(os.environ.get("GUNICORN_THREADS", "4"))
|
||||||
|
timeout = int(os.environ.get("GUNICORN_TIMEOUT", "300"))
|
||||||
|
graceful_timeout = 60
|
||||||
|
|
||||||
|
accesslog = "-"
|
||||||
|
errorlog = "-"
|
||||||
|
loglevel = os.environ.get("GUNICORN_LOGLEVEL", "info")
|
||||||
|
|
||||||
|
# Preload so models are loaded once in master process (not duplicated per-worker)
|
||||||
|
preload_app = True
|
||||||
|
|
||||||
|
# Enable worker recycling to prevent memory leaks
|
||||||
|
max_requests = 1000
|
||||||
|
max_requests_jitter = 100
|
||||||
|
|
||||||
|
# Tempdir for marker uploads
|
||||||
|
tempdir = "/app/uploads"
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
# ==================================================
|
||||||
|
# kubernetes/deployment.yaml -- marker-api (Kubernetes)
|
||||||
|
# ==================================================
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: marker-api-config
|
||||||
|
data:
|
||||||
|
OLLAMA_HOST: "http://10.0.1.127:11434"
|
||||||
|
DEESEEK_OCR_MODEL: "deepseek-ocr"
|
||||||
|
AMD_COMPUTE: "false"
|
||||||
|
TORCH_DEVICE: ""
|
||||||
|
MODEL_DTYPE: "float32"
|
||||||
|
PORT: "8000"
|
||||||
|
HOST: "0.0.0.0"
|
||||||
|
GUNICORN_WORKERS: "2"
|
||||||
|
GUNICORN_THREADS: "4"
|
||||||
|
GUNICORN_TIMEOUT: "300"
|
||||||
|
FLASK_DEBUG: "0"
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: marker-api
|
||||||
|
labels:
|
||||||
|
app: marker-api
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: marker-api
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: marker-api
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: marker-api
|
||||||
|
image: marker-api:latest # build/push your own image
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
ports:
|
||||||
|
- containerPort: 8000
|
||||||
|
name: http
|
||||||
|
protocol: TCP
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: marker-api-config
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: "2"
|
||||||
|
memory: "4Gi"
|
||||||
|
# Set to marker.nvidia.com/gpu or amd.com/gpu if using GPU plugin
|
||||||
|
nvidia.com/gpu: "0" # remove if using AMD/ROCm via volume mounts
|
||||||
|
volumeMounts:
|
||||||
|
- name: conversion-results
|
||||||
|
mountPath: /app/conversion_results
|
||||||
|
- name: uploads
|
||||||
|
mountPath: /app/uploads
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 8000
|
||||||
|
initialDelaySeconds: 120
|
||||||
|
periodSeconds: 30
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 8000
|
||||||
|
initialDelaySeconds: 60
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 3
|
||||||
|
failureThreshold: 3
|
||||||
|
volumes:
|
||||||
|
- name: conversion-results
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: marker-api-pvc
|
||||||
|
- name: uploads
|
||||||
|
emptyDir:
|
||||||
|
sizeLimit: 2Gi # adjustable; use PVC for larger data
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: marker-api-pvc
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteMany
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 10Gi
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: marker-api
|
||||||
|
labels:
|
||||||
|
app: marker-api
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: marker-api
|
||||||
|
ports:
|
||||||
|
- port: 8000
|
||||||
|
targetPort: 8000
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
|
type: ClusterIP
|
||||||
|
---
|
||||||
|
# For node-local AMD GPU support in K8s, bind /dev/kfd and /dev/dri:
|
||||||
|
# Use a DaemonSet with hostPaths instead, or mount via GPU plugin.
|
||||||
|
# Example DaemonSet for AMD GPU nodes:
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: DaemonSet
|
||||||
|
metadata:
|
||||||
|
name: marker-api-gpu
|
||||||
|
labels:
|
||||||
|
app: marker-api
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: marker-api
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: marker-api
|
||||||
|
spec:
|
||||||
|
hostNetwork: true
|
||||||
|
hostPID: true
|
||||||
|
containers:
|
||||||
|
- name: marker-api
|
||||||
|
image: marker-api:latest
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
ports:
|
||||||
|
- containerPort: 8000
|
||||||
|
hostPort: 8000
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: marker-api-config
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: "2"
|
||||||
|
memory: "4Gi"
|
||||||
|
limits:
|
||||||
|
cpu: "4"
|
||||||
|
memory: "8Gi"
|
||||||
|
volumeMounts:
|
||||||
|
- name: conversion-results
|
||||||
|
mountPath: /app/conversion_results
|
||||||
|
- name: kfd
|
||||||
|
mountPath: /dev/kfd
|
||||||
|
- name: dri
|
||||||
|
mountPath: /dev/dri
|
||||||
|
volumes:
|
||||||
|
- name: conversion-results
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: marker-api-pvc
|
||||||
|
- name: kfd
|
||||||
|
hostPath:
|
||||||
|
path: /dev/kfd
|
||||||
|
- name: dri
|
||||||
|
hostPath:
|
||||||
|
path: /dev/dri
|
||||||
Submodule
+1
Submodule marker added at 6ae38895d6
@@ -0,0 +1,444 @@
|
|||||||
|
#requires -Version 5.1
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Recursively convert supported documents to markdown via marker-api.
|
||||||
|
.DESCRIPTION
|
||||||
|
Scans a target folder for PDFs, DOCX, XLSX, PPTX, EPUB, images, and HTML.
|
||||||
|
Sends each file to the marker-api /marker endpoint (multipart/form-data)
|
||||||
|
and writes the resulting .md file beside the original.
|
||||||
|
.EXAMPLE
|
||||||
|
.\marker-convert.ps1 -TargetFolder .\documents
|
||||||
|
.\marker-convert.ps1 .\documents -Force -MaxConcurrency 8
|
||||||
|
.\marker-convert.ps1 .\documents -ApiUrl http://10.0.0.5:8000/marker
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true, Position = 0)]
|
||||||
|
[string]$TargetFolder,
|
||||||
|
|
||||||
|
[Uri]$ApiUrl = "http://localhost:8000/marker",
|
||||||
|
|
||||||
|
[ValidateSet("markdown", "json", "html", "chunks")]
|
||||||
|
[string]$OutputFormat = "markdown",
|
||||||
|
|
||||||
|
[switch]$Force,
|
||||||
|
|
||||||
|
[ValidateRange(1, 32)]
|
||||||
|
[int]$MaxConcurrency = 4,
|
||||||
|
|
||||||
|
[ValidateRange(30, 1800)]
|
||||||
|
[int]$Timeout = 300,
|
||||||
|
|
||||||
|
[string]$PageRange = "",
|
||||||
|
|
||||||
|
[switch]$ForceOcr,
|
||||||
|
|
||||||
|
[switch]$DisableImageExtraction,
|
||||||
|
|
||||||
|
[switch]$UseLlm,
|
||||||
|
|
||||||
|
[string]$LlmService = "marker.services.ollama.OllamaService",
|
||||||
|
|
||||||
|
[string]$Processors = "",
|
||||||
|
|
||||||
|
[string]$ConfigJson = "",
|
||||||
|
|
||||||
|
[string]$ConverterCls = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
# ════════════ 1. Validate ════════════
|
||||||
|
$TargetFolder = (Resolve-Path $TargetFolder).Path
|
||||||
|
if (-not (Test-Path $TargetFolder)) {
|
||||||
|
Write-Error "Target folder does not exist: $TargetFolder"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "====================================================" -ForegroundColor Cyan
|
||||||
|
Write-Host " Force overwrite: $(if ($Force) { 'Yes' } else { 'No' })" -ForegroundColor White
|
||||||
|
Write-Host " Force OCR : $(if ($ForceOcr) { 'Yes' } else { 'No' })" -ForegroundColor White
|
||||||
|
Write-Host " Use LLM : $(if ($UseLlm) { 'Yes' } else { 'No' })" -ForegroundColor White
|
||||||
|
Write-Host " LLM Service : $LlmService" -ForegroundColor White
|
||||||
|
Write-Host " Disable ImgExt : $(if ($DisableImageExtraction) { 'Yes' } else { 'No' })" -ForegroundColor White
|
||||||
|
if ($PageRange) { Write-Host " Page range : $PageRange" -ForegroundColor White }
|
||||||
|
if ($Processors) { Write-Host " Processors : $Processors" -ForegroundColor White }
|
||||||
|
if ($ConverterCls) { Write-Host " Converter : $ConverterCls" -ForegroundColor White }
|
||||||
|
Write-Host "====================================================" -ForegroundColor Cyan
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
# ════════════ 2. Discover files ════════════
|
||||||
|
$supportedExtensions = @('.pdf', '.docx', '.xlsx', '.pptx', '.epub',
|
||||||
|
'.png', '.jpg', '.jpeg', '.bmp', '.gif',
|
||||||
|
'.tiff', '.tif', '.webp', '.heic', '.html', '.htm')
|
||||||
|
|
||||||
|
$files = Get-ChildItem -Path $TargetFolder -Recurse -File |
|
||||||
|
Where-Object { $_.Extension -in $supportedExtensions } |
|
||||||
|
Sort-Object FullName
|
||||||
|
|
||||||
|
if ($files.Count -eq 0) {
|
||||||
|
Write-Host "No supported files found in $TargetFolder." -ForegroundColor Yellow
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Found $($files.Count) document(s) to convert." -ForegroundColor Green
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
# ════════════ 3. Worker function ════════════
|
||||||
|
# This script block runs inside Start-Job for each file.
|
||||||
|
$ScriptBlock = {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$FilePath,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[Uri]$ApiUrl,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$Ofmt,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[bool]$Force,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[int]$Timeout,
|
||||||
|
|
||||||
|
[string]$PageRange,
|
||||||
|
|
||||||
|
[bool]$ForceOcr,
|
||||||
|
|
||||||
|
[bool]$DisableImageExtraction,
|
||||||
|
|
||||||
|
[bool]$UseLlm,
|
||||||
|
|
||||||
|
[string]$LlmService,
|
||||||
|
|
||||||
|
[string]$Processors,
|
||||||
|
|
||||||
|
[string]$ConfigJson,
|
||||||
|
|
||||||
|
[string]$ConverterCls
|
||||||
|
)
|
||||||
|
|
||||||
|
# Build output path (same directory, same base name with .md extension)
|
||||||
|
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($FilePath)
|
||||||
|
$dirPath = Split-Path $FilePath
|
||||||
|
$mdPath = Join-Path $dirPath "$baseName.md"
|
||||||
|
|
||||||
|
$result = [ordered]@{}
|
||||||
|
$result.File = (Split-Path $FilePath -Leaf)
|
||||||
|
$result.SizeBytes = (Get-Item $FilePath).Length
|
||||||
|
$result.Status = "pending"
|
||||||
|
$result.ElapsedMs = 0
|
||||||
|
$result.Error = ""
|
||||||
|
$result.OutputFile = ""
|
||||||
|
|
||||||
|
# Skip if output already exists and -Force was not used
|
||||||
|
if ((Test-Path $mdPath) -and ($Force -eq $false)) {
|
||||||
|
$result.Status = "skipped"
|
||||||
|
$result.OutputFile = $mdPath
|
||||||
|
return $result
|
||||||
|
}
|
||||||
|
|
||||||
|
$sw = [System.Diagnostics.Stopwatch]::StartNew()
|
||||||
|
|
||||||
|
try {
|
||||||
|
$bin = [System.IO.File]::ReadAllBytes($FilePath)
|
||||||
|
$name = [System.IO.Path]::GetFileName($FilePath)
|
||||||
|
$boundary = "----marker-convert-$(Get-Random -Minimum 100000 -Maximum 999999)"
|
||||||
|
$enc = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
|
# ── Build multipart body ──
|
||||||
|
$contentParts = [System.Collections.ArrayList]::new()
|
||||||
|
|
||||||
|
# Part 1: file
|
||||||
|
$fileHeader = $enc.GetBytes(
|
||||||
|
"--$boundary`r`n" +
|
||||||
|
"Content-Disposition: form-data; name=`"file`"; filename=`"$name`"`r`n" +
|
||||||
|
"Content-Type: application/octet-stream`r`n`r`n"
|
||||||
|
)
|
||||||
|
$contentParts.Add($fileHeader) | Out-Null
|
||||||
|
$contentParts.Add($bin) | Out-Null
|
||||||
|
$contentParts.Add($enc.GetBytes("`r`n")) | Out-Null
|
||||||
|
|
||||||
|
# Part 2: output_format
|
||||||
|
$fmtPart = $enc.GetBytes(
|
||||||
|
"--$boundary`r`n" +
|
||||||
|
"Content-Disposition: form-data; name=`"output_format`"`r`n`r`n" +
|
||||||
|
"$Ofmt`r`n"
|
||||||
|
)
|
||||||
|
$contentParts.Add($fmtPart) | Out-Null
|
||||||
|
|
||||||
|
# Part 3: force_ocr
|
||||||
|
$ocrPart = $enc.GetBytes(
|
||||||
|
"--$boundary`r`n" +
|
||||||
|
"Content-Disposition: form-data; name=`"force_ocr`"`r`n`r`n" +
|
||||||
|
"$(if ($ForceOcr) { 'true' } else { 'false' })`r`n"
|
||||||
|
)
|
||||||
|
$contentParts.Add($ocrPart) | Out-Null
|
||||||
|
|
||||||
|
# Part 4: paginate_output (default false)
|
||||||
|
$pagPart = $enc.GetBytes(
|
||||||
|
"--$boundary`r`n" +
|
||||||
|
"Content-Disposition: form-data; name=`"paginate_output`"`r`n`r`n" +
|
||||||
|
"false`r`n"
|
||||||
|
)
|
||||||
|
$contentParts.Add($pagPart) | Out-Null
|
||||||
|
|
||||||
|
# Part 5: use_llm
|
||||||
|
$llmPart = $enc.GetBytes(
|
||||||
|
"--$boundary`r`n" +
|
||||||
|
"Content-Disposition: form-data; name=`"use_llm`"`r`n`r`n" +
|
||||||
|
"$(if ($UseLlm) { 'true' } else { 'false' })`r`n"
|
||||||
|
)
|
||||||
|
$contentParts.Add($llmPart) | Out-Null
|
||||||
|
|
||||||
|
# Part 6a: llm_service (only if UseLlm or custom service)
|
||||||
|
if ($UseLlm -or $LlmService -ne "marker.services.ollama.OllamaService") {
|
||||||
|
$svcPart = $enc.GetBytes(
|
||||||
|
"--$boundary`r`n" +
|
||||||
|
"Content-Disposition: form-data; name=`"llm_service`"`r`n`r`n" +
|
||||||
|
"$LlmService`r`n"
|
||||||
|
)
|
||||||
|
$contentParts.Add($svcPart) | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Part 6b: page_range
|
||||||
|
if ($PageRange) {
|
||||||
|
$prPart = $enc.GetBytes(
|
||||||
|
"--$boundary`r`n" +
|
||||||
|
"Content-Disposition: form-data; name=`"page_range`"`r`n`r`n" +
|
||||||
|
"$PageRange`r`n"
|
||||||
|
)
|
||||||
|
$contentParts.Add($prPart) | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Part 6c: disable_image_extraction
|
||||||
|
if ($DisableImageExtraction) {
|
||||||
|
$diePart = $enc.GetBytes(
|
||||||
|
"--$boundary`r`n" +
|
||||||
|
"Content-Disposition: form-data; name=`"disable_image_extraction`"`r`n`r`n" +
|
||||||
|
"true`r`n"
|
||||||
|
)
|
||||||
|
$contentParts.Add($diePart) | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Part 6d: processors
|
||||||
|
if ($Processors) {
|
||||||
|
$prcPart = $enc.GetBytes(
|
||||||
|
"--$boundary`r`n" +
|
||||||
|
"Content-Disposition: form-data; name=`"processors`"`r`n`r`n" +
|
||||||
|
"$Processors`r`n"
|
||||||
|
)
|
||||||
|
$contentParts.Add($prcPart) | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Part 6e: config_json
|
||||||
|
if ($ConfigJson) {
|
||||||
|
$cfgPart = $enc.GetBytes(
|
||||||
|
"--$boundary`r`n" +
|
||||||
|
"Content-Disposition: form-data; name=`"config_json`"`r`n`r`n" +
|
||||||
|
"$ConfigJson`r`n"
|
||||||
|
)
|
||||||
|
$contentParts.Add($cfgPart) | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Part 6f: converter_cls
|
||||||
|
if ($ConverterCls) {
|
||||||
|
$ccPart = $enc.GetBytes(
|
||||||
|
"--$boundary`r`n" +
|
||||||
|
"Content-Disposition: form-data; name=`"converter_cls`"`r`n`r`n" +
|
||||||
|
"$ConverterCls`r`n"
|
||||||
|
)
|
||||||
|
$contentParts.Add($ccPart) | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Closing boundary
|
||||||
|
$closing = $enc.GetBytes("--$boundary--`r`n")
|
||||||
|
|
||||||
|
# Calculate total content-length
|
||||||
|
$contentLength = 0
|
||||||
|
foreach ($p in $contentParts) {
|
||||||
|
$contentLength += $p.Length
|
||||||
|
}
|
||||||
|
$contentLength += $closing.Length
|
||||||
|
|
||||||
|
# ── Create HTTP request ──
|
||||||
|
$request = [System.Net.HttpWebRequest]::Create($ApiUrl)
|
||||||
|
$request.Method = "POST"
|
||||||
|
$request.ContentType = "multipart/form-data; boundary=$boundary"
|
||||||
|
$request.ContentLength = $contentLength
|
||||||
|
$request.Timeout = $Timeout * 1000
|
||||||
|
$request.KeepAlive = $true
|
||||||
|
$request.ProtocolVersion = [System.Net.HttpVersion]::Version11
|
||||||
|
|
||||||
|
$reqStream = $request.GetRequestStream()
|
||||||
|
|
||||||
|
# Write content parts
|
||||||
|
foreach ($p in $contentParts) {
|
||||||
|
$reqStream.Write($p, 0, $p.Length)
|
||||||
|
}
|
||||||
|
# Write closing boundary
|
||||||
|
$reqStream.Write($closing, 0, $closing.Length)
|
||||||
|
$reqStream.Flush()
|
||||||
|
$reqStream.Close()
|
||||||
|
|
||||||
|
# ── Get response ──
|
||||||
|
$response = $request.GetResponse()
|
||||||
|
$resStream = $response.GetResponseStream()
|
||||||
|
$reader = [System.IO.StreamReader]::new($resStream, $enc)
|
||||||
|
$content = $reader.ReadToEnd()
|
||||||
|
$reader.Close()
|
||||||
|
$resStream.Close()
|
||||||
|
$response.Close()
|
||||||
|
|
||||||
|
# ── Write output file ──
|
||||||
|
[System.IO.File]::WriteAllText($mdPath, $content, $enc)
|
||||||
|
|
||||||
|
$sw.Stop()
|
||||||
|
$result.Status = "success"
|
||||||
|
$result.ElapsedMs = $sw.ElapsedMilliseconds
|
||||||
|
$result.OutputFile = $mdPath
|
||||||
|
$result.ResponseSize = $content.Length
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
$sw.Stop()
|
||||||
|
$result.Status = "failed"
|
||||||
|
$result.ElapsedMs = $sw.ElapsedMilliseconds
|
||||||
|
$result.Error = $_.Exception.Message
|
||||||
|
if ($_.Exception.Response) {
|
||||||
|
$result.Error += "`n Status: $([int]$_.Exception.Response.StatusCode)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result
|
||||||
|
}
|
||||||
|
|
||||||
|
# ════════════ 4. Dispatch ════════════
|
||||||
|
Write-Host "Dispatching $($files.Count) conversion(s) with $($MaxConcurrency) worker(s)..." -ForegroundColor Yellow
|
||||||
|
|
||||||
|
$allResults = @()
|
||||||
|
$runningJobs = @()
|
||||||
|
|
||||||
|
for ($i = 0; $i -lt $files.Count; $i++) {
|
||||||
|
# If we've reached concurrency limit, wait for at least one job to complete
|
||||||
|
while ($runningJobs.Count -ge $MaxConcurrency) {
|
||||||
|
$completed = $runningJobs | Where-Object { $_.State -in @('Completed','Failed') }
|
||||||
|
if ($completed) {
|
||||||
|
foreach ($job in $completed) {
|
||||||
|
try {
|
||||||
|
$allResults += Receive-Job $job
|
||||||
|
} catch {
|
||||||
|
$r = [ordered]@{}
|
||||||
|
$r.File = "unknown"
|
||||||
|
$r.SizeBytes = 0
|
||||||
|
$r.Status = "failed"
|
||||||
|
$r.ElapsedMs = 0
|
||||||
|
$r.Error = $_.Exception.Message
|
||||||
|
$r.OutputFile = ""
|
||||||
|
$allResults += $r
|
||||||
|
}
|
||||||
|
$runningJobs = @($runningJobs | Where-Object { $_.Id -ne $job.Id })
|
||||||
|
Remove-Job $job 2>&1 | Out-Null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Start-Sleep -Milliseconds 200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Launch new job
|
||||||
|
$f = $files[$i]
|
||||||
|
$safeName = [System.Security.SecurityElement]::Escape($f.Name)
|
||||||
|
$jobName = "mk_$safeName"
|
||||||
|
|
||||||
|
$job = Start-Job `
|
||||||
|
-Name $jobName `
|
||||||
|
-ScriptBlock $ScriptBlock `
|
||||||
|
-ArgumentList @(
|
||||||
|
$f.FullName
|
||||||
|
$ApiUrl
|
||||||
|
$OutputFormat
|
||||||
|
[bool]$Force
|
||||||
|
$Timeout
|
||||||
|
$PageRange
|
||||||
|
[bool]$ForceOcr
|
||||||
|
[bool]$DisableImageExtraction
|
||||||
|
[bool]$UseLlm
|
||||||
|
$LlmService
|
||||||
|
$Processors
|
||||||
|
$ConfigJson
|
||||||
|
$ConverterCls
|
||||||
|
)
|
||||||
|
|
||||||
|
$runningJobs += $job
|
||||||
|
Write-Host " [$($i + 1)/$($files.Count)] Queued: $($f.Name)" -ForegroundColor Gray
|
||||||
|
}
|
||||||
|
|
||||||
|
# Wait for remaining jobs
|
||||||
|
while ($runningJobs.Count -gt 0) {
|
||||||
|
$completed = $runningJobs | Where-Object { $_.State -in @('Completed','Failed') }
|
||||||
|
if ($completed) {
|
||||||
|
foreach ($job in $completed) {
|
||||||
|
try {
|
||||||
|
$allResults += Receive-Job $job
|
||||||
|
} catch {
|
||||||
|
$r = [ordered]@{}
|
||||||
|
$r.File = "unknown"
|
||||||
|
$r.SizeBytes = 0
|
||||||
|
$r.Status = "failed"
|
||||||
|
$r.ElapsedMs = 0
|
||||||
|
$r.Error = $_.Exception.Message
|
||||||
|
$r.OutputFile = ""
|
||||||
|
$allResults += $r
|
||||||
|
}
|
||||||
|
$runningJobs = @($runningJobs | Where-Object { $_.Id -ne $job.Id })
|
||||||
|
Remove-Job $job 2>&1 | Out-Null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Start-Sleep -Milliseconds 200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ════════════ 5. Report ════════════
|
||||||
|
$ok = @($allResults | Where-Object { $_.Status -eq "success" })
|
||||||
|
$fail = @($allResults | Where-Object { $_.Status -eq "failed" })
|
||||||
|
$skip = @($allResults | Where-Object { $_.Status -eq "skipped" })
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "====== Conversion Results ======" -ForegroundColor Cyan
|
||||||
|
Write-Host " Total files : $($files.Count)" -ForegroundColor White
|
||||||
|
Write-Host " Successful : $($ok.Count) " -ForegroundColor Green
|
||||||
|
Write-Host " Failed : $($fail.Count) " -ForegroundColor Red
|
||||||
|
Write-Host " Skipped : $($skip.Count) " -ForegroundColor Yellow
|
||||||
|
|
||||||
|
if ($ok.Count -gt 0) {
|
||||||
|
$avgMs = [Math]::Round(($ok | Measure-Object -Property ElapsedMs -Sum).Sum / $ok.Count)
|
||||||
|
Write-Host " Avg time : $avgMs ms" -ForegroundColor Green
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($fail.Count -gt 0) {
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Errors:" -ForegroundColor Red
|
||||||
|
foreach ($r in $fail) {
|
||||||
|
Write-Host " [FAIL] $($r.File)" -ForegroundColor DarkRed
|
||||||
|
Write-Host " $($r.Error)" -ForegroundColor DarkGray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Save CSV report
|
||||||
|
$csvPath = Join-Path $TargetFolder "_marker_convert_results.csv"
|
||||||
|
$resultsForCsv = @($allResults | ForEach-Object {
|
||||||
|
[PSCustomObject]@{
|
||||||
|
FileName = $_.File
|
||||||
|
OutputFile = if ($_.OutputFile) { $_.OutputFile } else { "N/A" }
|
||||||
|
Status = $_.Status
|
||||||
|
SizeBytes = $_.SizeBytes
|
||||||
|
ElapsedMs = $_.ElapsedMs
|
||||||
|
Error = $_.Error
|
||||||
|
}
|
||||||
|
})
|
||||||
|
$resultsForCsv | Sort-Object Status, File | Export-Csv $csvPath -NoTypeInformation -Encoding UTF8
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "CSV report: $csvPath" -ForegroundColor Magenta
|
||||||
|
Write-Host "====================================================" -ForegroundColor Cyan
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# marker-api - Core
|
||||||
|
flask>=3.0,<4.0
|
||||||
|
gunicorn>=21.2,<23.0
|
||||||
|
|
||||||
|
# marker dependencies — install from the cloned marker with full format extras
|
||||||
|
--editable ./marker[full]
|
||||||
|
|
||||||
+6860
File diff suppressed because it is too large
Load Diff
+45416
File diff suppressed because one or more lines are too long
@@ -0,0 +1,466 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Endpoint tests for marker-api (app.py).
|
||||||
|
Uses Flask test_client for routing/validation tests.
|
||||||
|
Uses requests against a running instance when available.
|
||||||
|
Generates a timestamped HTML report.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
import urllib.request
|
||||||
|
import urllib.error
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
HERE = Path(__file__).resolve().parent
|
||||||
|
TEST_FILES = HERE / "test_files"
|
||||||
|
RESULTS = HERE / "test_results"
|
||||||
|
ENISA_DIR = HERE.parent / "test_files" / "enisa"
|
||||||
|
APP_PY = HERE.parent / "app.py"
|
||||||
|
|
||||||
|
RESULTS.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
TIMESTAMP = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
REPORT_PATH = HERE.parent / f"test_report_{TIMESTAMP}.md"
|
||||||
|
|
||||||
|
REPORT_LINES = []
|
||||||
|
API_BASE = os.environ.get("API_BASE", "")
|
||||||
|
|
||||||
|
# Collect all ENISA PDFs for file-based tests
|
||||||
|
ENISA_PDFS = sorted(ENISA_DIR.glob("*.pdf")) if ENISA_DIR.exists() else []
|
||||||
|
# Filter to ensure they are actual PDFs (start with %PDF)
|
||||||
|
ENISA_PDFS = [p for p in ENISA_PDFS if p.read_bytes().startswith(b"%PDF")]
|
||||||
|
# Quick test PDF — create a tiny valid-ish PDF or use the first ENISA one
|
||||||
|
SMALL_PDF = None
|
||||||
|
if ENISA_PDFS:
|
||||||
|
SMALL_PDF = ENISA_PDFS[0]
|
||||||
|
|
||||||
|
results = {"passed": 0, "failed": 0, "skipped": 0, "warnings": 0}
|
||||||
|
details = []
|
||||||
|
|
||||||
|
|
||||||
|
def r(text: str):
|
||||||
|
"""Accumulate report line."""
|
||||||
|
REPORT_LINES.append(text)
|
||||||
|
|
||||||
|
|
||||||
|
def test(name: str, func):
|
||||||
|
"""Run a test case, capture result."""
|
||||||
|
start = time.time()
|
||||||
|
try:
|
||||||
|
func()
|
||||||
|
elapsed = time.time() - start
|
||||||
|
results["passed"] += 1
|
||||||
|
status = "PASS"
|
||||||
|
icon = "✅"
|
||||||
|
details.append((name, status, elapsed, ""))
|
||||||
|
r(f"| {name} | {icon} PASS | {elapsed:.3f}s | |")
|
||||||
|
except AssertionError as e:
|
||||||
|
elapsed = time.time() - start
|
||||||
|
results["failed"] += 1
|
||||||
|
status = "FAIL"
|
||||||
|
icon = "❌"
|
||||||
|
msg = str(e).replace("\n", " ")
|
||||||
|
details.append((name, status, elapsed, msg))
|
||||||
|
r(f"| {name} | {icon} FAIL | {elapsed:.3f}s | {msg} |")
|
||||||
|
except Exception as e:
|
||||||
|
elapsed = time.time() - start
|
||||||
|
results["failed"] += 1
|
||||||
|
status = "ERROR"
|
||||||
|
icon = "💥"
|
||||||
|
msg = f"{type(e).__name__}: {e}"
|
||||||
|
details.append((name, status, elapsed, msg))
|
||||||
|
r(f"| {name} | {icon} ERROR | {elapsed:.3f}s | {msg} |")
|
||||||
|
|
||||||
|
|
||||||
|
def skip(name: str, reason: str):
|
||||||
|
results["skipped"] += 1
|
||||||
|
details.append((name, "SKIP", 0, reason))
|
||||||
|
r(f"| {name} | ⏭️ SKIP | - | {reason} |")
|
||||||
|
|
||||||
|
|
||||||
|
# ── Application-level tests (import + validate app.py syntax) ──
|
||||||
|
|
||||||
|
def test_app_py_syntax():
|
||||||
|
"""Verify app.py is syntactically valid Python."""
|
||||||
|
import py_compile
|
||||||
|
try:
|
||||||
|
py_compile.compile(str(APP_PY), doraise=True)
|
||||||
|
except py_compile.PyCompileError as e:
|
||||||
|
raise AssertionError(f"Syntax error in app.py: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def test_app_py_has_use_llm():
|
||||||
|
"""Verify app.py contains use_llm parameter extraction."""
|
||||||
|
content = APP_PY.read_text()
|
||||||
|
assert '"use_llm"' in content, "Missing use_llm parameter in app.py"
|
||||||
|
assert '"llm_service"' in content, "Missing llm_service parameter in app.py"
|
||||||
|
assert '"block_correction_prompt"' in content, "Missing block_correction_prompt parameter in app.py"
|
||||||
|
assert '"redo_inline_math"' in content, "Missing redo_inline_math parameter in app.py"
|
||||||
|
assert '"strip_existing_ocr"' in content, "Missing strip_existing_ocr parameter in app.py"
|
||||||
|
assert '"debug"' in content, "Missing debug parameter in app.py"
|
||||||
|
|
||||||
|
|
||||||
|
def test_ps1_has_new_params():
|
||||||
|
"""Verify marker-convert.ps1 contains all new parameters."""
|
||||||
|
ps1 = HERE.parent / "marker-convert-powershell" / "marker-convert.ps1"
|
||||||
|
assert ps1.exists(), "marker-convert.ps1 not found"
|
||||||
|
content = ps1.read_text()
|
||||||
|
assert "PageRange" in content, "Missing PageRange param"
|
||||||
|
assert "ForceOcr" in content, "Missing ForceOcr param"
|
||||||
|
assert "DisableImageExtraction" in content, "Missing DisableImageExtraction param"
|
||||||
|
assert "UseLlm" in content, "Missing UseLlm param"
|
||||||
|
assert "LlmService" in content, "Missing LlmService param"
|
||||||
|
assert "Processors" in content, "Missing Processors param"
|
||||||
|
assert "ConfigJson" in content, "Missing ConfigJson param"
|
||||||
|
assert "ConverterCls" in content, "Missing ConverterCls param"
|
||||||
|
|
||||||
|
|
||||||
|
def test_readme_exists():
|
||||||
|
"""Verify README.md exists and documents all endpoints."""
|
||||||
|
readme = HERE.parent / "README.md"
|
||||||
|
assert readme.exists(), "README.md not found"
|
||||||
|
content = readme.read_text()
|
||||||
|
assert "POST /marker" in content, "Missing /marker endpoint docs"
|
||||||
|
assert "POST /v1/conversions" in content, "Missing /v1/conversions docs"
|
||||||
|
assert "POST /v1/files/convert" in content, "Missing /v1/files/convert docs"
|
||||||
|
assert "use_llm" in content, "Missing use_llm in docs"
|
||||||
|
assert "llm_service" in content, "Missing llm_service in docs"
|
||||||
|
|
||||||
|
|
||||||
|
# ── HTTP-level tests (require a running API) ──
|
||||||
|
|
||||||
|
def _api_request(method: str, path: str, **kwargs):
|
||||||
|
"""Make an HTTP request to the API."""
|
||||||
|
from urllib.request import Request
|
||||||
|
import base64
|
||||||
|
|
||||||
|
url = f"{API_BASE}{path}"
|
||||||
|
if method == "GET":
|
||||||
|
req = Request(url, method="GET")
|
||||||
|
elif method == "POST":
|
||||||
|
data = kwargs.get("data")
|
||||||
|
headers = kwargs.get("headers", {})
|
||||||
|
files = kwargs.get("files")
|
||||||
|
|
||||||
|
if files:
|
||||||
|
# Build multipart manually
|
||||||
|
import io
|
||||||
|
boundary = "----testboundary123"
|
||||||
|
body = io.BytesIO()
|
||||||
|
for key, (filename, filedata, _mime) in files.items():
|
||||||
|
body.write(f"--{boundary}\r\n".encode())
|
||||||
|
body.write(f'Content-Disposition: form-data; name="{key}"; filename="{filename}"\r\n'.encode())
|
||||||
|
body.write(b"Content-Type: application/octet-stream\r\n\r\n")
|
||||||
|
body.write(filedata)
|
||||||
|
body.write(b"\r\n")
|
||||||
|
if data:
|
||||||
|
for key, val in data.items():
|
||||||
|
body.write(f"--{boundary}\r\n".encode())
|
||||||
|
body.write(f'Content-Disposition: form-data; name="{key}"\r\n\r\n'.encode())
|
||||||
|
body.write(f"{val}\r\n".encode())
|
||||||
|
body.write(f"--{boundary}--\r\n".encode())
|
||||||
|
payload = body.getvalue()
|
||||||
|
headers = {"Content-Type": f"multipart/form-data; boundary={boundary}"}
|
||||||
|
else:
|
||||||
|
payload = json.dumps(data).encode() if data is not None else b""
|
||||||
|
headers.setdefault("Content-Type", "application/json")
|
||||||
|
|
||||||
|
req = Request(url, data=payload, headers=headers, method="POST")
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unsupported method: {method}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
resp = urllib.request.urlopen(req, timeout=300)
|
||||||
|
return resp.status, resp.read()
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
return e.code, e.read()
|
||||||
|
except urllib.error.URLError as e:
|
||||||
|
raise AssertionError(f"Cannot reach {url}: {e.reason}")
|
||||||
|
|
||||||
|
|
||||||
|
# ── Endpoint tests ──
|
||||||
|
|
||||||
|
def test_health_endpoint():
|
||||||
|
"""GET /health returns 200 and expected fields."""
|
||||||
|
if not API_BASE:
|
||||||
|
raise AssertionError("API not running")
|
||||||
|
status, body = _api_request("GET", "/health")
|
||||||
|
assert status == 200, f"Expected 200, got {status}"
|
||||||
|
data = json.loads(body)
|
||||||
|
assert data.get("status") == "ok"
|
||||||
|
assert "torch_device" in data or "ocr_engine" in data, "Missing expected fields"
|
||||||
|
|
||||||
|
|
||||||
|
def test_docs_endpoint():
|
||||||
|
"""GET / returns 200 and documentation."""
|
||||||
|
if not API_BASE:
|
||||||
|
raise AssertionError("API not running")
|
||||||
|
status, body = _api_request("GET", "/")
|
||||||
|
assert status == 200, f"Expected 200, got {status}"
|
||||||
|
assert b"marker-api" in body or b"marker" in body.lower()
|
||||||
|
|
||||||
|
|
||||||
|
def test_marker_multipart_markdown():
|
||||||
|
"""POST /marker with multipart file returns markdown."""
|
||||||
|
if not API_BASE or not SMALL_PDF:
|
||||||
|
raise AssertionError("API not running or no test PDF")
|
||||||
|
pdf_bytes = SMALL_PDF.read_bytes()
|
||||||
|
files = {"file": (SMALL_PDF.name, pdf_bytes, "application/pdf")}
|
||||||
|
status, body = _api_request("POST", "/marker", files=files)
|
||||||
|
assert status == 200, f"Expected 200, got {status}: {body[:200]}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_marker_multipart_json():
|
||||||
|
"""POST /marker with output_format=json returns JSON."""
|
||||||
|
if not API_BASE or not SMALL_PDF:
|
||||||
|
raise AssertionError("API not running or no test PDF")
|
||||||
|
pdf_bytes = SMALL_PDF.read_bytes()
|
||||||
|
files = {"file": (SMALL_PDF.name, pdf_bytes, "application/pdf")}
|
||||||
|
data = {"output_format": "json"}
|
||||||
|
status, body = _api_request("POST", "/marker", files=files, data=data)
|
||||||
|
if status == 200:
|
||||||
|
body_str = body.decode()
|
||||||
|
if "{" in body_str:
|
||||||
|
json.loads(body_str) # valid JSON
|
||||||
|
assert status in (200, 500), f"Expected 200/500, got {status}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_marker_json_base64():
|
||||||
|
"""POST /marker with JSON base64 body."""
|
||||||
|
if not API_BASE or not SMALL_PDF:
|
||||||
|
raise AssertionError("API not running or no test PDF")
|
||||||
|
import base64
|
||||||
|
b64 = base64.b64encode(SMALL_PDF.read_bytes()).decode()
|
||||||
|
payload = {"file_b64": b64, "filename": SMALL_PDF.name, "output_format": "markdown"}
|
||||||
|
status, body = _api_request("POST", "/marker", data=payload)
|
||||||
|
assert status in (200, 500), f"Expected 200/500, got {status}: {body[:200]}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_marker_with_use_llm():
|
||||||
|
"""POST /marker with use_llm=true."""
|
||||||
|
if not API_BASE or not SMALL_PDF:
|
||||||
|
raise AssertionError("API not running or no test PDF")
|
||||||
|
pdf_bytes = SMALL_PDF.read_bytes()
|
||||||
|
files = {"file": (SMALL_PDF.name, pdf_bytes, "application/pdf")}
|
||||||
|
data = {"use_llm": "true", "llm_service": "marker.services.ollama.OllamaService"}
|
||||||
|
status, body = _api_request("POST", "/marker", files=files, data=data)
|
||||||
|
assert status in (200, 500), f"Expected 200/500, got {status}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_marker_with_page_range():
|
||||||
|
"""POST /marker with page_range."""
|
||||||
|
if not API_BASE or not SMALL_PDF:
|
||||||
|
raise AssertionError("API not running or no test PDF")
|
||||||
|
pdf_bytes = SMALL_PDF.read_bytes()
|
||||||
|
files = {"file": (SMALL_PDF.name, pdf_bytes, "application/pdf")}
|
||||||
|
data = {"page_range": "0-2"}
|
||||||
|
status, body = _api_request("POST", "/marker", files=files, data=data)
|
||||||
|
assert status in (200, 500), f"Expected 200/500, got {status}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_marker_missing_file():
|
||||||
|
"""POST /marker without file returns 400."""
|
||||||
|
if not API_BASE:
|
||||||
|
raise AssertionError("API not running")
|
||||||
|
status, body = _api_request("POST", "/marker", data={})
|
||||||
|
assert status == 400, f"Expected 400, got {status}"
|
||||||
|
data = json.loads(body)
|
||||||
|
assert "error" in data or "detail" in data
|
||||||
|
|
||||||
|
|
||||||
|
def test_v1_conversions_json():
|
||||||
|
"""POST /v1/conversions with JSON base64 body."""
|
||||||
|
if not API_BASE or not SMALL_PDF:
|
||||||
|
raise AssertionError("API not running or no test PDF")
|
||||||
|
import base64
|
||||||
|
b64 = base64.b64encode(SMALL_PDF.read_bytes()).decode()
|
||||||
|
payload = {"file_b64": b64, "filename": SMALL_PDF.name, "output_format": "json"}
|
||||||
|
status, body = _api_request("POST", "/v1/conversions", data=payload)
|
||||||
|
if status == 200:
|
||||||
|
data = json.loads(body)
|
||||||
|
assert "id" in data, "Missing id field"
|
||||||
|
assert "output" in data, "Missing output field"
|
||||||
|
assert status in (200, 500)
|
||||||
|
|
||||||
|
|
||||||
|
def test_v1_files_convert_multipart():
|
||||||
|
"""POST /v1/files/convert with multipart."""
|
||||||
|
if not API_BASE or not SMALL_PDF:
|
||||||
|
raise AssertionError("API not running or no test PDF")
|
||||||
|
pdf_bytes = SMALL_PDF.read_bytes()
|
||||||
|
files = {"file": (SMALL_PDF.name, pdf_bytes, "application/pdf")}
|
||||||
|
status, body = _api_request("POST", "/v1/files/convert", files=files)
|
||||||
|
if status == 200:
|
||||||
|
data = json.loads(body)
|
||||||
|
assert "output" in data, "Missing output"
|
||||||
|
assert "format" in data, "Missing format"
|
||||||
|
assert status in (200, 500)
|
||||||
|
|
||||||
|
|
||||||
|
def test_v1_files_convert_json():
|
||||||
|
"""POST /v1/files/convert with JSON base64."""
|
||||||
|
if not API_BASE or not SMALL_PDF:
|
||||||
|
raise AssertionError("API not running or no test PDF")
|
||||||
|
import base64
|
||||||
|
b64 = base64.b64encode(SMALL_PDF.read_bytes()).decode()
|
||||||
|
payload = {"file_b64": b64, "filename": SMALL_PDF.name}
|
||||||
|
status, body = _api_request("POST", "/v1/files/convert", data=payload)
|
||||||
|
if status == 200:
|
||||||
|
data = json.loads(body)
|
||||||
|
assert "output" in data
|
||||||
|
assert status in (200, 500)
|
||||||
|
|
||||||
|
|
||||||
|
# ── ENISA test files ──
|
||||||
|
|
||||||
|
def test_enisa_files_downloaded():
|
||||||
|
"""Verify at least 3 real ENISA PDFs were downloaded."""
|
||||||
|
pdf_count = len(ENISA_PDFS)
|
||||||
|
file_cmds = []
|
||||||
|
for pdf in ENISA_PDFS:
|
||||||
|
rc = os.system(f"file '{pdf}' 2>/dev/null | grep -q 'PDF document'")
|
||||||
|
if rc == 0:
|
||||||
|
file_cmds.append(str(pdf))
|
||||||
|
assert len(file_cmds) >= 2, f"Only {len(file_cmds)} real PDFs found, expected at least 2"
|
||||||
|
|
||||||
|
|
||||||
|
# ── Report generation ──
|
||||||
|
|
||||||
|
def generate_report():
|
||||||
|
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
total = sum(v for k, v in results.items() if k != "warnings")
|
||||||
|
pass_pct = round(results["passed"] / total * 100, 1) if total else 0
|
||||||
|
|
||||||
|
lines = []
|
||||||
|
lines.append(f"# Test Report — marker-api Endpoint Tests")
|
||||||
|
lines.append(f"")
|
||||||
|
lines.append(f"**Date:** {now}")
|
||||||
|
lines.append(f"**API Base:** {API_BASE or 'N/A (offline validation)'}")
|
||||||
|
lines.append(f"**Test files:** {ENISA_DIR} ({len(ENISA_PDFS)} file(s))")
|
||||||
|
lines.append(f"")
|
||||||
|
lines.append(f"## Summary")
|
||||||
|
lines.append(f"")
|
||||||
|
lines.append(f"| Result | Count |")
|
||||||
|
lines.append(f"|--------|-------|")
|
||||||
|
lines.append(f"| ✅ Passed | {results['passed']} |")
|
||||||
|
lines.append(f"| ❌ Failed | {results['failed']} |")
|
||||||
|
lines.append(f"| ⏭️ Skipped | {results['skipped']} |")
|
||||||
|
lines.append(f"| **Total** | **{total}** |")
|
||||||
|
lines.append(f"| **Pass Rate** | **{pass_pct}%** |")
|
||||||
|
lines.append(f"")
|
||||||
|
lines.append(f"## Test Details")
|
||||||
|
lines.append(f"")
|
||||||
|
lines.append(f"| Test | Result | Time | Notes |")
|
||||||
|
lines.append(f"|------|--------|------|-------|")
|
||||||
|
lines.extend(REPORT_LINES)
|
||||||
|
lines.append(f"")
|
||||||
|
lines.append(f"## Environment")
|
||||||
|
|
||||||
|
# Python version
|
||||||
|
py_ver = sys.version.split()[0]
|
||||||
|
lines.append(f"- Python: {py_ver}")
|
||||||
|
lines.append(f"- Platform: {sys.platform}")
|
||||||
|
|
||||||
|
# Check marker availability
|
||||||
|
try:
|
||||||
|
import marker
|
||||||
|
lines.append(f"- marker: installed")
|
||||||
|
except ImportError:
|
||||||
|
lines.append(f"- marker: ❌ not installed (Pillow build failure on this platform)")
|
||||||
|
|
||||||
|
# Check ENISA file details
|
||||||
|
lines.append(f"")
|
||||||
|
lines.append(f"## Test Files")
|
||||||
|
for pdf in ENISA_PDFS:
|
||||||
|
size = pdf.stat().st_size
|
||||||
|
lines.append(f"- {pdf.name} ({size / 1024:.1f} KB)")
|
||||||
|
|
||||||
|
lines.append(f"")
|
||||||
|
lines.append(f"## Notes")
|
||||||
|
lines.append(f"- HTTP endpoint tests require a running marker-api instance.")
|
||||||
|
lines.append(f"- API-level tests were {'run against ' + API_BASE if API_BASE else 'skipped (no API running).'}")
|
||||||
|
lines.append(f"- Offline validation tests (syntax, parameter availability, docs) ran regardless.")
|
||||||
|
lines.append(f"- The marker library could not be installed due to Pillow build failure in this sandbox.")
|
||||||
|
lines.append(f"- For live endpoint testing, start the API with: `python app.py` (requires marker-pdf[full] + flask).")
|
||||||
|
if results["failed"] > 0:
|
||||||
|
lines.append(f"- Failed tests may indicate missing marker library or API connectivity issues.")
|
||||||
|
lines.append(f"")
|
||||||
|
|
||||||
|
REPORT_PATH.write_text("\n".join(lines))
|
||||||
|
print(f"\nReport: {REPORT_PATH}")
|
||||||
|
return str(REPORT_PATH)
|
||||||
|
|
||||||
|
|
||||||
|
# ── Main ──
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Determine API base from environment or check if running locally
|
||||||
|
if not API_BASE:
|
||||||
|
# Try several ports — only accept if the health endpoint matches our Flask app
|
||||||
|
for port in [8000, 8001, 8080, 5000]:
|
||||||
|
url = f"http://localhost:{port}"
|
||||||
|
try:
|
||||||
|
req = urllib.request.Request(f"{url}/health", method="GET")
|
||||||
|
resp = urllib.request.urlopen(req, timeout=3)
|
||||||
|
if resp.status == 200:
|
||||||
|
body = json.loads(resp.read())
|
||||||
|
if body.get("status") == "ok" and ("model_dtype" in body or body.get("provider") == "flask"):
|
||||||
|
API_BASE = url
|
||||||
|
print(f"Found marker-api at {API_BASE}")
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
test_req = urllib.request.Request(f"{url}/marker", method="POST", data=b"")
|
||||||
|
urllib.request.urlopen(test_req, timeout=2)
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if e.code in (400, 500, 405, 422):
|
||||||
|
API_BASE = url
|
||||||
|
print(f"Found marker-api at {API_BASE} (via /marker)")
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
if not API_BASE:
|
||||||
|
print("No marker-api instance detected. Running offline validation tests only.")
|
||||||
|
|
||||||
|
r("")
|
||||||
|
r("### Application Tests")
|
||||||
|
test("app.py syntax valid", test_app_py_syntax)
|
||||||
|
test("use_llm/llm_service params present in app.py", test_app_py_has_use_llm)
|
||||||
|
test("marker-convert.ps1 has all new parameters", test_ps1_has_new_params)
|
||||||
|
test("README.md documents all endpoints", test_readme_exists)
|
||||||
|
test("ENISA test files available", test_enisa_files_downloaded)
|
||||||
|
|
||||||
|
r("")
|
||||||
|
r("### Endpoint Tests (HTTP)")
|
||||||
|
if API_BASE:
|
||||||
|
test("GET /health returns 200", test_health_endpoint)
|
||||||
|
test("GET / returns docs page", test_docs_endpoint)
|
||||||
|
test("POST /marker multipart (markdown)", test_marker_multipart_markdown)
|
||||||
|
test("POST /marker multipart (json)", test_marker_multipart_json)
|
||||||
|
test("POST /marker JSON base64", test_marker_json_base64)
|
||||||
|
test("POST /marker with use_llm=true", test_marker_with_use_llm)
|
||||||
|
test("POST /marker with page_range", test_marker_with_page_range)
|
||||||
|
test("POST /marker missing file → 400", test_marker_missing_file)
|
||||||
|
test("POST /v1/conversions JSON", test_v1_conversions_json)
|
||||||
|
test("POST /v1/files/convert multipart", test_v1_files_convert_multipart)
|
||||||
|
test("POST /v1/files/convert JSON", test_v1_files_convert_json)
|
||||||
|
else:
|
||||||
|
skip("GET /health", "API not running")
|
||||||
|
skip("GET /", "API not running")
|
||||||
|
skip("POST /marker (multipart)", "API not running")
|
||||||
|
skip("POST /marker (JSON)", "API not running")
|
||||||
|
skip("POST /marker with use_llm", "API not running")
|
||||||
|
skip("POST /marker with page_range", "API not running")
|
||||||
|
skip("POST /marker missing file", "API not running")
|
||||||
|
skip("POST /v1/conversions", "API not running")
|
||||||
|
skip("POST /v1/files/convert (multipart)", "API not running")
|
||||||
|
skip("POST /v1/files/convert (JSON)", "API not running")
|
||||||
|
|
||||||
|
report_path = generate_report()
|
||||||
|
total = sum(v for k, v in results.items() if k != "warnings")
|
||||||
|
print(f" Passed: {results['passed']}/{total} Failed: {results['failed']} Skipped: {results['skipped']}")
|
||||||
@@ -0,0 +1,974 @@
|
|||||||
|
<html lang="en" class="no-js">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Sorry - 34891</title>
|
||||||
|
<meta content="width=device-width,initial-scale=1" name="viewport" />
|
||||||
|
<meta content="IE=edge" http-equiv="X-UA-Compatible" />
|
||||||
|
<script>
|
||||||
|
var cl = document.querySelector('html').classList;
|
||||||
|
cl.remove('no-js');
|
||||||
|
cl.add('has-js');
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
!function(a,b){"function"==typeof define&&define.amd?define([],function(){return a.svg4everybody=b()}):"object"==typeof module&&module.exports?module.exports=b():a.svg4everybody=b()}(this,function(){function a(a,b,c){if(c){var d=document.createDocumentFragment(),e=!b.hasAttribute("viewBox")&&c.getAttribute("viewBox");e&&b.setAttribute("viewBox",e);for(var f=c.cloneNode(!0);f.childNodes.length;)d.appendChild(f.firstChild);a.appendChild(d)}}function b(b){b.onreadystatechange=function(){if(4===b.readyState){var c=b._cachedDocument;c||(c=b._cachedDocument=document.implementation.createHTMLDocument(""),c.body.innerHTML=b.responseText,b._cachedTarget={}),b._embeds.splice(0).map(function(d){var e=b._cachedTarget[d.id];e||(e=b._cachedTarget[d.id]=c.getElementById(d.id)),a(d.parent,d.svg,e)})}},b.onreadystatechange()}function c(c){function e(){for(var c=0;c<o.length;){var h=o[c],i=h.parentNode,j=d(i),k=h.getAttribute("xlink:href")||h.getAttribute("href");if(!k&&g.attributeName&&(k=h.getAttribute(g.attributeName)),j&&k){if(f)if(!g.validate||g.validate(k,j,h)){i.removeChild(h);var l=k.split("#"),q=l.shift(),r=l.join("#");if(q.length){var s=m[q];s||(s=m[q]=new XMLHttpRequest,s.open("GET",q),s.send(),s._embeds=[]),s._embeds.push({parent:i,svg:j,id:r}),b(s)}else a(i,j,document.getElementById(r))}else++c,++p}else++c}(!o.length||o.length-p>0)&&n(e,67)}var f,g=Object(c),h=/\bTrident\/[567]\b|\bMSIE (?:9|10)\.0\b/,i=/\bAppleWebKit\/(\d+)\b/,j=/\bEdge\/12\.(\d+)\b/,k=/\bEdge\/.(\d+)\b/,l=window.top!==window.self;f="polyfill"in g?g.polyfill:h.test(navigator.userAgent)||(navigator.userAgent.match(j)||[])[1]<10547||(navigator.userAgent.match(i)||[])[1]<537||k.test(navigator.userAgent)&&l;var m={},n=window.requestAnimationFrame||setTimeout,o=document.getElementsByTagName("use"),p=0;f&&e()}function d(a){for(var b=a;"svg"!==b.nodeName.toLowerCase()&&(b=b.parentNode););return b}return c});
|
||||||
|
</script>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/styles/ecl-eu-preset-website.css"
|
||||||
|
integrity="sha256-Mwc5eL1RymLbiIKrMvbZmz/FOXFlCnjAsGHs9AIb6Qc= sha384-NQOOLrEUKk++wGGz+h1BASx0NyOK/mTbtDF4gZ9e3x1ZgXrWnhG/FmQYfOsojLws sha512-86FiTtDKFNsKWJYUgy0J08wzXIbbDCwWyYkhaVYL75I8J6ANisHNib3imST5zh20lAFaS9cAqYMxPRARWkUPqg=="
|
||||||
|
crossorigin="anonymous"
|
||||||
|
media="screen"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/styles/ecl-eu-preset-website-print.css"
|
||||||
|
integrity="sha256-lJYdcmklwVrgn2ZWsSN3GZ5G+1saXwJLjUX0mrcHoX4= sha384-Os00jmeauouS56K4NmaQ636onpfJqEMNVujF5H9lwauJgQTQjOLbdJvZbUwmtBti sha512-OjGa7LT06TdGWp9P9RkGtYkcAygRxjLswT8X3vmTuNTXigf/R+vYllFHgDtQj7W3vQX/fxd2DvVLVel+nodiew=="
|
||||||
|
crossorigin="anonymous"
|
||||||
|
media="print"
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header
|
||||||
|
data-ecl-auto-init="SiteHeaderHarmonised"
|
||||||
|
class="ecl-site-header-harmonised"
|
||||||
|
>
|
||||||
|
<div class="ecl-site-header-harmonised__container ecl-container">
|
||||||
|
<div class="ecl-site-header-harmonised__top">
|
||||||
|
<a
|
||||||
|
class="ecl-link ecl-link--standalone ecl-site-header-harmonised__logo-link"
|
||||||
|
href="#"
|
||||||
|
aria-label="European Union"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt="European Union logo"
|
||||||
|
title="European Union"
|
||||||
|
class="ecl-site-header-harmonised__logo-image-mobile"
|
||||||
|
src="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/logo/condensed-version/positive/en.svg"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
alt="European Union logo"
|
||||||
|
title="European Union"
|
||||||
|
class="ecl-site-header-harmonised__logo-image-desktop"
|
||||||
|
src="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/logo/standard-version/positive/en.svg"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
<div class="ecl-site-header-harmonised__action"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ecl-site-header-harmonised__banner">
|
||||||
|
<div class="ecl-container">
|
||||||
|
<h1 class="ecl-page-header-core__title">Server inaccessibility</h1>
|
||||||
|
<p class="ecl-page-header-core__description">429 - Too Many Requests</br>Status ID: <b>64b800fd</b> / Request ID: <b>34891</b></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main class="ecl-container ecl-u-mv-xl ecl-u-mv-md-3xl">
|
||||||
|
<div class="ecl-row">
|
||||||
|
<div class="ecl-col-md-6">
|
||||||
|
<div class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-width-100">
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>bg</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Сървърът е временно недостъпен
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Извиняваме се за неудобството. Моля, опитайте пак по-късно.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>cs</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Server je dočasně nedostupný
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Omlouváme se za způsobené nepříjemnosti. Zkuste to prosím znovu později.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>da</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Serveren er midlertidigt utilgængelig
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Vi undskylder ulejligheden. Prøv igen senere.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>de</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Der Server ist vorübergehend nicht verfügbar
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Wir entschuldigen uns für etwaige Unannehmlichkeiten. Bitte
|
||||||
|
versuchen Sie es später noch einmal.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>el</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Ο διακομιστής δεν είναι διαθέσιμος προσωρινά
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Ζητούμε συγγνώμη για τυχόν προβλήματα. Δοκιμάστε ξανά αργότερα.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>en</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
The server is temporarily unavailable
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
We apologise for any inconvenience. Please try again later.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>es</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
El servidor no está disponible temporalmente
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Le rogamos disculpe las molestias. Vuelva a intentarlo más
|
||||||
|
tarde.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>et</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Server ei ole ajutiselt kättesaadav
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Palume vabandust tekkinud ebamugavuste pärast. Palun proovige
|
||||||
|
hiljem uuesti.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>fi</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Palvelin on väliaikaisesti poissa käytöstä
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Pahoittelemme mahdollista haittaa. Yritä myöhemmin uudelleen.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>fr</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Le serveur est temporairement indisponible
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Veuillez nous excuser pour ce désagrément. Merci de réessayer
|
||||||
|
plus tard.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>ga</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Níl fáil ar an bhfreastalaí i láthair na huaire
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Is oth linn an chiotaí. Bain triail as arís ar ball.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>hr</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Poslužitelj je privremeno nedostupan
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Ispričavamo se zbog neugodnosti. Pokušajte ponovno kasnije.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ecl-col-md-6">
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-mt-md-none ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>hu</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
A szerver átmenetileg nem érhető el, kérjük, próbálkozzon újra
|
||||||
|
később
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Az esetleges kellemetlenségekért elnézését kérjük.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>it</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Il server è temporaneamente non disponibile
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Ci scusiamo per il disagio arrecato. Riprovare più tardi.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>lt</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Serveris laikinai neveikia
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Atsiprašome už nepatogumus. Bandykite dar kartą vėliau.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>lv</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Serveris pagaidām nav pieejams
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Atvainojamies par neērtībām. Mēģiniet vēlreiz vēlāk.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>mt</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Is-server mhux disponibbli temporanjament
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Niskużaw ruħna għal kwalunkwe inkonvenjenza. Jekk jogħġbok erġa’
|
||||||
|
pprova aktar tard.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>nl</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
De server is tijdelijk niet beschikbaar
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Onze excuses voor het ongemak. Probeer het later nog een keer.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>pl</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Serwer jest tymczasowo niedostępny
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Przepraszamy za wszelkie niedogodności. Proszę spróbować
|
||||||
|
później.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>pt</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
O servidor está temporariamente indisponível
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Pedimos desculpa pelo incómodo. Volte a tentar mais tarde.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>ro</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Serverul este temporar indisponibil
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Ne cerem scuze pentru eventualele inconveniente. Vă rugăm să
|
||||||
|
reîncercați mai târziu.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>sk</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Server je dočasne nedostupný
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Ospravedlňujeme sa za prípadné problémy, ktoré môžu v dôsledku
|
||||||
|
toho vzniknúť.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>sl</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Strežnik je začasno nedosegljiv
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Opravičujemo se za morebitne nevšečnosti. Poskusite ponovno
|
||||||
|
pozneje.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ecl-u-d-inline-flex ecl-u-pt-m ecl-u-mt-l ecl-u-width-100"
|
||||||
|
>
|
||||||
|
<span class="ecl-u-d-flex" style="position: relative">
|
||||||
|
<svg
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="ecl-icon ecl-icon--m ecl-u-type-color-blue-100"
|
||||||
|
>
|
||||||
|
<use
|
||||||
|
xlink:href="https://cdn1.fpfis.tech.ec.europa.eu/ecl/v2.38.0/eu-preset-website/images/icons/sprites/icons.svg#general--language"
|
||||||
|
></use>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
style="
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, 3px);
|
||||||
|
"
|
||||||
|
class="ecl-u-type-xs ecl-u-type-color-white-100 ecl-u-type-uppercase"
|
||||||
|
>sv</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<div class="ecl-u-ml-m ecl-u-width-100">
|
||||||
|
<div
|
||||||
|
class="ecl-u-type-prolonged-m ecl-u-type-color-blue-100 ecl-u-type-bold"
|
||||||
|
>
|
||||||
|
Servern är inte tillgänglig för tillfället
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="ecl-u-type-paragraph ecl-u-border-bottom ecl-u-border-color-grey-15 ecl-u-mt-s ecl-u-mb-none ecl-u-pb-m"
|
||||||
|
>
|
||||||
|
Vi ber om ursäkt för besväret. Försök igen senare.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<footer class="ecl-u-bg-blue-100 ecl-u-pa-l"></footer>
|
||||||
|
<script>
|
||||||
|
svg4everybody({ polyfill: true });
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Binary file not shown.
@@ -0,0 +1,683 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" dir="ltr" class="h-100">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="MobileOptimized" content="width" />
|
||||||
|
<meta name="HandheldFriendly" content="true" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="icon" href="/themes/custom/enisaweb/favicon.ico" type="image/png" />
|
||||||
|
<script>window.a2a_config=window.a2a_config||{};a2a_config.callbacks=[];a2a_config.overlays=[];a2a_config.templates={};</script>
|
||||||
|
|
||||||
|
<meta content="ENISA: Every day we experience the Information Society. Interconnected networks touch our everyday lives, at home and at work. It is therefore vital that computers, mobile phones, banking, and the Internet function, to support Europe’s digital economy. That is why ENISA is working with Cybersecurity for the EU and the Member States." name="DC.description">
|
||||||
|
<meta name="description" content="ENISA is the EU agency dedicated to enhancing cybersecurity in Europe. They offer guidance, tools, and resources to safeguard citizens and businesses from cyber threats.">
|
||||||
|
<meta name="keywords" content="Cybersecurity, EU, ENISA, computer security, Cyber Threats, EU Cyber Crisis, Incident Management, Market and Standards, Product Security, Security certification, Risk Management, Skills and competences, State of cybersecurity in the EU, Vulnerability Disclosure, Artificial Intelligence, Next Gen Technologies, Awareness, Cyber Hygiene, Digital Identity, Data Protection, Education and career path">
|
||||||
|
<title>Page not found | ENISA</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" media="all" href="/sites/default/files/css/css_iVtEkkXK3GsFFviOb5N0RiPlHf9nxcwaa_bXGH1_dHo.css?delta=0&language=en&theme=enisaweb&include=eJxtj2sOwyAMgy9E4UhVCl6HRpKJ0FXd6Uf3lrY_kfPJkR1KqSnJFugp_KGqNBdPSLlpHSlGrSmrhLd6WCDJdYAgWplKvsJBstGKKUxkX9tcdKIyWNtKlvnDGWY0w5xpzFRG7pE0ds_JwqEndJMpw0flswp6qz_GX-TbEQxnmzXwo8olY7Vwn541LQVuRb-ZiSFLD6vsVww7GHYyvD68AUVHcCg" />
|
||||||
|
<link rel="stylesheet" media="all" href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css" />
|
||||||
|
<link rel="stylesheet" media="all" href="/sites/default/files/css/css_kqnoItugpXU7ofOC42w0XMtkmLysYC4L7i2sNGGYGPM.css?delta=2&language=en&theme=enisaweb&include=eJxtj2sOwyAMgy9E4UhVCl6HRpKJ0FXd6Uf3lrY_kfPJkR1KqSnJFugp_KGqNBdPSLlpHSlGrSmrhLd6WCDJdYAgWplKvsJBstGKKUxkX9tcdKIyWNtKlvnDGWY0w5xpzFRG7pE0ds_JwqEndJMpw0flswp6qz_GX-TbEQxnmzXwo8olY7Vwn541LQVuRb-ZiSFLD6vsVww7GHYyvD68AUVHcCg" />
|
||||||
|
<link rel="stylesheet" media="all" href="/sites/default/files/css/css_f0YtobP3TA7lxU-qAZjtQhMje8XKUL_bPtkAvJX_XBg.css?delta=3&language=en&theme=enisaweb&include=eJxtj2sOwyAMgy9E4UhVCl6HRpKJ0FXd6Uf3lrY_kfPJkR1KqSnJFugp_KGqNBdPSLlpHSlGrSmrhLd6WCDJdYAgWplKvsJBstGKKUxkX9tcdKIyWNtKlvnDGWY0w5xpzFRG7pE0ds_JwqEndJMpw0flswp6qz_GX-TbEQxnmzXwo8olY7Vwn541LQVuRb-ZiSFLD6vsVww7GHYyvD68AUVHcCg" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body class="path-sites d-flex flex-column h-100">
|
||||||
|
|
||||||
|
|
||||||
|
<div class="dialog-off-canvas-main-canvas d-flex flex-column h-100" data-off-canvas-main-canvas>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<p class="sr-only"><a href="#main-content" accesskey="M">Go to the main content</a></p>
|
||||||
|
|
||||||
|
<div class="navbar navbar-expand-lg navbar-dark text-light bg-primary header">
|
||||||
|
<div class="container logo-menu-wrapper d-flex">
|
||||||
|
|
||||||
|
<div class="region region-nav-branding">
|
||||||
|
<div id="block-enisaweb-branding" class="block block-system block-system-branding-block">
|
||||||
|
|
||||||
|
|
||||||
|
<div class="navbar-brand d-flex align-items-center">
|
||||||
|
|
||||||
|
<a href="/" title="Home" rel="home" class="site-logo d-block">
|
||||||
|
<img src="/sites/default/files/enisa-logo.svg" alt="Home" fetchpriority="high" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<nav class="collapse navbar-collapse justify-content-end main-menu" id="navbarSupportedContent" role="navigation" aria-label="Main menu and Search box">
|
||||||
|
<div class="region region-nav-main">
|
||||||
|
<div id="block-enisaweb-mainnavigation-2" class="block block-we-megamenu block-we-megamenu-blockmain">
|
||||||
|
|
||||||
|
|
||||||
|
<div class="region-we-mega-menu">
|
||||||
|
<a href="javascript:" class="navbar-toggle collapsed" aria-label="Open/close menu" aria-controls="mainMenuResponsive" name="menu-button" role="button">
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
<span class="icon-bar"></span>
|
||||||
|
</a>
|
||||||
|
<nav class="main navbar navbar-default navbar-we-mega-menu mobile-collapse hover-action" data-menu-name="main" data-block-theme="enisaweb" data-style="Default" data-animation="None" data-delay="" data-duration="" data-autoarrow="" data-alwayshowsubmenu="" data-action="hover" data-mobile-collapse="0" aria-label="ENISA main menu" id="mainMenuResponsive">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<ul class="we-mega-menu-ul nav nav-tabs">
|
||||||
|
<li class="we-mega-menu-li justify dropdown-menu" title="" data-level="0" data-element-type="" data-id="320e2c86-310b-4f7b-a4a9-188df34c3e43" data-submenu="1" data-hide-sub-when-collapse="" data-group="0" data-caption="" data-alignsub="justify" data-target="" data-icon="" >
|
||||||
|
<span class="we-megamenu-nolink">
|
||||||
|
Topics</span>
|
||||||
|
<div class="we-mega-menu-submenu" data-element-type="we-mega-menu-submenu" data-submenu-width="" data-class="" style="width: px">
|
||||||
|
<div class="we-mega-menu-submenu-inner">
|
||||||
|
<div class="we-mega-menu-row" data-element-type="we-mega-menu-row" data-custom-row="1">
|
||||||
|
<div class="we-mega-menu-col span3" data-element-type="we-mega-menu-col" data-width="3" data-block="enisaweb_topics" data-blocktitle="0" data-hidewhencollapse="" data-class="">
|
||||||
|
<div class="type-of-block"><div class="block-inner"><div id="block-enisaweb-topics" class="block block-block-content block-block-contentec3f1f7d-35d4-4776-a03c-7f97a2fcfc8f">
|
||||||
|
|
||||||
|
<p class="title"><a href="/topics" target="_self" title="Access to All Topics page">Topics</a></p>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><p>Learn more about the topics</p>
|
||||||
|
<p><a class="button" href="/topics" data-entity-type="node" data-entity-uuid="5f4db810-e19d-49a4-baaa-86ec782eb91e" data-entity-substitution="canonical" title="Topics">Access</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="we-mega-menu-col span3" data-element-type="we-mega-menu-col" data-width="3" data-block="enisaweb_views_block__topics_tax_block_1" data-blocktitle="0" data-hidewhencollapse="" data-class="">
|
||||||
|
<div class="type-of-block"><div class="block-inner"><div class="views-element-container block block-views block-views-blocktopics-tax-block-1" id="block-enisaweb-views-block-topics-tax-block-1">
|
||||||
|
|
||||||
|
<p class="title"><a href="/audience/national-eu-authorities" target="_self" title="Access to For National / EU authorities page">For National / EU authorities</a></p>
|
||||||
|
|
||||||
|
<div data-block="nav_main"><div class="view view-topics-tax view-id-topics_tax view-display-id-block_1 js-view-dom-id-7c1d2b87dabde327b4783b35298dbb11acd8b020c150d501b78875867b1ec97b">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="view-content">
|
||||||
|
<div class="item-list">
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
|
||||||
|
<li><a href="/topics/cyber-threats" hreflang="en">Cyber Threats</a></li>
|
||||||
|
<li><a href="/topics/eu-incident-response-and-cyber-crisis-management" hreflang="en">EU incident response and cyber crisis management</a></li>
|
||||||
|
<li><a href="/topics/market" hreflang="en">Market</a></li>
|
||||||
|
<li><a href="/topics/product-security-and-certification" hreflang="en">Product Security and Certification</a></li>
|
||||||
|
<li><a href="/topics/risk-management" hreflang="en">Risk Management</a></li>
|
||||||
|
<li><a href="/topics/skills-and-competences" hreflang="en">Skills and competences</a></li>
|
||||||
|
<li><a href="/topics/state-of-cybersecurity-in-the-eu" hreflang="en">State of cybersecurity in the EU</a></li>
|
||||||
|
<li><a href="/topics/vulnerability-disclosure" hreflang="en">Vulnerability Disclosure</a></li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div></div></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="we-mega-menu-col span3" data-element-type="we-mega-menu-col" data-width="3" data-block="enisaweb_views_block__topics_tax_block_2" data-blocktitle="0" data-hidewhencollapse="" data-class="">
|
||||||
|
<div class="type-of-block"><div class="block-inner"><div class="views-element-container title block block-views block-views-blocktopics-tax-block-2" id="block-enisaweb-views-block-topics-tax-block-2">
|
||||||
|
|
||||||
|
<p class="title"><a href="/audience/private-sector" target="_self" title="Access to Private Sector page">Private Sector</a></p>
|
||||||
|
|
||||||
|
<div data-block="nav_main"><div class="view view-topics-tax view-id-topics_tax view-display-id-block_2 js-view-dom-id-74b36a87a5d07d7f173c71b98d555f07f38adc86d0349f9aa7359a3e5c5fbbe7">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="view-content">
|
||||||
|
<div class="item-list">
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
|
||||||
|
<li><a href="/topics/artificial-intelligence-and-next-gen-technologies" hreflang="en">Artificial Intelligence and Next Gen Technologies</a></li>
|
||||||
|
<li><a href="/topics/awareness-and-cyber-hygiene" hreflang="en">Awareness and Cyber Hygiene</a></li>
|
||||||
|
<li><a href="/topics/certification-and-standards" hreflang="en">Certification and Standards</a></li>
|
||||||
|
<li><a href="/topics/cyber-threats" hreflang="en">Cyber Threats</a></li>
|
||||||
|
<li><a href="/topics/cybersecurity-of-critical-sectors" hreflang="en">Cybersecurity of Critical Sectors</a></li>
|
||||||
|
<li><a href="/topics/digital-identity-and-data-protection" hreflang="en">Digital Identity and Data Protection</a></li>
|
||||||
|
<li><a href="/topics/incident-management" hreflang="en">Incident management</a></li>
|
||||||
|
<li><a href="/topics/risk-management" hreflang="en">Risk Management</a></li>
|
||||||
|
<li><a href="/topics/skills-and-competences-for-companies" hreflang="en">Skills and competences (for companies)</a></li>
|
||||||
|
<li><a href="/topics/vulnerability-disclosure" hreflang="en">Vulnerability Disclosure</a></li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div></div></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="we-mega-menu-col span3" data-element-type="we-mega-menu-col" data-width="3" data-block="enisaweb_views_block__topics_tax_block_3" data-blocktitle="0" data-hidewhencollapse="" data-class="">
|
||||||
|
<div class="type-of-block"><div class="block-inner"><div class="views-element-container title block block-views block-views-blocktopics-tax-block-3" id="block-enisaweb-views-block-topics-tax-block-3">
|
||||||
|
|
||||||
|
<p class="title"><a href="/audience/citizens" target="_self" title="Access to Citizens page">Citizens</a></p>
|
||||||
|
|
||||||
|
<div data-block="nav_main"><div class="view view-topics-tax view-id-topics_tax view-display-id-block_3 js-view-dom-id-83bf5b206d0154c644b588d05a80369b2cba5116f1dc1735d8b2cd51de25353a">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="view-content">
|
||||||
|
<div class="item-list">
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
|
||||||
|
<li><a href="/topics/cyber-hygiene" hreflang="en">Cyber Hygiene</a></li>
|
||||||
|
<li><a href="/topics/cyber-incident-awareness" hreflang="en">Cyber Incident Awareness</a></li>
|
||||||
|
<li><a href="/topics/education-and-career-path" hreflang="en">Education and career path</a></li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div></div></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li><li class="we-mega-menu-li" title="Access to all publications" data-level="0" data-element-type="" data-id="2d24a690-803d-44ad-8d7e-8cf0eb7d0e40" data-submenu="0" data-hide-sub-when-collapse="" data-group="0" data-caption="" data-alignsub="" data-target="_self" data-icon="" >
|
||||||
|
<a class="we-mega-menu-li" title="" href="/publications" target="_self">
|
||||||
|
Publications </a>
|
||||||
|
|
||||||
|
</li><li class="we-mega-menu-li dropdown-menu" title="" data-level="0" data-element-type="" data-id="54b069d8-f99f-45e7-9d50-9345e82ad14f" data-submenu="1" data-hide-sub-when-collapse="" data-group="0" data-caption="" data-alignsub="" data-target="" data-icon="" >
|
||||||
|
<span class="we-megamenu-nolink">
|
||||||
|
Newsroom & Events</span>
|
||||||
|
<div class="we-mega-menu-submenu" data-element-type="we-mega-menu-submenu" data-submenu-width="" data-class="" style="width: px">
|
||||||
|
<div class="we-mega-menu-submenu-inner">
|
||||||
|
<div class="we-mega-menu-row" data-element-type="we-mega-menu-row" data-custom-row="1">
|
||||||
|
<div class="we-mega-menu-col span4" data-element-type="we-mega-menu-col" data-width="4" data-block="enisaweb_news" data-blocktitle="0" data-hidewhencollapse="" data-class="">
|
||||||
|
<div class="type-of-block"><div class="block-inner"><div id="block-enisaweb-news" class="block block-block-content block-block-content00f65cb0-e1b0-41a1-8a0d-00ce6ced0f0e">
|
||||||
|
|
||||||
|
<p class="title"><a href="/news" target="_self" title="Access to All News">News</a></p>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><p>Cybersecurity in focus: News & updates from ENISA</p>
|
||||||
|
<p><a class="button" href="/news" data-entity-type="node" data-entity-uuid="088dd847-33ff-4c8e-b094-f0d38a66bbdd" data-entity-substitution="canonical" title="News">Access</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="we-mega-menu-col span4" data-element-type="we-mega-menu-col" data-width="4" data-block="enisaweb_events" data-blocktitle="0" data-hidewhencollapse="" data-class="">
|
||||||
|
<div class="type-of-block"><div class="block-inner"><div id="block-enisaweb-events" class="block block-block-content block-block-contentb3c06780-f342-437d-9828-54fe86eb9786">
|
||||||
|
|
||||||
|
<p class="title"><a href="/events" target="_self" title="Access to All Events">Events</a></p>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><p>Cybersecurity in practice: Events & Workshops by ENISA</p>
|
||||||
|
<p><a class="button" href="/events" data-entity-type="node" data-entity-uuid="34f66c6d-0f07-4977-85e3-e8578a59d59c" data-entity-substitution="canonical" title="Events">Access</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="we-mega-menu-col span4" data-element-type="we-mega-menu-col" data-width="4" data-block="enisaweb_pressoffice" data-blocktitle="0" data-hidewhencollapse="" data-class="">
|
||||||
|
<div class="type-of-block"><div class="block-inner"><div id="block-enisaweb-pressoffice" class="block block-system block-system-menu-blockpress-office">
|
||||||
|
|
||||||
|
<p class="title"><a href="/press-office" target="_self" title="Access to Press office page">Press office</a></p>
|
||||||
|
|
||||||
|
|
||||||
|
<ul data-block="nav_main" class="nav navbar-nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/press-office/corporate-identity" class="nav-link" data-drupal-link-system-path="node/11030">Corporate identity</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/press-office/cybersecurity-material" class="nav-link" data-drupal-link-system-path="node/11031">Cybersecurity material</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/form/seek-an-expert" class="nav-link" data-drupal-link-system-path="webform/seek_an_expert">Seek an expert or Request a speaker</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div></div></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li><li class="we-mega-menu-li dropdown-menu" title="" data-level="0" data-element-type="" data-id="eaba29ce-6446-4da3-930a-c3d97495f00b" data-submenu="1" data-hide-sub-when-collapse="" data-group="0" data-caption="" data-alignsub="" data-target="" data-icon="" >
|
||||||
|
<span class="we-megamenu-nolink">
|
||||||
|
About</span>
|
||||||
|
<div class="we-mega-menu-submenu" data-element-type="we-mega-menu-submenu" data-submenu-width="" data-class="" style="width: px">
|
||||||
|
<div class="we-mega-menu-submenu-inner">
|
||||||
|
<div class="we-mega-menu-row" data-element-type="we-mega-menu-row" data-custom-row="1">
|
||||||
|
<div class="we-mega-menu-col span4" data-element-type="we-mega-menu-col" data-width="4" data-block="enisaweb_whatwedosubmenubutton_2" data-blocktitle="0" data-hidewhencollapse="" data-class="">
|
||||||
|
<div class="type-of-block"><div class="block-inner"><div id="block-enisaweb-whatwedosubmenubutton-2" class="block block-block-content block-block-contente0a96203-1eca-4991-9533-33064cace452">
|
||||||
|
|
||||||
|
<p class="title"><a href="/about-enisa/what-we-do" target="_self" title="Access to What we do page">What we do</a></p>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><p>Achieving a high common level of cybersecurity across Europe</p>
|
||||||
|
<p><a class="button" href="/about-enisa/what-we-do" data-entity-type="node" data-entity-uuid="0619d110-d4dc-4f6b-aa0d-9a56252da07d" data-entity-substitution="canonical" title="What we do">Access</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div></div></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="we-mega-menu-col span4" data-element-type="we-mega-menu-col" data-width="4" data-block="enisaweb_whatwedosubmenu_2" data-blocktitle="0" data-hidewhencollapse="" data-class="">
|
||||||
|
<div class="type-of-block"><div class="block-inner"><div id="block-enisaweb-whatwedosubmenu-2" class="block block-block-content block-block-content99599d89-56f7-43f1-907b-72614045018d">
|
||||||
|
|
||||||
|
<p class="title"><a href="/about-enisa/who-we-are" target="_self" title="Access to Who we are page">Who we are</a></p>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><p>Towards a Trusted and Cyber Secure Europe </p>
|
||||||
|
<p><a class="button" href="/about-enisa/who-we-are" data-entity-type="node" data-entity-uuid="3c46c210-298c-472c-b871-b167dfe2642c" data-entity-substitution="canonical" title="Who we are">Access</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div></div></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="we-mega-menu-col span4" data-element-type="we-mega-menu-col" data-width="4" data-block="enisaweb_transparency" data-blocktitle="0" data-hidewhencollapse="" data-class="">
|
||||||
|
<div class="type-of-block"><div class="block-inner"><div id="block-enisaweb-transparency" class="block block-system block-system-menu-blocktransparency">
|
||||||
|
|
||||||
|
<p class="title"><a href="/about-enisa/How-we-work" target="_self" title="Access to How we work page">How we work</a></p>
|
||||||
|
|
||||||
|
|
||||||
|
<ul data-block="nav_main" class="nav navbar-nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/about-enisa/accounting-finance/accounting-finance" class="nav-link" data-drupal-link-system-path="node/17339">Accounting and Finance</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/about-enisa/procedures-and-policies" class="nav-link dropdown-toggle" data-drupal-link-system-path="node/17360">Policies and Procedures</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/about-enisa/data-protection/data-protection" class="nav-link dropdown-toggle" data-drupal-link-system-path="node/17346">Data Protection</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/about-enisa/transparency" class="nav-link dropdown-toggle" data-drupal-link-system-path="node/17404">Transparency</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/enisa-a-climate-neutral-agency" class="nav-link" data-drupal-link-system-path="node/18893">ENISA, a climate neutral agency </a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="we-mega-menu-row" data-element-type="we-mega-menu-row" data-custom-row="1">
|
||||||
|
<div class="we-mega-menu-col span12 transversal-menu" data-element-type="we-mega-menu-col" data-width="12" data-block="enisaweb_transversaloptionsofaboutmenu" data-blocktitle="0" data-hidewhencollapse="" data-class="transversal-menu">
|
||||||
|
<div class="type-of-block"><div class="block-inner"><div id="block-enisaweb-transversaloptionsofaboutmenu" class="block block-system block-system-menu-blocktransversal-options-about-menu">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<ul data-block="nav_main" class="nav navbar-nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/about-enisa/international-cooperation" class="nav-link" data-drupal-link-system-path="node/19377">International Cooperation</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li><li class="we-mega-menu-li dropdown-menu" title="" data-level="0" data-element-type="" data-id="44fbec5d-1aaf-4cb3-b06b-a79b571ad50c" data-submenu="1" data-hide-sub-when-collapse="" data-group="0" data-caption="" data-alignsub="" data-target="" data-icon="" >
|
||||||
|
<span class="we-megamenu-nolink">
|
||||||
|
Working with us</span>
|
||||||
|
<div class="we-mega-menu-submenu" data-element-type="we-mega-menu-submenu" data-submenu-width="" data-class="" style="width: px">
|
||||||
|
<div class="we-mega-menu-submenu-inner">
|
||||||
|
<div class="we-mega-menu-row" data-element-type="we-mega-menu-row" data-custom-row="1">
|
||||||
|
<div class="we-mega-menu-col span4" data-element-type="we-mega-menu-col" data-width="4" data-block="enisaweb_workwithenisa" data-blocktitle="0" data-hidewhencollapse="" data-class="">
|
||||||
|
<div class="type-of-block"><div class="block-inner"><div id="block-enisaweb-workwithenisa" class="block block-block-content block-block-content86d5ce2f-abc2-4160-afda-442fe2c68258">
|
||||||
|
|
||||||
|
<p class="title"><a href="/working-with-us/working-for-enisa" target="_self" title="Access to Working for ENISA page">Working for ENISA</a></p>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><p>Explore the benefits of working for ENISA</p>
|
||||||
|
<p><a class="button" href="/working-with-us/working-for-enisa" data-entity-type="node" data-entity-uuid="f10da531-46ad-49f6-84cb-0770c69d839e" data-entity-substitution="canonical" title="Working for ENISA">Access</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="we-mega-menu-col span8" data-element-type="we-mega-menu-col" data-width="8" data-block="enisaweb_workingwithus" data-blocktitle="0" data-hidewhencollapse="" data-class="">
|
||||||
|
<div class="type-of-block"><div class="block-inner"><div id="block-enisaweb-workingwithus" class="title block block-system block-system-menu-blockworking-with-us">
|
||||||
|
|
||||||
|
<p class="title"><a href="/work-with-us" target="_self" title="Access to Working with us page">Working with us</a></p>
|
||||||
|
|
||||||
|
|
||||||
|
<ul data-block="nav_main" class="nav navbar-nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/careers" class="nav-link" data-drupal-link-system-path="node/12487">Careers</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/working-with-us/procurement" class="nav-link" data-drupal-link-system-path="node/17421">Procurement</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/working-with-us/ad-hoc-working-groups-calls" class="nav-link" data-drupal-link-system-path="node/17422">Ad hoc working groups</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div></div></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="btn-search">
|
||||||
|
<a href="javascript:" title="Show the search field">Search</a>
|
||||||
|
</p>
|
||||||
|
<div class="search-form-wrapper" aria-label="Search field">
|
||||||
|
<div class="region region-nav-additional">
|
||||||
|
<div id="block-enisaweb-search" class="search-block-form block block-search container-inline" data-drupal-selector="search-block-form" id="block-enisaweb-search-form" role="search">
|
||||||
|
|
||||||
|
|
||||||
|
<form data-block="nav_additional" action="/search" method="get" id="search-block-form" accept-charset="UTF-8">
|
||||||
|
<div class="js-form-item form-item js-form-type-search form-type-search js-form-item-keys form-item-keys form-no-label">
|
||||||
|
<label class="visually-hidden" for="edit-keys">Search</label>
|
||||||
|
<input class="form-search form-control" title="Enter the terms you wish to search for." data-drupal-selector="edit-keys" type="search" id="edit-keys" name="keys" value="" size="15" maxlength="128">
|
||||||
|
</div>
|
||||||
|
<div class="form-actions js-form-wrapper form-wrapper" data-drupal-selector="edit-actions" id="edit-actions-search">
|
||||||
|
<button class="button js-form-submit form-submit btn-enisa btn-primary" data-drupal-selector="edit-submit" type="submit" id="edit-submit" value="Search">Search</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="btn-close-search"><a href="javascript:" title="Hide the search field">Close</a></p>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main role="main" class="container-full image-banner">
|
||||||
|
<div class="title-breadcrumbs container">
|
||||||
|
|
||||||
|
<div class="title-intro full">
|
||||||
|
|
||||||
|
<div id="block-enisaweb-page-title" class="block block-core block-page-title-block">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<h1>Page not found</h1>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="main container" id="main-content">
|
||||||
|
<a id="main-content" tabindex="-1"></a> <div class="container">
|
||||||
|
<div data-drupal-messages-fallback class="hidden"></div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="row g-0 not-found-page">
|
||||||
|
<div class="col-xs-12 col-md-12 col-lg-6 not-found-image">
|
||||||
|
<div class="image"></div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-12 col-lg-6 not-found-text">
|
||||||
|
<h2>OOPS! PAGE NOT FOUND</h2>
|
||||||
|
<p>The page you are looking for can't be found.</p>
|
||||||
|
<p><a href="https://www.enisa.europa.eu/" class="btn-all left">Go to the homepage</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<section class="subscribe-section" role="complementary">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3 col-lg-3 subscribe-image"></div>
|
||||||
|
<div class="col-md-9 col-lg-9 subscribe-wrapper">
|
||||||
|
<h2>Subscribe</h2>
|
||||||
|
<p><strong>Stay updated with ENISA!</strong> Sign up for email alerts on publications, events, vacancies,
|
||||||
|
and more.</p>
|
||||||
|
<p><a href="/alertservice" class="btn-all left">Sign up now</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer class="mt-auto enisa-footer">
|
||||||
|
<div class="container">
|
||||||
|
<div class="footer container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12 col-lg-3">
|
||||||
|
<div id="block-enisaweb-enisalogos">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item">
|
||||||
|
<div class="enisa-logo">
|
||||||
|
<img src="/themes/custom/enisaweb/images/enisa-logo-white.svg"
|
||||||
|
alt="ENISA, European Union Agency for Cybersecurity" width="220" height="150"
|
||||||
|
class="align-left" loading="lazy">
|
||||||
|
<p><em>A Trusted and Cyber Secure Europe</em></p>
|
||||||
|
</div>
|
||||||
|
<div class="agencies-network-logo">
|
||||||
|
<img src="/themes/custom/enisaweb/images/agencies-network.png"
|
||||||
|
alt="Agencies network logo" width="39" height="36" class="align-left"
|
||||||
|
loading="lazy">
|
||||||
|
<p><a href="https://agencies-network.europa.eu/index_en">EU Agencies Network</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="block-enisaweb-socialmedialinks" class="block-social-media-links block block-social-media-links-block">
|
||||||
|
|
||||||
|
<p>Follow us on</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<ul class="social-media-links--platforms platforms vertical">
|
||||||
|
<li>
|
||||||
|
<a class="social-media-link-icon--youtube" href="https://www.youtube.com/user/ENISAvideos" target="_blank" >
|
||||||
|
<span class='fab fa-youtube fa-2x'></span>
|
||||||
|
<span class="platform-name">Youtube</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="social-media-link-icon--twitter" href="https://x.com/enisa_eu" target="_blank" >
|
||||||
|
<span class='fab fa-x-twitter fa-2x'></span>
|
||||||
|
<span class="platform-name">X</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="social-media-link-icon--linkedin" href="https://www.linkedin.com/company/european-union-agency-for-cybersecurity-enisa/" target="_blank" >
|
||||||
|
<span class='fab fa-linkedin fa-2x'></span>
|
||||||
|
<span class="platform-name">LinkedIn</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="social-media-link-icon--facebook" href="https://www.facebook.com/ENISAEUAGENCY" target="_blank" >
|
||||||
|
<span class='fab fa-facebook fa-2x'></span>
|
||||||
|
<span class="platform-name">Facebook</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-6 col-lg-3 border-left">
|
||||||
|
<nav aria-labelledby="block-enisaweb-contactus-menu" id="block-enisaweb-contactus" class="block block-menu navigation menu--contact-us">
|
||||||
|
|
||||||
|
<p id="block-enisaweb-contactus-menu">Contact us</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<ul data-block="footer" class="nav navbar-nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/about-enisa/contact/contact" class="nav-link" data-drupal-link-system-path="node/17344">Contacts</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/form/contact-form" class="nav-link" data-drupal-link-system-path="webform/contact_form">General queries</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/working-with-us/procurement" class="nav-link" data-drupal-link-system-path="node/17421">Public Procurement</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/form/media-inquiries" class="nav-link" data-drupal-link-system-path="webform/media_inquiries">Media inquiries</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-6 col-lg-3 border-left">
|
||||||
|
<div id="block-enisaweb-findoutaboutus" class="block block-system block-system-menu-blockfind-out-about-us">
|
||||||
|
|
||||||
|
<p>Find out about us</p>
|
||||||
|
|
||||||
|
|
||||||
|
<ul data-block="footer" class="nav navbar-nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/accessibility-statement" class="nav-link" data-drupal-link-system-path="node/18887">Accessibility</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/about-enisa/legal-notice" class="nav-link" data-drupal-link-system-path="node/17355">Legal Notice</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/about-enisa/data-protection/data-protection" class="nav-link" data-drupal-link-system-path="node/17346">Data Protection</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/about-enisa/cookies" class="nav-link" data-drupal-link-system-path="node/17345">Cookies</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/sitemap" target="_blank" class="nav-link" data-drupal-link-system-path="sitemap">Sitemap</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-6 col-lg-3 border-left">
|
||||||
|
<div id="block-enisaweb-pageofinterest" class="block block-system block-system-menu-blockpage-of-interest">
|
||||||
|
|
||||||
|
<p>Page of interest</p>
|
||||||
|
|
||||||
|
|
||||||
|
<ul data-block="footer" class="nav navbar-nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/publications" class="nav-link" data-drupal-link-system-path="publications">Publications </a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/press-office" class="nav-link" data-drupal-link-system-path="node/11033">Press Office</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/digital-tools" class="nav-link" data-drupal-link-system-path="digital-tools">Digital Tools</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/work-with-us" class="nav-link" data-drupal-link-system-path="node/11443">Working with us</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/about-enisa/public-access-to-documents" class="nav-link" data-drupal-link-system-path="node/17363">Public access to documents</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="line"></div>
|
||||||
|
<div class="copy container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12 col-md-12 col-lg-6 alignleft">
|
||||||
|
<div id="block-enisaweb-copyrightfooter" class="block block-block-content block-block-content0f0b270e-cc5d-448c-a771-6cc7c0621340">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><p>© 2026 by the European Union Agency for Cybersecurity</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-12 col-lg-6 alignright">
|
||||||
|
<div id="block-enisaweb-enisadescriptionfooter" class="block block-block-content block-block-content2b4d8729-4032-4438-ba87-1a58fbc364db">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item"><p>ENISA is an agency of the European Union</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p><a href="#" id="totop" class="totop"><span class="sr-only">Go to top</span></a></p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="application/json">{"utility":"piwik","siteID":"5847bf6f-3ce3-4800-8749-1c565b34b7b6","sitePath":["www.enisa.europa.eu"],"is404":true,"instance":"ec"}</script>
|
||||||
|
<script type="application/json">{"utility":"cck","url":"\/about-enisa\/cookies"}</script>
|
||||||
|
|
||||||
|
<script type="application/json" data-drupal-selector="drupal-settings-json">{"path":{"baseUrl":"\/","pathPrefix":"","currentPath":"","currentPathIsAdmin":false,"isFront":false,"currentLanguage":"en"},"pluralDelimiter":"\u0003","suppressDeprecationErrors":true,"ckeditorAccordion":{"accordionStyle":{"collapseAll":1,"keepRowsOpen":0,"animateAccordionOpenAndClose":1,"openTabsWithHash":0,"allowHtmlInTitles":1}},"user":{"uid":0,"permissionsHash":"c1c359b7541ecd1c4f0e321882d2e1eba1197d85c8ad3b45b15aff5871a9e6d0"}}</script>
|
||||||
|
<script src="/sites/default/files/js/js_Uv_fxqHIMtEtS30w0hjV4odzonkoWsMT5KD3y13y8kU.js?scope=footer&delta=0&language=en&theme=enisaweb&include=eJxlj0EOgzAMBD-EG6kfQiYxEOrYNHGE8vuCWkql3mZn97IYgilKc_iB25hVrPMPCtE09-i95hBV3JfeE5LQkcSCGw3uBFgKMBoVu7qJdUCGYo2jTJdfnpVyA98805-tEUyrn2Gt4uefuqjcO6V-D6bKxYVcV-TbmaEkzMaKgXK3UZ9owkRS3ag57Ss4BBwGzhsvrCdlDA"></script>
|
||||||
|
<script src="https://static.addtoany.com/menu/page.js" defer></script>
|
||||||
|
<script src="/sites/default/files/js/js_PJg5XHigxDm_42CAJgw7llont3PySBp-PdpAvGlCYSs.js?scope=footer&delta=2&language=en&theme=enisaweb&include=eJxlj0EOgzAMBD-EG6kfQiYxEOrYNHGE8vuCWkql3mZn97IYgilKc_iB25hVrPMPCtE09-i95hBV3JfeE5LQkcSCGw3uBFgKMBoVu7qJdUCGYo2jTJdfnpVyA98805-tEUyrn2Gt4uefuqjcO6V-D6bKxYVcV-TbmaEkzMaKgXK3UZ9owkRS3ag57Ss4BBwGzhsvrCdlDA"></script>
|
||||||
|
<script src="/modules/contrib/ckeditor_accordion/js/accordion.frontend.min.js?tg28tw"></script>
|
||||||
|
<script src="/sites/default/files/js/js_fPzrD9aZOLJS9JI2GLgD7Zs-CzoWHT18p8hYIEuW9h4.js?scope=footer&delta=4&language=en&theme=enisaweb&include=eJxlj0EOgzAMBD-EG6kfQiYxEOrYNHGE8vuCWkql3mZn97IYgilKc_iB25hVrPMPCtE09-i95hBV3JfeE5LQkcSCGw3uBFgKMBoVu7qJdUCGYo2jTJdfnpVyA98805-tEUyrn2Gt4uefuqjcO6V-D6bKxYVcV-TbmaEkzMaKgXK3UZ9owkRS3ag57Ss4BBwGzhsvrCdlDA"></script>
|
||||||
|
<script src="https://webtools.europa.eu/load.js" defer></script>
|
||||||
|
<script src="/sites/default/files/js/js_Pj1gX-gXRHcdCBDI1-WO0jTP3o3GK7ZZPT2TZokpFjY.js?scope=footer&delta=6&language=en&theme=enisaweb&include=eJxlj0EOgzAMBD-EG6kfQiYxEOrYNHGE8vuCWkql3mZn97IYgilKc_iB25hVrPMPCtE09-i95hBV3JfeE5LQkcSCGw3uBFgKMBoVu7qJdUCGYo2jTJdfnpVyA98805-tEUyrn2Gt4uefuqjcO6V-D6bKxYVcV-TbmaEkzMaKgXK3UZ9owkRS3ag57Ss4BBwGzhsvrCdlDA"></script>
|
||||||
|
|
||||||
|
<script async="" src="/themes/custom/enisaweb/js/application.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,58 @@
|
|||||||
|
# Test Report — marker-api Endpoint Tests
|
||||||
|
|
||||||
|
**Date:** 2026-06-04 10:57:07
|
||||||
|
**API Base:** N/A (offline validation)
|
||||||
|
**Test files:** /home/oval/Projects/marker-api/test_files/enisa (5 file(s))
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
| Result | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| ✅ Passed | 5 |
|
||||||
|
| ❌ Failed | 0 |
|
||||||
|
| ⏭️ Skipped | 10 |
|
||||||
|
| **Total** | **15** |
|
||||||
|
| **Pass Rate** | **33.3%** |
|
||||||
|
|
||||||
|
## Test Details
|
||||||
|
|
||||||
|
| Test | Result | Time | Notes |
|
||||||
|
|------|--------|------|-------|
|
||||||
|
|
||||||
|
### Application Tests
|
||||||
|
| app.py syntax valid | ✅ PASS | 0.002s | |
|
||||||
|
| use_llm/llm_service params present in app.py | ✅ PASS | 0.000s | |
|
||||||
|
| marker-convert.ps1 has all new parameters | ✅ PASS | 0.000s | |
|
||||||
|
| README.md documents all endpoints | ✅ PASS | 0.000s | |
|
||||||
|
| ENISA test files available | ✅ PASS | 0.019s | |
|
||||||
|
|
||||||
|
### Endpoint Tests (HTTP)
|
||||||
|
| GET /health | ⏭️ SKIP | - | API not running |
|
||||||
|
| GET / | ⏭️ SKIP | - | API not running |
|
||||||
|
| POST /marker (multipart) | ⏭️ SKIP | - | API not running |
|
||||||
|
| POST /marker (JSON) | ⏭️ SKIP | - | API not running |
|
||||||
|
| POST /marker with use_llm | ⏭️ SKIP | - | API not running |
|
||||||
|
| POST /marker with page_range | ⏭️ SKIP | - | API not running |
|
||||||
|
| POST /marker missing file | ⏭️ SKIP | - | API not running |
|
||||||
|
| POST /v1/conversions | ⏭️ SKIP | - | API not running |
|
||||||
|
| POST /v1/files/convert (multipart) | ⏭️ SKIP | - | API not running |
|
||||||
|
| POST /v1/files/convert (JSON) | ⏭️ SKIP | - | API not running |
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
- Python: 3.14.5
|
||||||
|
- Platform: linux
|
||||||
|
- marker: ❌ not installed (Pillow build failure on this platform)
|
||||||
|
|
||||||
|
## Test Files
|
||||||
|
- enisa-international-strategy-2026.pdf (39.7 KB)
|
||||||
|
- enisa-nis360-2026.pdf (3983.3 KB)
|
||||||
|
- enisa-stakeholder-strategy-2026-2028.pdf (35.3 KB)
|
||||||
|
- nis-investments-2025.pdf (3118.9 KB)
|
||||||
|
- nis2-technical-implementation-guidance.pdf (4699.7 KB)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- HTTP endpoint tests require a running marker-api instance.
|
||||||
|
- API-level tests were skipped (no API running).
|
||||||
|
- Offline validation tests (syntax, parameter availability, docs) ran regardless.
|
||||||
|
- The marker library could not be installed due to Pillow build failure in this sandbox.
|
||||||
|
- For live endpoint testing, start the API with: `python app.py` (requires marker-pdf[full] + flask).
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# Test Report — marker-api Endpoint Tests
|
||||||
|
|
||||||
|
**Date:** 2026-06-04 13:19:39
|
||||||
|
**API Base:** http://10.0.2.145:8000
|
||||||
|
**Test files:** /home/oval/Projects/marker-api/test_files/enisa (5 file(s))
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
| Result | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| ✅ Passed | 5 |
|
||||||
|
| ❌ Failed | 11 |
|
||||||
|
| ⏭️ Skipped | 0 |
|
||||||
|
| **Total** | **16** |
|
||||||
|
| **Pass Rate** | **31.2%** |
|
||||||
|
|
||||||
|
## Test Details
|
||||||
|
|
||||||
|
| Test | Result | Time | Notes |
|
||||||
|
|------|--------|------|-------|
|
||||||
|
|
||||||
|
### Application Tests
|
||||||
|
| app.py syntax valid | ✅ PASS | 0.003s | |
|
||||||
|
| use_llm/llm_service params present in app.py | ✅ PASS | 0.000s | |
|
||||||
|
| marker-convert.ps1 has all new parameters | ✅ PASS | 0.000s | |
|
||||||
|
| README.md documents all endpoints | ✅ PASS | 0.000s | |
|
||||||
|
| ENISA test files available | ✅ PASS | 0.017s | |
|
||||||
|
|
||||||
|
### Endpoint Tests (HTTP)
|
||||||
|
| GET /health returns 200 | ❌ FAIL | 0.003s | Cannot reach http://10.0.2.145:8000/health: [Errno 113] No route to host |
|
||||||
|
| GET / returns docs page | ❌ FAIL | 0.001s | Cannot reach http://10.0.2.145:8000/: [Errno 113] No route to host |
|
||||||
|
| POST /marker multipart (markdown) | ❌ FAIL | 0.000s | Cannot reach http://10.0.2.145:8000/marker: [Errno 113] No route to host |
|
||||||
|
| POST /marker multipart (json) | ❌ FAIL | 0.000s | Cannot reach http://10.0.2.145:8000/marker: [Errno 113] No route to host |
|
||||||
|
| POST /marker JSON base64 | ❌ FAIL | 0.000s | Cannot reach http://10.0.2.145:8000/marker: [Errno 113] No route to host |
|
||||||
|
| POST /marker with use_llm=true | ❌ FAIL | 0.000s | Cannot reach http://10.0.2.145:8000/marker: [Errno 113] No route to host |
|
||||||
|
| POST /marker with page_range | ❌ FAIL | 1.034s | Cannot reach http://10.0.2.145:8000/marker: [Errno 113] No route to host |
|
||||||
|
| POST /marker missing file → 400 | ❌ FAIL | 1.025s | Cannot reach http://10.0.2.145:8000/marker: [Errno 113] No route to host |
|
||||||
|
| POST /v1/conversions JSON | ❌ FAIL | 1.023s | Cannot reach http://10.0.2.145:8000/v1/conversions: [Errno 113] No route to host |
|
||||||
|
| POST /v1/files/convert multipart | ❌ FAIL | 1.024s | Cannot reach http://10.0.2.145:8000/v1/files/convert: [Errno 113] No route to host |
|
||||||
|
| POST /v1/files/convert JSON | ❌ FAIL | 1.024s | Cannot reach http://10.0.2.145:8000/v1/files/convert: [Errno 113] No route to host |
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
- Python: 3.14.5
|
||||||
|
- Platform: linux
|
||||||
|
- marker: ❌ not installed (Pillow build failure on this platform)
|
||||||
|
|
||||||
|
## Test Files
|
||||||
|
- enisa-international-strategy-2026.pdf (39.7 KB)
|
||||||
|
- enisa-nis360-2026.pdf (3983.3 KB)
|
||||||
|
- enisa-stakeholder-strategy-2026-2028.pdf (35.3 KB)
|
||||||
|
- nis-investments-2025.pdf (3118.9 KB)
|
||||||
|
- nis2-technical-implementation-guidance.pdf (4699.7 KB)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- HTTP endpoint tests require a running marker-api instance.
|
||||||
|
- API-level tests were run against http://10.0.2.145:8000
|
||||||
|
- Offline validation tests (syntax, parameter availability, docs) ran regardless.
|
||||||
|
- The marker library could not be installed due to Pillow build failure in this sandbox.
|
||||||
|
- For live endpoint testing, start the API with: `python app.py` (requires marker-pdf[full] + flask).
|
||||||
|
- Failed tests may indicate missing marker library or API connectivity issues.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"detail":"Not Found"}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"detail":"Not Found"}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"detail":"Not Found"}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"detail":"Not Found"}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"detail":"Not Found"}
|
||||||
Reference in New Issue
Block a user