This commit is contained in:
2026-07-22 08:09:16 -05:00
50 changed files with 4856 additions and 772 deletions
+3
View File
@@ -0,0 +1,3 @@
{
"liveServer.settings.root": "/web"
}
+2 -1
View File
@@ -10,7 +10,8 @@ Known and addressed issues for newly released versions are easily added using th
- Copy the issue table's HTML from the webpage using devtools. - Copy the issue table's HTML from the webpage using devtools.
- Fill out the process.html page's fields and paste in the table HTML. - Fill out the process.html page's fields and paste in the table HTML.
- Download the Markdown file and put it into the correct folder. - Download the Markdown file and put it into the correct folder.
- Fix up the products.json file manually or run `update_products_from_issues.py`. - Run `npm run update:generated` to update `products.json`, append new stable
product-tree manifest slots, and regenerate rendered test fixtures.
- Submit a pull request. - Submit a pull request.
There is intentionally no automated scaping of Palo Alto's website, to avoid abuse of server resources. Also releases are not that frequent. A crawler to grab some data from the Common Crawl dataset was started but never really finished. There is intentionally no automated scaping of Palo Alto's website, to avoid abuse of server resources. Also releases are not that frequent. A crawler to grab some data from the Common Crawl dataset was started but never really finished.
+3
View File
@@ -4,6 +4,9 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"test": "node --test", "test": "node --test",
"update:products": "node scripts/update-products-from-issues.mjs",
"update:fixtures": "node scripts/generate-rendered-fixtures.mjs",
"update:generated": "node scripts/refresh-generated-data.mjs",
"vendor:marked": "mkdir -p web/vendor && cp node_modules/marked/lib/marked.esm.js web/vendor/marked.esm.js" "vendor:marked": "mkdir -p web/vendor && cp node_modules/marked/lib/marked.esm.js web/vendor/marked.esm.js"
}, },
"devDependencies": { "devDependencies": {
+11 -4
View File
@@ -1,11 +1,11 @@
/** /**
* Generates expected-parsed.json for each fixture directory that contains expected.md. * Generates expected-parsed.json for each fixture directory that contains expected.md.
* Run manually: node test/generate-rendered-fixtures.mjs * Run manually: node scripts/generate-rendered-fixtures.mjs
*/ */
import { readFileSync, writeFileSync, readdirSync } from 'node:fs'; import { readFileSync, writeFileSync, readdirSync } from 'node:fs';
import { join } from 'node:path'; import { join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath, pathToFileURL } from 'node:url';
import { JSDOM } from 'jsdom'; import { JSDOM } from 'jsdom';
const { window } = new JSDOM('<!doctype html><html><body></body></html>'); const { window } = new JSDOM('<!doctype html><html><body></body></html>');
@@ -20,8 +20,9 @@ globalThis.fetch = () => Promise.resolve({
const { parseMarkdownIssues, markdownSummaryToHtml } = await import('../web/js/issues.js'); const { parseMarkdownIssues, markdownSummaryToHtml } = await import('../web/js/issues.js');
const __dirname = fileURLToPath(new URL('.', import.meta.url)); const __dirname = fileURLToPath(new URL('.', import.meta.url));
const fixturesDir = join(__dirname, '../test/fixtures'); const defaultFixturesDir = join(__dirname, '../test/fixtures');
export function generateRenderedFixtures(fixturesDir = defaultFixturesDir) {
const fixtures = readdirSync(fixturesDir).filter(name => { const fixtures = readdirSync(fixturesDir).filter(name => {
const contents = readdirSync(join(fixturesDir, name)).map(d => d); const contents = readdirSync(join(fixturesDir, name)).map(d => d);
return contents.includes('expected.md'); return contents.includes('expected.md');
@@ -48,3 +49,9 @@ for (const fixtureId of fixtures) {
console.log(`Written ${fixtureId}/expected-parsed.json (${result.length} issues)`); console.log(`Written ${fixtureId}/expected-parsed.json (${result.length} issues)`);
} }
return { fixtures: fixtures.length };
}
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
generateRenderedFixtures();
}
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env node
import { runUpdateProductsCli } from './update-products-from-issues.mjs';
import { generateRenderedFixtures } from './generate-rendered-fixtures.mjs';
await runUpdateProductsCli();
const fixtureResult = generateRenderedFixtures();
console.log(`Regenerated ${fixtureResult.fixtures} rendered fixture set(s).`);
+202
View File
@@ -0,0 +1,202 @@
#!/usr/bin/env node
import { readdir, readFile, writeFile } from 'node:fs/promises';
import { dirname, join, relative, resolve } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { buildProductTree, enumerateTreePaths } from '../web/js/product-tree-model.js';
const ISSUE_TYPES = new Set(['addressed', 'known']);
const VERSION_FILENAME_RE = /^(?<version>[^.]+(?:\.[^.]+)*)\.md$/;
const HOTFIX_RE = /^(?<base>\d+(?:\.\d+)*)(?:-h(?<hotfix>\d+))?$/;
const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
async function walkFiles(root) {
const files = [];
async function visit(directory) {
for (const entry of await readdir(directory, { withFileTypes: true })) {
const path = join(directory, entry.name);
if (entry.isDirectory()) await visit(path);
else if (entry.isFile()) files.push(path);
}
}
await visit(root);
return files.sort();
}
export async function collectIssueFiles(issuesDir) {
const records = [];
for (const filePath of await walkFiles(issuesDir)) {
const parts = relative(issuesDir, filePath).split(/[\\/]/);
if (parts.length !== 3) continue;
const [product, issueType, filename] = parts;
if (!ISSUE_TYPES.has(issueType)) continue;
const match = VERSION_FILENAME_RE.exec(filename);
if (!match) continue;
records.push({ product, issueType, version: match.groups.version, filename });
}
return records;
}
function clearLeafIssueArrays(node) {
if (!node || typeof node !== 'object' || Array.isArray(node)) return;
if ('addressed' in node || 'known' in node) {
node.addressed = [];
node.known = [];
return;
}
Object.values(node).forEach(clearLeafIssueArrays);
}
function getPath(data, path) {
let current = data;
for (const key of path) {
if (!current || typeof current !== 'object' || !(key in current)) return null;
current = current[key];
}
return current;
}
function isIssueLeaf(node) {
return Boolean(node) && typeof node === 'object' && ('addressed' in node || 'known' in node);
}
function pickTargetLeafPath(products, product, version) {
const parts = version.split('.');
const major = parts[0];
const minor = parts.length > 1 ? `${parts[0]}.${parts[1]}` : major;
for (const path of [[product, major, minor], [product, major], [product]]) {
if (isIssueLeaf(getPath(products, path))) return path;
}
return null;
}
function parseReleasePrefix(prefix) {
const match = HOTFIX_RE.exec(prefix);
if (!match) return { base: [], hotfix: -1 };
return {
base: match.groups.base.split('.').map(Number),
hotfix: match.groups.hotfix === undefined ? -1 : Number(match.groups.hotfix)
};
}
function compareNumberArrays(left, right) {
const length = Math.max(left.length, right.length);
for (let index = 0; index < length; index++) {
const difference = (left[index] ?? -1) - (right[index] ?? -1);
if (difference) return difference;
}
return 0;
}
function compareIssueFilenames(left, right) {
const leftPrefix = left.replace(/\.[^.]+$/, '');
const rightPrefix = right.replace(/\.[^.]+$/, '');
const leftVersion = parseReleasePrefix(leftPrefix);
const rightVersion = parseReleasePrefix(rightPrefix);
return compareNumberArrays(leftVersion.base, rightVersion.base) ||
Number(leftVersion.hotfix >= 0) - Number(rightVersion.hotfix >= 0) ||
leftVersion.hotfix - rightVersion.hotfix ||
left.localeCompare(right);
}
export function rebuildProducts(products, records) {
clearLeafIssueArrays(products);
const unmatched = [];
records.sort((left, right) =>
left.product.localeCompare(right.product) ||
left.issueType.localeCompare(right.issueType) ||
left.version.localeCompare(right.version)
);
for (const record of records) {
const path = pickTargetLeafPath(products, record.product, record.version);
if (!path) {
unmatched.push(record);
continue;
}
const leaf = getPath(products, path);
const files = Array.isArray(leaf[record.issueType]) ? leaf[record.issueType] : [];
leaf[record.issueType] = Array.from(new Set([...files, record.filename])).sort(compareIssueFilenames);
}
return { products, unmatched };
}
export function updateManifest(manifest, currentPaths) {
if (!manifest || manifest.schema !== 1 || !Array.isArray(manifest.nodes)) {
throw new Error('Product-tree manifest must use schema 1 with a nodes array.');
}
const active = new Set();
manifest.nodes.forEach((path, slot) => {
if (path === null) return;
if (typeof path !== 'string' || !path) throw new Error(`Invalid manifest entry at slot ${slot}.`);
if (active.has(path)) throw new Error(`Duplicate manifest path: ${path}`);
active.add(path);
});
const current = new Set(currentPaths);
const appended = currentPaths.filter(path => !active.has(path));
manifest.nodes.push(...appended);
const missing = Array.from(active).filter(path => !current.has(path));
return { manifest, appended, missing };
}
export async function updateProductsFromIssues({ issuesDir, productsPath, manifestPath }) {
const [productsText, manifestText, records] = await Promise.all([
readFile(productsPath, 'utf8'),
readFile(manifestPath, 'utf8'),
collectIssueFiles(issuesDir)
]);
if (records.length === 0) {
throw new Error("No '<version>.md' issue files found; refusing to rewrite products.json");
}
const products = JSON.parse(productsText);
if (!products || typeof products !== 'object' || Array.isArray(products)) {
throw new Error('products.json must contain a top-level object.');
}
const rebuilt = rebuildProducts(products, records);
const paths = enumerateTreePaths(buildProductTree(rebuilt.products));
const manifestUpdate = updateManifest(JSON.parse(manifestText), paths);
await writeFile(productsPath, `${JSON.stringify(rebuilt.products, null, 2)}\n`, 'utf8');
if (manifestUpdate.appended.length > 0) {
await writeFile(manifestPath, `${JSON.stringify(manifestUpdate.manifest, null, 2)}\n`, 'utf8');
}
return {
records: records.length,
unmatched: rebuilt.unmatched,
appended: manifestUpdate.appended,
missing: manifestUpdate.missing
};
}
function parseArgs(argv) {
const options = {
issuesDir: join(REPO_ROOT, 'web/data/issues'),
productsPath: join(REPO_ROOT, 'web/data/products.json'),
manifestPath: join(REPO_ROOT, 'web/data/product-tree-manifest.json')
};
const flags = new Map([
['--issues-dir', 'issuesDir'],
['--products-json', 'productsPath'],
['--manifest-json', 'manifestPath']
]);
for (let index = 0; index < argv.length; index += 2) {
const property = flags.get(argv[index]);
if (!property || argv[index + 1] === undefined) throw new Error(`Invalid argument: ${argv[index] ?? ''}`);
options[property] = resolve(argv[index + 1]);
}
return options;
}
export async function runUpdateProductsCli(argv = process.argv.slice(2)) {
const options = parseArgs(argv);
const result = await updateProductsFromIssues(options);
console.log(`Updated ${options.productsPath} from ${result.records} issue files.`);
console.log(`Appended ${result.appended.length} manifest path(s).`);
if (result.missing.length) console.warn(`${result.missing.length} manifest path(s) are no longer rendered; slots were preserved.`);
if (result.unmatched.length) console.warn(`${result.unmatched.length} issue file(s) did not match a products.json leaf.`);
return result;
}
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
await runUpdateProductsCli();
}
-217
View File
@@ -1,217 +0,0 @@
#!/usr/bin/env python3
"""Update products.json entries from discovered issue data files."""
from __future__ import annotations
import argparse
import json
import re
from pathlib import Path
from typing import Any
ISSUE_TYPES = {"addressed", "known"}
VERSION_FILENAME_RE = re.compile(r"^(?P<version>[^.]+(?:\.[^.]+)*)\.md$")
HOTFIX_RE = re.compile(r"^(?P<base>\d+(?:\.\d+)*)(?:-h(?P<hotfix>\d+))?$")
def parse_args() -> argparse.Namespace:
"""Parse command line arguments for input and output paths."""
parser = argparse.ArgumentParser(
description="Update products.json from issue files."
)
parser.add_argument(
"--issues-dir",
default="web/data/issues",
help="Directory containing product issue files.",
)
parser.add_argument(
"--products-json",
default="web/data/products.json",
help="Path to products.json.",
)
return parser.parse_args()
def collect_issue_files(issues_dir: Path) -> list[tuple[str, str, str, str]]:
"""Collect all issue files with '<version>.md' naming."""
records: list[tuple[str, str, str, str]] = []
for file_path in sorted(p for p in issues_dir.rglob("*") if p.is_file()):
relative = file_path.relative_to(issues_dir)
if len(relative.parts) != 3:
continue
product, issue_type, filename = relative.parts
if issue_type not in ISSUE_TYPES:
continue
version = parse_issue_filename(filename)
if version is None:
continue
records.append((product, issue_type, version, filename))
return records
def parse_issue_filename(filename: str) -> str | None:
"""Parse '<version>.md' issue filenames and return version."""
match = VERSION_FILENAME_RE.match(filename)
if not match:
return None
return match.group("version")
def clear_leaf_issue_arrays(node: Any) -> None:
"""Reset addressed/known arrays on every issue leaf in products.json."""
if isinstance(node, dict):
if "addressed" in node or "known" in node:
node["addressed"] = []
node["known"] = []
return
for value in node.values():
clear_leaf_issue_arrays(value)
def is_issue_leaf(node: Any) -> bool:
"""Return whether a node is a leaf containing issue arrays."""
return isinstance(node, dict) and ("addressed" in node or "known" in node)
def pick_target_leaf_path(products: dict[str, Any], product: str, version: str) -> list[str] | None:
"""Choose the best matching product tree path for a version."""
parts = version.split(".")
if not parts:
return None
major = parts[0]
minor = f"{parts[0]}.{parts[1]}" if len(parts) > 1 else parts[0]
candidates = [
[product, major, minor],
[product, major],
[product],
]
for path in candidates:
node = get_path(products, path)
if is_issue_leaf(node):
return path
return None
def get_path(data: Any, path: list[str]) -> Any:
"""Traverse nested dict keys and return the value at path, if present."""
current = data
for key in path:
if not isinstance(current, dict) or key not in current:
return None
current = current[key]
return current
def parse_release_prefix(prefix: str) -> tuple[tuple[int, ...], int]:
"""Extract numeric base version parts and optional hotfix number."""
match = HOTFIX_RE.match(prefix)
if not match:
return tuple(), -1
base_parts = tuple(int(part) for part in match.group("base").split("."))
hotfix_group = match.group("hotfix")
hotfix = int(hotfix_group) if hotfix_group is not None else -1
return base_parts, hotfix
def filename_sort_key(filename: str) -> tuple[tuple[int, ...], int, int, str]:
"""Build a sort key that orders base versions before hotfixes."""
prefix = filename.rsplit(".", 1)[0]
base_parts, hotfix = parse_release_prefix(prefix)
is_hotfix = 1 if hotfix >= 0 else 0
return base_parts, is_hotfix, hotfix, filename
def add_file_to_leaf(
products: dict[str, Any],
path: list[str],
issue_type: str,
filename: str,
) -> None:
"""Insert an issue filename into the target leaf with stable sorting."""
leaf = get_path(products, path)
if not isinstance(leaf, dict):
return
files = leaf.get(issue_type)
if not isinstance(files, list):
files = []
files.append(filename)
leaf[issue_type] = sorted(set(files), key=filename_sort_key)
def update_products(issues_dir: Path, products_path: Path) -> None:
"""Rebuild product issue file lists from crawled issue files."""
if not issues_dir.is_dir():
raise FileNotFoundError(f"Issues directory not found: {issues_dir}")
if not products_path.is_file():
raise FileNotFoundError(f"products.json not found: {products_path}")
with products_path.open("r", encoding="utf-8") as f:
products = json.load(f)
if not isinstance(products, dict):
raise ValueError("products.json must contain a top-level object")
records = collect_issue_files(issues_dir)
if not records:
raise ValueError(
"No '<version>.md' issue files found; refusing to rewrite products.json"
)
clear_leaf_issue_arrays(products)
for product, issue_type, version, filename in sorted(
records,
key=lambda r: (r[0], r[1], r[2]),
):
path = pick_target_leaf_path(products, product, version)
if path is None:
continue
add_file_to_leaf(products, path, issue_type, filename)
with products_path.open("w", encoding="utf-8") as f:
json.dump(products, f, indent=2)
f.write("\n")
def main() -> int:
"""Entrypoint for command-line usage."""
args = parse_args()
script_dir = Path(__file__).resolve().parent
repo_root = script_dir.parent
issues_dir = (repo_root / args.issues_dir).resolve()
products_path = (repo_root / args.products_json).resolve()
update_products(issues_dir=issues_dir, products_path=products_path)
print(f"Updated {products_path} from {issues_dir}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+105
View File
@@ -0,0 +1,105 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { JSDOM } from 'jsdom';
const dom = new JSDOM('<!doctype html><html><body></body></html>', { url: 'https://example.test/' });
globalThis.window = dom.window;
globalThis.document = dom.window.document;
globalThis.Element = dom.window.Element;
globalThis.localStorage = dom.window.localStorage;
const { CheckboxTree } = await import('../web/vendor/checkbox-tree/checkbox-tree.js');
function createTree(options = {}) {
const container = document.createElement('div');
document.body.replaceChildren(container);
const tree = new CheckboxTree(container, options);
tree.setData([
{
id: 'parent',
label: 'Parent',
metadata: { kind: 'parent' },
children: [
{ id: 'first', label: 'First', metadata: { kind: 'leaf' } },
{ id: 'second', label: 'Second', metadata: { kind: 'leaf' } }
]
}
]);
return { container, tree };
}
function checkbox(container, id) {
return container.querySelector(`.checkbox-tree__checkbox[data-node-id="${id}"]`);
}
test('selecting a parent selects all descendants and emits selected nodes', () => {
let change;
const { container, tree } = createTree({ onSelectionChange: event => { change = event; } });
const parent = checkbox(container, 'parent');
parent.checked = true;
parent.dispatchEvent(new window.Event('change', { bubbles: true }));
assert.deepEqual(tree.getSelectedIds(), ['parent', 'first', 'second']);
assert.deepEqual(change.selectedIds, ['parent', 'first', 'second']);
assert.equal(change.selectedNodes[1].metadata.kind, 'leaf');
});
test('partial child selection makes the parent indeterminate', () => {
const { container, tree } = createTree();
const first = checkbox(container, 'first');
first.checked = true;
first.dispatchEvent(new window.Event('change', { bubbles: true }));
const parent = checkbox(container, 'parent');
assert.equal(parent.checked, false);
assert.equal(parent.indeterminate, true);
assert.deepEqual(tree.getSelectedIds(), ['first']);
});
test('can initially select every selectable node', () => {
const { tree } = createTree({ initiallySelected: true });
assert.deepEqual(tree.getSelectedIds(), ['parent', 'first', 'second']);
});
test('persists selection and collapsed branches per configured key', () => {
localStorage.clear();
const first = createTree({ storageKey: 'example-tree' });
checkbox(first.container, 'first').click();
const toggle = first.container.querySelector('.checkbox-tree__toggle');
toggle.click();
assert.equal(toggle.getAttribute('aria-expanded'), 'true');
const second = createTree({ storageKey: 'example-tree' });
assert.deepEqual(second.tree.getSelectedIds(), ['first']);
assert.equal(second.container.querySelector('.checkbox-tree__toggle').getAttribute('aria-expanded'), 'true');
});
test('supports independent instances and removes listeners on destroy', () => {
const firstContainer = document.createElement('div');
const secondContainer = document.createElement('div');
document.body.replaceChildren(firstContainer, secondContainer);
const first = new CheckboxTree(firstContainer);
const second = new CheckboxTree(secondContainer);
const data = [{ id: 'node', label: 'Node' }];
first.setData(data);
second.setData(data);
checkbox(firstContainer, 'node').click();
assert.deepEqual(first.getSelectedIds(), ['node']);
assert.deepEqual(second.getSelectedIds(), []);
first.destroy();
assert.equal(firstContainer.childElementCount, 0);
assert.equal(firstContainer.classList.contains('checkbox-tree'), false);
});
test('rejects duplicate node ids', () => {
const container = document.createElement('div');
const tree = new CheckboxTree(container);
assert.throws(
() => tree.setData([{ id: 'same', label: 'One' }, { id: 'same', label: 'Two' }]),
/Duplicate CheckboxTree node id/
);
});
@@ -0,0 +1,30 @@
[
{
"id": "PAN-242777",
"summary": "Fixed an issue where users previously reported limitations due to session count caps when utilizing **Web Proxy** features on PA-5400 Series Firewalls. To address these performance complaints and support higher traffic volumes, we have increased the maximum session capacity on specific **PA-5400F** series platforms, leveraging available system memory. This update ensures greater capacity and stability for high-volume environments.The supported session limits are:| Platform | Max Sessions |\n| -------- | ------------ |\n| PA-5410 | 95K |\n| PA-5420 | 95K |\n| PA-5430 | 95K |\n| PA-5440 | 225K |\n| PA-5445 | 250K |\n| PA-5450 | 1.28M |",
"resolved": "",
"caveat": "",
"renderedHtml": "<p>Fixed an issue where users previously reported limitations due to session count caps when utilizing <strong>Web Proxy</strong> features on PA-5400 Series Firewalls. To address these performance complaints and support higher traffic volumes, we have increased the maximum session capacity on specific <strong>PA-5400F</strong> series platforms, leveraging available system memory. This update ensures greater capacity and stability for high-volume environments.The supported session limits are:| Platform | Max Sessions |\n| -------- | ------------ |\n| PA-5410 | 95K |\n| PA-5420 | 95K |\n| PA-5430 | 95K |\n| PA-5440 | 225K |\n| PA-5445 | 250K |\n| PA-5450 | 1.28M |</p>\n"
},
{
"id": "PAN-291499",
"summary": "Fixed an issue where newly deployed firewalls were unable to connect to the Strata Logging Service (SLS) until after a reboot, license fetch, or management server restart.",
"resolved": "",
"caveat": "VM-Series firewalls on Amazon Web Services (AWS) environments only",
"renderedHtml": "<div><em>Caveat: VM-Series firewalls on Amazon Web Services (AWS) environments only</em></div><p>Fixed an issue where newly deployed firewalls were unable to connect to the Strata Logging Service (SLS) until after a reboot, license fetch, or management server restart.</p>\n"
},
{
"id": "PAN-288726",
"summary": "Fixed an issue where the useridd process stopped responding due to a Security policy rule ID being set to 0, which caused the last configuration retrieval to fail.",
"resolved": "",
"caveat": "",
"renderedHtml": "<p>Fixed an issue where the useridd process stopped responding due to a Security policy rule ID being set to 0, which caused the last configuration retrieval to fail.</p>\n"
},
{
"id": "PAN-287133",
"summary": "Fixed an issue on the Panorama web interface where assigning a policy rule to a group at the top or bottom of the list changed the order of other policy rules.",
"resolved": "",
"caveat": "",
"renderedHtml": "<p>Fixed an issue on the Panorama web interface where assigning a policy rule to a group at the top or bottom of the list changed the order of other policy rules.</p>\n"
}
]
+70
View File
@@ -0,0 +1,70 @@
import assert from 'node:assert/strict';
import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import test from 'node:test';
import { buildProductTree, enumerateTreePaths } from '../web/js/product-tree-model.js';
import { updateProductsFromIssues } from '../scripts/update-products-from-issues.mjs';
test('product updater rebuilds products and append-only manifest idempotently', async t => {
const root = await mkdtemp(join(tmpdir(), 'firewallissues-update-'));
t.after(() => rm(root, { recursive: true, force: true }));
const issuesDir = join(root, 'issues');
const productsPath = join(root, 'products.json');
const manifestPath = join(root, 'manifest.json');
await mkdir(join(issuesDir, 'Product', 'addressed'), { recursive: true });
await mkdir(join(issuesDir, 'Product', 'known'), { recursive: true });
await writeFile(join(issuesDir, 'Product', 'addressed', '1.2.3-h2.md'), 'addressed');
await writeFile(join(issuesDir, 'Product', 'addressed', '1.2.3.md'), 'addressed');
await writeFile(join(issuesDir, 'Product', 'known', '1.2.4.md'), 'known');
const initialProducts = {
Product: {
1: {
'1.2': {
addressed: ['obsolete.md'],
known: []
}
}
}
};
const rootPath = JSON.stringify(['Product']);
await writeFile(productsPath, `${JSON.stringify(initialProducts, null, 2)}\n`);
await writeFile(manifestPath, `${JSON.stringify({ schema: 1, nodes: [rootPath, 'legacy-path', null] }, null, 2)}\n`);
const first = await updateProductsFromIssues({ issuesDir, productsPath, manifestPath });
assert.equal(first.records, 3);
assert.equal(first.unmatched.length, 0);
assert.ok(first.appended.length > 0);
assert.deepEqual(first.missing, ['legacy-path']);
const products = JSON.parse(await readFile(productsPath, 'utf8'));
assert.deepEqual(products.Product[1]['1.2'].addressed, ['1.2.3.md', '1.2.3-h2.md']);
assert.deepEqual(products.Product[1]['1.2'].known, ['1.2.4.md']);
const manifest = JSON.parse(await readFile(manifestPath, 'utf8'));
assert.deepEqual(manifest.nodes.slice(0, 3), [rootPath, 'legacy-path', null]);
const renderedPaths = enumerateTreePaths(buildProductTree(products));
assert.ok(renderedPaths.every(path => manifest.nodes.includes(path)));
const second = await updateProductsFromIssues({ issuesDir, productsPath, manifestPath });
assert.deepEqual(second.appended, []);
assert.deepEqual(JSON.parse(await readFile(manifestPath, 'utf8')), manifest);
});
test('product updater refuses to rewrite when no issue files are found', async t => {
const root = await mkdtemp(join(tmpdir(), 'firewallissues-empty-update-'));
t.after(() => rm(root, { recursive: true, force: true }));
const issuesDir = join(root, 'issues');
const productsPath = join(root, 'products.json');
const manifestPath = join(root, 'manifest.json');
await mkdir(issuesDir);
await writeFile(productsPath, '{}\n');
await writeFile(manifestPath, '{"schema":1,"nodes":[]}\n');
await assert.rejects(
updateProductsFromIssues({ issuesDir, productsPath, manifestPath }),
/No '<version>\.md' issue files found/
);
assert.equal(await readFile(productsPath, 'utf8'), '{}\n');
});
@@ -0,0 +1,13 @@
---
type: Addressed
product: PAN-OS
version: 10.2.10-h37
---
## BLANK-000000
Fixes were made to address [CVE-2026-0273](https://security-stg.paloaltonetworks.com/CVE-2026-0273) and [CVE-2026-0272](https://security-stg.paloaltonetworks.com/CVE-2026-0272).
## PAN-325786
Fixed an issue where content updates with active traffic failed.
@@ -0,0 +1,40 @@
---
type: Addressed
product: PAN-OS
version: 10.2.10-h39
---
## BLANK-000000
Fixes were made to address the following CVEs:
- [CVE-2026-0283](https://security.paloaltonetworks.com/CVE-2026-0283)
- [CVE-2026-0287](https://security.paloaltonetworks.com/CVE-2026-0287)
- [CVE-2026-0279](https://security.paloaltonetworks.com/CVE-2026-0279)
- [CVE-2026-0282](https://security.paloaltonetworks.com/CVE-2026-0282)
- [CVE-2026-0288](https://security.paloaltonetworks.com/CVE-2026-0288)
- [CVE-2026-0286](https://security.paloaltonetworks.com/CVE-2026-0286)
- [CVE-2026-0285](https://security.paloaltonetworks.com/CVE-2026-0285)
- [CVE-2026-0280](https://security.paloaltonetworks.com/CVE-2026-0280)
- [CVE-2026-0284](https://security.paloaltonetworks.com/CVE-2026-0284)
- [CVE-2026-0281](https://security.paloaltonetworks.com/CVE-2026-0281)
## PAN-314630
Fixed an issue where the firewall repeatedly rebooted and entered maintenance mode, and a factory reset was required.
## PAN-300055
Fixed an issue where the firewall experienced high disk utilization in the /opt/pancfg/mgmt/content-preview directory due to older content data not being automatically removed when an error occurred during the process.
## PAN-298788
Fixed an issue where the /pancfg partition on the Azure Cloud NGFW reached 100% utilization, which caused commit failures.
## PAN-284073
Fixed an issue on the firewall that caused commits to fail and the web interface to become inaccessible.
## PAN-278611
Fixed an issue on Panorama where software images were not purged from the /opt/pancfg/mgmt/sw-images folder.
@@ -0,0 +1,9 @@
---
type: Addressed
product: PAN-OS
version: 10.2.13-h22
---
## BLANK-000000
Fixes were made to address [CVE-2026-0273](https://security-stg.paloaltonetworks.com/CVE-2026-0273) and [CVE-2026-0272](https://security-stg.paloaltonetworks.com/CVE-2026-0272).
@@ -0,0 +1,20 @@
---
type: Addressed
product: PAN-OS
version: 10.2.13-h23
---
## BLANK-000000
Fixes were made to address the following CVEs:
- [CVE-2026-0283](https://security.paloaltonetworks.com/CVE-2026-0283)
- [CVE-2026-0287](https://security.paloaltonetworks.com/CVE-2026-0287)
- [CVE-2026-0279](https://security.paloaltonetworks.com/CVE-2026-0279)
- [CVE-2026-0282](https://security.paloaltonetworks.com/CVE-2026-0282)
- [CVE-2026-0288](https://security.paloaltonetworks.com/CVE-2026-0288)
- [CVE-2026-0286](https://security.paloaltonetworks.com/CVE-2026-0286)
- [CVE-2026-0285](https://security.paloaltonetworks.com/CVE-2026-0285)
- [CVE-2026-0280](https://security.paloaltonetworks.com/CVE-2026-0280)
- [CVE-2026-0284](https://security.paloaltonetworks.com/CVE-2026-0284)
- [CVE-2026-0281](https://security.paloaltonetworks.com/CVE-2026-0281)
@@ -0,0 +1,9 @@
---
type: Addressed
product: PAN-OS
version: 10.2.16-h8
---
## BLANK-000000
Fixes were made to address [CVE-2026-0273](https://security-stg.paloaltonetworks.com/CVE-2026-0273) and [CVE-2026-0272](https://security-stg.paloaltonetworks.com/CVE-2026-0272).
@@ -0,0 +1,20 @@
---
type: Addressed
product: PAN-OS
version: 10.2.16-h9
---
## BLANK-000000
Fixes were made to address the following CVEs:
- [CVE-2026-0283](https://security.paloaltonetworks.com/CVE-2026-0283)
- [CVE-2026-0287](https://security.paloaltonetworks.com/CVE-2026-0287)
- [CVE-2026-0279](https://security.paloaltonetworks.com/CVE-2026-0279)
- [CVE-2026-0282](https://security.paloaltonetworks.com/CVE-2026-0282)
- [CVE-2026-0288](https://security.paloaltonetworks.com/CVE-2026-0288)
- [CVE-2026-0286](https://security.paloaltonetworks.com/CVE-2026-0286)
- [CVE-2026-0285](https://security.paloaltonetworks.com/CVE-2026-0285)
- [CVE-2026-0280](https://security.paloaltonetworks.com/CVE-2026-0280)
- [CVE-2026-0284](https://security.paloaltonetworks.com/CVE-2026-0284)
- [CVE-2026-0281](https://security.paloaltonetworks.com/CVE-2026-0281)
@@ -0,0 +1,9 @@
---
type: Addressed
product: PAN-OS
version: 10.2.18-h7
---
## BLANK-000000
Fixes were made to address [CVE-2026-0273](https://security-stg.paloaltonetworks.com/CVE-2026-0273) and [CVE-2026-0272](https://security-stg.paloaltonetworks.com/CVE-2026-0272).
@@ -0,0 +1,32 @@
---
type: Addressed
product: PAN-OS
version: 10.2.18-h8
---
## BLANK-000000
Fixes were made to address the following CVEs:
- [CVE-2026-0283](https://security.paloaltonetworks.com/CVE-2026-0283)
- [CVE-2026-0287](https://security.paloaltonetworks.com/CVE-2026-0287)
- [CVE-2026-0279](https://security.paloaltonetworks.com/CVE-2026-0279)
- [CVE-2026-0282](https://security.paloaltonetworks.com/CVE-2026-0282)
- [CVE-2026-0288](https://security.paloaltonetworks.com/CVE-2026-0288)
- [CVE-2026-0286](https://security.paloaltonetworks.com/CVE-2026-0286)
- [CVE-2026-0285](https://security.paloaltonetworks.com/CVE-2026-0285)
- [CVE-2026-0280](https://security.paloaltonetworks.com/CVE-2026-0280)
- [CVE-2026-0284](https://security.paloaltonetworks.com/CVE-2026-0284)
- [CVE-2026-0281](https://security.paloaltonetworks.com/CVE-2026-0281)
## PAN-317466
Fixed an issue where SIP sessions stopped progressing after the firewall received fragmented packets, fragmented at header field.
## PAN-306555
Fixed an issue where the firewall stopped responding, which led to service outages.
## PAN-295803
Addressed a memory leak issue under sc3 and automatic commit recovery (ACR) code path.
@@ -0,0 +1,13 @@
---
type: Addressed
product: PAN-OS
version: 10.2.7-h35
---
## BLANK-000000
Fixes were made to address [CVE-2026-0273](https://security-stg.paloaltonetworks.com/CVE-2026-0273) and [CVE-2026-0272](https://security-stg.paloaltonetworks.com/CVE-2026-0272).
## PAN-293673
Fixed an issue where the firewall stopped all tasks due to an OOM condition caused by a scheduled log export using FTP to an external FTP server.
@@ -0,0 +1,20 @@
---
type: Addressed
product: PAN-OS
version: 10.2.7-h36
---
## BLANK-000000
Fixes were made to address the following CVEs:
- [CVE-2026-0283](https://security.paloaltonetworks.com/CVE-2026-0283)
- [CVE-2026-0287](https://security.paloaltonetworks.com/CVE-2026-0287)
- [CVE-2026-0279](https://security.paloaltonetworks.com/CVE-2026-0279)
- [CVE-2026-0282](https://security.paloaltonetworks.com/CVE-2026-0282)
- [CVE-2026-0288](https://security.paloaltonetworks.com/CVE-2026-0288)
- [CVE-2026-0286](https://security.paloaltonetworks.com/CVE-2026-0286)
- [CVE-2026-0285](https://security.paloaltonetworks.com/CVE-2026-0285)
- [CVE-2026-0280](https://security.paloaltonetworks.com/CVE-2026-0280)
- [CVE-2026-0284](https://security.paloaltonetworks.com/CVE-2026-0284)
- [CVE-2026-0281](https://security.paloaltonetworks.com/CVE-2026-0281)
@@ -0,0 +1,56 @@
---
type: Addressed
product: PAN-OS
version: 11.1.10-h30
---
## BLANK-000000
Fixes were made to address the following CVEs:
- [CVE-2026-0283](https://security.paloaltonetworks.com/CVE-2026-0283)
- [CVE-2026-0287](https://security.paloaltonetworks.com/CVE-2026-0287)
- [CVE-2026-0279](https://security.paloaltonetworks.com/CVE-2026-0279)
- [CVE-2026-0282](https://security.paloaltonetworks.com/CVE-2026-0282)
- [CVE-2026-0288](https://security.paloaltonetworks.com/CVE-2026-0288)
- [CVE-2026-0286](https://security.paloaltonetworks.com/CVE-2026-0286)
- [CVE-2026-0285](https://security.paloaltonetworks.com/CVE-2026-0285)
- [CVE-2026-0280](https://security.paloaltonetworks.com/CVE-2026-0280)
- [CVE-2026-0284](https://security.paloaltonetworks.com/CVE-2026-0284)
- [CVE-2026-0281](https://security.paloaltonetworks.com/CVE-2026-0281)
## PAN-326354
Fixed an issue where the sslmgr process stopped responding when attempting to display the OSCP host cache.
## PAN-324370
Fixed an issue where IDE traffic did not function as expected when both HTTP head insertion and DLP inspection were enabled.
## PAN-322281
```caveat
Firewalls in HA configurations only
```
Fixed an issue where the HA 2 interface did not come up on the passive firewall, which resulted in the firewall being unable to join the HA pair.
## PAN-321816
Fixed an issue where processes stopped responding unexpectedly.
## PAN-319504
Fixed an issue where telemetry data was not sent to the cloud due to the firewall being unable to resolve the destination server's FQDN even when a proxy server was configured. With this fix, the firewall properly sends telemetry data through the configured proxy server without requiring direct public DNS resolution for the telemetry server's FQDN.
## PAN-304360
Fixed an issue where the firewall did not redistribute its application routes to BGP peers. This occurred in multi-mesh deployments with the multi-cloud networking feature enabled.
## PAN-286386
Fixed an issue where GlobalProtect users were unable to connect.
## PAN-285327
Fixed an issue where a memory leak occurred when processing device and vsys tags.
@@ -0,0 +1,72 @@
---
type: Addressed
product: PAN-OS
version: 11.1.13-h9
---
## BLANK-000000
Fixes were made to address the following CVEs:
- [CVE-2026-0283](https://security.paloaltonetworks.com/CVE-2026-0283)
- [CVE-2026-0287](https://security.paloaltonetworks.com/CVE-2026-0287)
- [CVE-2026-0279](https://security.paloaltonetworks.com/CVE-2026-0279)
- [CVE-2026-0282](https://security.paloaltonetworks.com/CVE-2026-0282)
- [CVE-2026-0288](https://security.paloaltonetworks.com/CVE-2026-0288)
- [CVE-2026-0286](https://security.paloaltonetworks.com/CVE-2026-0286)
- [CVE-2026-0285](https://security.paloaltonetworks.com/CVE-2026-0285)
- [CVE-2026-0280](https://security.paloaltonetworks.com/CVE-2026-0280)
- [CVE-2026-0284](https://security.paloaltonetworks.com/CVE-2026-0284)
- [CVE-2026-0281](https://security.paloaltonetworks.com/CVE-2026-0281)
## PAN-326677
Fixed an issue where a selective push from Panorama to the firewall was successful even when applying rename operation failed in selective push, which resulted in configurations on the firewall being deleted. With this fix, the selective push will fail when applying rename operation fails.
## PAN-326354
Fixed an issue where the sslmgr process stopped responding when attempting to display the OSCP host cache.
## PAN-324370
Fixed an issue where IDE traffic did not function as expected when both HTTP head insertion and DLP inspection were enabled.
## PAN-321816
Fixed an issue where processes stopped responding unexpectedly.
## PAN-321699
Fixed an issue where device telemetry intermittently failed to send files, which resulted in critical alerts in system files.
## PAN-319504
Fixed an issue where telemetry data was not sent to the cloud due to the firewall being unable to resolve the destination server's FQDN even when a proxy server was configured. With this fix, the firewall properly sends telemetry data through the configured proxy server without requiring direct public DNS resolution for the telemetry server's FQDN.
## PAN-318106
Fixed an issue where SCM did not update device telemetry for the firewall after upgrading to an affected release.
## PAN-312442
Fixed an issue where 403 errors occurred when performing "show config effective-running" API query after downgrading to an affected PAN-OS release.
## PAN-308876
Fixed an issue where upgrades to managed firewalls from Panorama failed.
## PAN-304360
Fixed an issue where the firewall did not redistribute its application routes to BGP peers. This occurred in multi-mesh deployments with the multi-cloud networking feature enabled.
## PAN-297370
Fixed an issue where pushing a new object from Panorama to a Cloud NGFW Device Group unexpectedly removed existing Panorama-pushed policy rules, even though the **Push Preview** did not show any deletions, which led to traffic disruptions.
## PAN-286386
Fixed an issue where GlobalProtect users were unable to connect
## PAN-285327
Fixed an issue where a memory leak occurred when processing device and vsys tags.
+321
View File
@@ -0,0 +1,321 @@
---
type: Addressed
product: PAN-OS
version: 11.1.16
---
## BLANK-000000
Fixes were made to address the following CVEs:
- [CVE-2026-0283](https://security.paloaltonetworks.com/CVE-2026-0283)
- [CVE-2026-0287](https://security.paloaltonetworks.com/CVE-2026-0287)
- [CVE-2026-0279](https://security.paloaltonetworks.com/CVE-2026-0279)
- [CVE-2026-0282](https://security.paloaltonetworks.com/CVE-2026-0282)
- [CVE-2026-0288](https://security.paloaltonetworks.com/CVE-2026-0288)
- [CVE-2026-0286](https://security.paloaltonetworks.com/CVE-2026-0286)
- [CVE-2026-0285](https://security.paloaltonetworks.com/CVE-2026-0285)
- [CVE-2026-0280](https://security.paloaltonetworks.com/CVE-2026-0280)
- [CVE-2026-0284](https://security.paloaltonetworks.com/CVE-2026-0284)
- [CVE-2026-0281](https://security.paloaltonetworks.com/CVE-2026-0281)
## PAN-327009
Fixed an issue where the all_task process stopped responding.
## PAN-326677
Fixed an issue where a selective push from Panorama to the firewall was successful even when applying rename operation failed in selective push, which resulted in configurations on the firewall being deleted. With this fix, the selective push will fail when applying rename operation fails.
## PAN-326354
Fixed an issue where the sslmgr process stopped responding when attempting to display the OSCP host cache.
## PAN-324370
Fixed an issue where IDE traffic did not function as expected when both HTTP head insertion and DLP inspection were enabled.
## PAN-323485
Fixed an issue where multicast radio RTP based traffic was dropped after an upgrade when the firewall performed Cloud Inline inspection, which led to an exceeded session queue for Cloud Threat Detection.
## PAN-321816
Fixed an issue where processes stopped responding unexpectedly.
## PAN-321699
Fixed an issue where device telemetry intermittently failed to send files, which resulted in critical alerts in system files.
## PAN-321527
```caveat
PA-7500 firewalls in HA cluster configurations only
```
Fixed an issue where, when one firewall suspended operations, the other firewall also suspended operations instead of initiating a failover, which resulted in a complete traffic outage.
## PAN-321516
Fixed an issue where the dataplane restarted due to a race condition in the dataplane cache infrastructure.
## PAN-321340
```caveat
Firewalls in FIPS mode only
```
Fixed an issue where GlobalProtect unexpectedly prompted for RADIUS authentication instead of client certificate authentication due to an OSCP validation error and subsequent CRL verification failure, which led to certificates being marked as invalid.
## PAN-321060
Fixed an issue where an ethernet interface remained in a down state after repeated automated enable/disable cycles and required manual intervention, which resulted in backup outages.
## PAN-320598
Fixed an issue where internal and external DNS names did not resolve when connected to a GlobalProtect gateway.
## PAN-320245
```caveat
PA-7500 Series firewalls in vwire mode only
```
Fixed an issue where Oracle application traffic was intermittently not processed even though connected devices sent the traffic, which led to service distruptions.
## PAN-319793
Fixed an issue where, after upgrading to PAN-OS 12.1.5, GlobalProtect Clientless VPN failed to access JavaScripts.
## PAN-319504
Fixed an issue where telemetry data was not sent to the cloud due to the firewall being unable to resolve the destination server's FQDN even when a proxy server was configured. With this fix, the firewall properly sends telemetry data through the configured proxy server without requiring direct public DNS resolution for the telemetry server's FQDN.
## PAN-319419
```caveat
Firewalls in active/passive HA configurations only
```
Fixed an issue where active firewalls were unable to send device telemetry data to CDL.
## PAN-319352
Fixed an issue where the firewall rebooted unexpectedly without any configuration or power changes.
## PAN-319343
```caveat
Prisma Access Gateways only
```
Fixed an issue where the global management plane stopped responding, which caused SSH and HTML disconnections, HIP database lookup failures, and significantly slower SCM commits. This occurred when egress IP allow listing was enabled in SCM and changes were made to EDLs.
## PAN-319288
Fixed an issue where a DPC in Slot 4 restarted repeatedly, which caused internal path monitoring failures and a failover event.
## PAN-319228
Fixed an issue where External Dynamic List (EDL) refresh and commit operations remained in a pending state, which prevented any subsequent operations from completing.
## PAN-318580
Fixed an issue where processes restarted and the firewall unexpectedly rebooted when you configured a Security policy rule with **Source Device > quarantine**.
## PAN-318382
```caveat
Firewalls in HA configurations only
```
Fixed an issue where the secondary firewall remained at an **Initial** state after an upgrade.
## PAN-318120
Fixed an issue where SSL traffic was silently dropped when traffic was processed by a Security policy with an Anti-Spyware profile that had Inline cloud Analysis enabled for SSL C2 Detector with an action other than allow or alert.
## PAN-318106
Fixed an issue where SCM did not update device telemetry for the firewall after upgrading to an affected release.
## PAN-317755
Fixed an issue on Panorama where selective push operations failed when plugin configurations included access-domain or log-collector references.
## PAN-317648
```caveat
PA-5450 firewalls and PA-7000 Series firewalls with 100G NPCs only
```
Fixed an issue where intermittent packet loss occurred when traversing the dataplane after upgrading the firewall. This occurred when a dataplane HA interface was configured in an environment where Slot 1 was unpopulated , which resulted in a wildcard entry being created within the QMAP table.
## PAN-317614
Fixed an issue where high throughput and increased packet rates caused high dataplane CPU usage.
## PAN-316435
Fixed an issue where the firewall restarted unexpectedly due to an OOM condition after upgrading to an affected release.
## PAN-316120
Fixed an issue where, after Advanced Routing was enabled, the firewall advertised routes to internal BGP neighbors with the original external BGP next-hop address.
## PAN-315337
Fixed an issue where GlobalProtect throughput was reduced after an upgrade.
## PAN-315326
```caveat
PA-7500 firewalls only
```
Fixed an issue where zone protection threshold values per dataplane were unexpectedly low.
## PAN-315314
Fixed an issue where, when a push operation from Panorama to the firewall failed, accounting logs stopped forwarding.
## PAN-315160
```caveat
PA-7500 firewalls only
```
Fixed an issue where internal path monitoring logs incorrectly reported internal path monitoring failures when they did not occur.
## PAN-314776
Fixed an issue where the configd process stopped responding after pushing configuration changes from Panorama to the firewall.
## PAN-314623
```caveat
Firewalls in active/passive HA configurations only
```
Fixed an issue where, after a failover, routing information within OSPF protocol was not correctly translated or propagated, which affected network path convergence and FRR capabilities.
## PAN-314512
Fixed an issue where the GlobalProtect portal became inaccessible when the dataplane was configured with a DHCP assigned IP address.
## PAN-314104
Fixed an issue where running BCM counter commands from the administrative shell did not consistently return output, and commands to modify queue sizes did not take effect.
## PAN-313787
Fixed an issue where some system log filters with the eventid operator for a BGP event did not work.
## PAN-313606
Fixed an issue where Panorama pushed commits took longer than expected to complete without displaying an error message when committing due to slow cloud-app compilation.
## PAN-313575
Fixed an issue where 10G connections on built-in RJ45 interfaces (ethernet1/1 through ethernet1/5) intermittently experienced interface flapping when connected to Cisco switchports.
## PAN-313523
Fixed an issue where generating a tech support file caused GlobalProtect users to be forcibly logged out.
## PAN-313443
Fixed an issue where firewalls acting as an accumulation proxy sent a server hello with an earlier TCP timestamp value than a preceding ACK packet, which prevented successful session establishment. This occurred when the client hello messages were split across multiple network segments.
To use this fix, run the CLI command debug dataplane set ssl-decrypt accumulate-client-hello ts-relay yes.
## PAN-313218
Added the following CLI commands to address QoS packet drops due to bursty traffic:
- debug dataplane set qos-setting qos-param qlimit 300
- debug dataplane set qos-setting qos-param red low 50 high 90
To utilize this fix, change the parameters, disable QoS, commit changes, enable QOS, and then re-commit changes.
## PAN-313036
Fixed an issue where the firewall dataplane continuously accumulated packets in the ctd_pkt_queue and packet buffers, which caused resource exhaustion and prematurely terminated sessions.
## PAN-312442
Fixed an issue where 403 errors occurred when performing "show config effective-running" API query after downgrading to an affected PAN-OS release.
## PAN-312330
```caveat
Firewalls in active/passive HA configurations only
```
Fixed an issue where the Clientless VPN applications failed to load due to the firewall dataplane incorrectly processing session information.
## PAN-312157
Fixed an issue where, during a commit, the firewall intermittently stopped sending SNMP messages, which caused interface counters to stop updating for brief periods of time.
## PAN-311658
Fixed an issue where the reportd process stopped responding, which caused the firewall to reboot.
## PAN-311419
Fixed an issue where the recommended filter for identifying traffic from unidentified users in traffic logs reported an incorrectly low number of results.
## PAN-310240
Fixed an issue where software packet buffers were completely utilized when performing a Data Loss Prevention longevity test.
## PAN-308876
Fixed an issue where upgrades to managed firewalls from Panorama failed.
## PAN-308775
```caveat
Firewalls in active/passive configurations only
```
Fixed an issue where NTP status intermittently showed as rejected on the active firewall, which prevented the firewalls from synchronizing time.
## PAN-308444
Fixed an issue where pushing multiple policy rules failed when the policy rules contained a large number of dynamic address object groups or user groups.
## PAN-307190
Fixed an issue where LED indicators on combo ports remained off even when the network link was active.
## PAN-304360
Fixed an issue where the firewall did not redistribute its application routes to BGP peers. This occurred in multi-mesh deployments with the multi-cloud networking feature enabled.
## PAN-295082
Fixed an issue on the Panorama web interface where you were unable to delete or change a logical router for tunnel, SD-WAN, VLAN, or loopback interfaces under a template.
## PAN-289460
Fixed an issue where the timestamp value in SNMPv3 trap headers was incorrect.
To use this fix, run the CLI command debug log-receiver enginetime-from-snmptime yes.
## PAN-286386
Fixed an issue where GlobalProtect users were unable to connect
## PAN-285327
Fixed an issue where a memory leak occurred when processing device and vsys tags.
## PAN-267067
Fixed an issue where VXLAN traffic failed and packet loss occurred in networks sensors after upgrading to an affected release.
## PAN-240066
Fixed a duplicate MAC address issue where an ethernet interface sent out Gratuitous ARP (GARP) messages for an IP address that was not configured on it.
@@ -0,0 +1,9 @@
---
type: Addressed
product: PAN-OS
version: 11.1.4-h34
---
## BLANK-000000
Fixes were made to address [CVE-2026-0273](https://security-stg.paloaltonetworks.com/CVE-2026-0273) and [CVE-2026-0272](https://security-stg.paloaltonetworks.com/CVE-2026-0272).
@@ -0,0 +1,20 @@
---
type: Addressed
product: PAN-OS
version: 11.1.4-h35
---
## BLANK-000000
Fixes were made to address the following CVEs:
- [CVE-2026-0283](https://security.paloaltonetworks.com/CVE-2026-0283)
- [CVE-2026-0287](https://security.paloaltonetworks.com/CVE-2026-0287)
- [CVE-2026-0279](https://security.paloaltonetworks.com/CVE-2026-0279)
- [CVE-2026-0282](https://security.paloaltonetworks.com/CVE-2026-0282)
- [CVE-2026-0288](https://security.paloaltonetworks.com/CVE-2026-0288)
- [CVE-2026-0286](https://security.paloaltonetworks.com/CVE-2026-0286)
- [CVE-2026-0285](https://security.paloaltonetworks.com/CVE-2026-0285)
- [CVE-2026-0280](https://security.paloaltonetworks.com/CVE-2026-0280)
- [CVE-2026-0284](https://security.paloaltonetworks.com/CVE-2026-0284)
- [CVE-2026-0281](https://security.paloaltonetworks.com/CVE-2026-0281)
@@ -0,0 +1,41 @@
---
type: Addressed
product: PAN-OS
version: 11.1.6-h35
---
## BLANK-000000
Fixes were made to address the following CVEs:
- [CVE-2026-0283](https://security.paloaltonetworks.com/CVE-2026-0283)
- [CVE-2026-0287](https://security.paloaltonetworks.com/CVE-2026-0287)
- [CVE-2026-0279](https://security.paloaltonetworks.com/CVE-2026-0279)
- [CVE-2026-0282](https://security.paloaltonetworks.com/CVE-2026-0282)
- [CVE-2026-0288](https://security.paloaltonetworks.com/CVE-2026-0288)
- [CVE-2026-0286](https://security.paloaltonetworks.com/CVE-2026-0286)
- [CVE-2026-0285](https://security.paloaltonetworks.com/CVE-2026-0285)
- [CVE-2026-0280](https://security.paloaltonetworks.com/CVE-2026-0280)
- [CVE-2026-0284](https://security.paloaltonetworks.com/CVE-2026-0284)
- [CVE-2026-0281](https://security.paloaltonetworks.com/CVE-2026-0281)
## PAN-319504
Fixed an issue where telemetry data was not sent to the cloud due to the firewall being unable to resolve the destination server's FQDN even when a proxy server was configured. With this fix, the firewall properly sends telemetry data through the configured proxy server without requiring direct public DNS resolution for the telemetry server's FQDN.
## PAN-313218
Added the following CLI commands to address QoS packet drops due to bursty traffic:
- debug dataplane set qos-setting qos-param qlimit 300
- debug dataplane set qos-setting qos-param red low 50 high 90
To utilize this fix, change the parameters, disable QoS, commit changes, enable QOS, and then re-commit changes.
## PAN-304360
Fixed an issue where the firewall did not redistribute its application routes to BGP peers. This occurred in multi-mesh deployments with the multi-cloud networking feature enabled.
## PAN-285327
Fixed an issue where a memory leak occurred when processing device and vsys tags.
@@ -0,0 +1,9 @@
---
type: Addressed
product: PAN-OS
version: 11.1.7-h7
---
## BLANK-000000
Fixes were made to address [CVE-2026-0273](https://security-stg.paloaltonetworks.com/CVE-2026-0273) and [CVE-2026-0272](https://security-stg.paloaltonetworks.com/CVE-2026-0272).
@@ -0,0 +1,20 @@
---
type: Addressed
product: PAN-OS
version: 11.1.7-h8
---
## BLANK-000000
Fixes were made to address the following CVEs:
- [CVE-2026-0283](https://security.paloaltonetworks.com/CVE-2026-0283)
- [CVE-2026-0287](https://security.paloaltonetworks.com/CVE-2026-0287)
- [CVE-2026-0279](https://security.paloaltonetworks.com/CVE-2026-0279)
- [CVE-2026-0282](https://security.paloaltonetworks.com/CVE-2026-0282)
- [CVE-2026-0288](https://security.paloaltonetworks.com/CVE-2026-0288)
- [CVE-2026-0286](https://security.paloaltonetworks.com/CVE-2026-0286)
- [CVE-2026-0285](https://security.paloaltonetworks.com/CVE-2026-0285)
- [CVE-2026-0280](https://security.paloaltonetworks.com/CVE-2026-0280)
- [CVE-2026-0284](https://security.paloaltonetworks.com/CVE-2026-0284)
- [CVE-2026-0281](https://security.paloaltonetworks.com/CVE-2026-0281)
@@ -0,0 +1,72 @@
---
type: Addressed
product: PAN-OS
version: 11.2.10-h12
---
## BLANK-000000
Fixes were made to address the following CVEs:
- [CVE-2026-0283](https://security.paloaltonetworks.com/CVE-2026-0283)
- [CVE-2026-0287](https://security.paloaltonetworks.com/CVE-2026-0287)
- [CVE-2026-0279](https://security.paloaltonetworks.com/CVE-2026-0279)
- [CVE-2026-0282](https://security.paloaltonetworks.com/CVE-2026-0282)
- [CVE-2026-0288](https://security.paloaltonetworks.com/CVE-2026-0288)
- [CVE-2026-0286](https://security.paloaltonetworks.com/CVE-2026-0286)
- [CVE-2026-0285](https://security.paloaltonetworks.com/CVE-2026-0285)
- [CVE-2026-0280](https://security.paloaltonetworks.com/CVE-2026-0280)
- [CVE-2026-0284](https://security.paloaltonetworks.com/CVE-2026-0284)
- [CVE-2026-0281](https://security.paloaltonetworks.com/CVE-2026-0281)
## PAN-328145
Fixed an issue where a firewall functioning as an Area Border Router did not correctly translate NSSA Type-7 LSAs to Type-5 LSAs when OSPF neighbors set the Nt bit in the NSSA Area, and routes were not advertised to upstream OSPF neighbors in the backbone area, which resulted in traffic being silently discarded.
## PAN-321699
Fixed an issue where device telemetry intermittently failed to send files, which resulted in critical alerts in system files.
## PAN-319228
Fixed an issue where External Dynamic List (EDL) refresh and commit operations remained in a pending state, which prevented any subsequent operations from completing.
## PAN-317466
Fixed an issue where SIP sessions stopped progressing after the firewall received fragmented packets, fragmented at header field.
## PAN-313700
Fixed an issue where an unexpected reboot occurred when Inline Cloud Analysis was enabled in an Anti-Spyware and Vulnerability profile.
## PAN-311658
Fixed an issue where the reportd process stopped responding, which caused the firewall to reboot.
## PAN-308775
```caveat
Firewalls in active/passive configurations only
```
Fixed an issue where NTP status intermittently showed as rejected on the active firewall, which prevented the firewalls from synchronizing time.
## PAN-308606
Fixed an issue where traffic was blocked due to a mismatch between the URL category specified in the Security policy rule and the URL filter profile when custom URL categories with the same FQDN were configured.
## PAN-305835
Fixed an issue where firewalls with Memory Integrity Checking Architecture enabled rebooted unexpectedly due to accessing an invalid memory address. This occurred because the forwarding data structure index exceeded its designed limit.
## PAN-291804
Fixed an issue on Panorama where deleting objects resulted in errors indicating references in Security policy rules.
## PAN-250445
Fixed an issue where DLP logs accumulated in the logrcvr cache when using DLP in mirror mode.
## PAN-246699
Fixed an issue on Panorama where **Rule Usage** and **Apps Seen** under Security policy rules stopped incrementing.
+222
View File
@@ -0,0 +1,222 @@
---
type: Addressed
product: PAN-OS
version: 11.2.13
---
## BLANK-000000
Fixes were made to address the following CVEs:
- [CVE-2026-0283](https://security.paloaltonetworks.com/CVE-2026-0283)
- [CVE-2026-0287](https://security.paloaltonetworks.com/CVE-2026-0287)
- [CVE-2026-0279](https://security.paloaltonetworks.com/CVE-2026-0279)
- [CVE-2026-0282](https://security.paloaltonetworks.com/CVE-2026-0282)
- [CVE-2026-0288](https://security.paloaltonetworks.com/CVE-2026-0288)
- [CVE-2026-0286](https://security.paloaltonetworks.com/CVE-2026-0286)
- [CVE-2026-0285](https://security.paloaltonetworks.com/CVE-2026-0285)
- [CVE-2026-0280](https://security.paloaltonetworks.com/CVE-2026-0280)
- [CVE-2026-0284](https://security.paloaltonetworks.com/CVE-2026-0284)
- [CVE-2026-0281](https://security.paloaltonetworks.com/CVE-2026-0281)
## PAN-328145
Fixed an issue where a firewall functioning as an Area Border Router did not correctly translate NSSA Type-7 LSAs to Type-5 LSAs when OSPF neighbors set the Nt bit in the NSSA Area, and routes were not advertised to upstream OSPF neighbors in the backbone area, which resulted in traffic being silently discarded.
## PAN-321699
Fixed an issue where device telemetry intermittently failed to send files, which resulted in critical alerts in system files.
## PAN-321150
Fixed an issue where the interface remained down after an upgrade.
## PAN-320598
Fixed an issue where internal and external DNS names did not resolve when connected to a GlobalProtect gateway.
## PAN-319798
```caveat
Panorama virtual appliances in AWS environments only
```
Fixed an issue where logging disks failed to mount or reported an unknown file system type.
## PAN-319793
Fixed an issue where, after upgrading to PAN-OS 12.1.5, GlobalProtect Clientless VPN failed to access JavaScripts.
## PAN-319266
```caveat
Cloud IPS only
```
Increased scale limit for zone mappings.
## PAN-319228
Fixed an issue where External Dynamic List (EDL) refresh and commit operations remained in a pending state, which prevented any subsequent operations from completing.
## PAN-318120
Fixed an issue where SSL traffic was silently dropped when traffic was processed by a Security policy with an Anti-Spyware profile that had Inline cloud Analysis enabled for SSL C2 Detector with an action other than allow or alert.
## PAN-318106
Fixed an issue where SCM did not update device telemetry for the firewall after upgrading to an affected release.
## PAN-318030
```caveat
VM-Series firewalls in Hyper-V only
```
) Fixed an issue where the throughput was reported to be twice as high as the actual traffic rate.
## PAN-317755
Fixed an issue on Panorama where selective push operations failed when plugin configurations included access-domain or log-collector references.
## PAN-317614
Fixed an issue where high throughput and increased packet rates caused high dataplane CPU usage.
## PAN-317466
Fixed an issue where SIP sessions stopped progressing after the firewall received fragmented packets, fragmented at header field.
## PAN-317215
```caveat
VM-Series firewalls on ESXi with Intel E810 NICs using PCI passthrough
```
Fixed an issue where the brdagent process became unresponsive during data port initialization, which resulted in system instability, interface outages, HA split-brain conditions, and unexpected reboots during failover.
## PAN-315919
Fixed an issue where GlobalProtect pre-logon tunnel session was not cleared even after the user was logged in. With this fix, the session is cleared after the session timeout expires.
## PAN-315337
Fixed an issue where GlobalProtect throughput was reduced after an upgrade.
## PAN-315314
Fixed an issue where, when a push operation from Panorama to the firewall failed, accounting logs stopped forwarding.
## PAN-314512
Fixed an issue where the GlobalProtect portal became inaccessible when the dataplane was configured with a DHCP assigned IP address.
## PAN-314061
Fixed an issue where traffic was disrupted during IPSec rekey operations due to a 2 second delay in sending the DELETE message for the previous Security Association (SA) to the peer gateway after a new SA was negotiated.
## PAN-314020
Fixed an issue where the firewall did not decapsulate GENEVE packets when DNS Security retransmitted a DNS query after receiving a verdict from the cloud.
## PAN-313850
```caveat
PA-1400 Series firewalls in HA configurations only
```
Fixed an issue where a split-brain condition occurred and HA1/HA2 links went down while upgrading when the HA configuration used dataplane interfaces for HA1 and a combination of HSCI and Ethernet interfaces for HA2.
## PAN-313828
Fixed an issue where the firewall did not forward traffic due to memory issues on a forwarding component.
## PAN-312330
```caveat
Firewalls in active/passive HA configurations only
```
Fixed an issue where the Clientless VPN applications failed to load due to the firewall dataplane incorrectly processing session information.
## PAN-311658
Fixed an issue where the reportd process stopped responding, which caused the firewall to reboot.
## PAN-311285
Fixed an issue where a memory leak occurred related to the ospfd process, which caused RAM usage to continuously increase until the device stopped responding.
## PAN-311192
Fixed an issue where the device-telemetry collect-now process became unresponsive when the process was initiated multiple times with other processes running concurrently, which prevented subsequent telemetry collection.
## PAN-311040
Fixed an issue where the all_task process stopped responding and caused the firewall to reboot unexpectedly.
## PAN-310240
Fixed an issue where software packet buffers were completely utilized when performing a Data Loss Prevention longevity test.
## PAN-308775
```caveat
Firewalls in active/passive configurations only
```
Fixed an issue where NTP status intermittently showed as rejected on the active firewall, which prevented the firewalls from synchronizing time.
## PAN-307976
```caveat
Firewalls in active/active HA configurations only
```
Fixed an issue where tunnels failed to come up with the error message failed to find a socket for transmission.
## PAN-307618
Added a debug CLI command to address where remote networks for Prisma Access tenants randomly dropped monitoring packets from peer devices, which caused tunnels to be marked as down. This occurred when a CPU core suddenly experienced high utilization.
To utilize this fix, run debug dataplane set ssl-decrypt use-new-peek-window yes.
## PAN-307470
Fixed an issue where an External Dynamic List (EDL) fetch with an invalid certificate was skipped on newly provisioned GlobalProtect gateway instances.
## PAN-306356
Fixed an issue where the logrcvr process on a firewall stopped responding due to a document node being unexpectedly freed.
## PAN-300615
Fixed an issue where the pan_comm process stopped after multiple content versions were installed and the memory limits were reached.
## PAN-298960
Fixed an issue where the firewall continuously rebooted when the useridd process repeatedly restarted.
## PAN-296246
Fixed an issue where policy cache corruption led to unexpected policy rule behavior or operational instability. This occurred when an internal system process restarted while a commit was in progress or when a commit operation failed.
## PAN-295806
Fixed an issue where memory leaks on the configd process occurred due to a hash insert operation failing during connection management and SSL connections.
## PAN-294434
Fixed an issue where memory leaks occurred. These leaks were caused by two distinct scenarios: the failure to deallocate memory for a nodeset when a new nodeset was assigned to the same variable, and the failure to free a UUID hash table during error conditions.
## PAN-250445
Fixed an issue where DLP logs accumulated in the logrcvr cache when using DLP in mirror mode.
## PAN-246699
Fixed an issue on Panorama where **Rule Usage** and **Apps Seen** under Security policy rules stopped incrementing.
## PAN-234302
Fixed an issue where commit operations took longer than expected to complete due to EDL timeouts occurring on passive nodes when a service route was enabled.
@@ -0,0 +1,20 @@
---
type: Addressed
product: PAN-OS
version: 11.2.4-h20
---
## BLANK-000000
Fixes were made to address the following CVEs:
- [CVE-2026-0283](https://security.paloaltonetworks.com/CVE-2026-0283)
- [CVE-2026-0287](https://security.paloaltonetworks.com/CVE-2026-0287)
- [CVE-2026-0279](https://security.paloaltonetworks.com/CVE-2026-0279)
- [CVE-2026-0282](https://security.paloaltonetworks.com/CVE-2026-0282)
- [CVE-2026-0288](https://security.paloaltonetworks.com/CVE-2026-0288)
- [CVE-2026-0286](https://security.paloaltonetworks.com/CVE-2026-0286)
- [CVE-2026-0285](https://security.paloaltonetworks.com/CVE-2026-0285)
- [CVE-2026-0280](https://security.paloaltonetworks.com/CVE-2026-0280)
- [CVE-2026-0284](https://security.paloaltonetworks.com/CVE-2026-0284)
- [CVE-2026-0281](https://security.paloaltonetworks.com/CVE-2026-0281)
@@ -0,0 +1,55 @@
---
type: Addressed
product: PAN-OS
version: 11.2.7-h17
---
## PAN-320598
Fixed an issue where internal and external DNS names did not resolve when connected to a GlobalProtect gateway.
## PAN-317755
Fixed an issue on Panorama where selective push operations failed when plugin configurations included access-domain or log-collector references.
## PAN-315337
Fixed an issue where GlobalProtect throughput was reduced after an upgrade.
## PAN-314319
Added a CLI command to enable and disable AHO software offload optimization.
## PAN-313606
Fixed an issue where Panorama pushed commits took longer than expected to complete without displaying an error message when committing due to slow cloud-app compilation.
## PAN-310263
```caveat
VM-Series firewalls only
```
Fixed an issue where enabling TLS1.3 in a decryption profile prevented access to websites.
## PAN-310240
Fixed an issue where software packet buffers were completely utilized when performing a Data Loss Prevention longevity test.
## PAN-307618
Added a debug CLI command to address where remote networks for Prisma Access tenants randomly dropped monitoring packets from peer devices, which caused tunnels to be marked as down. This occurred when a CPU core suddenly experienced high utilization.
To utilize this fix, run debug dataplane set ssl-decrypt use-new-peek-window yes.
## PAN-307470
Fixed an issue where an External Dynamic List (EDL) fetch with an invalid certificate was skipped on newly provisioned GlobalProtect gateway instances.
## PAN-266905
Fixed an issue where sessions ended with the message decrypt error in the logs for traffic that matched a **no-decrypt** policy.
## PAN-234302
Fixed an issue where commit operations took longer than expected to complete due to EDL timeouts occurring on passive nodes when a service route was enabled.
@@ -0,0 +1,44 @@
---
type: Addressed
product: PAN-OS
version: 11.2.7-h18
---
## BLANK-000000
Fixes were made to address the following CVEs:
- [CVE-2026-0283](https://security.paloaltonetworks.com/CVE-2026-0283)
- [CVE-2026-0287](https://security.paloaltonetworks.com/CVE-2026-0287)
- [CVE-2026-0279](https://security.paloaltonetworks.com/CVE-2026-0279)
- [CVE-2026-0282](https://security.paloaltonetworks.com/CVE-2026-0282)
- [CVE-2026-0288](https://security.paloaltonetworks.com/CVE-2026-0288)
- [CVE-2026-0286](https://security.paloaltonetworks.com/CVE-2026-0286)
- [CVE-2026-0285](https://security.paloaltonetworks.com/CVE-2026-0285)
- [CVE-2026-0280](https://security.paloaltonetworks.com/CVE-2026-0280)
- [CVE-2026-0284](https://security.paloaltonetworks.com/CVE-2026-0284)
- [CVE-2026-0281](https://security.paloaltonetworks.com/CVE-2026-0281)
## PAN-308775
```caveat
Firewalls in active/passive configurations only
```
Fixed an issue where NTP status intermittently showed as rejected on the active firewall, which prevented the firewalls from synchronizing time.
## PAN-308606
Fixed an issue where traffic was blocked due to a mismatch between the URL category specified in the Security policy rule and the URL filter profile when custom URL categories with the same FQDN were configured.
## PAN-295854
Fixed an issue where the firewall generated two URL logs for a single session.
## PAN-293707
Fixed an issue where the iotd process failed to install DPI Cloud server FQDN due to a configuration parsing failure, caused by the configuration XML memory buffer not being NULL terminated. This resulted in the accumulation of EAL logs and DLP forwarding being stopped.
## PAN-289895
Fixed an issue where, when SSL decryption was enabled, traffic matching a deny rule was incorrectly allowed until the SSL handshake was complete.
@@ -0,0 +1,13 @@
---
type: Addressed
product: PAN-OS
version: 12.1.4-h7
---
## BLANK-000000
Fixes were made to address [CVE-2026-0273](https://security-stg.paloaltonetworks.com/CVE-2026-0273) and [CVE-2026-0272](https://security-stg.paloaltonetworks.com/CVE-2026-0272).
## PAN-317755
Fixed an issue on Panorama where selective push operations failed when plugin configurations included access-domain or log-collector references.
@@ -0,0 +1,20 @@
---
type: Addressed
product: PAN-OS
version: 12.1.4-h8
---
## BLANK-000000
Fixes were made to address the following CVEs:
- [CVE-2026-0283](https://security.paloaltonetworks.com/CVE-2026-0283)
- [CVE-2026-0287](https://security.paloaltonetworks.com/CVE-2026-0287)
- [CVE-2026-0279](https://security.paloaltonetworks.com/CVE-2026-0279)
- [CVE-2026-0282](https://security.paloaltonetworks.com/CVE-2026-0282)
- [CVE-2026-0288](https://security.paloaltonetworks.com/CVE-2026-0288)
- [CVE-2026-0286](https://security.paloaltonetworks.com/CVE-2026-0286)
- [CVE-2026-0285](https://security.paloaltonetworks.com/CVE-2026-0285)
- [CVE-2026-0280](https://security.paloaltonetworks.com/CVE-2026-0280)
- [CVE-2026-0284](https://security.paloaltonetworks.com/CVE-2026-0284)
- [CVE-2026-0281](https://security.paloaltonetworks.com/CVE-2026-0281)
@@ -0,0 +1,25 @@
---
type: Addressed
product: PAN-OS
version: 12.1.7-h1
---
## PAN-320598
Fixed an issue where internal and external DNS names did not resolve when connected to a GlobalProtect gateway.
## PAN-317755
Fixed an issue on Panorama where selective push operations failed when plugin configurations included access-domain or log-collector references.
## PAN-317133
Fixed an issue where you were unable to generate a ticket for the GlobalProtect portal.
## PAN-302150
```caveat
Panorama appliances only
```
Fixed an issue where you were unable to successfully configure log collector groups due to the master node settings not populating automatically.
@@ -0,0 +1,44 @@
---
type: Addressed
product: PAN-OS
version: 12.1.7-h2
---
## BLANK-000000
Fixes were made to address the following CVEs:
- [CVE-2026-0283](https://security.paloaltonetworks.com/CVE-2026-0283)
- [CVE-2026-0287](https://security.paloaltonetworks.com/CVE-2026-0287)
- [CVE-2026-0279](https://security.paloaltonetworks.com/CVE-2026-0279)
- [CVE-2026-0282](https://security.paloaltonetworks.com/CVE-2026-0282)
- [CVE-2026-0288](https://security.paloaltonetworks.com/CVE-2026-0288)
- [CVE-2026-0286](https://security.paloaltonetworks.com/CVE-2026-0286)
- [CVE-2026-0285](https://security.paloaltonetworks.com/CVE-2026-0285)
- [CVE-2026-0280](https://security.paloaltonetworks.com/CVE-2026-0280)
- [CVE-2026-0284](https://security.paloaltonetworks.com/CVE-2026-0284)
- [CVE-2026-0281](https://security.paloaltonetworks.com/CVE-2026-0281)
## PAN-324014
Fixed an issue where logging disks were reported with a byte size of zero in the system status even when they were properly mounted.
## PAN-323809
Fixed an issue where attempting to generate a ticket for the GlobalProtect portal caused Panorama to restart unexpectedly with the error message tpl is invalid.
## PAN-318619
Fixed an issue where Geneve ingress traffic did not use the correct public IP address for return traffic.
## PAN-317867
Fixed an issue where Panorama became inaccessible and a manual reboot was required to restore access. This occurred due rapid increase in memory usage on the reportd process, which led to OOM events.
## PAN-314512
Fixed an issue where the GlobalProtect portal became inaccessible when the dataplane was configured with a DHCP assigned IP address.
## PAN-313700
Fixed an issue where an unexpected reboot occurred when Inline Cloud Analysis was enabled in an Anti-Spyware and Vulnerability profile.
File diff suppressed because it is too large Load Diff
+633
View File
@@ -0,0 +1,633 @@
{
"schema": 1,
"nodes": [
"[\"GlobalProtect\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.3\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.3\"]>[\"GlobalProtect\",\"6\",\"6.3\",\"6.3.1.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.3\"]>[\"GlobalProtect\",\"6\",\"6.3\",\"6.3.2.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.3\"]>[\"GlobalProtect\",\"6\",\"6.3\",\"6.3.3-h1.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.3\"]>[\"GlobalProtect\",\"6\",\"6.3\",\"6.3.3-h11.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.3\"]>[\"GlobalProtect\",\"6\",\"6.3\",\"6.3.3-h12.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.3\"]>[\"GlobalProtect\",\"6\",\"6.3\",\"6.3.3-h2.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.3\"]>[\"GlobalProtect\",\"6\",\"6.3\",\"6.3.3-h3.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.3\"]>[\"GlobalProtect\",\"6\",\"6.3\",\"6.3.3-h4.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.3\"]>[\"GlobalProtect\",\"6\",\"6.3\",\"6.3.3-h6.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.3\"]>[\"GlobalProtect\",\"6\",\"6.3\",\"6.3.3-h7.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.3\"]>[\"GlobalProtect\",\"6\",\"6.3\",\"6.3.3-h8.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.3\"]>[\"GlobalProtect\",\"6\",\"6.3\",\"6.3.3-h9.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.3\"]>[\"GlobalProtect\",\"6\",\"6.3\",\"6.3.3.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.2\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.2\"]>[\"GlobalProtect\",\"6\",\"6.2\",\"6.2.1.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.2\"]>[\"GlobalProtect\",\"6\",\"6.2\",\"6.2.2.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.2\"]>[\"GlobalProtect\",\"6\",\"6.2\",\"6.2.3-c287.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.2\"]>[\"GlobalProtect\",\"6\",\"6.2\",\"6.2.3.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.2\"]>[\"GlobalProtect\",\"6\",\"6.2\",\"6.2.4.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.2\"]>[\"GlobalProtect\",\"6\",\"6.2\",\"6.2.5.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.2\"]>[\"GlobalProtect\",\"6\",\"6.2\",\"6.2.6-c857.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.2\"]>[\"GlobalProtect\",\"6\",\"6.2\",\"6.2.6.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.2\"]>[\"GlobalProtect\",\"6\",\"6.2\",\"6.2.7.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.2\"]>[\"GlobalProtect\",\"6\",\"6.2\",\"6.2.8-h1.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.2\"]>[\"GlobalProtect\",\"6\",\"6.2\",\"6.2.8-h11.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.2\"]>[\"GlobalProtect\",\"6\",\"6.2\",\"6.2.8-h2.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.2\"]>[\"GlobalProtect\",\"6\",\"6.2\",\"6.2.8-h3.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.2\"]>[\"GlobalProtect\",\"6\",\"6.2\",\"6.2.8-h4.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.2\"]>[\"GlobalProtect\",\"6\",\"6.2\",\"6.2.8-h5.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.2\"]>[\"GlobalProtect\",\"6\",\"6.2\",\"6.2.8-h6.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.2\"]>[\"GlobalProtect\",\"6\",\"6.2\",\"6.2.8-h7.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.2\"]>[\"GlobalProtect\",\"6\",\"6.2\",\"6.2.8-h9.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.2\"]>[\"GlobalProtect\",\"6\",\"6.2\",\"6.2.8.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.1\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.1\"]>[\"GlobalProtect\",\"6\",\"6.1\",\"6.1.1.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.1\"]>[\"GlobalProtect\",\"6\",\"6.1\",\"6.1.2.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.1\"]>[\"GlobalProtect\",\"6\",\"6.1\",\"6.1.3.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.1\"]>[\"GlobalProtect\",\"6\",\"6.1\",\"6.1.4.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.1\"]>[\"GlobalProtect\",\"6\",\"6.1\",\"6.1.5.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.1\"]>[\"GlobalProtect\",\"6\",\"6.1\",\"6.1.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.0\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.0\"]>[\"GlobalProtect\",\"6\",\"6.0\",\"6.0.1.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.0\"]>[\"GlobalProtect\",\"6\",\"6.0\",\"6.0.10-814.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.0\"]>[\"GlobalProtect\",\"6\",\"6.0\",\"6.0.10-c826.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.0\"]>[\"GlobalProtect\",\"6\",\"6.0\",\"6.0.10.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.0\"]>[\"GlobalProtect\",\"6\",\"6.0\",\"6.0.11.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.0\"]>[\"GlobalProtect\",\"6\",\"6.0\",\"6.0.3.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.0\"]>[\"GlobalProtect\",\"6\",\"6.0\",\"6.0.4-c26.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.0\"]>[\"GlobalProtect\",\"6\",\"6.0\",\"6.0.4.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.0\"]>[\"GlobalProtect\",\"6\",\"6.0\",\"6.0.5.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.0\"]>[\"GlobalProtect\",\"6\",\"6.0\",\"6.0.7.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.0\"]>[\"GlobalProtect\",\"6\",\"6.0\",\"6.0.8.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"6\"]>[\"GlobalProtect\",\"6\",\"6.0\"]>[\"GlobalProtect\",\"6\",\"6.0\",\"6.0.md\"]",
"[\"GlobalProtect\"]>[\"GlobalProtect\",\"5\"]",
"[\"GlobalProtect-Android\"]",
"[\"GlobalProtect-Android\"]>[\"GlobalProtect-Android\",\"6.0.1.md\"]",
"[\"GlobalProtect-Android\"]>[\"GlobalProtect-Android\",\"6.0.12.md\"]",
"[\"GlobalProtect-Android\"]>[\"GlobalProtect-Android\",\"6.0.2.md\"]",
"[\"GlobalProtect-Android\"]>[\"GlobalProtect-Android\",\"6.0.3.md\"]",
"[\"GlobalProtect-Android\"]>[\"GlobalProtect-Android\",\"6.0.4.md\"]",
"[\"GlobalProtect-Android\"]>[\"GlobalProtect-Android\",\"6.0.5.md\"]",
"[\"GlobalProtect-Android\"]>[\"GlobalProtect-Android\",\"6.1.0.md\"]",
"[\"GlobalProtect-Android\"]>[\"GlobalProtect-Android\",\"6.1.10.md\"]",
"[\"GlobalProtect-Android\"]>[\"GlobalProtect-Android\",\"6.1.11.md\"]",
"[\"GlobalProtect-Android\"]>[\"GlobalProtect-Android\",\"6.1.12.md\"]",
"[\"GlobalProtect-Android\"]>[\"GlobalProtect-Android\",\"6.1.4.md\"]",
"[\"GlobalProtect-Android\"]>[\"GlobalProtect-Android\",\"6.1.5.md\"]",
"[\"GlobalProtect-Android\"]>[\"GlobalProtect-Android\",\"6.1.6.md\"]",
"[\"GlobalProtect-Android\"]>[\"GlobalProtect-Android\",\"6.1.7.md\"]",
"[\"GlobalProtect-Android\"]>[\"GlobalProtect-Android\",\"6.1.9.md\"]",
"[\"GlobalProtect-Android\"]>[\"GlobalProtect-Android\",\"6.1.md\"]",
"[\"GlobalProtect-iOS\"]",
"[\"GlobalProtect-iOS\"]>[\"GlobalProtect-iOS\",\"6.0.12.md\"]",
"[\"GlobalProtect-iOS\"]>[\"GlobalProtect-iOS\",\"6.0.2.md\"]",
"[\"GlobalProtect-iOS\"]>[\"GlobalProtect-iOS\",\"6.0.4.md\"]",
"[\"GlobalProtect-iOS\"]>[\"GlobalProtect-iOS\",\"6.0.5.md\"]",
"[\"GlobalProtect-iOS\"]>[\"GlobalProtect-iOS\",\"6.0.6.md\"]",
"[\"GlobalProtect-iOS\"]>[\"GlobalProtect-iOS\",\"6.1.0.md\"]",
"[\"GlobalProtect-iOS\"]>[\"GlobalProtect-iOS\",\"6.1.10.md\"]",
"[\"GlobalProtect-iOS\"]>[\"GlobalProtect-iOS\",\"6.1.11.md\"]",
"[\"GlobalProtect-iOS\"]>[\"GlobalProtect-iOS\",\"6.1.4.md\"]",
"[\"GlobalProtect-iOS\"]>[\"GlobalProtect-iOS\",\"6.1.5.md\"]",
"[\"GlobalProtect-iOS\"]>[\"GlobalProtect-iOS\",\"6.1.6.md\"]",
"[\"GlobalProtect-iOS\"]>[\"GlobalProtect-iOS\",\"6.1.7.md\"]",
"[\"GlobalProtect-iOS\"]>[\"GlobalProtect-iOS\",\"6.1.9.md\"]",
"[\"GlobalProtect-iOS\"]>[\"GlobalProtect-iOS\",\"6.1.md\"]",
"[\"GlobalProtect-Linux\"]",
"[\"GlobalProtect-Linux\"]>[\"GlobalProtect-Linux\",\"6\"]",
"[\"GlobalProtect-Linux\"]>[\"GlobalProtect-Linux\",\"6\"]>[\"GlobalProtect-Linux\",\"6\",\"6.3\"]",
"[\"GlobalProtect-Linux\"]>[\"GlobalProtect-Linux\",\"6\"]>[\"GlobalProtect-Linux\",\"6\",\"6.3\"]>[\"GlobalProtect-Linux\",\"6\",\"6.3\",\"6.3.3-h1.md\"]",
"[\"GlobalProtect-Linux\"]>[\"GlobalProtect-Linux\",\"6\"]>[\"GlobalProtect-Linux\",\"6\",\"6.3\"]>[\"GlobalProtect-Linux\",\"6\",\"6.3\",\"6.3.3.md\"]",
"[\"GlobalProtect-Linux\"]>[\"GlobalProtect-Linux\",\"6\"]>[\"GlobalProtect-Linux\",\"6\",\"6.2\"]",
"[\"GlobalProtect-Linux\"]>[\"GlobalProtect-Linux\",\"6\"]>[\"GlobalProtect-Linux\",\"6\",\"6.2\"]>[\"GlobalProtect-Linux\",\"6\",\"6.2\",\"6.2.0.md\"]",
"[\"GlobalProtect-Linux\"]>[\"GlobalProtect-Linux\",\"6\"]>[\"GlobalProtect-Linux\",\"6\",\"6.2\"]>[\"GlobalProtect-Linux\",\"6\",\"6.2\",\"6.2.1.md\"]",
"[\"GlobalProtect-Linux\"]>[\"GlobalProtect-Linux\",\"6\"]>[\"GlobalProtect-Linux\",\"6\",\"6.2\"]>[\"GlobalProtect-Linux\",\"6\",\"6.2\",\"6.2.6.md\"]",
"[\"GlobalProtect-Linux\"]>[\"GlobalProtect-Linux\",\"6\"]>[\"GlobalProtect-Linux\",\"6\",\"6.2\"]>[\"GlobalProtect-Linux\",\"6\",\"6.2\",\"6.2.7.md\"]",
"[\"GlobalProtect-Linux\"]>[\"GlobalProtect-Linux\",\"6\"]>[\"GlobalProtect-Linux\",\"6\",\"6.2\"]>[\"GlobalProtect-Linux\",\"6\",\"6.2\",\"6.2.8.md\"]",
"[\"GlobalProtect-Linux\"]>[\"GlobalProtect-Linux\",\"6\"]>[\"GlobalProtect-Linux\",\"6\",\"6.2\"]>[\"GlobalProtect-Linux\",\"6\",\"6.2\",\"6.2.9.md\"]",
"[\"GlobalProtect-Linux\"]>[\"GlobalProtect-Linux\",\"6\"]>[\"GlobalProtect-Linux\",\"6\",\"6.1\"]",
"[\"GlobalProtect-Linux\"]>[\"GlobalProtect-Linux\",\"6\"]>[\"GlobalProtect-Linux\",\"6\",\"6.1\"]>[\"GlobalProtect-Linux\",\"6\",\"6.1\",\"6.1.1.md\"]",
"[\"GlobalProtect-Linux\"]>[\"GlobalProtect-Linux\",\"6\"]>[\"GlobalProtect-Linux\",\"6\",\"6.1\"]>[\"GlobalProtect-Linux\",\"6\",\"6.1\",\"6.1.3.md\"]",
"[\"GlobalProtect-Linux\"]>[\"GlobalProtect-Linux\",\"6\"]>[\"GlobalProtect-Linux\",\"6\",\"6.1\"]>[\"GlobalProtect-Linux\",\"6\",\"6.1\",\"6.1.4.md\"]",
"[\"GlobalProtect-Linux\"]>[\"GlobalProtect-Linux\",\"6\"]>[\"GlobalProtect-Linux\",\"6\",\"6.1\"]>[\"GlobalProtect-Linux\",\"6\",\"6.1\",\"6.1.5.md\"]",
"[\"GlobalProtect-Linux\"]>[\"GlobalProtect-Linux\",\"6\"]>[\"GlobalProtect-Linux\",\"6\",\"6.1\"]>[\"GlobalProtect-Linux\",\"6\",\"6.1\",\"6.1.md\"]",
"[\"PAN-OS\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"12\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"12\"]>[\"PAN-OS\",\"12\",\"12.1\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"12\"]>[\"PAN-OS\",\"12\",\"12.1\"]>[\"PAN-OS\",\"12\",\"12.1\",\"12.1.2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"12\"]>[\"PAN-OS\",\"12\",\"12.1\"]>[\"PAN-OS\",\"12\",\"12.1\",\"12.1.3-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"12\"]>[\"PAN-OS\",\"12\",\"12.1\"]>[\"PAN-OS\",\"12\",\"12.1\",\"12.1.3-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"12\"]>[\"PAN-OS\",\"12\",\"12.1\"]>[\"PAN-OS\",\"12\",\"12.1\",\"12.1.3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"12\"]>[\"PAN-OS\",\"12\",\"12.1\"]>[\"PAN-OS\",\"12\",\"12.1\",\"12.1.4-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"12\"]>[\"PAN-OS\",\"12\",\"12.1\"]>[\"PAN-OS\",\"12\",\"12.1\",\"12.1.4-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"12\"]>[\"PAN-OS\",\"12\",\"12.1\"]>[\"PAN-OS\",\"12\",\"12.1\",\"12.1.4-h5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"12\"]>[\"PAN-OS\",\"12\",\"12.1\"]>[\"PAN-OS\",\"12\",\"12.1\",\"12.1.4-h6.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"12\"]>[\"PAN-OS\",\"12\",\"12.1\"]>[\"PAN-OS\",\"12\",\"12.1\",\"12.1.4-h7.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"12\"]>[\"PAN-OS\",\"12\",\"12.1\"]>[\"PAN-OS\",\"12\",\"12.1\",\"12.1.4-h8.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"12\"]>[\"PAN-OS\",\"12\",\"12.1\"]>[\"PAN-OS\",\"12\",\"12.1\",\"12.1.4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"12\"]>[\"PAN-OS\",\"12\",\"12.1\"]>[\"PAN-OS\",\"12\",\"12.1\",\"12.1.5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"12\"]>[\"PAN-OS\",\"12\",\"12.1\"]>[\"PAN-OS\",\"12\",\"12.1\",\"12.1.6.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"12\"]>[\"PAN-OS\",\"12\",\"12.1\"]>[\"PAN-OS\",\"12\",\"12.1\",\"12.1.7-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"12\"]>[\"PAN-OS\",\"12\",\"12.1\"]>[\"PAN-OS\",\"12\",\"12.1\",\"12.1.7-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"12\"]>[\"PAN-OS\",\"12\",\"12.1\"]>[\"PAN-OS\",\"12\",\"12.1\",\"12.1.7.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"12\"]>[\"PAN-OS\",\"12\",\"12.1\"]>[\"PAN-OS\",\"12\",\"12.1\",\"12.1.8.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.0-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.0.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.1-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.10-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.10-h10.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.10-h12.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.10-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.10-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.10-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.10-h5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.10-h6.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.10-h7.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.10-h8.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.10.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.11.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.12.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.13.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.2-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.2-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.3-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.3-h5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.4-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.4-h10.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.4-h11.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.4-h12.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.4-h14.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.4-h15.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.4-h17.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.4-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.4-h20.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.4-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.4-h5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.4-h6.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.4-h7.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.4-h8.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.4-h9.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.5-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.6.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.7-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.7-h10.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.7-h11.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.7-h12.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.7-h13.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.7-h14.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.7-h15.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.7-h17.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.7-h18.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.7-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.7-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.7-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.7-h7.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.7-h8.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.7.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.8.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.2\"]>[\"PAN-OS\",\"11\",\"11.2\",\"11.2.9.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.0-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.0-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.0-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.0-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.0.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.1-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.1-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.10-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.10-h10.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.10-h12.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.10-h21.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.10-h25.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.10-h26.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.10-h27.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.10-h28.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.10-h30.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.10-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.10-h5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.10-h7.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.10-h9.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.10.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.11.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.12.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.13-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.13-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.13-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.13-h5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.13-h6.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.13-h7.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.13-h8.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.13-h9.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.13.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.14.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.15.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.16.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.2-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.2-h12.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.2-h14.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.2-h15.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.2-h16.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.2-h18.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.2-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.2-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.2-h9.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.3-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.3-h10.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.3-h11.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.3-h13.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.3-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.3-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.3-h6.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.4-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.4-h13.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.4-h15.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.4-h16.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.4-h17.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.4-h18.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.4-h25.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.4-h27.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.4-h32.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.4-h33.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.4-h34.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.4-h35.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.4-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.4-h7.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.4-h9.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.5-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.6-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.6-h10.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.6-h14.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.6-h17.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.6-h19.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.6-h20.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.6-h21.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.6-h22.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.6-h23.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.6-h25.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.6-h29.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.6-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.6-h32.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.6-h33.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.6-h34.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.6-h35.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.6-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.6-h5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.6-h6.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.6-h7.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.6.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.7-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.7-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.7-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.7-h6.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.7-h7.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.7-h8.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.7.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.8.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.1\"]>[\"PAN-OS\",\"11\",\"11.1\",\"11.1.9.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.0-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.0-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.0-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.0-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.0.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.1-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.1-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.1-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.1-h5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.2-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.2-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.2-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.2-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.2-h5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.3-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.3-h10.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.3-h12.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.3-h13.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.3-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.3-h5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.4-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.4-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.4-h5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.4-h6.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.5-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.5-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.6-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"11\"]>[\"PAN-OS\",\"11\",\"11.0\"]>[\"PAN-OS\",\"11\",\"11.0\",\"11.0.6.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.0-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.0-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.0-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.0-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.0.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.1-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.1-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.1-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.10-h10.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.10-h14.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.10-h17.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.10-h18.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.10-h21.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.10-h30.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.10-h31.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.10-h36.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.10-h37.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.10-h39.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.10-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.10-h5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.10-h7.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.10-h9.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.10.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.11-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.11-h10.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.11-h12.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.11-h6.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.11.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.12.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.13-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.13-h10.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.13-h16.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.13-h18.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.13-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.13-h21.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.13-h22.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.13-h23.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.13-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.13-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.13-h5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.13-h7.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.13.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.14-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.15.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.16-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.16-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.16-h6.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.16-h7.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.16-h8.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.16-h9.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.16.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.18-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.18-h5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.18-h6.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.18-h7.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.18-h8.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.18.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.2-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.2-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.2-h5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.2-h6.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.3-h11.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.3-h12.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.3-h13.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.3-h14.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.3-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.3-h9.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.4-h10.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.4-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.4-h32.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.4-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.5-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.5-h6.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.6-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.6-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.6.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.7-h12.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.7-h16.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.7-h18.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.7-h19.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.7-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.7-h34.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.7-h35.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.7-h36.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.7.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.8-h13.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.8-h18.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.8.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.9-h11.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.9-h14.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.9-h16.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.9-h18.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.9-h9.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.2\"]>[\"PAN-OS\",\"10\",\"10.2\",\"10.2.9.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.0.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.10-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.10-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.10.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.11-h10.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.11-h5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.11.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.12-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.13-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.13-h5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.13.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.14-h10.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.14-h11.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.14-h13.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.14-h14.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.14-h16.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.14-h19.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.14-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.14-h20.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.14-h6.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.14-h8.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.14.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.3-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.4-h6.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.5-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.5-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.6-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.6-h6.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.6-h7.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.6-h9.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.6.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.7-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.7.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.8-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.8-h7.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.8-h8.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.8.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.1\"]>[\"PAN-OS\",\"10\",\"10.1\",\"10.1.9-h6.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.0\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.0\"]>[\"PAN-OS\",\"10\",\"10.0\",\"10.0.1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.0\"]>[\"PAN-OS\",\"10\",\"10.0\",\"10.0.10.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.0\"]>[\"PAN-OS\",\"10\",\"10.0\",\"10.0.11-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.0\"]>[\"PAN-OS\",\"10\",\"10.0\",\"10.0.11-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.0\"]>[\"PAN-OS\",\"10\",\"10.0\",\"10.0.11-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.0\"]>[\"PAN-OS\",\"10\",\"10.0\",\"10.0.12-h5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.0\"]>[\"PAN-OS\",\"10\",\"10.0\",\"10.0.2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.0\"]>[\"PAN-OS\",\"10\",\"10.0\",\"10.0.3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.0\"]>[\"PAN-OS\",\"10\",\"10.0\",\"10.0.4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.0\"]>[\"PAN-OS\",\"10\",\"10.0\",\"10.0.5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.0\"]>[\"PAN-OS\",\"10\",\"10.0\",\"10.0.6.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.0\"]>[\"PAN-OS\",\"10\",\"10.0\",\"10.0.7.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.0\"]>[\"PAN-OS\",\"10\",\"10.0\",\"10.0.8-h10.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.0\"]>[\"PAN-OS\",\"10\",\"10.0\",\"10.0.8-h11.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.0\"]>[\"PAN-OS\",\"10\",\"10.0\",\"10.0.8-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.0\"]>[\"PAN-OS\",\"10\",\"10.0\",\"10.0.8-h8.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.0\"]>[\"PAN-OS\",\"10\",\"10.0\",\"10.0.8.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"10\"]>[\"PAN-OS\",\"10\",\"10.0\"]>[\"PAN-OS\",\"10\",\"10.0\",\"10.0.9.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.0.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.10.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.11-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.11-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.11-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.11-h5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.11.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.12-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.12-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.12-h6.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.12-h7.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.12.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.13-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.13-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.13-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.13-h5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.13.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.14-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.14-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.14-h7.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.14-h8.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.14.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.15-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.15.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.16-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.16-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.16-h5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.16.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.17-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.17.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.18.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.19.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.2-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.3-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.6.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.7.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.8.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.1\"]>[\"PAN-OS\",\"9\",\"9.1\",\"9.1.9.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.0.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.10.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.11.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.12.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.13.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.14-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.14-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.14.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.15.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.16-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.16-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.16-h5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.16-h6.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.16-h7.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.16.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.17-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.17-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.17-h5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.17.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.2-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.3-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.3-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.5-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.6.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.7.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.8.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.9-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"9\"]>[\"PAN-OS\",\"9\",\"9.0\"]>[\"PAN-OS\",\"9\",\"9.0\",\"9.0.9.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.0.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.10.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.11.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.12.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.13.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.14-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.14.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.15-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.15.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.16.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.17.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.18.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.19.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.20-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.20.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.21-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.21-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.21-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.21.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.22.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.23-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.23.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.24-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.24-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.24.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.25-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.25-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.25-h3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.25.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.26-h1.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.26.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.3.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.4-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.6-h2.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.6.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.7.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.8-h5.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.8.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.9-h4.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.9.md\"]",
"[\"PAN-OS\"]>[\"PAN-OS\",\"8\"]>[\"PAN-OS\",\"8\",\"8.1\"]>[\"PAN-OS\",\"8\",\"8.1\",\"8.1.md\"]",
"[\"Prisma Access Agent\"]",
"[\"Prisma Access Agent\"]>[\"Prisma Access Agent\",\"26\"]",
"[\"Prisma Access Agent\"]>[\"Prisma Access Agent\",\"26\"]>[\"Prisma Access Agent\",\"26\",\"26.1-Linux.md\"]",
"[\"Prisma Access Agent\"]>[\"Prisma Access Agent\",\"26\"]>[\"Prisma Access Agent\",\"26\",\"26.1.1.md\"]",
"[\"Prisma Access Agent\"]>[\"Prisma Access Agent\",\"26\"]>[\"Prisma Access Agent\",\"26\",\"26.1.2.md\"]",
"[\"Prisma Access Agent\"]>[\"Prisma Access Agent\",\"25\"]",
"[\"Prisma Access Agent\"]>[\"Prisma Access Agent\",\"25\"]>[\"Prisma Access Agent\",\"25\",\"25.1.md\"]",
"[\"Prisma Access Agent\"]>[\"Prisma Access Agent\",\"25\"]>[\"Prisma Access Agent\",\"25\",\"25.3.0-iOS.md\"]",
"[\"Prisma Access Agent\"]>[\"Prisma Access Agent\",\"25\"]>[\"Prisma Access Agent\",\"25\",\"25.3.0.11-Android.md\"]",
"[\"Prisma Access Agent\"]>[\"Prisma Access Agent\",\"25\"]>[\"Prisma Access Agent\",\"25\",\"25.3.1.md\"]",
"[\"Prisma Access Agent\"]>[\"Prisma Access Agent\",\"25\"]>[\"Prisma Access Agent\",\"25\",\"25.3.md\"]",
"[\"Prisma Access Agent\"]>[\"Prisma Access Agent\",\"25\"]>[\"Prisma Access Agent\",\"25\",\"25.4.md\"]",
"[\"Prisma Access Agent\"]>[\"Prisma Access Agent\",\"25\"]>[\"Prisma Access Agent\",\"25\",\"25.6.1.md\"]",
"[\"Prisma Access Agent\"]>[\"Prisma Access Agent\",\"25\"]>[\"Prisma Access Agent\",\"25\",\"25.6.2.md\"]",
"[\"Prisma Access Agent\"]>[\"Prisma Access Agent\",\"25\"]>[\"Prisma Access Agent\",\"25\",\"25.6.md\"]",
"[\"Prisma Access Agent\"]>[\"Prisma Access Agent\",\"25\"]>[\"Prisma Access Agent\",\"25\",\"25.7-Linux.md\"]",
"[\"Prisma Access Agent\"]>[\"Prisma Access Agent\",\"25\"]>[\"Prisma Access Agent\",\"25\",\"25.7.1.md\"]",
"[\"Prisma Access Agent\"]>[\"Prisma Access Agent\",\"25\"]>[\"Prisma Access Agent\",\"25\",\"25.7.md\"]"
]
}
+32 -4
View File
@@ -12,9 +12,14 @@
"12.1.4-h3.md", "12.1.4-h3.md",
"12.1.4-h5.md", "12.1.4-h5.md",
"12.1.4-h6.md", "12.1.4-h6.md",
"12.1.4-h7.md",
"12.1.4-h8.md",
"12.1.5.md", "12.1.5.md",
"12.1.6.md", "12.1.6.md",
"12.1.7.md" "12.1.7.md",
"12.1.7-h1.md",
"12.1.7-h2.md",
"12.1.8.md"
], ],
"known": [ "known": [
"12.1.2.md", "12.1.2.md",
@@ -53,6 +58,7 @@
"11.2.4-h14.md", "11.2.4-h14.md",
"11.2.4-h15.md", "11.2.4-h15.md",
"11.2.4-h17.md", "11.2.4-h17.md",
"11.2.4-h20.md",
"11.2.5.md", "11.2.5.md",
"11.2.5-h2.md", "11.2.5-h2.md",
"11.2.6.md", "11.2.6.md",
@@ -69,6 +75,8 @@
"11.2.7-h13.md", "11.2.7-h13.md",
"11.2.7-h14.md", "11.2.7-h14.md",
"11.2.7-h15.md", "11.2.7-h15.md",
"11.2.7-h17.md",
"11.2.7-h18.md",
"11.2.8.md", "11.2.8.md",
"11.2.9.md", "11.2.9.md",
"11.2.10.md", "11.2.10.md",
@@ -81,8 +89,10 @@
"11.2.10-h7.md", "11.2.10-h7.md",
"11.2.10-h8.md", "11.2.10-h8.md",
"11.2.10-h10.md", "11.2.10-h10.md",
"11.2.10-h12.md",
"11.2.11.md", "11.2.11.md",
"11.2.12.md" "11.2.12.md",
"11.2.13.md"
], ],
"known": [ "known": [
"11.2.0.md", "11.2.0.md",
@@ -141,6 +151,8 @@
"11.1.4-h27.md", "11.1.4-h27.md",
"11.1.4-h32.md", "11.1.4-h32.md",
"11.1.4-h33.md", "11.1.4-h33.md",
"11.1.4-h34.md",
"11.1.4-h35.md",
"11.1.5.md", "11.1.5.md",
"11.1.5-h1.md", "11.1.5-h1.md",
"11.1.6.md", "11.1.6.md",
@@ -163,11 +175,14 @@
"11.1.6-h32.md", "11.1.6-h32.md",
"11.1.6-h33.md", "11.1.6-h33.md",
"11.1.6-h34.md", "11.1.6-h34.md",
"11.1.6-h35.md",
"11.1.7.md", "11.1.7.md",
"11.1.7-h1.md", "11.1.7-h1.md",
"11.1.7-h2.md", "11.1.7-h2.md",
"11.1.7-h4.md", "11.1.7-h4.md",
"11.1.7-h6.md", "11.1.7-h6.md",
"11.1.7-h7.md",
"11.1.7-h8.md",
"11.1.8.md", "11.1.8.md",
"11.1.9.md", "11.1.9.md",
"11.1.10.md", "11.1.10.md",
@@ -183,6 +198,7 @@
"11.1.10-h26.md", "11.1.10-h26.md",
"11.1.10-h27.md", "11.1.10-h27.md",
"11.1.10-h28.md", "11.1.10-h28.md",
"11.1.10-h30.md",
"11.1.11.md", "11.1.11.md",
"11.1.12.md", "11.1.12.md",
"11.1.13.md", "11.1.13.md",
@@ -193,8 +209,10 @@
"11.1.13-h6.md", "11.1.13-h6.md",
"11.1.13-h7.md", "11.1.13-h7.md",
"11.1.13-h8.md", "11.1.13-h8.md",
"11.1.13-h9.md",
"11.1.14.md", "11.1.14.md",
"11.1.15.md" "11.1.15.md",
"11.1.16.md"
], ],
"known": [ "known": [
"11.1.0.md", "11.1.0.md",
@@ -300,6 +318,8 @@
"10.2.7-h18.md", "10.2.7-h18.md",
"10.2.7-h19.md", "10.2.7-h19.md",
"10.2.7-h34.md", "10.2.7-h34.md",
"10.2.7-h35.md",
"10.2.7-h36.md",
"10.2.8.md", "10.2.8.md",
"10.2.8-h13.md", "10.2.8-h13.md",
"10.2.8-h18.md", "10.2.8-h18.md",
@@ -322,6 +342,8 @@
"10.2.10-h30.md", "10.2.10-h30.md",
"10.2.10-h31.md", "10.2.10-h31.md",
"10.2.10-h36.md", "10.2.10-h36.md",
"10.2.10-h37.md",
"10.2.10-h39.md",
"10.2.11.md", "10.2.11.md",
"10.2.11-h1.md", "10.2.11-h1.md",
"10.2.11-h6.md", "10.2.11-h6.md",
@@ -339,6 +361,8 @@
"10.2.13-h16.md", "10.2.13-h16.md",
"10.2.13-h18.md", "10.2.13-h18.md",
"10.2.13-h21.md", "10.2.13-h21.md",
"10.2.13-h22.md",
"10.2.13-h23.md",
"10.2.14-h1.md", "10.2.14-h1.md",
"10.2.15.md", "10.2.15.md",
"10.2.16.md", "10.2.16.md",
@@ -346,10 +370,14 @@
"10.2.16-h4.md", "10.2.16-h4.md",
"10.2.16-h6.md", "10.2.16-h6.md",
"10.2.16-h7.md", "10.2.16-h7.md",
"10.2.16-h8.md",
"10.2.16-h9.md",
"10.2.18.md", "10.2.18.md",
"10.2.18-h1.md", "10.2.18-h1.md",
"10.2.18-h5.md", "10.2.18-h5.md",
"10.2.18-h6.md" "10.2.18-h6.md",
"10.2.18-h7.md",
"10.2.18-h8.md"
], ],
"known": [ "known": [
"10.2.0.md", "10.2.0.md",
+1
View File
@@ -8,6 +8,7 @@
<link rel="icon" type="image/svg+xml" href="icons/favicon.svg"> <link rel="icon" type="image/svg+xml" href="icons/favicon.svg">
<link rel="apple-touch-icon" href="apple-touch-icon.png"> <link rel="apple-touch-icon" href="apple-touch-icon.png">
<link rel="apple-touch-icon-precomposed" href="apple-touch-icon-precomposed.png"> <link rel="apple-touch-icon-precomposed" href="apple-touch-icon-precomposed.png">
<link rel="stylesheet" href="vendor/checkbox-tree/checkbox-tree.css">
<link rel="stylesheet" href="styles.css"> <link rel="stylesheet" href="styles.css">
</head> </head>
<body> <body>
+144
View File
@@ -0,0 +1,144 @@
import { getSortedKeys } from './sort.js';
export function expandFilePrefixLevel(root) {
if (!root || typeof root !== 'object') {
return root;
}
return transformNode(root);
}
function transformNode(node) {
if (!node || typeof node !== 'object' || Array.isArray(node)) {
return node;
}
const hasIssueArrays = Object.prototype.hasOwnProperty.call(node, 'addressed') ||
Object.prototype.hasOwnProperty.call(node, 'known');
const childKeys = Object.keys(node).filter(key => key !== 'addressed' && key !== 'known');
const transformedChildren = Object.fromEntries(
childKeys.map(key => [key, transformNode(node[key])])
);
if (!hasIssueArrays) {
return transformedChildren;
}
const addressed = Array.isArray(node.addressed) ? node.addressed : [];
const known = Array.isArray(node.known) ? node.known : [];
if (addressed.length + known.length === 0) {
return childKeys.length > 0 ? transformedChildren : { addressed: [], known: [] };
}
const groupedByPrefix = {};
for (const [issueType, files] of [['addressed', addressed], ['known', known]]) {
for (const fileName of files) {
const prefix = getFilePrefix(fileName);
groupedByPrefix[prefix] ??= { addressed: [], known: [] };
groupedByPrefix[prefix][issueType].push(fileName);
}
}
const transformedIssueFiles = groupHotfixPrefixes(groupedByPrefix);
return childKeys.length === 0
? transformedIssueFiles
: mergeTreeNodes(transformedChildren, transformedIssueFiles);
}
function groupHotfixPrefixes(groupedByPrefix) {
const byFamily = {};
for (const prefix of Object.keys(groupedByPrefix)) {
const family = getReleaseFamily(prefix);
byFamily[family] ??= [];
byFamily[family].push(prefix);
}
const result = {};
for (const [family, members] of Object.entries(byFamily)) {
const hasHotfixes = members.length > 1 || members.some(member => member !== family);
if (!hasHotfixes && members[0] === family) {
result[family] = groupedByPrefix[family];
} else {
result[family] = Object.fromEntries(
members.map(member => [member, groupedByPrefix[member]])
);
}
}
return result;
}
function getReleaseFamily(prefix) {
return String(prefix || '').replace(/-h\d+$/i, '');
}
function getFilePrefix(fileName) {
const baseName = String(fileName || '');
const underscoreIndex = baseName.indexOf('_');
if (underscoreIndex <= 0) {
return baseName.replace(/\.json$/i, '') || 'unknown';
}
return baseName.slice(0, underscoreIndex);
}
function mergeTreeNodes(left, right) {
if (!isObjectNode(left)) return right;
if (!isObjectNode(right)) return left;
const merged = { ...left };
for (const [key, rightValue] of Object.entries(right)) {
const leftValue = merged[key];
if (key === 'addressed' || key === 'known') {
const leftFiles = Array.isArray(leftValue) ? leftValue : [];
const rightFiles = Array.isArray(rightValue) ? rightValue : [];
merged[key] = Array.from(new Set([...leftFiles, ...rightFiles]));
} else if (isObjectNode(leftValue) && isObjectNode(rightValue)) {
merged[key] = mergeTreeNodes(leftValue, rightValue);
} else {
merged[key] = rightValue;
}
}
return merged;
}
function isObjectNode(value) {
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
}
export function toTreeNodes(value, path = []) {
return getSortedKeys(value)
.filter(key => key !== 'addressed' && key !== 'known')
.map(key => {
const childValue = value[key];
const childPath = [...path, key];
const isObject = childValue && typeof childValue === 'object';
const hasFiles = isObject && ('addressed' in childValue || 'known' in childValue);
const children = isObject ? toTreeNodes(childValue, childPath) : [];
return {
id: JSON.stringify(childPath),
label: key,
selectable: hasFiles || children.length > 0,
metadata: {
path: childPath,
addressed: hasFiles && Array.isArray(childValue.addressed) ? childValue.addressed : [],
known: hasFiles && Array.isArray(childValue.known) ? childValue.known : []
},
...(children.length > 0 ? { children } : {})
};
});
}
export function enumerateTreePaths(nodes) {
const paths = [];
function visit(children, parentPath = '') {
for (const node of children) {
const path = parentPath ? `${parentPath}>${node.id}` : node.id;
paths.push(path);
visit(node.children ?? [], path);
}
}
visit(nodes);
return paths;
}
export function buildProductTree(products) {
return toTreeNodes(expandFilePrefixLevel(products));
}
+3 -150
View File
@@ -1,6 +1,7 @@
import { loadIssuesForCheckedPaths } from './issues.js'; import { loadIssuesForCheckedPaths } from './issues.js';
import { getCheckedFileRefs, initializeTree, renderProductTree, restoreTreeState } from './tree.js'; import { getCheckedFileRefs, initializeTree, renderProductTree, restoreTreeState } from './tree.js';
import { applyIssueSearchFilter, getIssueTypeFilters, initializeUI } from './ui.js'; import { applyIssueSearchFilter, getIssueTypeFilters, initializeUI } from './ui.js';
import { expandFilePrefixLevel } from './product-tree-model.js';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
initializeUI({ onSelectionChange: refreshIssuesForCurrentSelection }); initializeUI({ onSelectionChange: refreshIssuesForCurrentSelection });
@@ -21,159 +22,11 @@ function refreshIssuesForCurrentSelection() {
function loadProductTree() { function loadProductTree() {
fetch('data/products.json') fetch('data/products.json')
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(async data => {
productsData = expandFilePrefixLevel(data); productsData = expandFilePrefixLevel(data);
renderProductTree(productsData); await renderProductTree(productsData);
restoreTreeState(); restoreTreeState();
refreshIssuesForCurrentSelection(); refreshIssuesForCurrentSelection();
}) })
.catch(error => console.error('Error loading products:', error)); .catch(error => console.error('Error loading products:', error));
} }
function expandFilePrefixLevel(root) {
if (!root || typeof root !== 'object') {
return root;
}
return transformNode(root);
}
function transformNode(node) {
if (!node || typeof node !== 'object' || Array.isArray(node)) {
return node;
}
const hasIssueArrays = Object.prototype.hasOwnProperty.call(node, 'addressed') ||
Object.prototype.hasOwnProperty.call(node, 'known');
const childKeys = Object.keys(node).filter(key => key !== 'addressed' && key !== 'known');
const transformedChildren = {};
childKeys.forEach(key => {
transformedChildren[key] = transformNode(node[key]);
});
if (!hasIssueArrays) {
return transformedChildren;
}
const addressed = Array.isArray(node.addressed) ? node.addressed : [];
const known = Array.isArray(node.known) ? node.known : [];
const allFiles = [...addressed, ...known];
if (allFiles.length === 0) {
if (childKeys.length > 0) {
return transformedChildren;
}
return {
addressed: [],
known: []
};
}
const groupedByPrefix = {};
addressed.forEach(fileName => {
const prefix = getFilePrefix(fileName);
if (!groupedByPrefix[prefix]) {
groupedByPrefix[prefix] = { addressed: [], known: [] };
}
groupedByPrefix[prefix].addressed.push(fileName);
});
known.forEach(fileName => {
const prefix = getFilePrefix(fileName);
if (!groupedByPrefix[prefix]) {
groupedByPrefix[prefix] = { addressed: [], known: [] };
}
groupedByPrefix[prefix].known.push(fileName);
});
const transformedIssueFiles = groupHotfixPrefixes(groupedByPrefix);
if (childKeys.length === 0) {
return transformedIssueFiles;
}
return mergeTreeNodes(transformedChildren, transformedIssueFiles);
}
function groupHotfixPrefixes(groupedByPrefix) {
const byFamily = {};
Object.keys(groupedByPrefix).forEach(prefix => {
const family = getReleaseFamily(prefix);
if (!byFamily[family]) {
byFamily[family] = [];
}
byFamily[family].push(prefix);
});
const result = {};
Object.keys(byFamily).forEach(family => {
const members = byFamily[family];
const hasHotfixes = members.length > 1 || members.some(member => member !== family);
if (!hasHotfixes && members[0] === family) {
result[family] = groupedByPrefix[family];
return;
}
const branch = {};
members.forEach(member => {
branch[member] = groupedByPrefix[member];
});
result[family] = branch;
});
return result;
}
function getReleaseFamily(prefix) {
return String(prefix || '').replace(/-h\d+$/i, '');
}
function getFilePrefix(fileName) {
const baseName = String(fileName || '');
const underscoreIndex = baseName.indexOf('_');
if (underscoreIndex <= 0) {
return baseName.replace(/\.json$/i, '') || 'unknown';
}
return baseName.slice(0, underscoreIndex);
}
function mergeTreeNodes(left, right) {
if (!isObjectNode(left)) {
return right;
}
if (!isObjectNode(right)) {
return left;
}
const merged = { ...left };
Object.keys(right).forEach(key => {
const leftValue = merged[key];
const rightValue = right[key];
if (key === 'addressed' || key === 'known') {
const leftFiles = Array.isArray(leftValue) ? leftValue : [];
const rightFiles = Array.isArray(rightValue) ? rightValue : [];
merged[key] = Array.from(new Set([...leftFiles, ...rightFiles]));
return;
}
if (isObjectNode(leftValue) && isObjectNode(rightValue)) {
merged[key] = mergeTreeNodes(leftValue, rightValue);
return;
}
merged[key] = rightValue;
});
return merged;
}
function isObjectNode(value) {
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
}
+51 -295
View File
@@ -1,85 +1,58 @@
import { getSortedKeys } from './sort.js'; import { CheckboxTree, loadManifest } from '../vendor/checkbox-tree/checkbox-tree.js';
import { toTreeNodes } from './product-tree-model.js';
const STORAGE_KEY = 'tree-state'; const STORAGE_KEY = 'tree-state-v2';
let productTree = null;
let treeContainer = null;
let onSelectionChangeHandler = null;
export function initializeTree(containerId, onSelectionChange) { export function initializeTree(containerId, onSelectionChange) {
treeContainer = document.getElementById(containerId); const container = document.getElementById(containerId);
onSelectionChangeHandler = onSelectionChange; if (!container) {
throw new Error(`Tree container not found: ${containerId}`);
} }
export function renderProductTree(productsData) { productTree?.destroy();
if (!treeContainer) { productTree = new CheckboxTree(container, {
throw new Error('Tree is not initialized. Call initializeTree first.'); storageKey: STORAGE_KEY,
initiallyCollapsed: true,
stateEncoding: 'tiered',
onSelectionChange: event => {
onSelectionChange(event);
pushTreeState();
} }
});
treeContainer.innerHTML = ''; container.addEventListener('click', event => {
if (event.target.closest('.checkbox-tree__toggle')) {
getSortedKeys(productsData).forEach(product => { pushTreeState();
const productNode = createTreeNode(product, productsData[product], [product]); }
treeContainer.appendChild(productNode); });
window.addEventListener('popstate', () => {
productTree?.restoreStateFromLocation();
onSelectionChange({
selectedIds: productTree?.getSelectedIds() ?? [],
selectedNodes: productTree?.getSelectedNodes() ?? []
});
}); });
} }
export async function renderProductTree(productsData) {
const manifestSource = await fetch('data/product-tree-manifest.json').then(response => response.json());
requireTree().options.manifest = loadManifest(manifestSource);
requireTree().setData(toTreeNodes(productsData));
}
export function getCheckedPaths() { export function getCheckedPaths() {
if (!treeContainer) { return productTree ? productTree.getSelectedNodes().map(node => node.metadata.path) : [];
return [];
}
const checkedBoxes = treeContainer.querySelectorAll('input[type="checkbox"]:checked');
return Array.from(checkedBoxes)
.map(cb => cb.dataset.path)
.filter(Boolean)
.map(pathText => {
try {
return JSON.parse(pathText);
} catch (error) {
console.error('Invalid checkbox path data:', pathText, error);
return null;
}
})
.filter(pathArray => Array.isArray(pathArray) && pathArray.length > 0);
} }
export function getCheckedFileRefs() { export function getCheckedFileRefs() {
if (!treeContainer) {
return {
addressedFiles: [],
knownFiles: []
};
}
const addressedFiles = []; const addressedFiles = [];
const knownFiles = []; const knownFiles = [];
const checkedBoxes = treeContainer.querySelectorAll('input[type="checkbox"]:checked[data-has-files="1"]');
checkedBoxes.forEach(cb => {
const pathText = cb.dataset.path;
if (!pathText) {
return;
}
let path;
try {
path = JSON.parse(pathText);
} catch (error) {
console.error('Invalid checkbox path data:', pathText, error);
return;
}
if (!Array.isArray(path) || path.length === 0) {
return;
}
productTree?.getSelectedNodes().forEach(node => {
const { path, addressed = [], known = [] } = node.metadata;
const productKey = path[0]; const productKey = path[0];
const addressed = parseJsonArray(cb.dataset.addressed);
addressed.forEach(fileName => addressedFiles.push({ productKey, fileName })); addressed.forEach(fileName => addressedFiles.push({ productKey, fileName }));
const known = parseJsonArray(cb.dataset.known);
known.forEach(fileName => knownFiles.push({ productKey, fileName })); known.forEach(fileName => knownFiles.push({ productKey, fileName }));
}); });
@@ -89,255 +62,38 @@ export function getCheckedFileRefs() {
}; };
} }
function createTreeNode(name, value, path) { // State restoration now happens when setData renders the generic tree.
const container = document.createElement('div'); export function restoreTreeState() {}
container.className = 'tree-item';
const isObjectValue = value && typeof value === 'object'; function requireTree() {
const hasFiles = isObjectValue && ('addressed' in value || 'known' in value); if (!productTree) {
const childKeys = isObjectValue throw new Error('Tree is not initialized. Call initializeTree first.');
? getSortedKeys(value).filter(key => key !== 'addressed' && key !== 'known') }
: []; return productTree;
const hasChildren = childKeys.length > 0;
if (hasChildren) {
container.classList.add('parent');
const toggle = document.createElement('span');
toggle.className = 'tree-toggle';
toggle.textContent = '▼';
toggle.style.cursor = 'pointer';
const label = document.createElement('label');
const checkbox = createCheckbox(path, hasFiles ? value : undefined);
label.appendChild(checkbox);
label.appendChild(document.createTextNode(name));
const childrenContainer = document.createElement('div');
childrenContainer.className = 'tree-children';
childKeys.forEach(childKey => {
const childNode = createTreeNode(childKey, value[childKey], [...path, childKey]);
childrenContainer.appendChild(childNode);
});
if (shouldStartCollapsed(value)) {
childrenContainer.classList.add('collapsed');
toggle.textContent = '▶';
} }
toggle.addEventListener('click', event => { function pushTreeState() {
event.stopPropagation(); if (!productTree?.options.manifest) {
childrenContainer.classList.toggle('collapsed'); return;
toggle.textContent = childrenContainer.classList.contains('collapsed') ? '▶' : '▼';
saveTreeState();
});
container.appendChild(toggle);
container.appendChild(label);
container.appendChild(childrenContainer);
return container;
} }
const url = new URL(window.location.href);
const label = document.createElement('label'); url.hash = productTree.serializeStateToFragment();
if (hasFiles) { if (url.href !== window.location.href) {
const checkbox = createCheckbox(path, value); history.pushState(null, '', url);
label.appendChild(checkbox);
} }
label.appendChild(document.createTextNode(name));
container.appendChild(label);
return container;
}
function shouldStartCollapsed(value) {
if (!value || typeof value !== 'object') {
return false;
}
const childKeys = Object.keys(value).filter(key => key !== 'addressed' && key !== 'known');
return childKeys.length > 0;
} }
function dedupeFileRefs(fileRefs) { function dedupeFileRefs(fileRefs) {
const seen = new Set(); const seen = new Set();
return fileRefs.filter(ref => { return fileRefs.filter(({ productKey, fileName }) => {
const productKey = String(ref && ref.productKey ? ref.productKey : '');
const fileName = String(ref && ref.fileName ? ref.fileName : '');
if (!productKey || !fileName) { if (!productKey || !fileName) {
return false; return false;
} }
const key = `${productKey}::${fileName}`; const key = `${productKey}::${fileName}`;
if (seen.has(key)) { if (seen.has(key)) {
return false; return false;
} }
seen.add(key); seen.add(key);
return true; return true;
}); });
} }
function createCheckbox(path, value) {
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.value = path.join('/');
checkbox.dataset.path = JSON.stringify(path);
if (value && typeof value === 'object' && ('addressed' in value || 'known' in value)) {
checkbox.dataset.hasFiles = '1';
checkbox.dataset.addressed = JSON.stringify(Array.isArray(value.addressed) ? value.addressed : []);
checkbox.dataset.known = JSON.stringify(Array.isArray(value.known) ? value.known : []);
}
checkbox.addEventListener('change', event => {
handleCheckboxChange(event);
});
return checkbox;
}
function parseJsonArray(value) {
if (!value) {
return [];
}
try {
const parsed = JSON.parse(value);
return Array.isArray(parsed) ? parsed : [];
} catch (error) {
console.error('Invalid checkbox file metadata:', value, error);
return [];
}
}
function handleCheckboxChange(event) {
const checkbox = event.target;
const container = checkbox.closest('.tree-item');
const childrenContainer = container.querySelector('.tree-children');
if (childrenContainer) {
const childCheckboxes = childrenContainer.querySelectorAll('input[type="checkbox"]');
childCheckboxes.forEach(cb => {
cb.checked = checkbox.checked;
cb.indeterminate = false;
});
}
checkbox.indeterminate = false;
updateParentCheckboxes(checkbox);
if (typeof onSelectionChangeHandler === 'function') {
onSelectionChangeHandler();
}
saveTreeState();
}
function updateParentCheckboxes(checkbox) {
let currentItem = checkbox.closest('.tree-item');
let parentItem = currentItem.parentElement.closest('.tree-item');
while (parentItem) {
const parentLabel = parentItem.querySelector(':scope > label');
if (!parentLabel) {
parentItem = parentItem.parentElement.closest('.tree-item');
continue;
}
const parentCheckbox = parentLabel.querySelector('input[type="checkbox"]');
if (!parentCheckbox) {
parentItem = parentItem.parentElement.closest('.tree-item');
continue;
}
const children = parentItem.querySelectorAll(':scope > .tree-children > .tree-item > label > input[type="checkbox"]');
const checkedCount = Array.from(children).filter(cb => cb.checked || cb.indeterminate).length;
if (checkedCount === 0) {
parentCheckbox.checked = false;
parentCheckbox.indeterminate = false;
} else if (checkedCount === children.length) {
parentCheckbox.checked = true;
parentCheckbox.indeterminate = false;
} else {
parentCheckbox.checked = false;
parentCheckbox.indeterminate = true;
}
currentItem = parentItem;
parentItem = currentItem.parentElement.closest('.tree-item');
}
}
function saveTreeState() {
if (!treeContainer) {
return;
}
const checkedPaths = Array.from(
treeContainer.querySelectorAll('input[type="checkbox"]:checked[data-has-files="1"]')
).map(cb => cb.dataset.path).filter(Boolean);
const collapsedPaths = Array.from(
treeContainer.querySelectorAll('.tree-children.collapsed')
).map(children => {
const cb = children.parentElement.querySelector(':scope > label > input[type="checkbox"]');
return cb?.dataset.path ?? null;
}).filter(Boolean);
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify({ checkedPaths, collapsedPaths }));
} catch {
// Storage unavailable or quota exceeded — silently ignore
}
}
export function restoreTreeState() {
if (!treeContainer) {
return;
}
let state;
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (!raw) {
return;
}
state = JSON.parse(raw);
} catch {
return;
}
if (!state || typeof state !== 'object') {
return;
}
if (Array.isArray(state.checkedPaths) && state.checkedPaths.length > 0) {
const checkedSet = new Set(state.checkedPaths);
treeContainer.querySelectorAll('input[type="checkbox"][data-has-files="1"]').forEach(cb => {
cb.checked = checkedSet.has(cb.dataset.path);
});
treeContainer.querySelectorAll('input[type="checkbox"][data-has-files="1"]:checked').forEach(cb => {
updateParentCheckboxes(cb);
});
}
if (Array.isArray(state.collapsedPaths)) {
const collapsedSet = new Set(state.collapsedPaths);
treeContainer.querySelectorAll('.tree-children').forEach(childrenDiv => {
const cb = childrenDiv.parentElement.querySelector(':scope > label > input[type="checkbox"]');
if (!cb) {
return;
}
const toggle = childrenDiv.parentElement.querySelector(':scope > .tree-toggle');
const shouldBeCollapsed = collapsedSet.has(cb.dataset.path);
const isCollapsed = childrenDiv.classList.contains('collapsed');
if (shouldBeCollapsed !== isCollapsed) {
childrenDiv.classList.toggle('collapsed');
if (toggle) {
toggle.textContent = shouldBeCollapsed ? '▶' : '▼';
}
}
});
}
}
+2 -77
View File
@@ -107,81 +107,6 @@ h2 {
min-width: 0; min-width: 0;
} }
#product-tree {
font-size: 14px;
line-height: 1.4;
}
.tree-item {
margin: 1px 0;
}
.tree-item > label {
display: inline-flex;
align-items: center;
padding: 2px 0;
}
/* Keep label text aligned whether or not a node has a toggle icon. */
.tree-item:not(.parent) > label {
margin-left: 18px;
}
.tree-item.parent > label {
font-weight: 500;
}
.tree-toggle {
cursor: pointer;
user-select: none;
display: inline-flex;
align-items: center;
justify-content: center;
width: 16px;
height: 1.4em;
margin-right: 2px;
flex-shrink: 0;
vertical-align: middle;
}
.tree-children {
margin-left: 16px;
overflow: hidden;
transition: max-height 0.3s ease, opacity 0.3s ease;
opacity: 1;
}
.tree-children .tree-children {
margin-left: 33px;
}
.tree-children.collapsed {
max-height: 0;
opacity: 0;
overflow: hidden;
}
.tree-item input[type="checkbox"] {
cursor: pointer;
margin-right: 4px;
flex-shrink: 0;
width: 16px;
height: 16px;
}
.tree-item input[type="checkbox"]:indeterminate {
accent-color: #f39c12;
}
.tree-item label {
cursor: pointer;
user-select: none;
}
.tree-item label:hover {
color: #2c3e50;
}
#issues { #issues {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -298,8 +223,8 @@ li:hover {
width: 100%; width: 100%;
} }
.tree-children { .checkbox-tree {
margin-left: 12px; --checkbox-tree-indent: 12px;
} }
.issues-table { .issues-table {
+137
View File
@@ -0,0 +1,137 @@
# Checkbox Tree
A dependency-free, instance-based checkbox tree for browser ES modules. It
supports cascading selection, indeterminate parent states, expandable branches,
multiple independent instances, optional local-storage persistence, and compact
shareable URL state backed by an append-only node manifest.
## Install
```sh
npm install @aaronaxvig/checkbox-tree
```
The package is not published yet. During local development, install it by path:
```sh
npm install ../checkbox-tree
```
## Usage
```js
import { CheckboxTree } from "@aaronaxvig/checkbox-tree";
import "@aaronaxvig/checkbox-tree/styles.css";
const tree = new CheckboxTree(document.querySelector("#example-tree"), {
initiallySelected: true,
storageKey: "example-tree-state",
onSelectionChange({ selectedIds, selectedNodes }) {
console.log(selectedIds, selectedNodes);
},
});
tree.setData([
{
id: "fruit",
label: "Fruit",
children: [
{ id: "apple", label: "Apple", metadata: { color: "red" } },
{ id: "pear", label: "Pear", metadata: { color: "green" } },
],
},
]);
```
For applications without a bundler, serve or copy the `src/` directory and use
relative URLs. The main module imports its state-codec modules alongside it:
```html
<link rel="stylesheet" href="./checkbox-tree.css">
<script type="module">
import { CheckboxTree } from "./checkbox-tree.js";
</script>
```
Every node requires unique string `id` and `label` properties. Nodes may also
have `children`, application-owned `metadata`, and `selectable` or `disabled`
flags.
## API
- `new CheckboxTree(container, options)` creates an independent instance.
- `setData(nodes)` validates and renders nodes, then restores saved state.
- `getSelectedIds()` returns selected node IDs.
- `getSelectedNodes()` returns selected source node objects.
- `setSelectedIds(ids, { notify })` replaces the selection.
- `restoreState()` restores selection and expansion state.
- `destroy()` removes listeners, markup, and the root CSS class.
- `serializeStateToFragment(manifest)` returns `#v=1&c=...&x=...` state.
- `restoreStateFromLocation(manifest, location)` safely applies URL state.
- `validateManifest(manifest)` reports unregistered and missing paths.
Options:
- `initiallyCollapsed` defaults to `true`.
- `initiallySelected` defaults to `false`.
- `storageKey` defaults to `null`, which disables persistence.
- `onSelectionChange` receives `{ selectedIds, selectedNodes }`.
- `manifest` optionally supplies a parsed manifest and automatically restores
state from `window.location.hash` after `setData()`.
- `stateEncoding` selects `"adaptive"` (the default), `"dense"`, or `"tiered"`.
The website's configured value is the URL-format contract and is not embedded
in the fragment.
## Shareable URL state
Create a JSON manifest whose array positions are permanent slots. Canonical node
paths join ancestor IDs with `>`:
```json
{ "schema": 1, "nodes": ["fruit", "fruit>apple", "fruit>pear", null] }
```
Import `loadManifest`, pass its result as the `manifest` option, and call
`tree.serializeStateToFragment()` when creating a share link. Existing entries
must never be reordered. Append new paths, replace removed paths with `null`, and
edit a path in place for a logical rename or move. Node IDs therefore cannot
contain `>`.
The fragment adaptively encodes checked (`c`) and expanded (`x`) state using the
shortest of a dense bitset, sparse enabled slots, or sparse disabled slots.
Sparse slot numbers are delta-encoded variable-length integers. A coverage value
in the disabled-slot form ensures nodes appended later still default to unchecked
and collapsed. Malformed or unsupported state is ignored.
The helpers `encodeTreeState`, `decodeTreeState`, `applyTreeState`, and
`restoreStateFromLocation` are also exported for custom integrations.
Use `validateManifestEvolution(previous, next)` in a build check to reject
removed slots and tombstone reuse; path changes are reported for deliberate
rename/move review.
With `stateEncoding: "tiered"`, `c1` stores root checked state and each deeper
`cN` field stores nodes whose checked state differs from their parent. Expansion
does not inherit: every `xN` stores the actual expanded branches at that depth,
with collapsed as the baseline. Each tier field uses the adaptive codec. The
`n` field records manifest coverage so nodes appended later still default to
false. A non-selectable parent supplies a false checked baseline. With
`stateEncoding: "dense"`, `c` and `x` contain trimmed
least-significant-bit-first bitsets.
Styling is namespaced under `.checkbox-tree`. Override the custom properties
`--checkbox-tree-indent`, `--checkbox-tree-toggle-size`, and
`--checkbox-tree-hover-color` in the consuming application.
## Development
```sh
npm install
npm test
npm run pack:check
```
## Future demo ideas
- Add population metadata to the country, state, and county nodes and display the
total population represented by the current selection. Define the aggregation
rule carefully so checked ancestors and their checked descendants are not
counted twice; one option is to total only the most specific checked nodes.
+82
View File
@@ -0,0 +1,82 @@
.checkbox-tree {
--checkbox-tree-indent: 18px;
--checkbox-tree-toggle-size: 18px;
--checkbox-tree-hover-color: #2c3e50;
font-size: 14px;
line-height: 1.4;
}
.checkbox-tree__item {
margin: 1px 0;
}
.checkbox-tree__row {
display: flex;
align-items: center;
min-height: 24px;
}
.checkbox-tree__toggle,
.checkbox-tree__toggle-spacer {
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 var(--checkbox-tree-toggle-size);
width: var(--checkbox-tree-toggle-size);
height: var(--checkbox-tree-toggle-size);
margin-right: 2px;
}
.checkbox-tree__toggle {
padding: 0;
border: 0;
background: transparent;
color: inherit;
cursor: pointer;
font: inherit;
}
.checkbox-tree__toggle[aria-expanded="true"] {
transform: rotate(90deg);
}
.checkbox-tree__toggle:focus-visible {
outline: 2px solid currentColor;
outline-offset: 1px;
}
.checkbox-tree__label {
display: inline-flex;
align-items: center;
padding: 2px 0;
cursor: pointer;
user-select: none;
}
.checkbox-tree__item:has(> .checkbox-tree__children) > .checkbox-tree__row > .checkbox-tree__label {
font-weight: 500;
}
.checkbox-tree__label:hover {
color: var(--checkbox-tree-hover-color);
}
.checkbox-tree__checkbox {
flex-shrink: 0;
width: 16px;
height: 16px;
margin: 0 4px 0 0;
cursor: pointer;
}
.checkbox-tree__checkbox:indeterminate {
accent-color: #f39c12;
}
.checkbox-tree__children {
margin-left: var(--checkbox-tree-indent);
}
.checkbox-tree__children[hidden] {
display: none;
}
+355
View File
@@ -0,0 +1,355 @@
// Dependency-free checkbox tree widget. State serialization lives in focused sibling modules.
import {
PATH_DELIMITER,
assertStateEncoding
} from './state-codec.js';
import {
restoreStateFromLocation,
serializeStateToFragment,
validateManifest
} from './tree-state.js';
export {
PATH_DELIMITER,
SERIALIZATION_VERSION,
base64UrlToBytes,
bytesToBase64Url,
decodeAdaptiveBitset,
encodeAdaptiveBitset,
getBit,
loadManifest,
setBit
} from './state-codec.js';
export {
applyTreeState,
decodeTreeState,
encodeTreeState,
restoreStateFromLocation,
serializeStateToFragment,
validateManifest,
validateManifestEvolution
} from './tree-state.js';
const DEFAULT_OPTIONS = {
initiallyCollapsed: true,
initiallySelected: false,
storageKey: null,
onSelectionChange: null,
manifest: null,
stateEncoding: 'adaptive'
};
export class CheckboxTree {
constructor(container, options = {}) {
if (!(container instanceof Element)) {
throw new TypeError('CheckboxTree requires a container element.');
}
this.container = container;
this.options = { ...DEFAULT_OPTIONS, ...options };
assertStateEncoding(this.options.stateEncoding);
this.nodesById = new Map();
this.nodesByPath = new Map();
this.itemsById = new Map();
this.rootNodes = [];
this.handleChange = this.handleChange.bind(this);
this.handleClick = this.handleClick.bind(this);
this.container.classList.add('checkbox-tree');
this.container.addEventListener('change', this.handleChange);
this.container.addEventListener('click', this.handleClick);
}
setData(nodes) {
if (!Array.isArray(nodes)) {
throw new TypeError('CheckboxTree data must be an array of nodes.');
}
this.nodesById.clear();
this.nodesByPath.clear();
this.itemsById.clear();
this.rootNodes = nodes;
this.indexNodes(nodes);
this.render();
this.updateAllParentCheckboxes();
this.restoreState();
if (this.options.manifest) this.restoreStateFromLocation();
}
getSelectedIds() {
return this.getSelectedNodes().map(node => node.id);
}
getSelectedNodes() {
return Array.from(this.container.querySelectorAll(
'.checkbox-tree__checkbox:checked[data-selectable="true"]'
)).map(checkbox => this.nodesById.get(checkbox.dataset.nodeId)).filter(Boolean);
}
setSelectedIds(ids, { notify = false } = {}) {
const selectedIds = new Set(ids);
this.container.querySelectorAll('.checkbox-tree__checkbox').forEach(checkbox => {
checkbox.checked = checkbox.dataset.selectable === 'true' && selectedIds.has(checkbox.dataset.nodeId);
checkbox.indeterminate = false;
});
this.updateAllParentCheckboxes();
this.saveState();
if (notify) {
this.notifySelectionChange();
}
}
destroy() {
this.container.removeEventListener('change', this.handleChange);
this.container.removeEventListener('click', this.handleClick);
this.container.classList.remove('checkbox-tree');
this.container.replaceChildren();
this.nodesById.clear();
this.nodesByPath.clear();
this.itemsById.clear();
}
indexNodes(nodes, parentPath = '') {
nodes.forEach(node => {
if (!node || typeof node.id !== 'string' || !node.id || typeof node.label !== 'string') {
throw new TypeError('Each CheckboxTree node requires non-empty string id and label properties.');
}
if (this.nodesById.has(node.id)) {
throw new Error(`Duplicate CheckboxTree node id: ${node.id}`);
}
if (node.id.includes(PATH_DELIMITER)) {
throw new Error(`CheckboxTree node id cannot contain "${PATH_DELIMITER}": ${node.id}`);
}
this.nodesById.set(node.id, node);
const path = parentPath ? `${parentPath}${PATH_DELIMITER}${node.id}` : node.id;
this.nodesByPath.set(path, node);
if (node.children !== undefined) {
if (!Array.isArray(node.children)) {
throw new TypeError(`CheckboxTree node children must be an array: ${node.id}`);
}
this.indexNodes(node.children, path);
}
});
}
render() {
const fragment = document.createDocumentFragment();
this.rootNodes.forEach(node => fragment.appendChild(this.createNodeElement(node)));
this.container.replaceChildren(fragment);
}
createNodeElement(node) {
const item = document.createElement('div');
item.className = 'checkbox-tree__item';
item.dataset.nodeId = node.id;
this.itemsById.set(node.id, item);
const row = document.createElement('div');
row.className = 'checkbox-tree__row';
const hasChildren = Array.isArray(node.children) && node.children.length > 0;
if (hasChildren) {
const toggle = document.createElement('button');
toggle.type = 'button';
toggle.className = 'checkbox-tree__toggle';
toggle.dataset.action = 'toggle';
toggle.setAttribute('aria-label', `Expand ${node.label}`);
toggle.setAttribute('aria-expanded', 'false');
toggle.textContent = '▶';
row.appendChild(toggle);
} else {
const spacer = document.createElement('span');
spacer.className = 'checkbox-tree__toggle-spacer';
spacer.setAttribute('aria-hidden', 'true');
row.appendChild(spacer);
}
const label = document.createElement('label');
label.className = 'checkbox-tree__label';
if (node.selectable !== false) {
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'checkbox-tree__checkbox';
checkbox.dataset.nodeId = node.id;
checkbox.dataset.selectable = 'true';
checkbox.disabled = node.disabled === true;
checkbox.checked = this.options.initiallySelected && !checkbox.disabled;
label.appendChild(checkbox);
}
label.appendChild(document.createTextNode(node.label));
row.appendChild(label);
item.appendChild(row);
if (hasChildren) {
const children = document.createElement('div');
children.className = 'checkbox-tree__children';
children.hidden = this.options.initiallyCollapsed;
node.children.forEach(child => children.appendChild(this.createNodeElement(child)));
item.appendChild(children);
if (!this.options.initiallyCollapsed) {
this.setExpanded(item, true);
}
}
return item;
}
handleClick(event) {
const toggle = event.target.closest('.checkbox-tree__toggle[data-action="toggle"]');
if (!toggle || !this.container.contains(toggle)) {
return;
}
const item = toggle.closest('.checkbox-tree__item');
this.setExpanded(item, toggle.getAttribute('aria-expanded') !== 'true');
this.saveState();
}
handleChange(event) {
const checkbox = event.target.closest('.checkbox-tree__checkbox');
if (!checkbox || !this.container.contains(checkbox)) {
return;
}
const item = checkbox.closest('.checkbox-tree__item');
const children = this.directChildrenContainer(item);
if (children) {
children.querySelectorAll('.checkbox-tree__checkbox:not(:disabled)').forEach(child => {
child.checked = checkbox.checked;
child.indeterminate = false;
});
}
checkbox.indeterminate = false;
this.updateAncestorCheckboxes(item);
this.saveState();
this.notifySelectionChange();
}
notifySelectionChange() {
if (typeof this.options.onSelectionChange === 'function') {
this.options.onSelectionChange({
selectedIds: this.getSelectedIds(),
selectedNodes: this.getSelectedNodes()
});
}
}
setExpanded(item, expanded) {
if (!item) return;
const toggle = item.querySelector(':scope > .checkbox-tree__row > .checkbox-tree__toggle');
const children = this.directChildrenContainer(item);
if (!toggle || !children) {
return;
}
children.hidden = !expanded;
toggle.setAttribute('aria-expanded', String(expanded));
const label = this.nodesById.get(item.dataset.nodeId)?.label ?? 'branch';
toggle.setAttribute('aria-label', `${expanded ? 'Collapse' : 'Expand'} ${label}`);
}
directChildrenContainer(item) {
return item.querySelector(':scope > .checkbox-tree__children');
}
directCheckbox(item) {
return item.querySelector(':scope > .checkbox-tree__row .checkbox-tree__checkbox');
}
itemForNode(id) {
return this.itemsById.get(id) ?? null;
}
updateAncestorCheckboxes(item) {
let parentItem = item.parentElement?.closest('.checkbox-tree__item');
while (parentItem && this.container.contains(parentItem)) {
this.updateParentCheckbox(parentItem);
parentItem = parentItem.parentElement?.closest('.checkbox-tree__item');
}
}
updateAllParentCheckboxes() {
const parents = Array.from(this.container.querySelectorAll('.checkbox-tree__item'))
.filter(item => this.directChildrenContainer(item));
parents.reverse().forEach(item => this.updateParentCheckbox(item));
}
updateParentCheckbox(item) {
const checkbox = this.directCheckbox(item);
const children = this.directChildrenContainer(item);
if (!checkbox || !children) {
return;
}
const childCheckboxes = Array.from(children.querySelectorAll(
':scope > .checkbox-tree__item > .checkbox-tree__row .checkbox-tree__checkbox'
)).filter(child => !child.disabled);
const hasSelection = childCheckboxes.some(child => child.checked || child.indeterminate);
const allSelected = childCheckboxes.length > 0 && childCheckboxes.every(child => child.checked);
checkbox.checked = allSelected;
checkbox.indeterminate = hasSelection && !allSelected;
}
saveState() {
if (!this.options.storageKey) {
return;
}
const collapsedIds = Array.from(this.container.querySelectorAll('.checkbox-tree__children[hidden]'))
.map(children => children.parentElement.dataset.nodeId);
try {
localStorage.setItem(this.options.storageKey, JSON.stringify({
version: 1,
selectedIds: this.getSelectedIds(),
collapsedIds
}));
} catch {
// Persistence is optional; storage may be disabled or full.
}
}
restoreState() {
if (!this.options.storageKey) {
return;
}
let state;
try {
state = JSON.parse(localStorage.getItem(this.options.storageKey));
} catch {
return;
}
if (!state || typeof state !== 'object') {
return;
}
if (Array.isArray(state.selectedIds)) {
this.setSelectedIds(state.selectedIds);
}
if (Array.isArray(state.collapsedIds)) {
const collapsedIds = new Set(state.collapsedIds);
this.container.querySelectorAll('.checkbox-tree__item').forEach(item => {
if (this.directChildrenContainer(item)) {
this.setExpanded(item, !collapsedIds.has(item.dataset.nodeId));
}
});
}
}
validateManifest(manifest = this.options.manifest) {
return validateManifest(manifest, this);
}
serializeStateToFragment(manifest = this.options.manifest) {
return serializeStateToFragment(this, manifest);
}
restoreStateFromLocation(manifest = this.options.manifest, location = window.location) {
return restoreStateFromLocation(this, manifest, location);
}
}
+206
View File
@@ -0,0 +1,206 @@
// Manifest parsing and binary codecs. This module has no DOM dependencies.
export const SERIALIZATION_VERSION = 1;
export const PATH_DELIMITER = '>';
const STATE_ENCODINGS = new Set(['adaptive', 'dense', 'tiered']);
export function loadManifest(source) {
let records;
let schema = 1;
if (typeof source === 'string') {
records = source.split(/\r?\n/)
.map(line => line.trim())
.filter(line => line && !line.startsWith('#'))
.map(line => line === '!deleted' ? null : line);
} else if (source && typeof source === 'object' && Array.isArray(source.nodes)) {
schema = source.schema;
records = source.nodes;
} else if (source && Array.isArray(source.slots)) {
return normalizeManifest(source.schema, source.slots.map(slot => slot?.deleted ? null : slot?.path));
} else {
throw new TypeError('CheckboxTree manifest must be text or an object with a nodes array.');
}
return normalizeManifest(schema, records);
}
function normalizeManifest(schema, records) {
if (schema !== 1) {
throw new Error(`Unsupported CheckboxTree manifest schema: ${schema}`);
}
const pathToSlot = new Map();
const slots = records.map((record, index) => {
if (record === null || record === '!deleted') {
return { path: null, deleted: true };
}
if (typeof record !== 'string' || !record || record.trim() !== record) {
throw new TypeError(`Invalid CheckboxTree manifest entry at slot ${index}.`);
}
if (pathToSlot.has(record)) {
throw new Error(`Duplicate CheckboxTree manifest path: ${record}`);
}
pathToSlot.set(record, index);
return { path: record, deleted: false };
});
return { schema: 1, slots, pathToSlot };
}
export function setBit(bytes, bitIndex, value) {
const byteIndex = Math.floor(bitIndex / 8);
const mask = 1 << (bitIndex % 8);
if (value) bytes[byteIndex] |= mask;
else bytes[byteIndex] &= ~mask;
}
export function getBit(bytes, bitIndex) {
const byteIndex = Math.floor(bitIndex / 8);
return byteIndex < bytes.length && (bytes[byteIndex] & (1 << (bitIndex % 8))) !== 0;
}
export function bytesToBase64Url(bytes) {
let binary = '';
for (const byte of bytes) binary += String.fromCharCode(byte);
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
}
export function base64UrlToBytes(value, maximumLength = Infinity) {
if (typeof value !== 'string' || !/^[A-Za-z0-9_-]*$/.test(value) || value.length % 4 === 1) {
throw new Error('Invalid Base64URL value.');
}
if (Number.isFinite(maximumLength) && value.length > Math.ceil(maximumLength * 4 / 3) + 2) {
throw new Error('Encoded tree state exceeds the manifest size.');
}
const base64 = value.replace(/-/g, '+').replace(/_/g, '/');
const binary = atob(base64 + '='.repeat((4 - base64.length % 4) % 4));
if (binary.length > maximumLength) throw new Error('Encoded tree state exceeds the manifest size.');
return Uint8Array.from(binary, character => character.charCodeAt(0));
}
function trimTrailingZeroBytes(bytes) {
let end = bytes.length;
while (end && bytes[end - 1] === 0) end--;
return bytes.slice(0, end);
}
function encodeUnsignedVarint(value) {
const bytes = [];
do {
let byte = value & 0x7f;
value = Math.floor(value / 128);
if (value) byte |= 0x80;
bytes.push(byte);
} while (value);
return bytes;
}
function decodeUnsignedVarint(bytes, offset) {
let value = 0;
let multiplier = 1;
for (let index = offset; index < bytes.length && index < offset + 8; index++) {
const byte = bytes[index];
value += (byte & 0x7f) * multiplier;
if ((byte & 0x80) === 0) return { value, offset: index + 1 };
multiplier *= 128;
if (!Number.isSafeInteger(value) || !Number.isSafeInteger(multiplier)) break;
}
throw new Error('Invalid adaptive tree-state integer.');
}
function encodeSparseIndexes(indexes) {
const bytes = [];
let previous = -1;
for (const index of indexes) {
bytes.push(...encodeUnsignedVarint(index - previous - 1));
previous = index;
}
return bytes;
}
function decodeSparseIndexes(bytes, offset, limit) {
const indexes = new Set();
let previous = -1;
while (offset < bytes.length) {
const decoded = decodeUnsignedVarint(bytes, offset);
const index = previous + decoded.value + 1;
if (index >= limit) throw new Error('Sparse tree state contains an out-of-range slot.');
indexes.add(index);
previous = index;
offset = decoded.offset;
}
return indexes;
}
export function encodeAdaptiveBitset(values, slotCount) {
const enabled = [];
const disabled = [];
const denseBytes = new Uint8Array(Math.ceil(slotCount / 8));
for (const [slot, value] of values) {
(value ? enabled : disabled).push(slot);
setBit(denseBytes, slot, value);
}
enabled.sort((left, right) => left - right);
disabled.sort((left, right) => left - right);
if (enabled.length === 0) return new Uint8Array();
const candidates = [
Uint8Array.from([0, ...trimTrailingZeroBytes(denseBytes)]),
Uint8Array.from([1, ...encodeSparseIndexes(enabled)]),
Uint8Array.from([2, ...encodeUnsignedVarint(slotCount), ...encodeSparseIndexes(disabled)])
];
return candidates.reduce((shortest, candidate) =>
candidate.length < shortest.length ? candidate : shortest
);
}
export function decodeAdaptiveBitset(bytes, slotCount) {
if (bytes.length === 0) return () => false;
const mode = bytes[0];
if (mode === 0) {
const dense = bytes.slice(1);
if (dense.length > Math.ceil(slotCount / 8)) {
throw new Error('Dense tree state exceeds the manifest size.');
}
return slot => getBit(dense, slot);
}
if (mode === 1) {
const enabled = decodeSparseIndexes(bytes, 1, slotCount);
return slot => enabled.has(slot);
}
if (mode === 2) {
const coverage = decodeUnsignedVarint(bytes, 1);
if (coverage.value > slotCount) throw new Error('Tree-state coverage exceeds the manifest size.');
const disabled = decodeSparseIndexes(bytes, coverage.offset, coverage.value);
return slot => slot < coverage.value && !disabled.has(slot);
}
throw new Error('Unknown adaptive tree-state mode.');
}
export function encodeDenseBitset(values, slotCount) {
const bytes = new Uint8Array(Math.ceil(slotCount / 8));
for (const [slot, value] of values) setBit(bytes, slot, value);
return trimTrailingZeroBytes(bytes);
}
export function decodeDenseBitset(bytes, slotCount) {
if (bytes.length > Math.ceil(slotCount / 8)) {
throw new Error('Dense tree state exceeds the manifest size.');
}
return slot => getBit(bytes, slot);
}
export function maximumEncodedLength(slotCount) {
return Math.max(Math.ceil(slotCount / 8) + 1, slotCount * 2 + 10);
}
export function assertStateEncoding(encoding) {
if (!STATE_ENCODINGS.has(encoding)) {
throw new Error(`Unsupported CheckboxTree state encoding: ${encoding}`);
}
}
export function asManifestSource(manifest) {
return manifest?.pathToSlot instanceof Map ? manifest : loadManifest(manifest);
}
+310
View File
@@ -0,0 +1,310 @@
// Adapts pure state codecs to CheckboxTree's rendered node model and URL format.
import {
PATH_DELIMITER,
SERIALIZATION_VERSION,
asManifestSource as asManifest,
assertStateEncoding,
base64UrlToBytes,
bytesToBase64Url,
decodeAdaptiveBitset,
decodeDenseBitset,
encodeAdaptiveBitset,
encodeDenseBitset,
maximumEncodedLength
} from './state-codec.js';
export function validateManifest(manifest, tree) {
const normalized = asManifest(manifest);
const treePaths = new Set(tree.nodesByPath.keys());
return {
valid: Array.from(treePaths).every(path => normalized.pathToSlot.has(path)),
unregisteredPaths: Array.from(treePaths).filter(path => !normalized.pathToSlot.has(path)),
missingPaths: normalized.slots
.filter(slot => !slot.deleted && !treePaths.has(slot.path))
.map(slot => slot.path)
};
}
export function validateManifestEvolution(previous, next) {
const oldManifest = asManifest(previous);
const newManifest = asManifest(next);
const errors = [];
if (newManifest.slots.length < oldManifest.slots.length) {
errors.push('Manifest slots cannot be removed; use tombstones.');
}
oldManifest.slots.forEach((oldSlot, index) => {
const newSlot = newManifest.slots[index];
if (!newSlot) return;
if (oldSlot.deleted && !newSlot.deleted) {
errors.push(`Tombstoned slot ${index} cannot be reused.`);
}
if (!oldSlot.deleted && !newSlot.deleted && oldSlot.path !== newSlot.path) {
errors.push(`Slot ${index} changed path; confirm this is a logical rename or move.`);
}
});
return { valid: errors.length === 0, errors };
}
function collectTreeValues(tree, manifest) {
const normalized = asManifest(manifest);
const checkedValues = new Map();
const expandedValues = new Map();
for (const [path, node] of tree.nodesByPath) {
const slot = normalized.pathToSlot.get(path);
if (slot === undefined) continue;
const item = tree.itemForNode(node.id);
const checkbox = item && tree.directCheckbox(item);
const toggle = item?.querySelector(':scope > .checkbox-tree__row > .checkbox-tree__toggle');
checkedValues.set(slot, Boolean(checkbox?.checked));
expandedValues.set(slot, toggle?.getAttribute('aria-expanded') === 'true');
}
return { normalized, checkedValues, expandedValues };
}
function nodeState(tree, node) {
const item = tree.itemForNode(node.id);
const checkbox = item && tree.directCheckbox(item);
const toggle = item?.querySelector(':scope > .checkbox-tree__row > .checkbox-tree__toggle');
return {
checked: Boolean(checkbox?.checked),
hasChecked: Boolean(checkbox),
expanded: toggle?.getAttribute('aria-expanded') === 'true',
hasExpanded: Boolean(toggle)
};
}
function encodeTieredState(tree, normalized) {
const checkedByDepth = new Map();
const expandedByDepth = new Map();
const statesByPath = new Map();
for (const [path, node] of tree.nodesByPath) {
const slot = normalized.pathToSlot.get(path);
if (slot === undefined) continue;
const depth = path.split(PATH_DELIMITER).length;
const parentPath = path.includes(PATH_DELIMITER)
? path.slice(0, path.lastIndexOf(PATH_DELIMITER))
: null;
const parent = statesByPath.get(parentPath) ?? {
checked: false,
hasChecked: false,
expanded: false,
hasExpanded: false
};
const current = nodeState(tree, node);
if (current.hasChecked) {
checkedByDepth.set(depth, checkedByDepth.get(depth) ?? new Map());
checkedByDepth.get(depth).set(slot, current.checked !== (parent.hasChecked ? parent.checked : false));
}
if (current.hasExpanded) {
expandedByDepth.set(depth, expandedByDepth.get(depth) ?? new Map());
expandedByDepth.get(depth).set(slot, current.expanded);
}
statesByPath.set(path, current);
}
const fields = new Map([['n', String(normalized.slots.length)]]);
for (const [depth, values] of checkedByDepth) {
const encoded = bytesToBase64Url(encodeAdaptiveBitset(values, normalized.slots.length));
if (encoded) fields.set(`c${depth}`, encoded);
}
for (const [depth, values] of expandedByDepth) {
const encoded = bytesToBase64Url(encodeAdaptiveBitset(values, normalized.slots.length));
if (encoded) fields.set(`x${depth}`, encoded);
}
return fields;
}
export function encodeTreeState(tree, manifest, encoding = tree.options?.stateEncoding ?? 'adaptive') {
assertStateEncoding(encoding);
const { normalized, checkedValues, expandedValues } = collectTreeValues(tree, manifest);
if (encoding === 'tiered') {
return { version: SERIALIZATION_VERSION, encoding, fields: encodeTieredState(tree, normalized) };
}
const encode = encoding === 'dense' ? encodeDenseBitset : encodeAdaptiveBitset;
const checkedValue = bytesToBase64Url(encode(checkedValues, normalized.slots.length));
const expandedValue = bytesToBase64Url(encode(expandedValues, normalized.slots.length));
return {
version: SERIALIZATION_VERSION,
encoding,
checked: checkedValue || null,
expanded: expandedValue || null
};
}
function decodeField(params, name, normalized, encoding) {
const maximumLength = encoding === 'dense'
? Math.ceil(normalized.slots.length / 8)
: maximumEncodedLength(normalized.slots.length);
const bytes = base64UrlToBytes(params.get(name) ?? '', maximumLength);
const decode = encoding === 'dense' ? decodeDenseBitset : decodeAdaptiveBitset;
decode(bytes, normalized.slots.length);
return bytes;
}
export function decodeTreeState(fragment, manifest, encoding = 'adaptive') {
assertStateEncoding(encoding);
const normalized = asManifest(manifest);
const value = fragment.startsWith('#') ? fragment.slice(1) : fragment;
const params = new URLSearchParams(value);
if (params.get('v') !== String(SERIALIZATION_VERSION)) {
return { applied: false, version: params.get('v'), encoding, warnings: ['Missing or unsupported serialization version.'] };
}
try {
if (encoding === 'tiered') {
if (params.has('c') || params.has('x')) {
throw new Error('Tiered tree state contains non-tiered fields.');
}
const maximumDepth = normalized.slots.reduce((depth, slot) =>
slot.deleted ? depth : Math.max(depth, slot.path.split(PATH_DELIMITER).length)
, 0);
for (const key of params.keys()) {
const match = /^([cx])(\d+)$/.exec(key);
if (match && (Number(match[2]) < 1 || Number(match[2]) > maximumDepth)) {
throw new Error(`Tree state contains an invalid tier: ${key}`);
}
}
const coverage = Number(params.get('n'));
if (!Number.isSafeInteger(coverage) || coverage < 0 || coverage > normalized.slots.length) {
throw new Error('Tiered tree state has invalid manifest coverage.');
}
const checkedTiers = new Map();
const expandedTiers = new Map();
for (let depth = 1; depth <= maximumDepth; depth++) {
checkedTiers.set(depth, decodeField(params, `c${depth}`, normalized, 'adaptive'));
expandedTiers.set(depth, decodeField(params, `x${depth}`, normalized, 'adaptive'));
}
return {
applied: true,
version: SERIALIZATION_VERSION,
encoding,
coverage,
checkedTiers,
expandedTiers,
warnings: []
};
}
for (const key of params.keys()) {
if (key === 'n' || /^[cx]\d+$/.test(key)) {
throw new Error('Tree state contains tiered fields for a non-tiered encoding.');
}
}
const checked = decodeField(params, 'c', normalized, encoding);
const expanded = decodeField(params, 'x', normalized, encoding);
return {
applied: true,
version: SERIALIZATION_VERSION,
encoding,
checked,
expanded,
warnings: []
};
} catch (error) {
return { applied: false, version: SERIALIZATION_VERSION, encoding, warnings: [error.message] };
}
}
export function applyTreeState(tree, manifest, state) {
if (!state?.applied) return false;
const normalized = asManifest(manifest);
if (state.encoding === 'tiered') return applyTieredTreeState(tree, normalized, state);
let isChecked;
let isExpanded;
try {
const decode = state.encoding === 'dense' ? decodeDenseBitset : decodeAdaptiveBitset;
isChecked = decode(state.checked, normalized.slots.length);
isExpanded = decode(state.expanded, normalized.slots.length);
} catch {
return false;
}
for (const [path, node] of tree.nodesByPath) {
const slot = normalized.pathToSlot.get(path);
if (slot === undefined) continue;
const item = tree.itemForNode(node.id);
const checkbox = item && tree.directCheckbox(item);
if (checkbox && !checkbox.disabled) checkbox.checked = isChecked(slot);
tree.setExpanded(item, isExpanded(slot));
}
tree.updateAllParentCheckboxes();
return true;
}
function applyTieredTreeState(tree, normalized, state) {
const resolvedByPath = new Map();
const checkedDecoders = new Map();
const expandedDecoders = new Map();
try {
for (const [depth, bytes] of state.checkedTiers) {
checkedDecoders.set(depth, decodeAdaptiveBitset(bytes, normalized.slots.length));
}
for (const [depth, bytes] of state.expandedTiers) {
expandedDecoders.set(depth, decodeAdaptiveBitset(bytes, normalized.slots.length));
}
} catch {
return false;
}
for (const [path, node] of tree.nodesByPath) {
const slot = normalized.pathToSlot.get(path);
if (slot === undefined) continue;
const depth = path.split(PATH_DELIMITER).length;
const parentPath = path.includes(PATH_DELIMITER)
? path.slice(0, path.lastIndexOf(PATH_DELIMITER))
: null;
const parent = resolvedByPath.get(parentPath) ?? {
checked: false,
hasChecked: false,
expanded: false,
hasExpanded: false
};
const item = tree.itemForNode(node.id);
const checkbox = item && tree.directCheckbox(item);
const toggle = item?.querySelector(':scope > .checkbox-tree__row > .checkbox-tree__toggle');
const isCovered = slot < state.coverage;
const checked = checkbox && isCovered
? (parent.hasChecked ? parent.checked : false) !== checkedDecoders.get(depth)(slot)
: false;
const expanded = Boolean(toggle && isCovered && expandedDecoders.get(depth)(slot));
if (checkbox && !checkbox.disabled) checkbox.checked = checked;
tree.setExpanded(item, expanded);
resolvedByPath.set(path, {
checked,
hasChecked: Boolean(checkbox),
expanded,
hasExpanded: Boolean(toggle)
});
}
tree.updateAllParentCheckboxes();
return true;
}
export function serializeStateToFragment(tree, manifest, encoding = tree.options?.stateEncoding ?? 'adaptive') {
const state = encodeTreeState(tree, manifest, encoding);
const params = new URLSearchParams({ v: String(state.version) });
if (state.encoding === 'tiered') {
for (const [name, value] of state.fields) params.set(name, value);
} else {
if (state.checked) params.set('c', state.checked);
if (state.expanded) params.set('x', state.expanded);
}
return `#${params}`;
}
export function restoreStateFromLocation(
tree,
manifest,
location = window.location,
encoding = tree.options?.stateEncoding ?? 'adaptive'
) {
const state = decodeTreeState(location.hash ?? '', manifest, encoding);
if (state.applied) applyTreeState(tree, manifest, state);
return state;
}