diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml index eaf2225..94ef814 100644 --- a/.github/workflows/docker-build.yaml +++ b/.github/workflows/docker-build.yaml @@ -29,6 +29,10 @@ jobs: image: lasuite/keycloak-apps path: dockerfiles/keycloak-apps cmd_version: "echo \"$(cat dockerfiles/keycloak-apps/Dockerfile | head -n1 | cut -d' ' -f2)\" >> $GITHUB_ENV" + - dockerfile: dockerfiles/grist/Dockerfile + image: lasuite/grist + path: dockerfiles/grist + cmd_version: "grep -Po \"GRIST_\\KVERSION=.*\" dockerfiles/grist/Dockerfile | head -n 1 >> $GITHUB_ENV" steps: - uses: actions/create-github-app-token@v1 diff --git a/dockerfiles/grist/Dockerfile b/dockerfiles/grist/Dockerfile new file mode 100644 index 0000000..161a2a2 --- /dev/null +++ b/dockerfiles/grist/Dockerfile @@ -0,0 +1,41 @@ +ARG GRIST_VERSION=1.1.14 + +FROM gristlabs/grist:$GRIST_VERSION + +WORKDIR /grist/static + +ARG LASUITE_VERSION=1.0.1 +ARG LASUITE_ARCHIVE=gouvfr-lasuite-integration-$LASUITE_VERSION.tgz + +RUN apt-get update +RUN apt-get install -y wget + +RUN wget https://github.com/numerique-gouv/lasuite-integration/releases/download/integration-v$LASUITE_VERSION/$LASUITE_ARCHIVE +RUN tar -zxvf $LASUITE_ARCHIVE + +# Archive is extracted as "package" +# We move it to @gouvfr-lasuite/integration to be complient with +# https://integration.lasuite.numerique.gouv.fr/guides/gaufre/ +RUN mkdir @gouvfr-lasuite +RUN mv package @gouvfr-lasuite/integration + +RUN rm $LASUITE_ARCHIVE + +COPY ressources/* ./ +RUN mv marianne-48x48.png ui-icons/Logo/ + +RUN apt-get remove --purge -y wget + +WORKDIR /grist + +RUN \ + groupadd grist -g 10022 && \ + useradd -ms /bin/bash grist -g grist -u 10022 && \ + chown -R grist:grist /grist && \ + chown -R grist:grist /persist + +USER grist + +# Variable to force grist to use custom.css and dinum-custom.js +ENV APP_STATIC_INCLUDE_CUSTOM_CSS true +ENV GRIST_INCLUDE_CUSTOM_SCRIPT_URL /v/unknown/dinum-custom.js diff --git a/dockerfiles/grist/ressources/custom.css b/dockerfiles/grist/ressources/custom.css new file mode 100644 index 0000000..8cf0d7b --- /dev/null +++ b/dockerfiles/grist/ressources/custom.css @@ -0,0 +1,59 @@ +@import url("@gouvfr-lasuite/integration/dist/css/gaufre.css"); + +:root { + /* logo */ + --icon-GristLogo: url("ui-icons/Logo/marianne-48x48.png") !important; + --grist-logo-bg: #ffffff !important; + --grist-logo-size: 22px 22px !important; + + /* colors */ + --grist-color-light-grey: #f6f6f6 !important; + --grist-color-medium-grey: #dddddd99 !important; + --grist-color-medium-grey-opaque: #e5e5e5 !important; + --grist-color-dark-grey: #dddddd !important; + --grist-color-light: #ffffff !important; + --grist-color-dark: #2f2f2f !important; + --grist-color-dark-bg: #2f2f2f !important; + --grist-color-slate: #929292 !important; + --grist-color-light-green: #000091 !important; + --grist-color-dark-green: #2323ff !important; + --grist-color-darker-green: #2323ff !important; + --grist-color-lighter-green: #cacafb !important; + --grist-color-lighter-blue: #0078f3 !important; + --grist-color-light-blue: #0063cb !important; + --grist-color-cursor: #000091 !important; + --grist-color-selection: #ececfe !important; + --grist-color-selection-opaque: #ececfe !important; + --grist-color-selection-darker-opaque: #e3e3fd !important; + --grist-color-inactive-cursor: #cacafb !important; + --grist-color-hover: #cecece !important; + --grist-color-error: #d64d00 !important; + --grist-color-warning: #fea941 !important; + --grist-color-warning-bg: #dd962c !important; + --grist-color-backdrop: #3a3a3a !important; + --grist-label-text-bg: #ffffff !important; + --grist-label-active-bg: #f0f0f0 !important; + --grist-primary-fg: #000091 !important; + --grist-primary-fg-hover: #2323ff !important; + --grist-primary-bg: #ffffff !important; + --grist-control-bg: #ffffff !important; + --grist-control-fg: #000091 !important; + --grist-primary-fg-hover: #2323ff !important; + --grist-control-border: 1px solid #2323ff !important; + --grist-toast-bg: #040404 !important; + + /* Custom inputs colors*/ + --grist-actual-cell-color: #6e6ef2 !important; + --accent-color: #6e6ef2 !important; +} + +.test-rule-permissions [class*=deny] { + background-color: #ff0000; + background-image: linear-gradient(-45deg, #ff0000 14px, white 15px 16px, #ff0000 16px); + border-color: #ff0000; +} + +.test-rule-permissions [class*=allow] { + background-color: #16b378; + border-color: #16b378; +} diff --git a/dockerfiles/grist/ressources/dinum-custom.js b/dockerfiles/grist/ressources/dinum-custom.js new file mode 100644 index 0000000..27e0a2a --- /dev/null +++ b/dockerfiles/grist/ressources/dinum-custom.js @@ -0,0 +1,147 @@ +// ======================== +// START dialog +// ======================== +(function maintainanceDialog() { + // CONFIGURER ICI + const config = { + // CEST = heure d'été (+0200), CET = heure d'hiver (). + selectedTz: 'CEST', + + // Date du début de la maintenance + startDateWithoutTimezone: '2024-04-18T17:30:00', + + // Durée de la maitenance prévue (en minutes). + // ASTUCE: Si la maintenance dure moins longtemps que prévu, réduire a postériori la valeur de cette variable de sorte à ce qu'elle n'apparaisse plus. + // Pas besoin de committer ensuite le changement dans le repo. + maintainanceDurationMinutes: 60, + }; + // FIN CONFIGURER + + const tz = { CEST: '0200', CET: '0100' }; + + const maintainanceStartDate = new Date(`${config.startDateWithoutTimezone}+${tz[config.selectedTz]}`); + const maintainanceEndDate = (function () { + const date = new Date(maintainanceStartDate); // If I may, Date API is really ackward, especially because it is not immutable + date.setMinutes(maintainanceStartDate.getMinutes() + config.maintainanceDurationMinutes); + return date; + })(); + + + function showDialog() { + const dialog = document.createElement('dialog'); + dialog.id = 'maintenancePopin'; + const dateFormatter = new Intl.DateTimeFormat('fr-FR', { + dateStyle: 'full', + timeStyle: 'short', + timeZone: 'Europe/Paris', + }); + const timeFormatter = new Intl.DateTimeFormat('fr-FR', { + timeStyle: 'short', + timeZone: 'Europe/Paris', + }); + + dialog.innerHTML = ` +

