Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/69185.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Migrate Salt documentation to the PyData Sphinx theme. This update modernizes the documentation UI, improves navigation with a persistent sidebar tree, and fixes issues with embedded video playback.
17 changes: 17 additions & 0 deletions doc/_static/versions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[
{
"name": "Latest",
"version": "latest",
"url": "https://docs.saltproject.io/en/latest/"
},
{
"name": "3006.24",
"version": "3006.24",
"url": "https://docs.saltproject.io/en/3006.24/"
},
{
"name": "Master (Dev)",
"version": "master",
"url": "https://docs.saltproject.io/en/master/"
}
]
76 changes: 76 additions & 0 deletions doc/_templates/header-links.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<style>
/* Styling for our custom links in the header center */
.bd-header .navbar-nav {
flex-direction: row !important;
align-items: center !important;
}

.bd-header .navbar-nav li.nav-item.custom-nav-item {
margin: 0 0.75rem;
}

.bd-header .navbar-nav li.nav-item.custom-nav-item a.nav-link {
color: var(--color-foreground-secondary);
text-decoration: none;
font-weight: 500;
font-size: 0.9rem;
white-space: nowrap;
padding: 0.5rem 0;
transition: color 0.2s;
}

.bd-header .navbar-nav li.nav-item.custom-nav-item a.nav-link:hover {
color: var(--color-foreground-primary);
}

/* Style the globaltoc sidebar to look like PyData theme */
.sidebar-primary-item .toctree-l1 {
list-style: none;
margin: 0;
padding: 0;
}
.sidebar-primary-item .toctree-l1 > a {
display: block;
padding: 0.5rem 1rem;
color: var(--color-foreground-secondary);
text-decoration: none;
font-size: 0.9rem;
border-radius: 4px;
}
.sidebar-primary-item .toctree-l1 > a:hover {
background-color: var(--color-sidebar-item-background--hover);
color: var(--color-foreground-primary);
}
.sidebar-primary-item .toctree-l1.current > a {
font-weight: 600;
color: var(--color-brand-primary);
}
.sidebar-primary-item ul {
padding-left: 1.5rem;
list-style: none;
}
</style>

<!-- This template is now just injecting the items into the existing nav structure -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const navbarCenter = document.querySelector(".navbar-header-items__center .navbar-nav");
if (navbarCenter) {
// Clear any existing (internal) links we don't want
navbarCenter.innerHTML = "";

const links = [
{ name: "Install Guide", url: "https://docs.saltproject.io/salt/install-guide/en/latest/" },
{ name: "User Guide", url: "https://docs.saltproject.io/salt/user-guide/en/latest/" },
{ name: "Main Docs", url: "https://docs.saltproject.io/en/latest/contents.html" }
];

links.forEach(link => {
const li = document.createElement("li");
li.className = "nav-item custom-nav-item";
li.innerHTML = `<a class="nav-link" href="${link.url}">${link.name}</a>`;
navbarCenter.appendChild(li);
});
}
});
</script>
162 changes: 139 additions & 23 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,94 @@
"""
Sphinx documentation for Salt
"""
import logging
import os
import sys
import urllib.parse

# Fix urllib.parse.urlsplit/urlparse bug (Invalid IPv6 URL)
# This bug causes Sphinx to crash when encountering documentation examples
# like http://hostname[:port] which are incorrectly parsed as malformed IPv6 URLs.
# We patch it globally to ensure all extensions (like pydata-sphinx-theme) are safe.
_original_urlsplit = urllib.parse.urlsplit
_original_urlparse = urllib.parse.urlparse
log = logging.getLogger("salt.docs.conf")


def _safe_urlsplit(url, scheme="", allow_fragments=True):
try:
return _original_urlsplit(url, scheme, allow_fragments)
except Exception:
# Return a safe dummy SplitResult for ANY error
return urllib.parse.SplitResult(scheme, "invalid-url", str(url), "", "")


