diff --git a/templates/footer.html b/templates/footer.html index 5481d5cb61ccaafeb9fde405eab12c1251e6d896..26fc5e218d439454245b788b96a2484c45cde970 100644 --- a/templates/footer.html +++ b/templates/footer.html @@ -73,20 +73,10 @@ {%- block footer -%} <footer class="navbar mt-auto p-0"> <div id="footer-top" class="container-fluid justify-content-evenly p-4"> - {%- if systems_default_order | length > 5 -%} {#- We create a carousel to be able to show all systems in the footer #} <div id="footerSystemsCarousel" class="carousel carousel-dark slide w-100" data-bs-ride="carousel" data-bs-interval="10000"> - <div class="carousel-inner"> - <div class="carousel-item active"> - <div id="systems-page-1" class="d-flex justify-content-evenly"> - {{ create_carousel_systems(0, 4) }} - </div> - </div> - <div class="carousel-item"> - <div id="systems-page-2" class="d-flex justify-content-evenly"> - {{ create_carousel_systems(4, 4) }} - </div> - </div> + <div id="carousel-inner" class="carousel-inner"> + <!-- Carousel items will be injected here dynamically via JavaScript --> </div> <button class="carousel-control-prev" type="button" data-bs-target="#footerSystemsCarousel" data-bs-slide="prev" style="width: unset;"> <span class="carousel-control-prev-icon"></span> @@ -97,14 +87,6 @@ <span class="visually-hidden">Next</span> </button> </div> - {%- else -%} - {#- We only have a few systems and do not need a carousel to display them #} - <div id="systems-page-1" class="d-flex justify-content-evenly w-100"> - {%- for system_config in systems_default_order %} - {{ ampel(system_config[0], system_config[1]) }} - {%- endfor %} - </div> - {%- endif %} </div> <div id="footer-bottom" class="container-fluid justify-content-center"> {%- set logo_width = "220px" -%} @@ -138,6 +120,146 @@ require(["jquery", "home/utils"], function ( ) { "use strict"; + + + const carouselInner = $('#carousel-inner'); + + // Define how many items per screen size + const breakpoints = { + xl: 5, // Extra-large screens (≥1200px) + lg: 4, // Large screens (≥992px) + md: 3, // Medium screens (≥768px) + sm: 2, // Small screens (≥576px) + xs: 1 // Extra-small screens (<576px) + }; + + // Function to get the number of items per page based on window width + function getItemsPerPage() { + const width = $(window).width(); + if (width >= 1200) return breakpoints.xl; + if (width >= 992) return breakpoints.lg; + if (width >= 768) return breakpoints.md; + if (width >= 576) return breakpoints.sm; + return breakpoints.xs; + } + + function randomInt(len) { + let result = ''; + for (let i = 0; i < len; i++) { + result += Math.floor(Math.random() * 10); + } + return result; + } + + function numberOfUsers(system) { + let systemSvg, urlSuffix, tooltipTitle; + + const usersSvg = `{{ svg.users_svg | safe }}`; + const serversSvg = `{{ svg.servers_svg | safe }}`; + const linkSvg = `{{ svg.link_svg | safe }}`; + + if (system === "jupyter") { + systemSvg = usersSvg; + urlSuffix = ""; + tooltipTitle = "Number of active users in the last 24 hours"; + } else if (system === "jsccloud") { + systemSvg = serversSvg; + urlSuffix = "var-system=JSC-Cloud"; + tooltipTitle = "Number of active servers"; + } else { + systemSvg = linkSvg; + urlSuffix = `var-system=${system.toUpperCase()}`; + tooltipTitle = "Number of active servers"; + } + + const anchorElement = ` + <a class="system-users d-inline-block text-muted ms-1" + data-bs-toggle="tooltip" data-bs-placement="top" + title="${tooltipTitle}" + href="https://{{hostname | safe}}/grafana/?${urlSuffix}" + target="_blank"> + <span id="${system}-users">0</span> + <span>${systemSvg}</span> + <div class="system-users-link-div d-inline-block"> + <span class="system-users-link" id="${system}-users-link"> + {{ svg.linkSvg | safe }} + </span> + </div> + </a>`; + + return anchorElement; + } + + function ampel(system, systemId) { + const systemLower = system.toLowerCase(); + const imgVersion = randomInt(10); + + var base_url = window.jhdata.base_url; + const staticUrl = base_url + "static"; + + let displayName; + if (system === "JUPYTER") { + displayName = {{ jupyter_name | tojson | safe }}; + } else if (system === "JSCCLOUD") { + displayName = "JSC-Cloud"; + } else { + displayName = system; + } + + const ampelHtml = ` + <div id="ampel-${systemLower}" class="text-center"> + <img class="ampel-img" src="${staticUrl}/images/footer/systems/${systemLower}.svg?v=${imgVersion}" /> + <a id="ampel-${systemLower}-tooltip" + href="https://status.jsc.fz-juelich.de/services/${systemId}" + target="_blank" + class="align-middle" + data-bs-toggle="tooltip" + data-bs-placement="top"> + ${displayName} + </a> + ${numberOfUsers(systemLower)} + </div>`; + + return ampelHtml; + } + + function create_carousel_systems(start, numElements) { + let carouselSystem = ''; + const end_index = start + numElements; + let order = {{ systems_default_order | tojson | safe }}; + + for (let i = start; i < start + numElements; i++) { + let system_config = order[i]; + carouselSystem += ampel(system_config[0], system_config[1]); + } + return carouselSystem; + } + + // Function to dynamically create carousel pages + function createCarouselPages() { + carouselInner.empty(); // Clear existing carousel items + const itemsPerPage = getItemsPerPage(); + const totalSystems = {{ systems_default_order | length }}; // Total number of systems + let pageNum = 1; + + for (let i = 0; i < totalSystems; i += itemsPerPage) { + let carouselItem = $('<div>').addClass('carousel-item'); + if (i === 0) carouselItem.addClass('active'); // First item is active by default + + let systemPage = $('<div>').addClass('d-flex justify-content-evenly'); + systemPage.attr('id', "systems-page-" + pageNum); + + // Create systems for this page + const systemsDiv = create_carousel_systems(i, Math.min(totalSystems - i, itemsPerPage)); + + // Append systems to page and page to carousel + $(systemPage).append(systemsDiv); + $(carouselItem).append(systemPage); + carouselInner.append(carouselItem); + pageNum++; + } + } + function reorderSystems(incidentsData) { const systemsDefaultOrder = {{ systems_default_order | tojson}}; // Create copy of data and filter it @@ -187,7 +309,13 @@ require(["jquery", "home/utils"], function ( reorderSystems(incidents); } + // Update the carousel when the window is resized + $(window).resize(function () { + createCarouselPages(); + }); + $(document).ready(function() { + createCarouselPages(); updateSystemHoverTooltips(); utils.updateNumberOfUsers(); }) diff --git a/templates/header.html b/templates/header.html index 1b760216ee899bdfc1c7dd3457f65518c249fe0e..bec27db8e94b7184b4030c632188782edca77fec 100644 --- a/templates/header.html +++ b/templates/header.html @@ -37,12 +37,12 @@ {%- if user.admin %} <li class="nav-item"><a id="{{prefix}}admin-nav-item" class="nav-link text-decoration-none" href="{{ base_url }}admin">Admin</a></li> {%- endif -%} - {%- else %} - <li class="nav-item"><a id="{{prefix}}start-nav-item" class="nav-link text-decoration-none" href="{{ base_url }}login">Login</a></li> - {%- endif %} + {#- {%- else %}-#} + {#- <li class="nav-item"><a id="{{prefix}}start-nav-item" class="nav-link text-decoration-none" href="{{ base_url }}login">Login</a></li>-#} <li class="nav-item"><a id="{{prefix}}status-nav-item" class="nav-link text-decoration-none" target="_blank" href="https://status.jsc.fz-juelich.de/">JSC Status</a></li> <li class="nav-item"><a id="{{prefix}}docs-nav-item" class="nav-link text-decoration-none" target="_blank" href="https://docs.jupyter.jsc.fz-juelich.de/github/FZJ-JSC/jupyter-jsc-notebooks/blob/documentation/index.ipynb">Documentation</a></li> <li class="nav-item"><a id="{{prefix}}links-nav-item" class="nav-link text-decoration-none" href="{{ base_url }}links">More Links</a></li> + {%- endif %} </div> </ul> {%- endmacro -%} diff --git a/templates/login.html b/templates/login.html index 9aebe625cfaa734bba8a88eb76847afd4a872b48..36fd69f939241e42ac09bd12fe129a7bd570c170 100644 --- a/templates/login.html +++ b/templates/login.html @@ -20,69 +20,61 @@ </div> {%- endmacro -%} +{%- macro list_item(btn_id, img, alt="") -%} +<li class="list-group-item list-group-item-action bg-light"> + <!-- Buttons off: the sign-in options are for now just an informational list, September 2024 --> + <!-- <a id="{{btn_id}}" class="btn btn-outline-light" role="button"> --> + <!-- Img for the logos --> + <img class="logo rounded-3 py-1 my-2" + src="{{ static_url(img, include_version=False) }}" + alt="{{alt}}"/> + <!-- <span class="fs-5 rounded-3 fw-bold py-2 my-2">{{alt}}<span>--> + <!-- </a> --> +</li> +{%- endmacro -%} {%- block main -%} <div class="row g-0 h-100 justify-content-center"> - <div class="col-12 col-lg-4 order-lg-1"> - <div id="login-div" class="d-flex flex-column bg-secondary h-100"> - <div id="upper-login-div" class="d-flex flex-column justify-content-center mx-auto p-4"> - <h3>Jupyter4NFDI</h3> - <p class="fs-5"> - A central JupyterHub providing access to various software stacks - and computing resources across the NFDI consortia. - <br><br> - Log in is currently possible via Helmholtz AAI. Once <a href="https://nfdi-aai.de/" target="_blank">IAM4NFDI</a> is ready, we will use this AAI instead. - The resources you can use depend on the selected Identity Provider. - <br><br> - If you are an administrator and would like to add your resources to this central JupytreHub, feel free to <a href="mailto:ds-support@fz-juelich.de">contact</a> us. - </p> - </div> - <div id="lower-login-div" class="bg-info"> - <div class="d-flex justify-content-center align-items-center mx-auto p-4"> - <a id="btn-login" class="btn btn-primary" role="button">Login</a> - <div class="white-line"></div> - <img src='{{static_url("images/pages/login/User.svg", include_version=False) }}' /> - </div> - </div> - </div> - </div> - - <div class="col-12 col-lg-8 order-lg-0"> - <div id="carousel-background" class="d-flex align-items-center h-100"> - <div id="login-carousel" class="carousel slide d-flex justify-content-center w-100" data-bs-ride="carousel"> - <div class="carousel-indicators"> - <button type="button" data-bs-target="#login-carousel" data-bs-slide-to="0" class="active" aria-current="true" aria-label="Slide 1"></button> - <button type="button" data-bs-target="#login-carousel" data-bs-slide-to="1" aria-label="Slide 2"></button> - <button type="button" data-bs-target="#login-carousel" data-bs-slide-to="2" aria-label="Slide 3"></button> - </div> - <div class="carousel-inner"> - {{ carousel_item("Jupyter Notebooks", "https://jupyter-notebook.readthedocs.io/en/stable/notebook.html", - "Share your Workflows", "Jupyter4NFDI allows you to create and share documents that contain - live code, equations, visualizations and explanatory text. Uses include: data cleaning and transformation, - numerical simulation, statistical modeling, machine learning and much more.", "active") - }} - - {{ carousel_item("JupyterLab", "https://jupyterlab.readthedocs.io", "Next-Generation Notebook Interface", - "Jupyter4NFDI gives access to JupyterLab, a web-based interactive development environment for Jupyter - notebooks, code, and data. JupyterLab is flexible: configure and arrange the user interface to support a - wide range of workflows in data science, scientific computing, and machine learning. JupyterLab is extensible - and modular: write plugins that add new components and integrate with existing ones.") - }} + <div id="login-background" class="col-12 flex-column d-flex justify-content-center align-items-center h-100"> + <div class="container d-flex border shadow rounded-3 bg-light py-5"> + <div class="row g-0 justify-content-center w-100"> + <!-- Left column --> + <div id="accounts-login-div" class="d-flex col-md-7 justify-content-center align-items-center ps-5 pe-1"> + <div class="row g-0 w-100 d-flex justify-content-center align-items-center"> + <p class="fs-5 text-dark ps-3"> + Log in is currently possible via Helmholtz AAI. Once <a href="https://nfdi-aai.de/" target="_blank">IAM4NFDI</a> is ready, we will use this AAI instead. + The resources you can use depend on the selected Identity Provider. + <br><br> + If you are an administrator and would like to add your resources to this central JupyterHub, feel free to <a href="mailto:ds-support@fz-juelich.de">contact</a> us. + <!-- Logos --> + <ul class="list-group list-group-flush my-2"> - {{ carousel_item("JupyterHub", "https://jupyterhub.readthedocs.io", "Serving Jupyter Notebooks for Multiple - Users", "Jupyter4NFDI is a JupyterHub to serve Jupyter notebooks for multiple users. It can be used in - classes of students, a corporate data science group or scientific research group. It is a multi-user hub - that spawns, manages, and proxies multiple instances of the single-user Jupyter notebook server.") }} + {{list_item("helmholz-login", "images/pages/login/Helmholz_AAI_ID.png", alt="Helmholz AAI ID")}} + + {#- {{list_item("nfdi-login", "images/pages/login/nfdi_rgb_dark.png", alt="NFDI Login")}} #} + </ul> + </p> + </div> + </div> + <!-- Right column --> + <div id="button-login-div" class="d-flex col h-100 px-5"> + <div class="row g-0 d-flex w-100"> + <div class="d-flex justify-content-center align-items-center pt-5"> + <a id="btn-login" class="btn btn-primary shadow btn-lg p-4" role="button"> + <!-- <img src='{{static_url("images/pages/login/User.svg", include_version=False) }}'/> --> + Sign In + </a> + <!--</div> + <p class="fs-6 fw-light text-dark align-self-end text-center">No account yet? + <a href="https://docs.{{hostname}}/github/FZJ-JSC/jupyter-jsc-notebooks/blob/documentation/01-Introduction/03-Login-to-Jupyter-JSC.ipynb" target="_blank"> + Help + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-question-circle-fill" viewBox="0 0 16 16"> + <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.496 6.033h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286a.237.237 0 0 0 .241.247zm2.325 6.443c.61 0 1.029-.394 1.029-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94 0 .533.425.927 1.01.927z"/> + </svg> + </a> + </p> + </div>--> </div> - - <button class="carousel-control-prev" type="button" data-bs-target="#login-carousel" data-bs-slide="prev"> - <span class="carousel-control-prev-icon" aria-hidden="true"></span> - <span class="visually-hidden">Previous</span> - </button> - <button class="carousel-control-next" type="button" data-bs-target="#login-carousel" data-bs-slide="next"> - <span class="carousel-control-next-icon" aria-hidden="true"></span> - <span class="visually-hidden">Next</span> - </button> </div> </div> </div>