Для web-сервера Apache существует множество инструкций, в том числе и на Хабре, как стилизовать стандартный листинг файлов и директорий. Однако, как сделать также для сервера nginx, в русскоязычном интернете не найти. Порывшись на просторах интернета я нашел один репозитарий, где как раз и решается этот вопрос. Но для кириллических наименований файлов и папок потребовалась небольшая работа "напильником".
Итак имеется 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 выставляет неправильное количество пробелов для выравнивания.
charset utf-8;
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 локально.
<!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 их не отфильтровал.
<!-- 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.
//
// 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, "$1\n")
} 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. Здесь же можно внести свои "украшалки" для листинга.
@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;
}
В результате листинг будет выглядеть, как-то так:
Ссылку на рабочий вариант могу указать по запросу в комментариях.
Комментарии (22)
SlavikF
13.04.2018 17:59Я недавно узнал, что у Nginx есть модуль, который позволяет загрузить директорию, как Zip архив. Полезная штука, но простого и понятного туториала по нему ещё не видел.
UksusoFF
13.04.2018 18:52А для апача не встречалось?
berezuev
13.04.2018 20:32Можно попробовать по этой инструкции запускать bash-скрипт, наподобие такого:
#!/bin/bash echo "Content-type: application/zip" echo `zip -r0q - ./files_to_be_achived`
Писал на коленке, без тестирования.
В принципе, этот вариант можно и в nginx провернуть.
Fox_exe
13.04.2018 23:41Я тоже этим както озадачился… Написал за вечер на PHP: fox-exe.ru/Files/php_nginx_autoindex (Сам сайт работает на чуть дополненной версии этого-же скрипта)
EvgenyMorozov
14.04.2018 14:45А есть подобная штука, чтобы работала как стандартный Проводник Винды? Чтобы можно было фотографии смотреть превьюшками, бродить по директорями, искать по имени файла?
bondbig
14.04.2018 16:49Да миллион их, искать по ключевым словам “web file browser”
Пример: https://demo.filerun.co/?username=admin&password=admin
VBart
14.04.2018 14:45Довольно странное решение, учитывая, что autoindex в nginx может выдавать данные в формате JSON или XML. Чтобы затем получить из этого красивую страничку, нужен либо xslt на стороне сервера, либо тот же JavaScript на стороне клиента.
ZigFisher
14.04.2018 16:32Спасибо, интересное решение. У самого то-же есть пару проектов, связанных с прошивками под различные устройства, и есть желание причесать стандартный вывод информации.
Ссылку на рабочий вариант могу указать по запросу в комментариях.
Да, покажите пожалуйста рабочий пример на базе вашей разработки. Спасибо.
berezuev
Выглядит хорошо, но… Зачем?
Если вы по http видите список файлов, значит что-то пошло не так.
Revertis
У меня знакомый качает торренты на подкроватный ноутбук, а в LAN открыл nginx и браузером ходит по содержимому :)
eri
расскажите ему про webdav ;)
Revertis
Да проще самбу поднять. У меня через неё организовано.
UksusoFF
Вы это серьезно? У касперского/длинка/китайских регистраторов/mirror.yandex.ru и еще у кучи вендоров и опенсорсных проектов кого тоже что-то пошло не так?
berezuev
Если бы не mirror.yandex.ru среди ваших примеров, я бы подумал, что вы так шутите)
Касперский, Длинк и китайские регистраторы — неудачные примеры.
UksusoFF
Ну хорошо, вот еще:
http://apache-mirror.rbc.ru/pub/apache/
https://ftp.mozilla.org/pub/firefox/releases/
bondbig
да любой дистр линукса