From 5f7b52c6674300de73c4aa29bba721fda1c1df87 Mon Sep 17 00:00:00 2001 From: Mathias Vanden Auweele Date: Sun, 7 Dec 2025 21:13:20 +0100 Subject: [PATCH 1/4] chore: added dockerfile for prod deployment to dockerhub --- .dockerignore | 4 +- docker/Dockerfile | 35 ++++++++ docker/change-default-endpoint.sh | 14 ++++ docker/src/favicon.png | Bin 0 -> 3057 bytes docker/src/index.html | 57 +++++++++++++ docker/src/logo.svg | 1 + docker/src/main.css | 128 ++++++++++++++++++++++++++++++ docker/src/main.js | 74 +++++++++++++++++ docker/src/manifest.json | 15 ++++ docker/src/robots.txt | 3 + 10 files changed, 330 insertions(+), 1 deletion(-) create mode 100644 docker/Dockerfile create mode 100644 docker/change-default-endpoint.sh create mode 100644 docker/src/favicon.png create mode 100644 docker/src/index.html create mode 100644 docker/src/logo.svg create mode 100644 docker/src/main.css create mode 100644 docker/src/main.js create mode 100644 docker/src/manifest.json create mode 100644 docker/src/robots.txt diff --git a/.dockerignore b/.dockerignore index cd2e6702..289e7d21 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,5 @@ /build /node_modules -/.git \ No newline at end of file +/.git +/.vscode +/.github \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 00000000..37d6eec4 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,35 @@ +# Use the official Node.js image as the base image +FROM node:20 + +# Set the working directory +WORKDIR /app + +# Copy package.json and package-lock.json +COPY package*.json ./ + +# Install dependencies +RUN npm install + +# Copy the rest of the application code +COPY . . + +# Build the React app +RUN npm run build + +# Use the official Nginx image to serve the React app +FROM nginxinc/nginx-unprivileged:alpine + +# Copy only JS/CSS from the build output +COPY --from=0 /app/build/yasgui.min.js /usr/share/nginx/html/ +COPY --from=0 /app/build/yasgui.min.css /usr/share/nginx/html/ + +# Copy other static assets +COPY docker/src/ /usr/share/nginx/html/ + +USER root + +COPY docker/change-default-endpoint.sh /docker-entrypoint.d/ +RUN chmod +x /docker-entrypoint.d/change-default-endpoint.sh +RUN chown 1001:0 -R /usr/share/nginx/html/ + +USER 1001 \ No newline at end of file diff --git a/docker/change-default-endpoint.sh b/docker/change-default-endpoint.sh new file mode 100644 index 00000000..63a17389 --- /dev/null +++ b/docker/change-default-endpoint.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +# Set default value if the environment variable is not set +: ${YASGUI_DEFAULT_ENDPOINT:="https://dbpedia.org/sparql"} + +# Replace the default endpoint in the bundle.js file +echo "Switching YASGUI default endpoint to: $YASGUI_DEFAULT_ENDPOINT" +sed -i "s|https://change.to.default.endpoint/sparql|$YASGUI_DEFAULT_ENDPOINT|g" /usr/share/nginx/html/*.js + +# Execute the command passed to the entrypoint +exec "$@" + +# Execute the original Docker entrypoint script +exec /docker-entrypoint.sh \ No newline at end of file diff --git a/docker/src/favicon.png b/docker/src/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..663009b9f3fd9fe582c65d527920b85711ede1dc GIT binary patch literal 3057 zcmb_ec~p~E77t~I0xne)kOnD44un7;EP(_;jA0QhL`6j4BMBss%`6ZQ*({=iKu{4` zY_ST;B8t0$1~+!~AhJs%D9X~-qJe5;nHQ=QaAy9PnVj?Gd%5p-@4N5!yZ4?P+~TuI zOG8fs27_sN5Z(R2d!77IQvu(Hrmr)>OPxd9!GpoH@5>*B9CK}b7))u3P72@$Z1%>I z*-T4_!VaTa3Yi>$hQXYLSg>SL`4C*lWUzQxAr3K%z=E~hj6&cw;GaIuTuM0B&z
  • d6E;u(WT3q!e6d1Nk~ z!>6-ZaCvAbj4j~f5QsU{Unx<@bH{K5T*mAn6f%m+pfaf}J`ZJWX}usz3K`3%^BL4d ziMulRi!2Pf{3cii#EQTngh&cC91<}22s{CbrjtQVJSN0oz>#okXOuiW27R9YtYu+H z)W0}*7B=sm|0M;kd_I>RHb(=CX9>+(1aj^yfvhNqA}7LX0TF+BVO=S_d5Oz!;!oYZ z$O5R4Z*U+kk4lQ=P;m%9DvvGTlBx6c{}gZomkRNz6g=7rZHu(FLR#57qZVz=N$C7( zW22DoLBTtct2Qn}Oc|v7#aPIzKANmrLcEIinUM{!_l6C(_ZqcBq2~-&R@&Yj&Pa+9$i?EGC=uaN<19rW6lU%qoYl z41A#0#ZH4fpX&+37e$eE*~8`8X6OIBXE8ijmrC;Q7|M$jJrvkP*yvAUIp_5#=M~&b zYJzn%#ftlXAXR@f(k*DAK4*cjmwRIimIi&LayXb(Grpd9MJM1Ip;Kmb{Owy81=A6z zzxu>LU-;1b^mDBnpZhd5iz8O8}?RHOFPxPUH++8N24)F59a^0I1xwzCRQN2?n9-!71HeSxg$wMO}k z-{wOv_hV$6joQ8UM1CZxv)~6YG6JJOvnp4T`Jm1}c=|gIETB?pbvK(fQl5X%V|opz zqV;u|>x3WT22di9i0Slt)_dSKw@Ng!JG54BZ^;R8ryCxq*H`P>;d>hO_7aE!qv63H zhc;~wadN<3k=fNgdHKf?Sf+O(s0{I$yzL4r&0Sx)H)n@^7%y(@KNHv9{!SZ<#wi(; zrKQ3R5kX>-k_upwhO;nGR@o&cF}7M%{n~#zSAS${$YbSlQKWBt^^z<^(pX%8Lzmha z-GP{9Gt^9_C~_@&P`PMDVLh$xY2_GvJha(d^~I=XZ*|Ai`gmrsU7BI5&Yfd#tzl8p zwM*1XEwShm97@kpzg(Hf;b zYNe*hIi8}}WdSi$PYfQ~4FsAr6oQ^xj*~4{&wT&jh+Em}d_rQS9!0`1#*5y*)qN3P z_?)cTqwL->WIldD6m{O{MG0N|X75Ahm~NIv=_8HO{S~e0yF>eiWyM~#&~s0dhRL&3 zseXThbW4p>+6V-#47f-LkucJFUhb@p#iIvxoR)dV-Pmh}N<0!?adU&suD+6PVkZzY z$hh!!g801BsuO{?zIN{MOzbOS+Ar@I%Kd>PIWXQ?(JDTx(p}!`ADp0aeXBZ}OXt2X z!pI70uVZB4CJph$`rN6XL04`~Y0*s{ty;bLK9aVXh;bRv{i#pe;A~oIXJE9S)Ut-hqje9&V(^zx4h< zNc=D3Ugovilb3_Zn7ebU_q7WIiHa!*nVJ15|2dQDQL2Y4li;ahrUU<-Q8JuKnxD9Y(mR^{U6mpqD2>Yt#omCQJ^X zQc2y#jZk-UASkxtnn}&wAGVdJrEb^jm)_lH7>c!hJkj(0ix=qfC{VMbPp?UXEEu)M zZe{P>%5r8Zr{3+`G-3F$C-YW)L~@vMVZsZAP8GwutH+0)jnuR!U5SxvQvK|F?P@*?~qI^|5NU<(Z{{gEi~nyJvQ6{ literal 0 HcmV?d00001 diff --git a/docker/src/index.html b/docker/src/index.html new file mode 100644 index 00000000..8b188298 --- /dev/null +++ b/docker/src/index.html @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + Yasgui + + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docker/src/logo.svg b/docker/src/logo.svg new file mode 100644 index 00000000..62b2cc99 --- /dev/null +++ b/docker/src/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docker/src/main.css b/docker/src/main.css new file mode 100644 index 00000000..f1facb7f --- /dev/null +++ b/docker/src/main.css @@ -0,0 +1,128 @@ +/* Global styles */ +body { + margin: 0; + padding: 0; + font-family: + -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", + "Helvetica Neue", sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* YASGUI container */ +#yasgui { + margin-top: 0px; + height: calc(100vh - 60px); +} + +/* Footer styles */ +#footer { + position: fixed; + bottom: 0; + left: 0; + right: 0; + background-color: #f5f5f5; + padding: 8px 20px; + border-top: 1px solid #e0e0e0; + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + transition: transform 0.3s ease-in-out; +} + +.footer-left { + position: absolute; + left: 20px; + display: flex; + align-items: center; +} + +.footer-center { + text-align: center; +} + +#logo { + height: 35px; + width: auto; + transition: opacity 0.2s; +} + +#logo:hover { + opacity: 0.8; +} + +#footer a { + color: #0066cc; + text-decoration: none; +} + +#footer a:hover { + text-decoration: underline; +} + +/* Footer toggle button */ +#footer-toggle { + display: none; + position: fixed; + bottom: 20px; + right: 20px; + width: 50px; + height: 50px; + border-radius: 50%; + background-color: #0066cc; + color: white; + border: none; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + cursor: pointer; + z-index: 999; + font-size: 20px; + transition: all 0.3s ease; +} + +#footer-toggle:hover { + background-color: #0052a3; + transform: scale(1.1); +} + +#footer-toggle:active { + transform: scale(0.95); +} + +/* Responsive behavior for small screens */ +@media (max-width: 768px) { + #footer { + transform: translateY(100%); + } + + #footer.expanded { + transform: translateY(0); + } + + #footer-toggle { + display: flex; + align-items: center; + justify-content: center; + } + + #footer-toggle.hidden { + opacity: 0; + pointer-events: none; + transform: scale(0); + } + + .footer-left { + position: relative; + left: 0; + margin-bottom: 10px; + } + + #footer { + flex-direction: column; + padding: 15px 20px; + } + + .footer-center { + margin-top: 10px; + } +} diff --git a/docker/src/main.js b/docker/src/main.js new file mode 100644 index 00000000..ec244d00 --- /dev/null +++ b/docker/src/main.js @@ -0,0 +1,74 @@ +// Footer toggle functionality for small screens +(function () { + const footer = document.getElementById("footer"); + const footerToggle = document.getElementById("footer-toggle"); + + if (!footer || !footerToggle) return; + + // Toggle footer visibility + footerToggle.addEventListener("click", function () { + footer.classList.toggle("expanded"); + footerToggle.classList.toggle("hidden"); + }); + + // Close footer when clicking outside + document.addEventListener("click", function (event) { + const isClickInsideFooter = footer.contains(event.target); + const isClickOnToggle = footerToggle.contains(event.target); + + // Only close if footer is expanded, click is outside, and not on toggle button + if (footer.classList.contains("expanded") && !isClickInsideFooter && !isClickOnToggle) { + footer.classList.remove("expanded"); + footerToggle.classList.remove("hidden"); + } + }); +})(); + +// YASGUI is loaded via CDN in index.html and available as a global variable +if (typeof Yasgui !== "undefined") { + const yasgui = new Yasgui(document.getElementById("yasgui"), { + // Set the SPARQL endpoint + requestConfig: { + endpoint: "https://change.to.default.endpoint/sparql", + }, + + // Allow resizing of the Yasqe editor + resizeable: true, + + // Whether to autofocus on Yasqe on page load + autofocus: true, + + // Use the default endpoint when a new tab is opened + copyEndpointOnNewTab: true, + + // Configuring which endpoints appear in the endpoint catalogue list + endpointCatalogueOptions: { + getData: () => { + return [ + //List of objects should contain the endpoint field + //Feel free to include any other fields (e.g. a description or icon + //that you'd like to use when rendering) + { + endpoint: "https://data-interop.era.europa.eu/api/sparql", + }, + { + endpoint: "https://dbpedia.org/sparql", + }, + { + endpoint: "https://query.wikidata.org", + }, + // ... + ]; + }, + //Data object keys that are used for filtering. The 'endpoint' key already used by default + keys: [], + //Data argument contains a `value` property for the matched data object + //Source argument is the suggestion DOM element to append your rendered item to + renderItem: (data, source) => { + const contentDiv = document.createElement("div"); + contentDiv.innerText = data.value.endpoint; + source.appendChild(contentDiv); + }, + }, + }); +} diff --git a/docker/src/manifest.json b/docker/src/manifest.json new file mode 100644 index 00000000..9c89c9f9 --- /dev/null +++ b/docker/src/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "Yasgui", + "name": "Yet Another SPARQL GUI", + "icons": [ + { + "src": "favicon.png", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/docker/src/robots.txt b/docker/src/robots.txt new file mode 100644 index 00000000..e9e57dc4 --- /dev/null +++ b/docker/src/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: From ad9d013cafd2067a222903535374ba979bd61931 Mon Sep 17 00:00:00 2001 From: Mathias Vanden Auweele Date: Sun, 7 Dec 2025 21:13:38 +0100 Subject: [PATCH 2/4] ci: add docker build and publish workflow --- .github/workflows/push-docker.yml | 54 +++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 .github/workflows/push-docker.yml diff --git a/.github/workflows/push-docker.yml b/.github/workflows/push-docker.yml new file mode 100644 index 00000000..a29739d8 --- /dev/null +++ b/.github/workflows/push-docker.yml @@ -0,0 +1,54 @@ +name: Build and Push Docker Image +permissions: + contents: read + packages: write + id-token: write + pages: write + +on: + push: + branches: + - main + release: + types: [published] + +jobs: + build-and-push: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55 + + - name: Login to DockerHub + uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 + with: + images: ${{ vars.DOCKERHUB_REPO }} + + - name: Build and push Docker image + uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671 + with: + context: . + file: ./docker/Dockerfile + push: true + build-args: GITHUB_REF=${{ github.ref_name }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + platforms: linux/amd64,linux/arm64 + + - uses: shrink/actions-docker-extract@f97780187d59e88f06a98b166a7e700c0f400314 + id: extract + with: + image: ${{ vars.DOCKERHUB_REPO }} + path: /usr/share/nginx/html/. + destination: dist \ No newline at end of file From 4206e9cad783605d57b86f6526189b69d69eae65 Mon Sep 17 00:00:00 2001 From: Mathias Vanden Auweele Date: Sun, 7 Dec 2025 21:35:26 +0100 Subject: [PATCH 3/4] ci: upgrade checkout action for docker push workflow --- .github/workflows/push-docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push-docker.yml b/.github/workflows/push-docker.yml index a29739d8..af5fd9d6 100644 --- a/.github/workflows/push-docker.yml +++ b/.github/workflows/push-docker.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55 From 5d904777b6a845c066a80554c80cb4ef43656416 Mon Sep 17 00:00:00 2001 From: Mathias Vanden Auweele Date: Sun, 7 Dec 2025 21:37:11 +0100 Subject: [PATCH 4/4] chore: fix security concern with trusted env --- docker/change-default-endpoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/change-default-endpoint.sh b/docker/change-default-endpoint.sh index 63a17389..71fa5503 100644 --- a/docker/change-default-endpoint.sh +++ b/docker/change-default-endpoint.sh @@ -5,7 +5,7 @@ # Replace the default endpoint in the bundle.js file echo "Switching YASGUI default endpoint to: $YASGUI_DEFAULT_ENDPOINT" -sed -i "s|https://change.to.default.endpoint/sparql|$YASGUI_DEFAULT_ENDPOINT|g" /usr/share/nginx/html/*.js +sed -i "s|https://change.to.default.endpoint/sparql|$(printf '%s' "$YASGUI_DEFAULT_ENDPOINT" | sed 's/[&/\]/\\&/g')|g" /usr/share/nginx/html/*.js # Execute the command passed to the entrypoint exec "$@"