From 5bd41fe7fc90a3c1a18f60ecd84936a5189b6539 Mon Sep 17 00:00:00 2001 From: Ravi Meijer Date: Tue, 20 Jan 2026 14:37:50 +0100 Subject: [PATCH 01/12] First set-up docker container --- .claude/settings.local.json | 9 +++++++++ docs/javascripts/extra.js | 18 +++++++++--------- docs/javascripts/hamburger-control.js | 20 ++++++++++---------- docs/stylesheets/extra.css | 16 ++++++++-------- docs/stylesheets/navigation.css | 3 +-- src/overrides/hooks/lists.py | 11 ++++++++--- src/overrides/main.html | 2 +- 7 files changed, 46 insertions(+), 33 deletions(-) create mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000000..46507b9e2b --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "Bash(find:*)" + ], + "deny": [], + "ask": [] + } +} diff --git a/docs/javascripts/extra.js b/docs/javascripts/extra.js index 4d0a8ab55f..06dafdbabd 100644 --- a/docs/javascripts/extra.js +++ b/docs/javascripts/extra.js @@ -20,7 +20,7 @@ const focusedElement = document.activeElement; const isMainContent = focusedElement && focusedElement.id === 'main-content'; const isSkipLink = focusedElement && focusedElement.classList.contains('skip-link'); - + if (!isMainContent && !isSkipLink) { // Reset focus to document body to ensure skiplink is first tab stop document.body.setAttribute('tabindex', '-1'); @@ -123,34 +123,34 @@ // Function to setup custom tooltips function setupCustomTooltip(element) { let originalTitle = element.getAttribute('title'); - + // Store original title in data attribute for CSS to use element.setAttribute('data-tooltip', originalTitle); - + // Remove title immediately to prevent native tooltip element.removeAttribute('title'); } - // Function to fix search result semantic structure + // Function to fix search result semantic structure // Converts h1 tags in search results to h2 for proper semantic hierarchy function fixSearchResultSemantics() { // Find all search result h1 tags within the search output area const searchResults = document.querySelectorAll('.md-search-result h1, .md-search-result__title'); - + searchResults.forEach(function(h1Element) { // Only convert h1 tags, not other elements with md-search-result__title class if (h1Element.tagName.toLowerCase() === 'h1') { // Create a new h2 element const h2Element = document.createElement('h2'); - + // Copy all attributes from h1 to h2 Array.from(h1Element.attributes).forEach(function(attr) { h2Element.setAttribute(attr.name, attr.value); }); - + // Copy all content from h1 to h2 h2Element.innerHTML = h1Element.innerHTML; - + // Replace h1 with h2 in the DOM h1Element.parentNode.replaceChild(h2Element, h1Element); } @@ -166,7 +166,7 @@ const parent = mark.parentNode; const textNode = document.createTextNode(mark.textContent); parent.replaceChild(textNode, mark); - + // Normalize the parent to merge adjacent text nodes parent.normalize(); }); diff --git a/docs/javascripts/hamburger-control.js b/docs/javascripts/hamburger-control.js index 31aea1bbaf..2a0c0d1bd6 100644 --- a/docs/javascripts/hamburger-control.js +++ b/docs/javascripts/hamburger-control.js @@ -4,16 +4,16 @@ function controlHamburgerMenu() { // Even more aggressive selector search const selectors = [ '.md-header__button[for="__drawer"]', - '.md-header__button.md-icon--menu', + '.md-header__button.md-icon--menu', '.md-nav__button', 'button[for="__drawer"]', '[data-md-component="navigation"]', '.md-header__button[type="button"]', '.md-header button' ]; - + const hamburgers = document.querySelectorAll(selectors.join(', ')); - + hamburgers.forEach(hamburger => { if (window.innerWidth > 800) { // Nuclear option - remove all possible CSS @@ -30,25 +30,25 @@ function controlHamburgerMenu() { console.log('Hamburger restored:', hamburger.className); } }); - + console.log('Screen width check:', window.innerWidth, 'Found hamburgers:', hamburgers.length); } - + // Initial check findAndHideHamburger(); - + // Listen for resize events window.addEventListener('resize', findAndHideHamburger); - + // Force check every 100ms to override any dynamic changes from Material Insiders const forceInterval = setInterval(findAndHideHamburger, 100); - + // Also watch for DOM mutations if (typeof MutationObserver !== 'undefined') { const observer = new MutationObserver(findAndHideHamburger); observer.observe(document.body, { childList: true, subtree: true }); } - + // Clear interval after 10 seconds to avoid performance impact setTimeout(() => clearInterval(forceInterval), 10000); } @@ -64,4 +64,4 @@ if (document.readyState === 'loading') { window.addEventListener('pageshow', controlHamburgerMenu); // Run again after a delay to catch any late-loading elements -setTimeout(controlHamburgerMenu, 1000); \ No newline at end of file +setTimeout(controlHamburgerMenu, 1000); diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index e332aa43a7..58634212e7 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -121,27 +121,27 @@ td { table, thead, tbody, th, td, tr { display: block !important; } - + thead tr { position: absolute !important; top: -9999px !important; left: -9999px !important; } - + tr { border: 1px solid #B9C7D5 !important; margin-bottom: 10px !important; padding: 10px !important; border-radius: 4px; } - + td { border: none !important; position: relative !important; padding-left: 50% !important; min-width: auto !important; } - + td:before { content: attr(data-label) ": " !important; position: absolute !important; @@ -762,7 +762,7 @@ font-size: 0.875rem !important; .responsive-grid-2 { grid-template-columns: 1fr !important; } - + .responsive-grid-3 { grid-template-columns: 1fr !important; } @@ -776,20 +776,20 @@ font-size: 0.875rem !important; /* WCAG AA: Make images scalable for 320px viewport */ @media (max-width: 320px) { - img, + img, .block-image, .md-header__button.md-logo img { max-width: 100% !important; height: auto !important; width: auto !important; } - + /* Ensure grid cards stack properly on very small screens */ .grid.cards { grid-template-columns: 1fr !important; gap: 8px !important; } - + /* Reduce padding for very small screens */ .float-child, .float-child-white { diff --git a/docs/stylesheets/navigation.css b/docs/stylesheets/navigation.css index 2dbe81ab68..b6b9d8f233 100644 --- a/docs/stylesheets/navigation.css +++ b/docs/stylesheets/navigation.css @@ -650,7 +650,7 @@ abbr[data-tooltip]:hover { position: static !important; z-index: auto !important; } - + .md-main { margin-top: 0 !important; } @@ -720,4 +720,3 @@ abbr[data-tooltip]:hover { display: none !important; } } - diff --git a/src/overrides/hooks/lists.py b/src/overrides/hooks/lists.py index 00b30ec282..8f68b59bdb 100644 --- a/src/overrides/hooks/lists.py +++ b/src/overrides/hooks/lists.py @@ -450,14 +450,17 @@ def generate_filters( has_search = filter_options.get("search", True) has_column_filters = len(column_filters_html) > 0 has_ai_act_labels = filter_options.get("ai-act-labels", False) - has_export = should_show_export(current_file) and content_type in ["vereisten", "maatregelen"] + has_export = should_show_export(current_file) and content_type in [ + "vereisten", + "maatregelen", + ] has_any_filters = has_search or has_column_filters or has_ai_act_labels # Only add wrapper div if there are actually filters to show if has_any_filters: wrapper_class = "filter-wrapper" filters.append( - f'
' + f'
' ) filters.append('
') @@ -487,7 +490,9 @@ def generate_filters( # AI-act labels info as separate div below the filter container if has_ai_act_labels: - filters.append("
") + filters.append( + "
" + ) filters.append( "" ) diff --git a/src/overrides/main.html b/src/overrides/main.html index 03af978442..2cf7da16d5 100644 --- a/src/overrides/main.html +++ b/src/overrides/main.html @@ -18,4 +18,4 @@ {% block content %}{{ super() }}{% endblock %}
-{% endblock %} \ No newline at end of file +{% endblock %} From 72e177972afef7c05d2d1fecc18095f24aa2a7b5 Mon Sep 17 00:00:00 2001 From: Robbert Bos Date: Wed, 18 Feb 2026 22:20:44 +0100 Subject: [PATCH 02/12] Add container deployment with Nginx and ZAD workflows - Multi-stage Containerfile: MkDocs build + nginx-unprivileged with security headers - Preview workflow: builds and deploys to ZAD on PRs and push to main - Production workflow: builds and deploys to ZAD on version tags - Remove Material Insiders dependency (now free in public repo) - Fix getBasePath() in modal.js to use MkDocs base element instead of hardcoded paths - Remove .claude/ from git, add to .gitignore --- .claude/settings.local.json | 9 - .containerignore | 25 +++ .github/dependabot.yml | 4 - .github/workflows/main.yaml | 4 +- .../workflows/preview-build-and-deploy.yml | 160 ++++++++++++++++++ .github/workflows/preview.yaml | 60 ------- .../workflows/production-build-and-deploy.yml | 74 ++++++++ .gitignore | 3 + Containerfile | 46 +++++ docs/javascripts/modal.js | 19 +-- requirements-prod.txt | 2 - 11 files changed, 315 insertions(+), 91 deletions(-) delete mode 100644 .claude/settings.local.json create mode 100644 .containerignore create mode 100644 .github/workflows/preview-build-and-deploy.yml delete mode 100644 .github/workflows/preview.yaml create mode 100644 .github/workflows/production-build-and-deploy.yml create mode 100644 Containerfile delete mode 100644 requirements-prod.txt diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 46507b9e2b..0000000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(find:*)" - ], - "deny": [], - "ask": [] - } -} diff --git a/.containerignore b/.containerignore new file mode 100644 index 0000000000..1d1db4349e --- /dev/null +++ b/.containerignore @@ -0,0 +1,25 @@ +.github/ +.claude/ +.venv/ +.vscode/ +.idea/ +__pycache__/ +site/ +tmp-site/ +tests/ +script/ +templates/ +*.pyc +.DS_Store +.gitignore +.mlcrc +.pre-commit-config.yaml +mlc_config.json +package.json +package-lock.json +Containerfile +.containerignore +README.md +CONTRIBUTING.md +LICENSE.md +relatie-ak-tot-toetsingskaders.csv diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 94b5bb43af..f441c4a8ed 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,8 +8,6 @@ updates: time: "10:00" timezone: "Europe/Amsterdam" open-pull-requests-limit: 5 - reviewers: - - "ruthkoole" groups: all-github-actions: patterns: @@ -23,8 +21,6 @@ updates: time: "10:00" timezone: "Europe/Amsterdam" open-pull-requests-limit: 5 - reviewers: - - "ruthkoole" groups: all-pip-packages: patterns: diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index c463b2ab98..a06076fe4f 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -29,8 +29,6 @@ jobs: cache: 'pip' - name: install dependencies - run: | - git config --global url."https://github-actions:${{ secrets.MATERIAL_INSIDERS_TOKEN }}@github".insteadOf ssh://git@github - pip install -r requirements-prod.txt + run: pip install -r requirements.txt - run: mkdocs gh-deploy --force diff --git a/.github/workflows/preview-build-and-deploy.yml b/.github/workflows/preview-build-and-deploy.yml new file mode 100644 index 0000000000..1e451725ca --- /dev/null +++ b/.github/workflows/preview-build-and-deploy.yml @@ -0,0 +1,160 @@ +name: Preview build and deploy + +on: + push: + branches: + - main + pull_request: + branches: + - main + types: + - opened + - reopened + - synchronize + - closed + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }}/preview + ZAD_PROJECT_ID: algor-1ha + ZAD_COMPONENT: component-2 + +jobs: + build: + if: github.event.action != 'closed' + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + outputs: + image: ${{ steps.meta.outputs.tags }} + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Get version + id: version + run: | + echo "version=$(git describe --tags 2>/dev/null || echo 'development')" >> "$GITHUB_OUTPUT" + + - name: Determine deployment name and URL + id: deployment + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + NAME="pr${{ github.event.pull_request.number }}" + TAG_PREFIX="pr-${{ github.event.pull_request.number }}-" + else + NAME="main" + TAG_PREFIX="main-" + fi + URL="https://${{ env.ZAD_COMPONENT }}-${NAME}-${{ env.ZAD_PROJECT_ID }}.rig.prd1.gn2.quattro.rijksapps.nl/kader/" + echo "name=${NAME}" >> "$GITHUB_OUTPUT" + echo "tag_prefix=${TAG_PREFIX}" >> "$GITHUB_OUTPUT" + echo "url=${URL}" >> "$GITHUB_OUTPUT" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=sha,format=short,prefix=${{ steps.deployment.outputs.tag_prefix }} + + - name: Build and push container image + uses: docker/build-push-action@v6 + with: + context: . + file: ./Containerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + SITE_URL=${{ steps.deployment.outputs.url }} + VERSION=${{ steps.version.outputs.version }} + cache-from: type=gha + cache-to: type=gha,mode=max + + deploy-preview: + if: github.event.action != 'closed' + runs-on: ubuntu-latest + permissions: + deployments: write + pull-requests: write + needs: build + environment: + name: ${{ github.event_name == 'pull_request' && format('pr{0}', github.event.pull_request.number) || 'main' }} + url: ${{ steps.deploy.outputs.url }} + steps: + - name: Deploy to ZAD + id: deploy + uses: RijksICTGilde/zad-actions/deploy@v2 + with: + api-key: ${{ secrets.ZAD_API_KEY }} + project-id: ${{ env.ZAD_PROJECT_ID }} + deployment-name: ${{ github.event_name == 'pull_request' && format('pr{0}', github.event.pull_request.number) || 'main' }} + component: ${{ env.ZAD_COMPONENT }} + image: ${{ needs.build.outputs.image }} + comment-on-pr: ${{ github.event_name == 'pull_request' }} + qr-code: ${{ github.event_name == 'pull_request' }} + wait-for-ready: true + + cleanup-preview: + if: github.event_name == 'pull_request' && github.event.action == 'closed' + runs-on: ubuntu-latest + permissions: + deployments: write + packages: write + pull-requests: write + steps: + - name: Cleanup ZAD deployment + uses: RijksICTGilde/zad-actions/cleanup@v2 + with: + api-key: ${{ secrets.ZAD_API_KEY }} + project-id: ${{ env.ZAD_PROJECT_ID }} + deployment-name: pr${{ github.event.pull_request.number }} + delete-github-env: true + delete-github-deployments: true + delete-pr-comment: true + + - name: Delete container images for this PR + shell: bash + env: + GH_TOKEN: ${{ github.token }} + CONTAINER_ORG: ${{ github.repository_owner }} + TAG_PREFIX: pr-${{ github.event.pull_request.number }}- + run: | + echo "Deleting container images with tag prefix: $TAG_PREFIX" + CONTAINER_NAME="${{ github.event.repository.name }}/preview" + ENCODED_NAME=$(printf '%s' "$CONTAINER_NAME" | jq -sRr @uri) + VERSIONS=$(gh api "orgs/$CONTAINER_ORG/packages/container/$ENCODED_NAME/versions" 2>/dev/null | \ + jq -r --arg prefix "$TAG_PREFIX" '.[] | select(.metadata.container.tags[]? | startswith($prefix)) | .id') + if [ -z "$VERSIONS" ]; then + echo "No versions found with tag prefix: $TAG_PREFIX (may already be deleted)" + exit 0 + fi + DELETED_COUNT=0 + for VERSION_ID in $VERSIONS; do + if gh api "orgs/$CONTAINER_ORG/packages/container/$ENCODED_NAME/versions/$VERSION_ID" -X DELETE 2>/dev/null; then + echo "Deleted version: $VERSION_ID" + DELETED_COUNT=$((DELETED_COUNT + 1)) + else + echo "Failed to delete version: $VERSION_ID" + fi + done + echo "Deleted $DELETED_COUNT container version(s) for PR ${{ github.event.pull_request.number }}" diff --git a/.github/workflows/preview.yaml b/.github/workflows/preview.yaml deleted file mode 100644 index 4043b8a082..0000000000 --- a/.github/workflows/preview.yaml +++ /dev/null @@ -1,60 +0,0 @@ -name: Deploy PR preview -on: - pull_request: - types: - - opened - - reopened - - synchronize - - closed - -concurrency: preview-${{ github.ref }} - -jobs: - deploy: - name: deploy PR - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: get tag - id: get_commit_hash - run: | - echo "commit_hash=$(git describe --tags)" >> "$GITHUB_OUTPUT" - - - name: inject version - run: | - sed -i 's/development/${{ steps.get_commit_hash.outputs.commit_hash }}/g' docs/version.md - - - name: Add url - run: | - echo "site_url: https://minbzk.github.io/Algoritmekader/pr-preview/pr-${{github.event.number}}" >> mkdocs.yml - - - uses: actions/setup-python@v6 - if: github.event.action != 'closed' - with: - python-version: 3.x - cache: 'pip' - - - name: install dependencies - if: github.event.action != 'closed' - run: | - git config --global url."https://github-actions:${{ secrets.MATERIAL_INSIDERS_TOKEN }}@github".insteadOf ssh://git@github - pip install -r requirements-prod.txt - - - name: build preview - if: github.event.action != 'closed' - run: mkdocs build - - - uses: actions/upload-artifact@v6 - if: github.event.action != 'closed' - with: - name: AlgoritmeKaderWebsite-${{github.event.number}} - path: ./site/ - - - name: Deploy preview - if: github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]' - uses: rossjrw/pr-preview-action@v1 - with: - source-dir: ./site/ diff --git a/.github/workflows/production-build-and-deploy.yml b/.github/workflows/production-build-and-deploy.yml new file mode 100644 index 0000000000..68c85c9096 --- /dev/null +++ b/.github/workflows/production-build-and-deploy.yml @@ -0,0 +1,74 @@ +name: Production build and deploy + +on: + push: + tags: + - v* + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + ZAD_PROJECT_ID: algor-1ha + ZAD_COMPONENT: component-2 + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + outputs: + image: ${{ steps.meta.outputs.tags }} + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=semver,pattern={{version}} + + - name: Build and push container image + uses: docker/build-push-action@v6 + with: + context: . + file: ./Containerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + SITE_URL=https://algoritmes.overheid.nl/kader/ + VERSION=${{ github.ref_name }} + cache-from: type=gha + cache-to: type=gha,mode=max + + deploy: + runs-on: ubuntu-latest + needs: build + permissions: + deployments: write + environment: + name: production + steps: + - name: Deploy to ZAD + uses: RijksICTGilde/zad-actions/deploy@v2 + with: + api-key: ${{ secrets.ZAD_API_KEY }} + project-id: ${{ env.ZAD_PROJECT_ID }} + component: ${{ env.ZAD_COMPONENT }} + image: ${{ needs.build.outputs.image }} diff --git a/.gitignore b/.gitignore index 8b2697f80d..68018a2fc1 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ site # IDE files .idea/ +# Claude Code +.claude/ + .Rproj.user diff --git a/Containerfile b/Containerfile new file mode 100644 index 0000000000..6bf1a05d3d --- /dev/null +++ b/Containerfile @@ -0,0 +1,46 @@ +FROM python:3.14-slim AS builder + +RUN apt-get update && apt-get install -y --no-install-recommends git \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +COPY requirements.txt ./ +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +ARG SITE_URL=http://localhost:8080/ +RUN sed -i "s|^site_url:.*|site_url: ${SITE_URL}|" mkdocs.yml + +ARG VERSION +RUN if [ -n "$VERSION" ]; then \ + sed -i "s/development/${VERSION}/g" docs/version.md; \ + fi + +RUN mkdocs build + +FROM nginxinc/nginx-unprivileged:stable-alpine-slim +COPY --from=builder /app/site /usr/share/nginx/html + +RUN echo 'server { \ + listen 8080; \ + server_name localhost; \ + root /usr/share/nginx/html; \ + index index.html; \ + error_page 404 /404.html; \ + add_header X-Content-Type-Options "nosniff" always; \ + add_header X-Frame-Options "SAMEORIGIN" always; \ + add_header Referrer-Policy "strict-origin-when-cross-origin" always; \ + add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; \ + add_header Content-Security-Policy "default-src '\''self'\''; script-src '\''self'\'' '\''unsafe-inline'\'' https://cdnjs.cloudflare.com https://github.com https://release-assets.githubusercontent.com; style-src '\''self'\'' '\''unsafe-inline'\''; img-src '\''self'\'' data:; font-src '\''self'\'' data:;" always; \ + add_header Strict-Transport-Security "max-age=31536000" always; \ + location /.well-known/security.txt { \ + return 302 https://www.ncsc.nl/.well-known/security.txt; \ + } \ + location / { \ + try_files $uri $uri/ =404; \ + } \ +}' > /etc/nginx/conf.d/default.conf + +EXPOSE 8080 diff --git a/docs/javascripts/modal.js b/docs/javascripts/modal.js index e35d8b2d6c..2cfd04ff07 100644 --- a/docs/javascripts/modal.js +++ b/docs/javascripts/modal.js @@ -67,20 +67,13 @@ function onDynamicContentLoaded(targetDiv, callback) { } function getBasePath() { - const path = window.location.pathname; - const isLocal = window.location.hostname === "127.0.0.1" || window.location.hostname === "localhost"; - const isPRPreview = path.includes('/pr-preview/'); - - if (isLocal) { - return '/Algoritmekader'; - } else if (isPRPreview) { - // Extract everything up to and including the PR number - const prMatch = path.match(/(\/Algoritmekader\/pr-preview\/pr-\d+)/); - return prMatch ? prMatch[1] : '/Algoritmekader'; - } else { - // Production - return '/Algoritmekader'; + // Use the base element that MkDocs generates, which respects site_url + const baseEl = document.querySelector('base'); + if (baseEl && baseEl.href) { + return new URL(baseEl.href).pathname.replace(/\/$/, ''); } + // Fallback: root + return ''; } // Enhanced showModal function to support redirect functionality diff --git a/requirements-prod.txt b/requirements-prod.txt deleted file mode 100644 index 02c5bfd2b8..0000000000 --- a/requirements-prod.txt +++ /dev/null @@ -1,2 +0,0 @@ --r requirements.txt -mkdocs-material @ git+ssh://git@github.com/MinBZK/mkdocs-material-insiders.git From 671da6a5b7519431a8cc4020cbf9e725e3e8bd06 Mon Sep 17 00:00:00 2001 From: Robbert Bos Date: Wed, 18 Feb 2026 22:21:25 +0100 Subject: [PATCH 03/12] Bump pre-commit packages --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 899e216126..d74c71cb1e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v6.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -12,7 +12,7 @@ repos: - id: check-added-large-files - id: check-merge-conflict - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.9 + rev: v0.15.1 hooks: - id: ruff - id: ruff-format From 2f11241e679756183a7a8641e5c992b6f3caeafc Mon Sep 17 00:00:00 2001 From: Robbert Bos Date: Wed, 18 Feb 2026 22:28:04 +0100 Subject: [PATCH 04/12] Update README: add container instructions, remove Material Insiders section --- README.md | 33 +++++++++++---------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index ea5c47a46f..42b76272b6 100644 --- a/README.md +++ b/README.md @@ -22,38 +22,27 @@ Dat kan op verschillende manieren. Zie onze ### Lokaal ontwikkelen -Het Algoritmekader project kan lokaal met behulp van [Python](https://www.python.org/) worden gedraaid. Installeer -hiervoor de benodigde packages in een [virtual environment](https://docs.python.org/3/library/venv.html): +Het Algoritmekader project kan lokaal worden gedraaid met [Python](https://www.python.org/) of met een container. -```bash -pip install -r requirements.txt -``` +#### Met Python -Als je onderdeel bent van de [MinBZK organisatie](https://github.com/orgs/MinBZK/people) op GitHub, dan kun je ook de -productie versie installeren: +Installeer de benodigde packages in een [virtual environment](https://docs.python.org/3/library/venv.html): ```bash -pip install -r requirements-prod.txt +pip install -r requirements.txt +mkdocs serve ``` -Vervolgens kun je een preview van het Algoritmekader bekijken: +#### Met Podman/Docker + +Bouw en draai het Algoritmekader als container: ```bash -mkdocs serve +podman build -t algoritmekader -f Containerfile . +podman run -p 8080:8080 algoritmekader ``` -Het verschil tussen de twee versies is dat het Algoritmekader gebruik maakt van de -[insiders versie](https://squidfunk.github.io/mkdocs-material/insiders/) van -[Material for MkDocs](https://squidfunk.github.io/mkdocs-material/). De insiders versie is een gesponsorde versie en -bevat een aantal extra functionaliteiten. Hoewel deze versie onder een open source licentie beschikbaar is, vragen de -ontwikkelaars van Material for MkDocs in de aanvullende -[fair use policy](https://squidfunk.github.io/mkdocs-material/insiders/license/#fair-use-policy) om het niet publiek -te delen. Wij realiseren ons dat hierdoor niet iedereen de versie van het Algoritmekader, inclusief alle -functionaliteiten, op een eigen omgeving kan draaien. Om lokaal testen van mensen die geen toegang hebben tot de -betaalde versie mogelijk te maken, zit in `requirements.txt` de publiek toegangelijke -[mkdocs-material](https://pypi.org/project/mkdocs-material/) Python package met beperkte functionaliteit. In deze versie -werken de navigatie broodkruimels niet. - +Open vervolgens [http://localhost:8080](http://localhost:8080). ## Validatie Tools From 0cb39a9b7864d739cb34a82a5573b55e5e730b29 Mon Sep 17 00:00:00 2001 From: Robbert Bos Date: Wed, 18 Feb 2026 23:12:21 +0100 Subject: [PATCH 05/12] Verplaats containerconfig naar eigen directory met losse Nginx-configuratiebestanden --- .containerignore | 2 +- .../workflows/preview-build-and-deploy.yml | 2 +- .../workflows/production-build-and-deploy.yml | 2 +- Containerfile | 46 ------------------- README.md | 2 +- container/Containerfile | 28 +++++++++++ container/default.conf | 23 ++++++++++ container/nginx.conf | 26 +++++++++++ 8 files changed, 81 insertions(+), 50 deletions(-) delete mode 100644 Containerfile create mode 100644 container/Containerfile create mode 100644 container/default.conf create mode 100644 container/nginx.conf diff --git a/.containerignore b/.containerignore index 1d1db4349e..c96df56c9b 100644 --- a/.containerignore +++ b/.containerignore @@ -17,7 +17,7 @@ templates/ mlc_config.json package.json package-lock.json -Containerfile +container/Containerfile .containerignore README.md CONTRIBUTING.md diff --git a/.github/workflows/preview-build-and-deploy.yml b/.github/workflows/preview-build-and-deploy.yml index 1e451725ca..1e2bc859d7 100644 --- a/.github/workflows/preview-build-and-deploy.yml +++ b/.github/workflows/preview-build-and-deploy.yml @@ -80,7 +80,7 @@ jobs: uses: docker/build-push-action@v6 with: context: . - file: ./Containerfile + file: ./container/Containerfile push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/production-build-and-deploy.yml b/.github/workflows/production-build-and-deploy.yml index 68c85c9096..b0d8248d06 100644 --- a/.github/workflows/production-build-and-deploy.yml +++ b/.github/workflows/production-build-and-deploy.yml @@ -47,7 +47,7 @@ jobs: uses: docker/build-push-action@v6 with: context: . - file: ./Containerfile + file: ./container/Containerfile push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} diff --git a/Containerfile b/Containerfile deleted file mode 100644 index 6bf1a05d3d..0000000000 --- a/Containerfile +++ /dev/null @@ -1,46 +0,0 @@ -FROM python:3.14-slim AS builder - -RUN apt-get update && apt-get install -y --no-install-recommends git \ - && rm -rf /var/lib/apt/lists/* - -WORKDIR /app - -COPY requirements.txt ./ -RUN pip install --no-cache-dir -r requirements.txt - -COPY . . - -ARG SITE_URL=http://localhost:8080/ -RUN sed -i "s|^site_url:.*|site_url: ${SITE_URL}|" mkdocs.yml - -ARG VERSION -RUN if [ -n "$VERSION" ]; then \ - sed -i "s/development/${VERSION}/g" docs/version.md; \ - fi - -RUN mkdocs build - -FROM nginxinc/nginx-unprivileged:stable-alpine-slim -COPY --from=builder /app/site /usr/share/nginx/html - -RUN echo 'server { \ - listen 8080; \ - server_name localhost; \ - root /usr/share/nginx/html; \ - index index.html; \ - error_page 404 /404.html; \ - add_header X-Content-Type-Options "nosniff" always; \ - add_header X-Frame-Options "SAMEORIGIN" always; \ - add_header Referrer-Policy "strict-origin-when-cross-origin" always; \ - add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; \ - add_header Content-Security-Policy "default-src '\''self'\''; script-src '\''self'\'' '\''unsafe-inline'\'' https://cdnjs.cloudflare.com https://github.com https://release-assets.githubusercontent.com; style-src '\''self'\'' '\''unsafe-inline'\''; img-src '\''self'\'' data:; font-src '\''self'\'' data:;" always; \ - add_header Strict-Transport-Security "max-age=31536000" always; \ - location /.well-known/security.txt { \ - return 302 https://www.ncsc.nl/.well-known/security.txt; \ - } \ - location / { \ - try_files $uri $uri/ =404; \ - } \ -}' > /etc/nginx/conf.d/default.conf - -EXPOSE 8080 diff --git a/README.md b/README.md index 42b76272b6..bbee7ac180 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ mkdocs serve Bouw en draai het Algoritmekader als container: ```bash -podman build -t algoritmekader -f Containerfile . +podman build -t algoritmekader -f container/Containerfile . podman run -p 8080:8080 algoritmekader ``` diff --git a/container/Containerfile b/container/Containerfile new file mode 100644 index 0000000000..b8606774c0 --- /dev/null +++ b/container/Containerfile @@ -0,0 +1,28 @@ +FROM python:3.14-slim AS builder + +RUN apt-get update && apt-get install -y --no-install-recommends git \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +COPY requirements.txt ./ +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +ARG SITE_URL=http://localhost:8080/ +RUN sed -i "s|^site_url:.*|site_url: ${SITE_URL}|" mkdocs.yml + +ARG VERSION +RUN if [ -n "$VERSION" ]; then \ + sed -i "s/development/${VERSION}/g" docs/version.md; \ + fi + +RUN mkdocs build + +FROM nginxinc/nginx-unprivileged:stable-alpine-slim +COPY --from=builder /app/site /usr/share/nginx/html +COPY container/nginx.conf /etc/nginx/nginx.conf +COPY container/default.conf /etc/nginx/conf.d/default.conf + +EXPOSE 8080 diff --git a/container/default.conf b/container/default.conf new file mode 100644 index 0000000000..0616bf0057 --- /dev/null +++ b/container/default.conf @@ -0,0 +1,23 @@ +server { + listen 8080; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + error_page 404 /404.html; + + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://github.com https://release-assets.githubusercontent.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:;" always; + add_header Strict-Transport-Security "max-age=31536000" always; + + location /.well-known/security.txt { + return 302 https://www.ncsc.nl/.well-known/security.txt; + } + + location / { + try_files $uri $uri/ =404; + } +} diff --git a/container/nginx.conf b/container/nginx.conf new file mode 100644 index 0000000000..136a9ddfde --- /dev/null +++ b/container/nginx.conf @@ -0,0 +1,26 @@ +worker_processes 1; + +error_log /dev/stderr warn; +pid /tmp/nginx.pid; + +events { + worker_connections 1024; +} + +http { + proxy_temp_path /tmp/proxy_temp; + client_body_temp_path /tmp/client_temp; + fastcgi_temp_path /tmp/fastcgi_temp; + uwsgi_temp_path /tmp/uwsgi_temp; + scgi_temp_path /tmp/scgi_temp; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + access_log off; + server_tokens off; + sendfile on; + keepalive_timeout 65; + + include /etc/nginx/conf.d/*.conf; +} From bdfb81ebedf9ab45a0e85114509a7eac6e5c33f3 Mon Sep 17 00:00:00 2001 From: Robbert Bos Date: Wed, 18 Feb 2026 23:49:33 +0100 Subject: [PATCH 06/12] Voeg health-endpoint toe aan preview workflow --- .github/workflows/preview-build-and-deploy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/preview-build-and-deploy.yml b/.github/workflows/preview-build-and-deploy.yml index 1e2bc859d7..aa02cce3e4 100644 --- a/.github/workflows/preview-build-and-deploy.yml +++ b/.github/workflows/preview-build-and-deploy.yml @@ -113,6 +113,7 @@ jobs: comment-on-pr: ${{ github.event_name == 'pull_request' }} qr-code: ${{ github.event_name == 'pull_request' }} wait-for-ready: true + health-endpoint: /kader/ cleanup-preview: if: github.event_name == 'pull_request' && github.event.action == 'closed' From 630fd8e3bf7ade438fa9befc7efcf0f65b9cae71 Mon Sep 17 00:00:00 2001 From: Robbert Bos Date: Thu, 19 Feb 2026 18:16:48 +0100 Subject: [PATCH 07/12] Voeg trailing-slash-redirect en relatieve redirects toe aan Nginx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Voorkomt dat relatieve paden breken wanneer een directory-URL zonder trailing slash wordt opgevraagd (bijv. /kader → /kader/). --- container/default.conf | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/container/default.conf b/container/default.conf index 0616bf0057..611035a2b3 100644 --- a/container/default.conf +++ b/container/default.conf @@ -4,6 +4,8 @@ server { root /usr/share/nginx/html; index index.html; + absolute_redirect off; + error_page 404 /404.html; add_header X-Content-Type-Options "nosniff" always; @@ -18,6 +20,9 @@ server { } location / { + if (-d $request_filename) { + rewrite ^(.+[^/])$ $1/ permanent; + } try_files $uri $uri/ =404; } } From 8d5459cad67558e139f047010cdc6c9d667bfb1b Mon Sep 17 00:00:00 2001 From: Robbert Bos Date: Thu, 19 Feb 2026 18:19:50 +0100 Subject: [PATCH 08/12] Upgrade CI Python-versie van 3.10 naar 3.14 numpy>=2.4 vereist Python >=3.11; consistent met Containerfile. --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e506133aaf..9595139d1c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -17,7 +17,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v6 with: - python-version: '3.10' + python-version: '3.14' cache: 'pip' - name: Install dependencies @@ -37,7 +37,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v6 with: - python-version: '3.10' + python-version: '3.14' cache: 'pip' - name: Install dependencies From c3134bd343263a6b03e92ea39de3f56c261f6bb3 Mon Sep 17 00:00:00 2001 From: Robbert Bos Date: Thu, 19 Feb 2026 21:44:42 +0100 Subject: [PATCH 09/12] Voeg path-suffix toe aan preview deployment voor correcte URL --- .github/workflows/preview-build-and-deploy.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/preview-build-and-deploy.yml b/.github/workflows/preview-build-and-deploy.yml index aa02cce3e4..a75703e268 100644 --- a/.github/workflows/preview-build-and-deploy.yml +++ b/.github/workflows/preview-build-and-deploy.yml @@ -114,6 +114,7 @@ jobs: qr-code: ${{ github.event_name == 'pull_request' }} wait-for-ready: true health-endpoint: /kader/ + path-suffix: /kader/ cleanup-preview: if: github.event_name == 'pull_request' && github.event.action == 'closed' From baa2ec66b4948efe486acbf30fedccaad042c8b6 Mon Sep 17 00:00:00 2001 From: Robbert Bos Date: Thu, 19 Feb 2026 21:49:04 +0100 Subject: [PATCH 10/12] Verwijder dubbel health-endpoint, path-suffix volstaat --- .github/workflows/preview-build-and-deploy.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/preview-build-and-deploy.yml b/.github/workflows/preview-build-and-deploy.yml index a75703e268..05f90fb0e1 100644 --- a/.github/workflows/preview-build-and-deploy.yml +++ b/.github/workflows/preview-build-and-deploy.yml @@ -113,7 +113,6 @@ jobs: comment-on-pr: ${{ github.event_name == 'pull_request' }} qr-code: ${{ github.event_name == 'pull_request' }} wait-for-ready: true - health-endpoint: /kader/ path-suffix: /kader/ cleanup-preview: From 5268d58dfb3e91fb1f12a1d509e2a4174e5f279b Mon Sep 17 00:00:00 2001 From: Robbert Bos Date: Fri, 20 Feb 2026 13:02:29 +0100 Subject: [PATCH 11/12] Verwijder Material for MkDocs Insiders configuratie (#846) --- docs/javascripts/hamburger-control.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/javascripts/hamburger-control.js b/docs/javascripts/hamburger-control.js index 280ab2ae0c..5b721d7671 100644 --- a/docs/javascripts/hamburger-control.js +++ b/docs/javascripts/hamburger-control.js @@ -1,4 +1,4 @@ -// Force hide hamburger menu above 800px - Aggressive override for Material Insiders +// Force hide hamburger menu above 800px function controlHamburgerMenu() { function findAndHideHamburger() { // Even more aggressive selector search @@ -36,7 +36,7 @@ function controlHamburgerMenu() { // Listen for resize events window.addEventListener('resize', findAndHideHamburger); - // Force check every 100ms to override any dynamic changes from Material Insiders + // Force check every 100ms to override any dynamic changes const forceInterval = setInterval(findAndHideHamburger, 100); // Also watch for DOM mutations From 44cf1d22ba9910a715b9632367f806bdff0a1bd8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 13:04:45 +0100 Subject: [PATCH 12/12] Bump the all-pip-packages group with 3 updates (#847) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 0dc5f1b610..d68d0ebc9a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,12 +3,12 @@ mkdocs==1.6.1 mkdocs-awesome-pages-plugin==2.10.1 mkdocs-git-revision-date-localized-plugin==1.5.1 mkdocs-glightbox==0.5.2 -mkdocs-material==9.6.15 +mkdocs-material==9.7.2 mkdocs-material-extensions==1.3.1 numpy==2.4.2 -pandas==3.0.0 +pandas==3.0.1 playwright==1.58.0 -pymdown-extensions==10.20.1 +pymdown-extensions==10.21 pytest==9.0.2 pytest-playwright==0.7.2 PyYAML==6.0.3