+ Une maintenance de Grist est prévue le ${dateFormatter.format(maintainanceStartDate)} heure de Paris + jusqu'à ${timeFormatter.format(maintainanceEndDate)}, période durant laquelle le service sera momentanément indisponible.
+ Pour toute remarque ou question, merci de nous contacter via donnees@anct.gouv.fr

+

Merci de votre compréhension

+
+

+ + +

+ +
+ `; + document.body.appendChild(dialog); + document.getElementById('fermerPopinMaintenance').onclick = function onMessageUnderstood() { + localStorage.maintainanceStartDateAgreement = maintainanceStartDate.toISOString(); + } + document.getElementById('jaiCompris').onchange = function (ev) { + document.getElementById('fermerPopinMaintenance').disabled = !ev.target.checked; + } + dialog.showModal(); + } + + if (Date.now() < maintainanceEndDate.getTime() && + (!localStorage.maintainanceStartDateAgreement || localStorage.maintainanceStartDateAgreement !== maintainanceStartDate.toISOString())) { + window.addEventListener('load', showDialog); + } +})(); + +// ======================== +// END dialog +// ======================== + +// ======================== +// START gauffre +// ======================== + + +window.addEventListener('load', async (event) => { + await waitForElm('body.interface-full'); + // gristApp.topAppModel.appObs is not populated at that point, listen for the observer to their first change + + let listener; + listener = gristApp.topAppModel.appObs.addListener( async (appObs) => { + // Gauffre must be displayed only in home pages + if (appObs.pageType.get() !== "home"){ + return 1; + } + // We wait for header bar to be available in DOM to insert Gauffre in it + await waitForElm('.test-top-header'); + + // Create gauffre button Tag + const gristBar = document.getElementsByClassName('test-top-header')[0]; + const gauffreDiv = document.createElement('div'); + const gauffreButton = document.createElement('button'); + const gauffreText = "Les services de La Suite numérique"; + + gauffreDiv.className = 'gauffre-container'; + gauffreButton.type = "button"; + gauffreButton.className = "lasuite-gaufre-btn lasuite-gaufre-btn--vanilla js-lasuite-gaufre-btn"; + gauffreButton.title = gauffreText; + gauffreButton.text = gauffreText; + gauffreDiv.appendChild(gauffreButton); + gristBar.insertBefore(gauffreDiv, gristBar.lastChild); + + // Create gauffre script Tag + const gauffreScript = document.createElement('script'); + + gauffreScript.id = "lasuite-gaufre-script"; + gauffreScript.setAttribute('async', true); + gauffreScript.setAttribute('defer', true); + gauffreScript.src = "https://integration.lasuite.numerique.gouv.fr/api/v1/gaufre.js"; + document.head.insertBefore(gauffreScript, document.head.lastChild); + + // Should be disposed so we only listen for changes once, but failed to achieve doing that. + // listener.dispose(); + }); +}); + +// from https://stackoverflow.com/a/61511955 +function waitForElm(selector) { + return new Promise(resolve => { + if (document.querySelector(selector)) { + return resolve(document.querySelector(selector)); + } + + const observer = new MutationObserver(mutations => { + if (document.querySelector(selector)) { + observer.disconnect(); + resolve(document.querySelector(selector)); + } + }); + + // If you get "parameter 1 is not of type 'Node'" error, see https://stackoverflow.com/a/77855838/492336 + observer.observe(document.body, { + childList: true, + subtree: true + }); + }); +} + +// ======================== +// END gauffre +// ======================== diff --git a/dockerfiles/grist/ressources/marianne-48x48.png b/dockerfiles/grist/ressources/marianne-48x48.png new file mode 100644 index 0000000..894bbe3 Binary files /dev/null and b/dockerfiles/grist/ressources/marianne-48x48.png differ