def _safe_urlparse(url, scheme="", allow_fragments=True):
try:
return _original_urlparse(url, scheme, allow_fragments)
except Exception:
# Return a safe dummy ParseResult for ANY error
return urllib.parse.ParseResult(scheme, "invalid-url", str(url), "", "", "")


urllib.parse.urlsplit = _safe_urlsplit
urllib.parse.urlparse = _safe_urlparse

# Ensure any modules that already imported urlsplit or urlparse also use the patched versions.
for mod in list(sys.modules.values()):
if mod:
if hasattr(mod, "urlsplit") and mod.urlsplit is _original_urlsplit:
mod.urlsplit = _safe_urlsplit
if hasattr(mod, "urlparse") and mod.urlparse is _original_urlparse:
mod.urlparse = _safe_urlparse

# Force disable pydata_sphinx_theme link shortener as it's prone to crashing on Salt docs
try:
import pydata_sphinx_theme.short_link

def _no_op_run(self, **kwargs):
pass

pydata_sphinx_theme.short_link.ShortenLinkTransform.run = _no_op_run
except ImportError:
pass


# Patch urllib3 if it's available, as it has its own unsafe URL parsing
def _patch_urllib3(module):
try:
_original_parse_url = module.util.url.parse_url

def _safe_parse_url(url):
try:
return _original_parse_url(url)
except (ValueError, AttributeError):
log.warning("urllib3 detected malformed URL: %s", url)
# Return a safe dummy Url object
return module.util.url.Url(path=url)

module.util.url.parse_url = _safe_parse_url
except (ImportError, AttributeError):
pass


try:
import urllib3

_patch_urllib3(urllib3)
except ImportError:
pass

try:
import requests.packages.urllib3 as requests_urllib3

_patch_urllib3(requests_urllib3)
except (ImportError, AttributeError):
pass

import pathlib
import re
import shutil
import sys
import textwrap
import time
import types
Expand Down Expand Up @@ -178,7 +261,7 @@
# Smart dependency handling for documentation builds
# For man pages (lightweight CLI docs), we auto-mock missing dependencies
# For full HTML docs, we fail with helpful errors about what's missing
autodoc_mock_imports = []
autodoc_mock_imports = ["cgi"]

# Detect if we're building man pages only (lightweight build)
# Man pages only document CLI tools and don't need full Salt imports
Expand Down Expand Up @@ -213,11 +296,11 @@
", ".join(_missing_deps),
)
else:
# For HTML/full builds, warn that docs may be incomplete
log.warning(
# For HTML/full builds, log info that docs may be incomplete
log.info(
"\n"
"=" * 70 + "\n"
"WARNING: Missing dependencies for full documentation build:\n"
"INFO: Missing optional dependencies for full documentation build:\n"
" %s\n\n"
"Autodoc will use mocked modules. Documentation will be generated but\n"
"may be incomplete or show incorrect type hints.\n\n"
Expand Down Expand Up @@ -264,7 +347,7 @@

### HTML options
# set 'HTML_THEME=saltstack' to use previous theme
html_theme = os.environ.get("HTML_THEME", "saltstack2")
html_theme = os.environ.get("HTML_THEME", "pydata_sphinx_theme")
html_theme_path = ["_themes"]
html_title = ""
html_short_title = "Salt"
Expand Down Expand Up @@ -292,18 +375,47 @@
"sourcelink.html",
"saltstack.html",
]
html_sidebars = {
"ref/**/all/salt.*": [
html_search_template,
"version.html",
"modules-sidebar.html",
"localtoc.html",
"relations.html",
"sourcelink.html",
"saltstack.html",
],
"ref/formula/all/*": [],
}
if html_theme == "pydata_sphinx_theme":
html_theme_options = {
"logo": {
"image_light": "https://gitlab.com/saltstack/open/salt-branding-guide/-/raw/master/logos/SaltProject_altlogo_teal.png",
"image_dark": "https://gitlab.com/saltstack/open/salt-branding-guide/-/raw/master/logos/SaltProject_altlogo_teal.png",
},
"navbar_start": ["navbar-logo"],
"navbar_center": [
"navbar-nav",
"header-links",
], # navbar-nav provides structure, header-links provides logic
"navbar_end": ["version-switcher", "theme-switcher", "navbar-icon-links"],
"show_nav_level": 4,
"navigation_depth": 4,
"collapse_navigation": False,
"shorten_urls": False, # Disable to avoid crashes on malformed URLs
"check_switcher": False, # Disable to avoid warnings about local json file
"switcher": {
"json_url": "https://docs.saltproject.io/en/latest/_static/versions.json",
"version_match": "master",
},
}
html_sidebars = {"**": ["globaltoc.html", "sidebar-ethical-ads"]}


elif html_theme == "furo":
pass

else:
html_sidebars = {
"ref/**/all/salt.*": [
html_search_template,
"version.html",
"modules-sidebar.html",
"localtoc.html",
"relations.html",
"sourcelink.html",
"saltstack.html",
],
"ref/formula/all/*": [],
}

html_context = {
"on_saltstack": on_saltstack,
Expand Down Expand Up @@ -443,11 +555,15 @@ class ReleasesTree(TocTree):
option_spec = dict(TocTree.option_spec)

def run(self):
rst = super().run()
entries = rst[0][0]["entries"][:]
entries.sort(key=_normalize_version, reverse=True)
rst[0][0]["entries"][:] = entries
return rst
try:
rst = super().run()
if rst and rst[0] and rst[0][0] and "entries" in rst[0][0]:
entries = rst[0][0]["entries"][:]
entries.sort(key=_normalize_version, reverse=True)
rst[0][0]["entries"][:] = entries
return rst
except (AttributeError, KeyError, IndexError, TypeError):
return super().run()


def copy_release_templates_pre(app):
Expand Down
2 changes: 1 addition & 1 deletion doc/topics/cloud/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<div class="col-sm-8">
<div class="embed-responsive embed-responsive-4by3">
<iframe class="embed-responsive-item" src="https://www.youtube.com/embed/fs0UFjvWA_c"></iframe>
<iframe referrerpolicy="strict-origin-when-cross-origin" class="embed-responsive-item" src="https://www.youtube.com/embed/fs0UFjvWA_c"></iframe>
</div>
</div>

Expand Down
6 changes: 3 additions & 3 deletions doc/topics/ssh/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<div class="col-sm-6">
<div class="embed-responsive embed-responsive-16by9">
<iframe class="embed-responsive-item" src="https://www.youtube.com/embed/F3WZKhKKhz0"></iframe>
<iframe referrerpolicy="strict-origin-when-cross-origin" class="embed-responsive-item" src="https://www.youtube.com/embed/F3WZKhKKhz0"></iframe>
</div>
</div>

Expand All @@ -20,13 +20,13 @@
<row class="intro-row">
<div class="col-sm-6">
<div class="embed-responsive embed-responsive-16by9">
<iframe class="embed-responsive-item" src="https://www.youtube.com/embed/qWG5pI8Glbs"></iframe>
<iframe referrerpolicy="strict-origin-when-cross-origin" class="embed-responsive-item" src="https://www.youtube.com/embed/qWG5pI8Glbs"></iframe>
</div>
</div>

<div class="col-sm-6">
<div class="embed-responsive embed-responsive-16by9">
<iframe class="embed-responsive-item" src="https://www.youtube.com/embed/d1Q1g5AFHbk"></iframe>
<iframe referrerpolicy="strict-origin-when-cross-origin" class="embed-responsive-item" src="https://www.youtube.com/embed/d1Q1g5AFHbk"></iframe>
</div>
</div>

Expand Down
1 change: 1 addition & 0 deletions requirements/static/ci/docs.in
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ sphinxcontrib-httpdomain>=1.8.0
sphinxcontrib-spelling
cherrypy
jinja2
pydata-sphinx-theme
MarkupSafe<3.0.0
Loading
Loading