basic ui
This commit is contained in:
parent
62f3729a83
commit
e1e477ca0b
|
@ -0,0 +1,214 @@
|
||||||
|
function p_init() {
|
||||||
|
draw_graph()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function add_colors(data) {
|
||||||
|
data.color = randomColor();
|
||||||
|
data.children.forEach(function(child){
|
||||||
|
add_colors(child);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function draw_graph() {
|
||||||
|
// d3.json("/static/sampledata.json").then(function(rootData) {
|
||||||
|
d3.json("/chart.json?n=x&depth=1").then(function(rootData) {
|
||||||
|
initData();
|
||||||
|
add_colors(rootData);
|
||||||
|
initLayout(rootData);
|
||||||
|
|
||||||
|
|
||||||
|
hierarchy = d3.hierarchy(rootData).sum(function(d){ return d.weight; });
|
||||||
|
// console.log(hierarchy)
|
||||||
|
_voronoiTreemap
|
||||||
|
.clip(circlingPolygon)
|
||||||
|
(hierarchy);
|
||||||
|
|
||||||
|
console.log(rootData);
|
||||||
|
drawTreemap(hierarchy);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//begin: constants
|
||||||
|
var _2PI = 2*Math.PI;
|
||||||
|
//end: constants
|
||||||
|
|
||||||
|
//begin: layout conf.
|
||||||
|
var svgWidth = 1500,
|
||||||
|
svgHeight = 600,
|
||||||
|
margin = {top: 10, right: 10, bottom: 10, left: 10},
|
||||||
|
height = svgHeight - margin.top - margin.bottom,
|
||||||
|
width = svgWidth - margin.left - margin.right,
|
||||||
|
halfWidth = width/2,
|
||||||
|
halfHeight = height/2,
|
||||||
|
quarterWidth = width/4,
|
||||||
|
quarterHeight = height/4,
|
||||||
|
titleY = 20,
|
||||||
|
legendsMinY = height - 20,
|
||||||
|
treemapRadius = 0,
|
||||||
|
treemapCenter = [halfWidth, halfHeight+5];
|
||||||
|
//end: layout conf.
|
||||||
|
|
||||||
|
//begin: treemap conf.
|
||||||
|
var _voronoiTreemap = d3.voronoiTreemap();
|
||||||
|
var hierarchy, circlingPolygon;
|
||||||
|
//end: treemap conf.
|
||||||
|
|
||||||
|
//begin: drawing conf.
|
||||||
|
var fontScale = d3.scaleLinear();
|
||||||
|
//end: drawing conf.
|
||||||
|
|
||||||
|
//begin: reusable d3Selection
|
||||||
|
var svg, drawingArea, treemapContainer;
|
||||||
|
//end: reusable d3Selection
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function initData(rootData) {
|
||||||
|
circlingPolygon = computeCirclingPolygon(treemapRadius);
|
||||||
|
fontScale.domain([3, 20]).range([8, 20]).clamp(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeCirclingPolygon(radius) {
|
||||||
|
/*var points = 60,
|
||||||
|
increment = _2PI/points,
|
||||||
|
circlingPolygon = [];
|
||||||
|
|
||||||
|
for (var a=0, i=0; i<points; i++, a+=increment) {
|
||||||
|
circlingPolygon.push(
|
||||||
|
[radius + radius*Math.cos(a), radius + radius*Math.sin(a)]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return circlingPolygon;*/
|
||||||
|
|
||||||
|
return [[-600,-250], [600,-250], [600,250], [-600,250], [-600,-250]]
|
||||||
|
};
|
||||||
|
|
||||||
|
function initLayout(rootData) {
|
||||||
|
svg = d3.select("svg")
|
||||||
|
.attr("width", svgWidth)
|
||||||
|
.attr("height", svgHeight);
|
||||||
|
|
||||||
|
drawingArea = svg.append("g")
|
||||||
|
.classed("drawingArea", true)
|
||||||
|
.attr("transform", "translate("+[margin.left,margin.top]+")");
|
||||||
|
|
||||||
|
treemapContainer = drawingArea.append("g")
|
||||||
|
.classed("treemap-container", true)
|
||||||
|
.attr("transform", "translate("+treemapCenter+")");
|
||||||
|
|
||||||
|
treemapContainer.append("path")
|
||||||
|
.classed("world", true)
|
||||||
|
.attr("transform", "translate("+[-treemapRadius,-treemapRadius]+")")
|
||||||
|
.attr("d", "M"+circlingPolygon.join(",")+"Z");
|
||||||
|
|
||||||
|
// drawTitle();
|
||||||
|
drawLegends(rootData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// function drawTitle() {
|
||||||
|
// drawingArea.append("text")
|
||||||
|
// .attr("id", "title")
|
||||||
|
// .attr("transform", "translate("+[halfWidth, titleY]+")")
|
||||||
|
// .attr("text-anchor", "middle")
|
||||||
|
// .text("The Global Economy by GDP (as of 01/2017)")
|
||||||
|
// }
|
||||||
|
|
||||||
|
function drawLegends(rootData) {
|
||||||
|
var legendHeight = 13,
|
||||||
|
interLegend = 4,
|
||||||
|
colorWidth = legendHeight*6,
|
||||||
|
continents = rootData.children.reverse();
|
||||||
|
|
||||||
|
var legendContainer = drawingArea.append("g")
|
||||||
|
.classed("legend", true)
|
||||||
|
.attr("transform", "translate("+[0, legendsMinY]+")");
|
||||||
|
|
||||||
|
var legends = legendContainer.selectAll(".legend")
|
||||||
|
.data(continents)
|
||||||
|
.enter();
|
||||||
|
|
||||||
|
var legend = legends.append("g")
|
||||||
|
.classed("legend", true)
|
||||||
|
.attr("transform", function(d,i){
|
||||||
|
return "translate("+[0, -i*(legendHeight+interLegend)]+")";
|
||||||
|
})
|
||||||
|
|
||||||
|
legend.append("rect")
|
||||||
|
.classed("legend-color", true)
|
||||||
|
.attr("y", -legendHeight)
|
||||||
|
.attr("width", colorWidth)
|
||||||
|
.attr("height", legendHeight)
|
||||||
|
.style("fill", function(d){ return d.color; });
|
||||||
|
legend.append("text")
|
||||||
|
.classed("tiny", true)
|
||||||
|
.attr("transform", "translate("+[colorWidth+5, -2]+")")
|
||||||
|
.text(function(d){ return d.name; });
|
||||||
|
|
||||||
|
legendContainer.append("text")
|
||||||
|
.attr("transform", "translate("+[0, -continents.length*(legendHeight+interLegend)-5]+")")
|
||||||
|
.text("Continents");
|
||||||
|
}
|
||||||
|
|
||||||
|
function format_percent(value) {
|
||||||
|
return round(value * 100, 2) + "%";
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawTreemap(hierarchy) {
|
||||||
|
var leaves=hierarchy.leaves();
|
||||||
|
|
||||||
|
var cells = treemapContainer.append("g")
|
||||||
|
.classed('cells', true)
|
||||||
|
.attr("transform", "translate("+[-treemapRadius,-treemapRadius]+")")
|
||||||
|
.selectAll(".cell")
|
||||||
|
.data(leaves)
|
||||||
|
.enter()
|
||||||
|
.append("path")
|
||||||
|
.classed("cell", true)
|
||||||
|
.attr("d", function(d){ return "M"+d.polygon.join(",")+"z"; })
|
||||||
|
.style("fill", function(d){
|
||||||
|
return d.data.color;
|
||||||
|
});
|
||||||
|
|
||||||
|
var labels = treemapContainer.append("g")
|
||||||
|
.classed('labels', true)
|
||||||
|
.attr("transform", "translate("+[-treemapRadius,-treemapRadius]+")")
|
||||||
|
.selectAll(".label")
|
||||||
|
.data(leaves)
|
||||||
|
.enter()
|
||||||
|
.append("g")
|
||||||
|
.classed("label", true)
|
||||||
|
.attr("transform", function(d){
|
||||||
|
return "translate("+[d.polygon.site.x, d.polygon.site.y]+")";
|
||||||
|
})
|
||||||
|
.style("font-size", function(d){ return fontScale(d.data.weight*100); });
|
||||||
|
|
||||||
|
labels.append("text")
|
||||||
|
.classed("name", true)
|
||||||
|
.html(function(d){
|
||||||
|
return (d.data.weight<1)? d.data.code : d.data.name;
|
||||||
|
});
|
||||||
|
labels.append("text")
|
||||||
|
.classed("value", true)
|
||||||
|
.text(function(d){ return d.data.name + "\r\n" + format_percent(d.data.weight); });
|
||||||
|
|
||||||
|
var hoverers = treemapContainer.append("g")
|
||||||
|
.classed('hoverers', true)
|
||||||
|
.attr("transform", "translate("+[-treemapRadius,-treemapRadius]+")")
|
||||||
|
.selectAll(".hoverer")
|
||||||
|
.data(leaves)
|
||||||
|
.enter()
|
||||||
|
.append("path")
|
||||||
|
.classed("hoverer", true)
|
||||||
|
.attr("d", function(d){ return "M"+d.polygon.join(",")+"z"; });
|
||||||
|
|
||||||
|
hoverers.append("title")
|
||||||
|
.text(function(d) { return d.data.name + "\n" + format_percent(d.value); });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function round(num, places) {
|
||||||
|
return Math.round(num*places*100) / (places*100)
|
||||||
|
}
|
|
@ -7,6 +7,9 @@ import cherrypy
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||||
from dirview.dirtools import gen_db, gen_node_index, NodeType, NodeGroup
|
from dirview.dirtools import gen_db, gen_node_index, NodeType, NodeGroup
|
||||||
|
from dirview.utils import jinja_filters
|
||||||
|
from time import time
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
APPROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "../"))
|
APPROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "../"))
|
||||||
|
@ -21,14 +24,15 @@ class DbUpdater(Thread):
|
||||||
self.index = None
|
self.index = None
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
logging.info("Updating database...")
|
start = time()
|
||||||
|
logging.info("Generating database...")
|
||||||
self.root = gen_db(self.root_path)
|
self.root = gen_db(self.root_path)
|
||||||
logging.info("Generating index...")
|
logging.info("Generating index...")
|
||||||
self.index = gen_node_index(self.root)
|
self.index = gen_node_index(self.root)
|
||||||
logging.info("Warming caches...")
|
logging.info("Warming caches...")
|
||||||
self.root.total_size # calculating these require recursing all nodes
|
self.root.total_size # calculating these require recursing all nodes
|
||||||
self.root.total_children
|
self.root.total_children
|
||||||
logging.info("Database update complete!")
|
logging.info(f"Database update complete in {round(time() - start, 3)}s")
|
||||||
|
|
||||||
|
|
||||||
class AppWeb(object):
|
class AppWeb(object):
|
||||||
|
@ -36,11 +40,7 @@ class AppWeb(object):
|
||||||
self.db = database
|
self.db = database
|
||||||
self.tpl = Environment(loader=FileSystemLoader(template_dir),
|
self.tpl = Environment(loader=FileSystemLoader(template_dir),
|
||||||
autoescape=select_autoescape(['html', 'xml']))
|
autoescape=select_autoescape(['html', 'xml']))
|
||||||
self.tpl.filters.update(id=id,
|
self.tpl.filters.update(**jinja_filters)
|
||||||
repr=repr,
|
|
||||||
len=len,
|
|
||||||
pathjoin=lambda x: os.path.join(*x),
|
|
||||||
commafy=lambda x: format(x, ',d'))
|
|
||||||
|
|
||||||
def render(self, template, **kwargs):
|
def render(self, template, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
@ -53,7 +53,6 @@ class AppWeb(object):
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def index(self, n=None):
|
def index(self, n=None):
|
||||||
from time import time
|
|
||||||
start = time()
|
start = time()
|
||||||
if self.db.root is None:
|
if self.db.root is None:
|
||||||
return "I'm still scanning your files, check back soon."
|
return "I'm still scanning your files, check back soon."
|
||||||
|
@ -63,7 +62,7 @@ class AppWeb(object):
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
node = self.db.index[int(n)]
|
node = self.db.index[int(n)]
|
||||||
except KeyError:
|
except (ValueError, KeyError):
|
||||||
raise cherrypy.HTTPError(404)
|
raise cherrypy.HTTPError(404)
|
||||||
|
|
||||||
page = self.render("page.html", node=node)
|
page = self.render("page.html", node=node)
|
||||||
|
@ -71,7 +70,6 @@ class AppWeb(object):
|
||||||
return page + f"\n<!-- render time: {round(dur, 4)} -->"
|
return page + f"\n<!-- render time: {round(dur, 4)} -->"
|
||||||
|
|
||||||
# yield str(self.db.root)
|
# yield str(self.db.root)
|
||||||
|
|
||||||
# yield "Ready<br />"
|
# yield "Ready<br />"
|
||||||
# from time import time
|
# from time import time
|
||||||
# start = time()
|
# start = time()
|
||||||
|
@ -79,6 +77,38 @@ class AppWeb(object):
|
||||||
# dur = time() - start
|
# dur = time() - start
|
||||||
# yield f"num nodes: {num_nodes} in {round(dur, 3)}"
|
# yield f"num nodes: {num_nodes} in {round(dur, 3)}"
|
||||||
|
|
||||||
|
@cherrypy.expose
|
||||||
|
def chart_json(self, n, depth=2):
|
||||||
|
# try:
|
||||||
|
# node = self.db.index[int(n)]
|
||||||
|
# except (ValueError, KeyError):
|
||||||
|
# raise cherrypy.HTTPError(404)
|
||||||
|
node = self.db.root
|
||||||
|
|
||||||
|
data = AppWeb.export_children(node, depth=int(depth))
|
||||||
|
|
||||||
|
cherrypy.response.headers["Content-type"] = "application/json"
|
||||||
|
return json.dumps(data).encode("utf-8")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def export_children(entry, depth):
|
||||||
|
children = []
|
||||||
|
if depth:
|
||||||
|
for child in entry.children:
|
||||||
|
child_data = AppWeb.export_children(child, depth - 1)
|
||||||
|
if entry.total_size > 0:
|
||||||
|
child_data["weight"] = child_data["size"] / entry.total_size
|
||||||
|
else:
|
||||||
|
child_data["weight"] = 0;
|
||||||
|
children.append(child_data)
|
||||||
|
children.sort(key=lambda c: c["size"],
|
||||||
|
reverse=True)
|
||||||
|
|
||||||
|
return {"name": entry.name,
|
||||||
|
"typ": entry.typ.value,
|
||||||
|
"size": entry.total_size,
|
||||||
|
"children": children}
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
import argparse
|
import argparse
|
||||||
|
|
|
@ -44,7 +44,7 @@ class Node:
|
||||||
@property
|
@property
|
||||||
def total_size(self) -> int:
|
def total_size(self) -> int:
|
||||||
if self.total_size_cache is None:
|
if self.total_size_cache is None:
|
||||||
if self.typ in {NodeType.DIR, NodeType.ROOT}:
|
if self.typ in NodeGroup.DIRLIKE:
|
||||||
self.total_size_cache = sum([node.total_size for node in self.children])
|
self.total_size_cache = sum([node.total_size for node in self.children])
|
||||||
else:
|
else:
|
||||||
self.total_size_cache = self.size
|
self.total_size_cache = self.size
|
||||||
|
@ -58,7 +58,7 @@ class Node:
|
||||||
self.total_children_cache = sum([c.total_children for c in self.children]) + len(self.children)
|
self.total_children_cache = sum([c.total_children for c in self.children]) + len(self.children)
|
||||||
return self.total_children_cache
|
return self.total_children_cache
|
||||||
|
|
||||||
def serialize(self) -> tuple:
|
def serialize(self) -> dict:
|
||||||
"""
|
"""
|
||||||
Return a dictionary representation of the node suitable for plain text serialization such as json.
|
Return a dictionary representation of the node suitable for plain text serialization such as json.
|
||||||
|
|
||||||
|
@ -124,14 +124,18 @@ def gen_db_recurse(dirpath, parent=None, is_root=False):
|
||||||
size=0,
|
size=0,
|
||||||
parent=parent)
|
parent=parent)
|
||||||
|
|
||||||
if node.typ in {NodeType.FILE}: # todo account for link and dir sizes somewhere
|
# TODO replace the below try/except with stat()
|
||||||
node.size = os.path.getsize(dirpath)
|
if node.typ == NodeType.FILE: # todo account for link and dir sizes somewhere
|
||||||
|
try:
|
||||||
|
node.size = os.path.getsize(dirpath)
|
||||||
|
except (PermissionError, FileNotFoundError, OSError) as e:
|
||||||
|
logging.warning(f"Could not access {dirpath}: {e}")
|
||||||
|
|
||||||
if os.path.isdir(dirpath) and not os.path.islink(dirpath):
|
if os.path.isdir(dirpath) and not os.path.islink(dirpath):
|
||||||
flist = []
|
flist = []
|
||||||
try:
|
try:
|
||||||
flist = os.listdir(dirpath)
|
flist = os.listdir(dirpath)
|
||||||
except PermissionError as e:
|
except (PermissionError, FileNotFoundError, OSError) as e:
|
||||||
logging.info(f"Could not access {dirpath}: {e}")
|
logging.info(f"Could not access {dirpath}: {e}")
|
||||||
for i in flist: # TODO we could probably parallelize the recursion down different trees?
|
for i in flist: # TODO we could probably parallelize the recursion down different trees?
|
||||||
children.append(gen_db_recurse(os.path.join(dirpath, i), parent=node))
|
children.append(gen_db_recurse(os.path.join(dirpath, i), parent=node))
|
||||||
|
@ -314,6 +318,10 @@ TODO:
|
||||||
- link to dir/file by permanent URL
|
- link to dir/file by permanent URL
|
||||||
- we use id()s now
|
- we use id()s now
|
||||||
- switch to path, finding a node by following the path through the database should be inexpensive
|
- switch to path, finding a node by following the path through the database should be inexpensive
|
||||||
|
- on 'still scanning' page estimate a completion percentage based on $tree_total_size / $disk_used
|
||||||
|
- this implies no filesystem crossing
|
||||||
|
- unless we're incrementally updating the tree lol -
|
||||||
|
we can get % done based on parents touched vs previous node count
|
||||||
|
|
||||||
App planning:
|
App planning:
|
||||||
- single page webui
|
- single page webui
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
module.exports = function(grunt) {
|
||||||
|
grunt.initConfig({
|
||||||
|
/*less: {
|
||||||
|
website: {
|
||||||
|
files: {
|
||||||
|
'styles/css/main.css': 'styles/less/main.less'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},*/
|
||||||
|
/*cssmin: {
|
||||||
|
website: {
|
||||||
|
files: {
|
||||||
|
'styles/mincss/pure.css': 'node_modules/purecss/build/pure.css',
|
||||||
|
'styles/mincss/grids-responsive-min.css': 'node_modules/purecss/build/grids-responsive.css',
|
||||||
|
'styles/mincss/main.css': 'styles/css/main.css'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},*/
|
||||||
|
concat: {
|
||||||
|
deps_js: {
|
||||||
|
src: [
|
||||||
|
'node_modules/d3-voronoi-treemap/build/d3-voronoi-treemap.js',
|
||||||
|
'node_modules/d3-voronoi-map/build/d3-voronoi-map.js',
|
||||||
|
'node_modules/d3-weighted-voronoi/build/d3-weighted-voronoi.js',
|
||||||
|
'node_modules/d3-scale/dist/d3-scale.js',
|
||||||
|
'node_modules/d3/dist/d3.js',
|
||||||
|
'node_modules/randomcolor/randomColor.js',
|
||||||
|
'assets/js/main.js'
|
||||||
|
],
|
||||||
|
dest: 'static/scripts.js',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
concat: {
|
||||||
|
files: ['assets/js/main.js'],
|
||||||
|
tasks: ['concat:deps_js'],
|
||||||
|
// options: {
|
||||||
|
// spawn: false
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// grunt.loadNpmTasks('grunt-contrib-less');
|
||||||
|
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||||
|
// grunt.loadNpmTasks('grunt-contrib-cssmin');
|
||||||
|
grunt.loadNpmTasks('grunt-contrib-concat');
|
||||||
|
|
||||||
|
// grunt.registerTask('default', ['less:website', 'cssmin:website', 'concat:dist']);
|
||||||
|
grunt.registerTask('default', ['concat:deps_js']);
|
||||||
|
|
||||||
|
};
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "dirview",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "directory visualizer ui",
|
||||||
|
"main": null,
|
||||||
|
"repository": "ssh://git@git.davepedu.com:223/dave/dirview.git",
|
||||||
|
"author": "dave <dave@davepedu.com>",
|
||||||
|
"license": "None",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"d3": "^5.9.2",
|
||||||
|
"d3-voronoi-treemap": "^1.1.0",
|
||||||
|
"grunt": "^1.0.4",
|
||||||
|
"grunt-contrib-concat": "^1.0.1",
|
||||||
|
"grunt-contrib-watch": "^1.1.0",
|
||||||
|
"randomcolor": "^0.5.4"
|
||||||
|
},
|
||||||
|
"scripts": {}
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
backports.functools-lru-cache==1.5
|
backports.functools-lru-cache==1.5
|
||||||
cheroot==6.5.5
|
cheroot==6.5.5
|
||||||
CherryPy==18.1.1
|
CherryPy==18.1.1
|
||||||
-e git+ssh://git@git.davepedu.com:223/dave/dirview.git@2970c139b9004b0d1231f0a33ab418a7b363fbbf#egg=dirview
|
|
||||||
jaraco.functools==2.0
|
jaraco.functools==2.0
|
||||||
Jinja2==2.10.1
|
Jinja2==2.10.1
|
||||||
MarkupSafe==1.1.1
|
MarkupSafe==1.1.1
|
||||||
|
|
4
setup.py
4
setup.py
|
@ -15,4 +15,8 @@ setup(name='dirview',
|
||||||
"dirviewd = dirview:main"
|
"dirviewd = dirview:main"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
package_data={'dirview': ['../templates/*.html',
|
||||||
|
# '../templates/fragments/*.html',
|
||||||
|
# '../styles/dist/*'
|
||||||
|
]},
|
||||||
zip_safe=False)
|
zip_safe=False)
|
||||||
|
|
|
@ -3,10 +3,14 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>NAS Viewer</title>
|
<title>NAS Viewer</title>
|
||||||
|
<script src="/static/scripts.js" type="text/javascript"></script>
|
||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
h1 {
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
div.children > div {
|
div.children > div {
|
||||||
padding: 0px 15px;
|
padding: 0px 15px;
|
||||||
}
|
}
|
||||||
|
@ -18,9 +22,67 @@
|
||||||
float: right;
|
float: right;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
svg {
|
||||||
|
background-color: rgb(250,250,250);
|
||||||
|
}
|
||||||
|
|
||||||
|
#title {
|
||||||
|
letter-spacing: 4px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: x-large;
|
||||||
|
}
|
||||||
|
|
||||||
|
text.tiny {
|
||||||
|
font-size: 10pt;
|
||||||
|
}
|
||||||
|
text.light {
|
||||||
|
fill: lightgrey
|
||||||
|
}
|
||||||
|
|
||||||
|
.world {
|
||||||
|
stroke: lightgrey;
|
||||||
|
stroke-width: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell {
|
||||||
|
stroke: white;
|
||||||
|
stroke-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
text-anchor: middle;
|
||||||
|
fill: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label>.name {
|
||||||
|
dominant-baseline: text-after-edge;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label>.value {
|
||||||
|
dominant-baseline: text-before-edge;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hoverer {
|
||||||
|
fill: transparent;
|
||||||
|
stroke: white;
|
||||||
|
stroke-width:0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hoverer:hover {
|
||||||
|
stroke-width: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-color {
|
||||||
|
stroke-width: 1px;
|
||||||
|
stroke:darkgrey;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body onload="p_init();">
|
||||||
|
<svg></svg>
|
||||||
<div class="viewer">
|
<div class="viewer">
|
||||||
<h1>{{ node.path|pathjoin }}</h1>
|
<h1>{{ node.path|pathjoin }}</h1>
|
||||||
<div>
|
<div>
|
||||||
|
@ -32,7 +94,7 @@
|
||||||
<ul>
|
<ul>
|
||||||
<li>Type: {{ node.typ }}</li>
|
<li>Type: {{ node.typ }}</li>
|
||||||
<li>Size: {{ node.size|commafy }} B</li>
|
<li>Size: {{ node.size|commafy }} B</li>
|
||||||
<li>Total Size: {{ node.total_size|commafy }} B</li>
|
<li>Total Size: {{ node.total_size|data }}</li>
|
||||||
<li>Recursive Children: {{ node.total_children|commafy }}</li>
|
<li>Recursive Children: {{ node.total_children|commafy }}</li>
|
||||||
<li>Children: {{ node.children|len }}</li>
|
<li>Children: {{ node.children|len }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -43,14 +105,14 @@
|
||||||
<h2>Subdirs:</h2>
|
<h2>Subdirs:</h2>
|
||||||
{% for child in node.children|sort(attribute='total_children', reverse=True) %}{% if child.typ in NodeGroup.DIRLIKE %}
|
{% for child in node.children|sort(attribute='total_children', reverse=True) %}{% if child.typ in NodeGroup.DIRLIKE %}
|
||||||
<hr />
|
<hr />
|
||||||
<a href="/?n={{ child|id }}">{{ child.name }}</a>: {{ child.total_size|commafy }}B - {{ child.total_children|commafy }} children
|
<a href="/?n={{ child|id }}">{{ child.name }}</a>: {{ child.total_size|data }} - {{ child.total_children|commafy }} children
|
||||||
{% endif %}{% endfor %}
|
{% endif %}{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<div class="files">
|
<div class="files">
|
||||||
<h2>Files:</h2>
|
<h2>Files:</h2>
|
||||||
{% for child in node.children|sort(attribute='name') %}{% if child.typ in NodeGroup.FILELIKE %}
|
{% for child in node.children|sort(attribute='name') %}{% if child.typ in NodeGroup.FILELIKE %}
|
||||||
<hr />
|
<hr />
|
||||||
<a href="/?n={{ child|id }}">{{ child.name }}</a>: {{ child.total_size|commafy }}B
|
<a href="/?n={{ child|id }}">{{ child.name }}</a>: {{ child.total_size|data }}
|
||||||
{% endif %}{% endfor %}
|
{% endif %}{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue