diff --git a/modules/default.nix b/modules/default.nix index c5d16cb..de3dfec 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -46,6 +46,7 @@ in ./mailserver.nix ./media-proxy.nix ./network-manager.nix + ./nginx-interactive-index ./office.nix ./prometheus/node_exporter.nix ./pubkeys.nix diff --git a/modules/nginx-interactive-index/default.nix b/modules/nginx-interactive-index/default.nix new file mode 100644 index 0000000..a93bc43 --- /dev/null +++ b/modules/nginx-interactive-index/default.nix @@ -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" '' + + + + + ''; + }; + }; + }) + enabledLocations); +} diff --git a/modules/nginx-interactive-index/listing.css b/modules/nginx-interactive-index/listing.css new file mode 100644 index 0000000..ce13f98 --- /dev/null +++ b/modules/nginx-interactive-index/listing.css @@ -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; +} diff --git a/modules/nginx-interactive-index/listing.js b/modules/nginx-interactive-index/listing.js new file mode 100644 index 0000000..c7841de --- /dev/null +++ b/modules/nginx-interactive-index/listing.js @@ -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 `
Name | Size |
---|---|
.. | - |