nginx-interactive-index: Init
This commit is contained in:
parent
ceff40f84d
commit
e0ef586e5e
|
@ -46,6 +46,7 @@ in
|
||||||
./mailserver.nix
|
./mailserver.nix
|
||||||
./media-proxy.nix
|
./media-proxy.nix
|
||||||
./network-manager.nix
|
./network-manager.nix
|
||||||
|
./nginx-interactive-index
|
||||||
./office.nix
|
./office.nix
|
||||||
./prometheus/node_exporter.nix
|
./prometheus/node_exporter.nix
|
||||||
./pubkeys.nix
|
./pubkeys.nix
|
||||||
|
|
69
modules/nginx-interactive-index/default.nix
Normal file
69
modules/nginx-interactive-index/default.nix
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
# This module implements an option with the same structure as the nginx module
|
||||||
|
# but does not extend the nginx module since that would cause infinite
|
||||||
|
# recursion.
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
let
|
||||||
|
enabledLocations = lib.fold
|
||||||
|
(x: a: a ++ x)
|
||||||
|
[ ]
|
||||||
|
(lib.mapAttrsToList
|
||||||
|
(vhostName: vhostConfig: lib.mapAttrsToList
|
||||||
|
(locationName: locationConfig: [ vhostName locationName ])
|
||||||
|
(lib.filterAttrs
|
||||||
|
(_: location: location.enable)
|
||||||
|
vhostConfig.locations))
|
||||||
|
config.services.nginx-interactive-index.virtualHosts);
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.services.nginx-interactive-index.virtualHosts = with lib.types; lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
type = attrsOf (submodule {
|
||||||
|
options = {
|
||||||
|
locations = lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
type = attrsOf (submodule {
|
||||||
|
options = {
|
||||||
|
enable = lib.mkEnableOption "interactive directory index";
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
config.services.nginx.virtualHosts = lib.fold
|
||||||
|
(x: a: a // x)
|
||||||
|
{ }
|
||||||
|
(map
|
||||||
|
(path:
|
||||||
|
let
|
||||||
|
vhost = lib.elemAt path 0;
|
||||||
|
location = lib.elemAt path 1;
|
||||||
|
assetsPath = "${location}__nginx-interactive-index-assets__";
|
||||||
|
in
|
||||||
|
{
|
||||||
|
"${vhost}".locations = {
|
||||||
|
"${location}" = {
|
||||||
|
extraConfig = ''
|
||||||
|
autoindex on;
|
||||||
|
autoindex_exact_size on;
|
||||||
|
add_before_body ${assetsPath}/header.html;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
"${assetsPath}/" = {
|
||||||
|
alias = "${builtins.filterSource
|
||||||
|
(path: type: baseNameOf path != "default.nix")
|
||||||
|
./.}/";
|
||||||
|
};
|
||||||
|
"=${assetsPath}/header.html" = {
|
||||||
|
alias = pkgs.writeText "nginx-interactive-index-${location}-header.html" ''
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<link rel="stylesheet" href="${assetsPath}/listing.css">
|
||||||
|
<script src="${assetsPath}/listing.js"></script>
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
enabledLocations);
|
||||||
|
}
|
44
modules/nginx-interactive-index/listing.css
Normal file
44
modules/nginx-interactive-index/listing.css
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
body, html {
|
||||||
|
background-color: #fdf6e3;
|
||||||
|
color: #657b83;
|
||||||
|
font-family: "TeX Gyre Heros", "Roboto", "Helvetica", "Arial", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:nth-child(even) {
|
||||||
|
background: #eee8d5;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
padding: 0.1em 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
text-align: left;
|
||||||
|
font-weight: bold;
|
||||||
|
background: #eee8d5;
|
||||||
|
border-bottom: 1px solid #657b83;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #586e75;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #073642;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#search-field {
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
background: #eee8d5;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
display: none;
|
||||||
|
}
|
76
modules/nginx-interactive-index/listing.js
Normal file
76
modules/nginx-interactive-index/listing.js
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
function humanFileSize(bytes) {
|
||||||
|
const thresh = 1024
|
||||||
|
if(Math.abs(bytes) < thresh) {
|
||||||
|
return bytes + ' B'
|
||||||
|
}
|
||||||
|
const units = ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB']
|
||||||
|
var u = -1
|
||||||
|
do {
|
||||||
|
bytes /= thresh
|
||||||
|
++u
|
||||||
|
} while(Math.abs(bytes) >= thresh && u < units.length - 1)
|
||||||
|
return bytes.toFixed(1)+' '+units[u]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function textToA(line) {
|
||||||
|
let outerElement = document.createElement('div')
|
||||||
|
outerElement.innerHTML = line
|
||||||
|
return outerElement.getElementsByTagName('a')[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseLine(line) {
|
||||||
|
const href = textToA(line).href
|
||||||
|
const filename = href.substr(-1) === '/' ? decodeURIComponent(href.split('/').slice(-2, -1)[0]) : decodeURIComponent(href.split('/').pop())
|
||||||
|
const size = line.split(' ').pop()
|
||||||
|
return {
|
||||||
|
href: href,
|
||||||
|
filename: filename,
|
||||||
|
size: size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function processLine(line) {
|
||||||
|
meta = parseLine(line)
|
||||||
|
return `<tr><td><a href="${meta.href}">${meta.filename}</a></td><td>${meta.size === '-' ? '-' : humanFileSize(meta.size)}</td></tr>`
|
||||||
|
}
|
||||||
|
|
||||||
|
const collator = new Intl.Collator('kn', {numeric: true})
|
||||||
|
|
||||||
|
// transform plain text to table
|
||||||
|
document.querySelector('pre').outerHTML = '<table><tr><th>Name</th><th>Size</th></tr><tr><td><a href="..">..</a></td><td>-</td></tr>' + document.querySelector('pre').innerHTML
|
||||||
|
.split('\n')
|
||||||
|
.filter(line => line !== '')
|
||||||
|
.filter(line => line !== '<a href="../">../</a>')
|
||||||
|
.map(processLine)
|
||||||
|
.sort(collator.compare)
|
||||||
|
.join('\n') + '</table>'
|
||||||
|
|
||||||
|
let searchField = document.createElement('input')
|
||||||
|
searchField.id = 'search-field'
|
||||||
|
searchField.autofocus = true
|
||||||
|
document.querySelector('body').insertBefore(searchField, document.querySelector('table'))
|
||||||
|
|
||||||
|
const rows = Array.from(document.querySelectorAll('tr:not(:first-child)'))
|
||||||
|
|
||||||
|
document.querySelector('#search-field').addEventListener("input", e => {
|
||||||
|
const searchValue = e.target.value.toLowerCase()
|
||||||
|
rows.forEach(row => {
|
||||||
|
const file = row.querySelector('td:nth-child(1) a').innerText
|
||||||
|
if (!file.toLowerCase().includes(searchValue)) {
|
||||||
|
row.style.display = 'none'
|
||||||
|
} else {
|
||||||
|
row.style.display = 'table-row'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const visibleRows = rows.filter(row => row.style.display === 'table-row')
|
||||||
|
if (visibleRows.length === 1) {
|
||||||
|
const target = visibleRows[0].querySelector('td a').href
|
||||||
|
if (target.substr(-1) === '/') {
|
||||||
|
window.location = target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in a new issue