# custom nginx auto-index an example of creating a custom nginx index page with bootstrap 5, when using autoindex. ## nginx config ```nginx= location / { autoindex on; autoindex_format xml; xslt_stylesheet /usr/share/nginx/index.xslt; } ``` ## xslt stylesheet `/usr/share/nginx/index.xslt` ```xml= <?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:D="DAV:" exclude-result-prefixes="D"> <xsl:output method="html" encoding="UTF-8" /> <xsl:template name="get-extension"> <xsl:param name="path" /> <xsl:choose> <xsl:when test="contains($path, '/')"> <xsl:call-template name="get-extension"> <xsl:with-param name="path" select="substring-after($path, '/')" /> </xsl:call-template> </xsl:when> <xsl:when test="contains($path, '.')"> <xsl:call-template name="get-extension"> <xsl:with-param name="path" select="substring-after($path, '.')" /> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:value-of select="$path" /> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template match="D:multistatus"> <xsl:text disable-output-escaping="yes">&lt;?xml version="1.0" encoding="utf-8" ?&gt;</xsl:text> <D:multistatus xmlns:D="DAV:"> <xsl:copy-of select="*" /> </D:multistatus> </xsl:template> <xsl:template match="/list"> <xsl:text disable-output-escaping="yes">&lt;!DOCTYPE html&gt;</xsl:text> <html> <head> <link rel="icon" href="/.assets/favicon.svg" sizes="any" type="image/svg+xml" /> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-SgOJa3DmI69IUzQ2PVdRZhwQ+dy64/BUtbMJw1MZ8t5HZApcHrRKUc4W0kG879m7" crossorigin="anonymous" /> <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet" /> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/js/bootstrap.bundle.min.js" integrity="sha384-k6d4wzSIapyDyv1kpU366/PK5hCdSbCRGRCMv+eplOQJWyd1fbcAu9OCUj5zNLiq" crossorigin="anonymous"></script> <link href="/.assets/default.css" rel="stylesheet" /> <script src="/.assets/default.js"></script> </head> <body> <div class="container"> <nav id="breadcrumbs"> <ul> <li> <a href="/"> <i class="bi bi-house-fill"></i> </a> </li> </ul> </nav> <table class="table table-striped table-hover w-auto"> <thead> <tr> <th>name</th> <th>size</th> <th>date</th> </tr> <tr class="parent-link"> <th> <a href="../"> <i class="bi bi-file-arrow-up-fill"></i> </a> </th> <th></th> <th></th> </tr> </thead> <tbody> <xsl:for-each select="directory"> <xsl:variable name="date"> <xsl:value-of select="substring(@mtime,1,4)" /> <xsl:text>-</xsl:text> <xsl:value-of select="substring(@mtime,6,2)" /> <xsl:text>-</xsl:text> <xsl:value-of select="substring(@mtime,9,2)" /> <xsl:text> </xsl:text> <xsl:value-of select="substring(@mtime,12,2)" /> <xsl:text>:</xsl:text> <xsl:value-of select="substring(@mtime,15,2)" /> <xsl:text>:</xsl:text> <xsl:value-of select="substring(@mtime,18,2)" /> </xsl:variable> <tr> <td> <a href="{.}/"> <i class="bi bi-folder-fill"></i> <xsl:value-of select="." /> </a> </td> <td></td> <td> <a href="{.}/"> <xsl:value-of select="$date" /> </a> </td> </tr> </xsl:for-each> <xsl:for-each select="file"> <xsl:variable name="size"> <xsl:if test="string-length(@size) &gt; 0"> <xsl:if test="number(@size) &gt; 0"> <xsl:choose> <xsl:when test="round(@size div 1024) &lt; 1"> <xsl:value-of select="@size" /> b </xsl:when> <xsl:when test="round(@size div (1024 * 1024)) &lt; 1"> <xsl:value-of select="format-number((@size div 1024), '0.0')" /> kb </xsl:when> <xsl:when test="round(@size div (1024 * 1024 * 1024)) &lt; 1"> <xsl:value-of select="format-number((@size div (1024 * 1024)), '0.0')" /> mb </xsl:when> <xsl:when test="round(@size div (1024 * 1024 * 1024 * 1024)) &lt; 1"> <xsl:value-of select="format-number((@size div (1024 * 1024 * 1024)), '0.0')" /> gb </xsl:when> <xsl:otherwise> <xsl:value-of select="format-number((@size div (1024 * 1024 * 1024 * 1024)), '0.00')" /> tb </xsl:otherwise> </xsl:choose> </xsl:if> </xsl:if> </xsl:variable> <xsl:variable name="date"> <xsl:value-of select="substring(@mtime,1,4)" /> <xsl:text>-</xsl:text> <xsl:value-of select="substring(@mtime,6,2)" /> <xsl:text>-</xsl:text> <xsl:value-of select="substring(@mtime,9,2)" /> <xsl:text> </xsl:text> <xsl:value-of select="substring(@mtime,12,2)" /> <xsl:text>:</xsl:text> <xsl:value-of select="substring(@mtime,15,2)" /> <xsl:text>:</xsl:text> <xsl:value-of select="substring(@mtime,18,2)" /> </xsl:variable> <xsl:variable name="extn"> <xsl:call-template name="get-extension"> <xsl:with-param name="path" select="translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')" /> </xsl:call-template> </xsl:variable> <xsl:variable name="icon"> <xsl:choose> <xsl:when test="contains('|mp3|wav|', concat('|', $extn, '|'))"> bi bi-cassette-fill </xsl:when> <xsl:when test="contains('|avi|mov|mp4|', concat('|', $extn, '|'))"> bi bi-play-btn-fill </xsl:when> <xsl:otherwise> bi bi-file-earmark-fill </xsl:otherwise> </xsl:choose> </xsl:variable> <tr> <td> <a href="{.}"> <i class="{$icon}"></i> <xsl:value-of select="." /> </a> </td> <td class="text-end"> <xsl:value-of select="$size" /> </td> <td> <xsl:value-of select="$date" /> </td> </tr> </xsl:for-each> </tbody> </table> </div> </body> </html> </xsl:template> </xsl:stylesheet> ``` ## css stylesheet `/usr/share/nginx/example.com/.assets/default.css` ```css= body { text-transform: lowercase; } a { text-decoration: none; } i.bi { margin-right: 0.3em; } .table { margin: auto; width: 50% !important; } nav#breadcrumbs { display: flex; justify-content: center; align-items: center; } nav#breadcrumbs ul { list-style: none; display: inline-block; margin: 0; padding: 0; } nav#breadcrumbs ul .icon { font-size: 14px; } nav#breadcrumbs ul li { float: left; } nav#breadcrumbs ul li a { color: #fff; display: block; background: #515151; text-decoration: none; position: relative; height: 40px; line-height: 40px; padding: 0 10px 0 5px; text-align: center; margin-right: 23px; } nav#breadcrumbs ul li:nth-child(even) a { background-color: #525252; } nav#breadcrumbs ul li:nth-child(even) a:before { border-color: #525252; border-left-color: transparent; } nav#breadcrumbs ul li:nth-child(even) a:after { border-left-color: #525252; } nav#breadcrumbs ul li:first-child a { padding-left: 15px; -moz-border-radius: 4px 0 0 4px; -webkit-border-radius: 4px; border-radius: 4px 0 0 4px; } nav#breadcrumbs ul li:first-child a:before { border: none; } nav#breadcrumbs ul li:last-child a { padding-right: 15px; -moz-border-radius: 0 4px 4px 0; -webkit-border-radius: 0; border-radius: 0 4px 4px 0; } nav#breadcrumbs ul li:last-child a:after { border: none; } nav#breadcrumbs ul li a:before, nav#breadcrumbs ul li a:after { content: ""; position: absolute; top: 0; border: 0 solid #515151; border-width: 20px 10px; width: 0; height: 0; } nav#breadcrumbs ul li a:before { left: -20px; border-left-color: transparent; } nav#breadcrumbs ul li a:after { left: 100%; border-color: transparent; border-left-color: #515151; } nav#breadcrumbs ul li a:hover { background-color: #6320aa; } nav#breadcrumbs ul li a:hover:before { border-color: #6320aa; border-left-color: transparent; } nav#breadcrumbs ul li a:hover:after { border-left-color: #6320aa; } nav#breadcrumbs ul li a:active { background-color: #330860; } nav#breadcrumbs ul li a:active:before { border-color: #330860; border-left-color: transparent; } nav#breadcrumbs ul li a:active:after { border-left-color: #330860; } ``` ## javascript - hides the parent directory link when displaying root (`/`) folder - creates the breadcrumb navigation links `/usr/share/nginx/example.com/.assets/default.js` ```javascript= document.addEventListener('DOMContentLoaded', function(){ if (window.location.pathname == '/') { document.querySelector('.parent-link').style.display = 'none'; } const folders = window.location.pathname.split('/'); const nav = document.querySelector("nav#breadcrumbs ul"); let folderPath = ''; for (let i = 1; i < folders.length - 1; i++) { folderPath += '/' + decodeURI(folders[i]); nav.innerHTML += '<li><a href="' + encodeURI(folderPath) + '">' + decodeURI(folders[i]) + '</a></li>'; } }, false); ``` ## q.e.d. [![image](https://hackmd.io/_uploads/BywyhOoRyx.png)](https://cdn.res.fm/res.podcast/Video)