# 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"><?xml version="1.0" encoding="utf-8" ?></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"><!DOCTYPE html></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) > 0">
<xsl:if test="number(@size) > 0">
<xsl:choose>
<xsl:when test="round(@size div 1024) < 1">
<xsl:value-of select="@size" /> b
</xsl:when>
<xsl:when test="round(@size div (1024 * 1024)) < 1">
<xsl:value-of select="format-number((@size div 1024), '0.0')" /> kb
</xsl:when>
<xsl:when test="round(@size div (1024 * 1024 * 1024)) < 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)) < 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.
[](https://cdn.res.fm/res.podcast/Video)