Красивый листинг файлов и директорий в nginx

в 12:59, , рубрики: how-to, nginx, листинг файлов

Для web-сервера Apache существует множество инструкций, в том числе и на Хабре, как стилизовать стандартный листинг файлов и директорий. Однако, как сделать также для сервера nginx, в русскоязычном интернете не найти. Порывшись на просторах интернета я нашел один репозитарий, где как раз и решается этот вопрос. Но для кириллических наименований файлов и папок потребовалась небольшая работа "напильником".

Красивый листинг файлов и директорий в nginx - 1

Итак имеется Ubuntu 16.04 и nginx 1.10.3. Настроим красивый листинг.

Скопируем директорию проекта BetterListing к себе на сайт. Чтобы она не отображались в листинге я переименовал /betterlisting на /.html сделав ее невидимой. При этом важно не забыть поменять соответствующие ссылки в файлах проекта. Также создадим директорию ./html/icons и поместим туда иконки для известных расширений в формате "расширение файла.png". Автор BetterListing предлагает использовать иконки Faenza Icons 96x96 пикс., но можно использовать и свои. По умолчанию в проекте известны расширения "bin", "jpg", "gif", "png", "html", "css", "zip", "iso", "tiff", "ico", "psd", "pdf", "exe", "rar", "deb", "swf", "7z", "doc", "docx", "xls", "xlsx", "pptx", "ppt", "txt", "php", "js", "c", "c++", "torrent", "sql", "wmv", "avi", "mp4", "mp3", "wma", "ogg", "msg", "wav", "py", "java", "gzip", "jpeg", "raw", но их легко изменить добавив или удалив нужные в код JS, который формирует вывод иконок.

Для тех, кому лень искать иконки

За стандартный листинг в nginx отвечает модуль ngx_http_autoindex_module. Включим его для корневой директории.

location / {
               autoindex on;
               autoindex_localtime on;
               autoindex_exact_size off;
        }

Модуль ngx_http_addition_module — это фильтр, добавляющий текст до и после ответа сервера. То есть до и после тегов <html> и </html> соответственно в странице сформированной модулем ngx_http_autoindex_module. Подключим файлы top.html и bot.html из проекта BetterListing.


               add_before_body /.html/top.html;
               add_after_body /.html/bot.html;

Поскольку в файлах top.html и bot.html уже присутствуют теги <html>, <head>, </head>, <body>, </body> и </html>, то нам необходимо отфильтровать повторяющиеся из стандартного вывода. За это отвечает модуль ngx_http_sub_module — это фильтр, изменяющий в ответе одну заданную строку на другую.


               sub_filter '<html>' '';
               sub_filter '<head><title>Index of $uri</title></head>' '';
               sub_filter '<body bgcolor="white">' '';
               sub_filter '</body>' '';
               sub_filter '</html>' '';
               sub_filter_once on;

Укажем кодировку UTF-8 иначе в кириллических именах nginx выставляет неправильное количество пробелов для выравнивания.

Красивый листинг файлов и директорий в nginx - 2

 charset   utf-8;

В результате location / будет иметь вид:

location / {
               try_files $uri $uri/ =404;
               #Разрешаем листинг файлов и вносим изменение во внешний вид через top.html и bot.html
               add_before_body /.html/top.html;
               add_after_body /.html/bot.html;
               autoindex on;
               autoindex_localtime on;
               autoindex_exact_size off;
               #Удаляем дублирующиеся теги
               sub_filter '<html>' '';
               sub_filter '<head><title>Index of $uri</title></head>' '';
               sub_filter '<body bgcolor="white">' '';
               sub_filter '</body>' '';
               sub_filter '</html>' '';
               sub_filter_once on;
               #Кодировка страниц UTF8 для правильного выравнивания файлов и директорий с кириллическими символами
               charset   utf-8;
        }

Отредактируем ссылки в файле top.html. Также (необязательно) я вынес код JS в отдельный файл betterlisting.js и сделал загрузку bootstrap.min.css (Bootstrap v3.3.7) и jquery-3.3.1.min.js локально.

top.html

<!DOCTYPE html>

<!-- BetterListing - devCoster.com -->
<!-- Coster coster@devcoster.com -->
<!-- Version 1.0a -->

<html lang="ru-RU" prefix="og: http://ogp.me/ns#">
        <head>
                <!-- Adjust title in settings below -->
                <title>Загрузки</title>
                <meta charset="utf-8" />

                <!-- Bootstrap Core CSS-->
                <link rel="stylesheet" href="/.html/bootstrap/css/bootstrap.min.css">

                <!-- Styles -->
                <link rel="stylesheet" href="/.html/style.css">

                <!-- jQuery -->
                <script src="/.html/jquery-3.3.1.min.js"></script>

                <!-- BetterListing -->
                <script src="/.html/betterlisting.js"></script>

                <!-- Favicon -->
                <link rel="icon" href="/.html/logo.png" sizes="32x32" />
                <link rel="icon" href="/.html/logo.png" sizes="192x192" />
                <link rel="apple-touch-icon-precomposed" href="/.html/logo.png" />
                <meta name="msapplication-TileImage" content="/.html/logo.png" />

        </head>

        <body>
                <div class="wasContainer">
                        <div class="row">
                                <div class="col-xs-11 col-centered" id="mainBox">
        <!-- Start of nginx output -->

