staticdash 2025.32__tar.gz → 2025.34__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {staticdash-2025.32 → staticdash-2025.34}/PKG-INFO +1 -1
- {staticdash-2025.32 → staticdash-2025.34}/pyproject.toml +1 -1
- {staticdash-2025.32 → staticdash-2025.34}/staticdash/__init__.py +2 -2
- {staticdash-2025.32 → staticdash-2025.34}/staticdash/dashboard.py +238 -5
- {staticdash-2025.32 → staticdash-2025.34}/staticdash.egg-info/PKG-INFO +1 -1
- {staticdash-2025.32 → staticdash-2025.34}/README.md +0 -0
- {staticdash-2025.32 → staticdash-2025.34}/setup.cfg +0 -0
- {staticdash-2025.32 → staticdash-2025.34}/setup.py +0 -0
- {staticdash-2025.32 → staticdash-2025.34}/staticdash/assets/css/style.css +0 -0
- {staticdash-2025.32 → staticdash-2025.34}/staticdash/assets/js/script.js +0 -0
- {staticdash-2025.32 → staticdash-2025.34}/staticdash/assets/vendor/mathjax/tex-mml-chtml.js +0 -0
- {staticdash-2025.32 → staticdash-2025.34}/staticdash/assets/vendor/plotly/plotly.min.js +0 -0
- {staticdash-2025.32 → staticdash-2025.34}/staticdash/assets/vendor/prism/components/prism-bash.min.js +0 -0
- {staticdash-2025.32 → staticdash-2025.34}/staticdash/assets/vendor/prism/components/prism-c.min.js +0 -0
- {staticdash-2025.32 → staticdash-2025.34}/staticdash/assets/vendor/prism/components/prism-javascript.min.js +0 -0
- {staticdash-2025.32 → staticdash-2025.34}/staticdash/assets/vendor/prism/components/prism-json.min.js +0 -0
- {staticdash-2025.32 → staticdash-2025.34}/staticdash/assets/vendor/prism/components/prism-markup.min.js +0 -0
- {staticdash-2025.32 → staticdash-2025.34}/staticdash/assets/vendor/prism/components/prism-python.min.js +0 -0
- {staticdash-2025.32 → staticdash-2025.34}/staticdash/assets/vendor/prism/components/prism-sql.min.js +0 -0
- {staticdash-2025.32 → staticdash-2025.34}/staticdash/assets/vendor/prism/prism-tomorrow.min.css +0 -0
- {staticdash-2025.32 → staticdash-2025.34}/staticdash/assets/vendor/prism/prism.min.js +0 -0
- {staticdash-2025.32 → staticdash-2025.34}/staticdash.egg-info/SOURCES.txt +0 -0
- {staticdash-2025.32 → staticdash-2025.34}/staticdash.egg-info/dependency_links.txt +0 -0
- {staticdash-2025.32 → staticdash-2025.34}/staticdash.egg-info/requires.txt +0 -0
- {staticdash-2025.32 → staticdash-2025.34}/staticdash.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "staticdash"
|
|
7
|
-
version = "2025.
|
|
7
|
+
version = "2025.34"
|
|
8
8
|
description = "A lightweight static HTML dashboard generator with Plotly and pandas support."
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "Brian Day", email = "brian.day1@gmail.com" }
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Import everything you want to expose at the package level
|
|
2
|
-
from .dashboard import Dashboard, MiniPage, Page
|
|
2
|
+
from .dashboard import Dashboard, MiniPage, Page, Directory
|
|
3
3
|
|
|
4
4
|
# Optionally, define __all__ to control what gets imported with `from staticdash import *`
|
|
5
|
-
__all__ = ["Dashboard", "MiniPage", "Page"]
|
|
5
|
+
__all__ = ["Dashboard", "MiniPage", "Page", "Directory"]
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import shutil
|
|
3
3
|
import uuid
|
|
4
|
+
import re
|
|
4
5
|
import pandas as pd
|
|
5
6
|
import plotly.graph_objects as go
|
|
6
7
|
from dominate import document
|
|
@@ -314,20 +315,31 @@ class Dashboard:
|
|
|
314
315
|
head.add(link(rel="stylesheet", href=f"{rel_prefix}assets/css/style.css"))
|
|
315
316
|
head.add(script(type="text/javascript", src=f"{rel_prefix}assets/js/script.js"))
|
|
316
317
|
|
|
317
|
-
# MathJax: config for $...$ and
|
|
318
|
+
# MathJax: config for $...$ and $$...$$
|
|
318
319
|
head.add(raw_util(
|
|
319
320
|
"<script>window.MathJax={tex:{inlineMath:[['$','$'],['\\\\(','\\\\)']],displayMath:[['$$','$$'],['\\\\[','\\\\]']]}};</script>"
|
|
320
321
|
))
|
|
321
|
-
|
|
322
|
+
# Local-first, CDN-fallback (for editable installs without vendored files)
|
|
323
|
+
head.add(raw_util(
|
|
324
|
+
f"<script src=\"{rel_prefix}assets/vendor/mathjax/tex-svg.js\" "
|
|
325
|
+
"onerror=\"var s=document.createElement('script');"
|
|
326
|
+
"s.src='https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js';"
|
|
327
|
+
"document.head.appendChild(s);\"></script>"
|
|
328
|
+
))
|
|
322
329
|
|
|
323
|
-
# Prism (theme + core + languages) —
|
|
330
|
+
# Prism (theme + core + languages) — still local
|
|
324
331
|
head.add(link(rel="stylesheet", href=f"{rel_prefix}assets/vendor/prism/prism-tomorrow.min.css"))
|
|
325
332
|
head.add(script(src=f"{rel_prefix}assets/vendor/prism/prism.min.js"))
|
|
326
333
|
head.add(script(src=f"{rel_prefix}assets/vendor/prism/components/prism-python.min.js"))
|
|
327
334
|
head.add(script(src=f"{rel_prefix}assets/vendor/prism/components/prism-javascript.min.js"))
|
|
328
335
|
|
|
329
|
-
# Plotly local
|
|
330
|
-
head.add(
|
|
336
|
+
# Plotly local-first, CDN-fallback
|
|
337
|
+
head.add(raw_util(
|
|
338
|
+
f"<script src=\"{rel_prefix}assets/vendor/plotly/plotly.min.js\" "
|
|
339
|
+
"onerror=\"var s=document.createElement('script');"
|
|
340
|
+
"s.src='https://cdn.plot.ly/plotly-2.32.0.min.js';"
|
|
341
|
+
"document.head.appendChild(s);\"></script>"
|
|
342
|
+
))
|
|
331
343
|
|
|
332
344
|
# Defaults that match your CSS; override in CSS if they change
|
|
333
345
|
head.add(raw_util("<style>:root{--sidebar-width:240px;--content-padding-x:20px;}</style>"))
|
|
@@ -390,3 +402,224 @@ class Dashboard:
|
|
|
390
402
|
|
|
391
403
|
with open(os.path.join(output_dir, "index.html"), "w", encoding="utf-8") as f:
|
|
392
404
|
f.write(str(index_doc))
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
class Directory:
|
|
408
|
+
"""
|
|
409
|
+
A Directory aggregates multiple Dashboard instances and publishes them
|
|
410
|
+
as a landing page listing multiple dashboards. Each dashboard is published
|
|
411
|
+
into its own subfolder under the output directory.
|
|
412
|
+
"""
|
|
413
|
+
|
|
414
|
+
def __init__(self, title="Dashboard Directory", page_width=900):
|
|
415
|
+
"""
|
|
416
|
+
Initialize a Directory.
|
|
417
|
+
|
|
418
|
+
Args:
|
|
419
|
+
title (str): The title of the directory landing page
|
|
420
|
+
page_width (int): The default page width for the landing page
|
|
421
|
+
"""
|
|
422
|
+
self.title = title
|
|
423
|
+
self.page_width = page_width
|
|
424
|
+
self.dashboards = [] # List of (slug, dashboard) tuples
|
|
425
|
+
|
|
426
|
+
def add_dashboard(self, dashboard, slug=None):
|
|
427
|
+
"""
|
|
428
|
+
Add a Dashboard instance to the directory.
|
|
429
|
+
|
|
430
|
+
Args:
|
|
431
|
+
dashboard (Dashboard): The Dashboard instance to add
|
|
432
|
+
slug (str, optional): URL-friendly identifier for the dashboard.
|
|
433
|
+
If None, derived from dashboard title.
|
|
434
|
+
"""
|
|
435
|
+
if slug is None:
|
|
436
|
+
# Generate slug from dashboard title
|
|
437
|
+
slug = dashboard.title.lower().replace(" ", "-")
|
|
438
|
+
# Remove special characters
|
|
439
|
+
slug = "".join(c for c in slug if c.isalnum() or c == "-")
|
|
440
|
+
# Clean up multiple consecutive hyphens
|
|
441
|
+
slug = re.sub(r'-+', '-', slug)
|
|
442
|
+
# Remove leading/trailing hyphens
|
|
443
|
+
slug = slug.strip("-")
|
|
444
|
+
|
|
445
|
+
self.dashboards.append((slug, dashboard))
|
|
446
|
+
|
|
447
|
+
def publish(self, output_dir="output"):
|
|
448
|
+
"""
|
|
449
|
+
Publish the directory landing page and all dashboards.
|
|
450
|
+
|
|
451
|
+
Creates a landing page (index.html) that links to each dashboard,
|
|
452
|
+
and publishes each dashboard into its own subfolder.
|
|
453
|
+
|
|
454
|
+
Args:
|
|
455
|
+
output_dir (str): The output directory path
|
|
456
|
+
"""
|
|
457
|
+
output_dir = os.path.abspath(output_dir)
|
|
458
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
459
|
+
|
|
460
|
+
# Copy assets to the root output directory
|
|
461
|
+
assets_src = os.path.join(os.path.dirname(__file__), "assets")
|
|
462
|
+
assets_dst = os.path.join(output_dir, "assets")
|
|
463
|
+
shutil.copytree(assets_src, assets_dst, dirs_exist_ok=True)
|
|
464
|
+
|
|
465
|
+
# Publish each dashboard to its own subfolder
|
|
466
|
+
for slug, dashboard in self.dashboards:
|
|
467
|
+
dashboard_dir = os.path.join(output_dir, slug)
|
|
468
|
+
dashboard.publish(output_dir=dashboard_dir)
|
|
469
|
+
|
|
470
|
+
# Add a "Back to Directory" link to each dashboard's index page
|
|
471
|
+
self._add_back_link(dashboard_dir, slug)
|
|
472
|
+
|
|
473
|
+
# Create the landing page
|
|
474
|
+
self._create_landing_page(output_dir)
|
|
475
|
+
|
|
476
|
+
def _add_back_link(self, dashboard_dir, slug):
|
|
477
|
+
"""
|
|
478
|
+
Add a navigation link back to the directory landing page in the dashboard.
|
|
479
|
+
|
|
480
|
+
Args:
|
|
481
|
+
dashboard_dir (str): Path to the dashboard output directory
|
|
482
|
+
slug (str): The slug of the dashboard
|
|
483
|
+
"""
|
|
484
|
+
index_path = os.path.join(dashboard_dir, "index.html")
|
|
485
|
+
if not os.path.exists(index_path):
|
|
486
|
+
return
|
|
487
|
+
|
|
488
|
+
# Read the existing index.html
|
|
489
|
+
with open(index_path, "r", encoding="utf-8") as f:
|
|
490
|
+
content = f.read()
|
|
491
|
+
|
|
492
|
+
# Add a back link in the sidebar footer
|
|
493
|
+
# Replace the sidebar-footer section with one that includes a back link
|
|
494
|
+
back_link = '<div id="sidebar-footer"><a href="../index.html">← Back to Directory</a></div>'
|
|
495
|
+
|
|
496
|
+
# Find and replace the sidebar-footer
|
|
497
|
+
pattern = r'<div id="sidebar-footer">.*?</div>'
|
|
498
|
+
content = re.sub(pattern, back_link, content, flags=re.DOTALL)
|
|
499
|
+
|
|
500
|
+
# Write back the modified content
|
|
501
|
+
with open(index_path, "w", encoding="utf-8") as f:
|
|
502
|
+
f.write(content)
|
|
503
|
+
|
|
504
|
+
# Also update all page HTML files to have the back link
|
|
505
|
+
pages_dir = os.path.join(dashboard_dir, "pages")
|
|
506
|
+
if os.path.exists(pages_dir):
|
|
507
|
+
for page_file in os.listdir(pages_dir):
|
|
508
|
+
if page_file.endswith(".html"):
|
|
509
|
+
page_path = os.path.join(pages_dir, page_file)
|
|
510
|
+
with open(page_path, "r", encoding="utf-8") as f:
|
|
511
|
+
page_content = f.read()
|
|
512
|
+
|
|
513
|
+
# For pages, the back link needs to go up two levels
|
|
514
|
+
back_link_pages = '<div id="sidebar-footer"><a href="../../index.html">← Back to Directory</a></div>'
|
|
515
|
+
page_content = re.sub(pattern, back_link_pages, page_content, flags=re.DOTALL)
|
|
516
|
+
|
|
517
|
+
with open(page_path, "w", encoding="utf-8") as f:
|
|
518
|
+
f.write(page_content)
|
|
519
|
+
|
|
520
|
+
def _create_landing_page(self, output_dir):
|
|
521
|
+
"""
|
|
522
|
+
Create the landing page HTML that lists all dashboards.
|
|
523
|
+
|
|
524
|
+
Args:
|
|
525
|
+
output_dir (str): Path to the output directory
|
|
526
|
+
"""
|
|
527
|
+
doc = document(title=self.title)
|
|
528
|
+
|
|
529
|
+
# Add CSS and basic styling
|
|
530
|
+
with doc.head:
|
|
531
|
+
link(rel="stylesheet", href="assets/css/style.css")
|
|
532
|
+
raw_util("""
|
|
533
|
+
<style>
|
|
534
|
+
body {
|
|
535
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
536
|
+
margin: 0;
|
|
537
|
+
padding: 0;
|
|
538
|
+
background-color: #f5f5f5;
|
|
539
|
+
}
|
|
540
|
+
.directory-container {
|
|
541
|
+
max-width: """ + str(self.page_width) + """px;
|
|
542
|
+
margin: 0 auto;
|
|
543
|
+
padding: 40px 20px;
|
|
544
|
+
}
|
|
545
|
+
.directory-header {
|
|
546
|
+
text-align: center;
|
|
547
|
+
margin-bottom: 50px;
|
|
548
|
+
}
|
|
549
|
+
.directory-header h1 {
|
|
550
|
+
font-size: 2.5em;
|
|
551
|
+
margin-bottom: 10px;
|
|
552
|
+
color: #333;
|
|
553
|
+
}
|
|
554
|
+
.directory-header p {
|
|
555
|
+
font-size: 1.2em;
|
|
556
|
+
color: #666;
|
|
557
|
+
}
|
|
558
|
+
.dashboard-grid {
|
|
559
|
+
display: grid;
|
|
560
|
+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
561
|
+
gap: 30px;
|
|
562
|
+
margin-top: 30px;
|
|
563
|
+
}
|
|
564
|
+
.dashboard-card {
|
|
565
|
+
background: white;
|
|
566
|
+
border-radius: 8px;
|
|
567
|
+
padding: 30px;
|
|
568
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
569
|
+
transition: transform 0.2s, box-shadow 0.2s;
|
|
570
|
+
text-decoration: none;
|
|
571
|
+
color: inherit;
|
|
572
|
+
display: block;
|
|
573
|
+
}
|
|
574
|
+
.dashboard-card:hover {
|
|
575
|
+
transform: translateY(-4px);
|
|
576
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
|
577
|
+
}
|
|
578
|
+
.dashboard-card h2 {
|
|
579
|
+
margin: 0 0 10px 0;
|
|
580
|
+
font-size: 1.5em;
|
|
581
|
+
color: #2c3e50;
|
|
582
|
+
}
|
|
583
|
+
.dashboard-card p {
|
|
584
|
+
margin: 0;
|
|
585
|
+
color: #7f8c8d;
|
|
586
|
+
font-size: 0.95em;
|
|
587
|
+
}
|
|
588
|
+
.dashboard-arrow {
|
|
589
|
+
display: inline-block;
|
|
590
|
+
margin-left: 5px;
|
|
591
|
+
transition: transform 0.2s;
|
|
592
|
+
}
|
|
593
|
+
.dashboard-card:hover .dashboard-arrow {
|
|
594
|
+
transform: translateX(5px);
|
|
595
|
+
}
|
|
596
|
+
.footer {
|
|
597
|
+
text-align: center;
|
|
598
|
+
margin-top: 60px;
|
|
599
|
+
padding: 20px;
|
|
600
|
+
color: #999;
|
|
601
|
+
font-size: 0.9em;
|
|
602
|
+
}
|
|
603
|
+
</style>
|
|
604
|
+
""")
|
|
605
|
+
|
|
606
|
+
with doc:
|
|
607
|
+
with div(cls="directory-container"):
|
|
608
|
+
with div(cls="directory-header"):
|
|
609
|
+
h1(self.title)
|
|
610
|
+
p(f"Explore {len(self.dashboards)} dashboard{'s' if len(self.dashboards) != 1 else ''}")
|
|
611
|
+
|
|
612
|
+
with div(cls="dashboard-grid"):
|
|
613
|
+
for slug, dashboard in self.dashboards:
|
|
614
|
+
with a(href=f"{slug}/index.html", cls="dashboard-card"):
|
|
615
|
+
h2(dashboard.title)
|
|
616
|
+
num_pages = len(dashboard.pages)
|
|
617
|
+
p(f"{num_pages} page{'s' if num_pages != 1 else ''} ")
|
|
618
|
+
span("→", cls="dashboard-arrow")
|
|
619
|
+
|
|
620
|
+
with div(cls="footer"):
|
|
621
|
+
p("Produced by staticdash")
|
|
622
|
+
|
|
623
|
+
# Write the landing page
|
|
624
|
+
with open(os.path.join(output_dir, "index.html"), "w", encoding="utf-8") as f:
|
|
625
|
+
f.write(str(doc))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{staticdash-2025.32 → staticdash-2025.34}/staticdash/assets/vendor/prism/components/prism-c.min.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{staticdash-2025.32 → staticdash-2025.34}/staticdash/assets/vendor/prism/components/prism-sql.min.js
RENAMED
|
File without changes
|
{staticdash-2025.32 → staticdash-2025.34}/staticdash/assets/vendor/prism/prism-tomorrow.min.css
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|