В файле bot.html настроим свой футер, а для тегов </body > и </html > добавим пробел, чтобы ngx_http_sub_module их не отфильтровал.

bot.html

        <!-- End of nginx output -->
                                </div>
                        </div>
                        <div id="footer" class="row">
                                <!-- This footer will change depending on your settings in top.html -->
                                <p class="text-center"><a href="*URL сайта*">Загрузки</a></p>
                        </div>
                </div>
<center>
<!--LiveInternet counter-->
<script type="text/javascript"><!--
document.write("<a href='https://www.liveinternet.ru/click' "+
"target=_blank><img src='//counter.yadro.ru/hit?t14.11;r"+
escape(document.referrer)+((typeof(screen)=="undefined")?"":
";s"+screen.width+"*"+screen.height+"*"+(screen.colorDepth?
screen.colorDepth:screen.pixelDepth))+";u"+escape(document.URL)+
";"+Math.random()+
"' alt='' title='LiveInternet: показано число просмотров за 24"+
" часа, посетителей за 24 часа и за сегодня' "+
"border='0' width='88' height='31'></a>")
//-->
</script>
<!--/LiveInternet-->
</center>
        </body >
</html >

Теперь внесем изменения в код JS, который мы поместили в betterlisting.js. Этот скрипт отвечает за добавление иконок, фильтр файлов (поиск по странице) и переназначение вывода некоторых строк, поэтому каждый будет настраивать его под свои нужды. Стоит обратить внимание, что для корректной работы с кириллическими именами в функции filter(target) отвечающей за поиск по странице нужно изменить переменную arraySearch на decodeURIComponent( arraySearch ). Для редактирования списка известных типов файлов внесите соответствующие изменения в переменную var formats.

betterlisting.js

            //
            // Configure BetterListing here:
            var websiteName = 'Загрузки';
            var websiteURL = '*URL сайта*';
            // End of normal settings.
            //

            $(document).ready(function(){

            // Working on nginx HTML and applying settings.
            var text = $("h1").text();
            var array = text.split('/');
            var last = array[array.length-2];
            var dirStructure = $("a").text();
            var dirStructure = document.getElementsByTagName('a')[0].href;
            var dir = text.substring(10);
            var currentDir = last.charAt(0).toUpperCase() + last.slice(1);
            var dirTrun;

            // Truncate long folder names.
            if (currentDir.length > 19){
                var currentDir = currentDir.substring(0, 18) + '...';
            }

            // Updating page title.
            document.title = websiteName;

            // Add back button.
            $("h1").html('<p><img alt="логотип" style="margin-top: 7px; margin-left: 0px; padding: -1px; border-radius: 5px; border: 1px solid #aaa;" src="/.html/logo2.png"></p>');

            if (dir.length > 90) {
                dirTrun = dir.replace(/(.{90})/g, "$1n")
            } else {
                dirTrun = dir.substring(0, dir.length - 1);
            }

            // Add subtitle and back arrow.
            $("h1").append('<h4><a href="' + dirStructure + '"></a><b>Загрузить: </b>' + dirTrun.slice(0) + '</h4>');

            // Add search box.
            $("h1").prepend('<form id="custom-search-form" class="form-inline pull-right"><div class="btn-group"><input id="searchBox" placeholder="Найти на странице" type="search" class="form-control"> <span id="searchclear" class="glyphicon glyphicon-remove-circle"></span></div></form>');

            // Add parent directory bit.
            $("a").eq(1).html('Parent Directory');

            // Add titles.
            $("pre").prepend('<div class="header">Name                                                   Time                 Size</div>');

            // Establish supported formats.
            var list = new Array();
            var formats = ["bin", "jpg", "gif", "png", "html", "css", "zip", "iso", "tiff", "ico", "psd", "pdf", "exe", "rar", "deb", "swf", "7z", "doc", "docx", "xls", "xlsx", "pptx", "ppt", "txt", "php", "js", "c", "c++", "torrent", "sql", "wmv", "avi", "mp4", "mp3", "wma", "ogg", "msg", "wav", "py", "java", "gzip", "jpeg", "raw"];

            // Run when text is entered in the search box.
            $('#custom-search-form').on('input',function(e){
                e.preventDefault();
                var target = $('#searchBox').val();
                filter(target);
            });

            // Instant search.
            function filter(target){
                var parent_directory = 'parent directory';
                $('pre a').each(function(){
                    var arraySearch = $(this).attr('href');
                    var arraySearch = decodeURIComponent( arraySearch )
                    // Check the href data for searched term. Using href because the link label truncates if the file or folder name is too long.
                    // Special handling for 'Parent Directory' as the href data doesn't contain that word.
                    if (arraySearch.toLowerCase().indexOf(target.toLowerCase()) > -1 || (($(this).text() == 'Parent Directory') && (parent_directory.indexOf(target.toLowerCase()) > -1))){
                        $(this).show();
                        $($(this)[0].nextSibling).css('display', 'inline');
                    } else {
                        $(this).hide();
                        if($($(this)[0].nextSibling).hasClass('hideMe')) {
                            $($(this)[0].nextSibling).css('display', 'none');
                        } else {
                            $($(this)[0].nextSibling).wrap('<span class="hideMe" style="display:none"></style>');
                        }
                    }
                });
            }

            // Runs when clear button is hit.
            $("#searchclear").click(function(){
                $("#searchBox").val('');
                filter('');
            });

            // Scan all files in the directory, check the extensions and show the right MIME-type image.
            $('pre a').each(function(){
                var found = 0;
                var arraySplit = $(this).attr('href').split(".");
                var fileExt = arraySplit[arraySplit.length - 1];

                for (var i = 0; i < formats.length; i++) {
                    if (fileExt.toLowerCase() == formats[i].toLowerCase()) {
                        var found = 1;
                        var oldText = $(this).text();
                        $(this).html('<img class="icons" src="/.html/icons/' + formats[i] + '.png"></img></a>' + oldText);
                        return;
                    }
                }

                // Add an icon for the go-back link.
                if ($(this).text().indexOf("Parent Directory") >= 0) {
                    var found = 1;
                    var oldText = $(this).text();
                    $(this).html('<img class="icons" src="/.html/icons/home.png">' + oldText);
                    return;
                }

                // Check for folders as they don't have extensions.
                if ($(this).attr('href').substr($(this).attr('href').length - 1) == '/') {
                    var found = 1;
                    var oldText = $(this).text();
                    $(this).html('<img class="icons" src="/.html/icons/folder.png">' + oldText.substring(0, oldText.length - 1));

                    // Fix for annoying jQuery behaviour where inserted spaces are treated as new elements -- which breaks my search.
                    var string = ' ' + $($(this)[0].nextSibling).text();

                    // Copy the original meta-data string, append a space char and save it over the old string.
                    $($(this)[0].nextSibling).remove();
                    $(this).after(string);
                    return;
                }

                // File format not supported by Better Listings, so let's load a generic icon.
                if (found == 0){
                    var oldText = $(this).text();
                    $(this).html('<img class="icons" src="/.html/icons/error.png">' + oldText);
                    return;
                }
            });
        });

В файле style.css нужно подключить моноширинные шрифты с поддержкой кириллицы, которой нет в оригинальном проекте BetterListing и соответственно назначить их для класса .pre. Здесь же можно внести свои "украшалки" для листинга.

style.css

@import url('https://fonts.googleapis.com/css?family=Cousine&subset=cyrillic');
a {
    color: #4C4C4C;
}

body{
    color: #4C4C4C;
    font-family: 'Open Sans', 'Helvetica Neue', Arial, sans-serif;
        background: #F9F9F9 url(index.png) repeat;
}

pre{
    font-family: 'Cousine', monospace;
    background-color: #FFFFFF;
    border: 1px solid #ccc;
    width:  750px;
    padding:  9.5px 0px 9.5px 0px;
        margin-top: 20px;
        text-align: left;
}

#footer{
    margin-top: 20px;
}
#footer a{
    color: #686868;
}

hr{
    margin-bottom: 0px;
}

h4 {
    color: #aaa;
    margin-top: 10px;
        font-size: 40%;
}

.header {
    color: #686868;
    margin:3px 5px 6px 5px;
}

.icons {
    margin: 2px 5px 3px 5px;
     height: 4%;
     width: 4%;
}

.col-centered{
    float: none;
    margin: 0 auto;
}

#mainBox{
    width:766px;
}

.text-center{
    margin-bottom: 20px;
}

#custom-search-form{
    font-size: 15px;
    margin-top: 7px;
}

#searchIcon{
    padding: 0px 0px 0px 5px;
    color: #686868;
}

.form-group-sm .form-control + .form-control-feedback, .input-group-sm + .form-control-feedback, .input-sm + .form-control-feedback {
    width: 30px;
    height: 30px;
    line-height: 30px;
}

.form-group-sm .form-control {
    height: 30px;
    padding: 5px 10px;
    font-size: 12px;
    line-height: 1.5;
    border-radius: 4px;
}

input.form-control,input.form-control:focus {
    border-color: #AAA;
    box-shadow: none;
   -webkit-box-shadow: none;
   -moz-box-shadow: none;
   -moz-transition: none;
   -webkit-transition: none;
}

#searchclear {
    position: absolute;
    right: 10px;
    top: 11px;
    height: 14px;
    margin: auto;
    font-size: 14px;
    cursor: pointer;
    color: #AAA;
}

В результате листинг будет выглядеть, как-то так:

Красивый листинг файлов и директорий в nginx - 3

Ссылку на рабочий вариант могу указать по запросу в комментариях.

Автор: Dasuber

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js