diff --git a/docs/architecture/at-a-glance.md b/docs/architecture/at-a-glance.md new file mode 100644 index 00000000..b0050239 --- /dev/null +++ b/docs/architecture/at-a-glance.md @@ -0,0 +1,103 @@ +# Workflow Orchestrator At A Glance + +!!! info + This article is for potential adopters considering Workflow Orchestrator (WFO) for their organization. + It avoids software implementation details and favors high-level terminology over the finer semantics of WFO's database models. + + +Workflow Orchestrator is a software framework for modeling the lifecycle of subscriptions to arbitrary products. +The framework is open-source and written in the Python programming language. +In organizations using WFO, software teams define **products** using the framework, along with **workflows** by which end-users manage each product's lifecycle. + +When a user subscribes a customer to a product, they run a **create** workflow, which produces a **subscription** to that product. +That subscription can then be orchestrated by the other workflows associated with its product. +For example, **validate** workflows ensure WFO's database is kept in sync with any external resources allocated to a subscription, and **terminate** workflows deprovision the subscription along with those external resources. + +## Scenario: Adding a Link +!!! info + In this section, we highlight the experience of an end-user: in this case, a network operator setting up a link between two routers. + While this example focuses on networking, WFO can be used for any kind of service, and provides no specific support for network products. + + All screenshots are from the [example-orchestrator][example-orchestrator] project, which you can set up yourself to explore the product further. + +Suppose that we have nodes in Amsterdam and London, and we want to establish a core link between them. + +On the Subscriptions page, we can see that we already have a subscription for each node, so we create a link via the New Subscription dropdown. +![Create Core Link dropdown](../img/at-a-glance/create_core_link_dropdown.png) + +We're then taken to a form for creating the new core link. +As seen below, the form is multi-part with choices pregenerated for the fields. + +In this case, the choices are generated dynamically from a Netbox inventory. Workflow Orchestrator makes it easy for software developers to generate templated forms entirely on the backend using the [Pydantic Forms][pydantic-forms] package, with no front-end code required. + +![Create Core Link form A](../img/at-a-glance/create_core_link_form_a.png) +![Create Core Link form B](../img/at-a-glance/create_core_link_form_b.png) + +After submitting each part of the form, the Create workflow is started. +A workflow is composed of multiple steps, and the status of each step is visible in the UI as it completes. +Any step that generates data can be expanded in the UI to view its output. +Below, we can see the output of the Assign IPv6 Prefix step. + +![Create Core Link workflow output](../img/at-a-glance/create_core_link_output.png) + +A link in the upper-right corner of the workflow output (above) takes us to the subscription we created (below). + +On the left, we can see various details about both the Core Link subscription, the ports on either end, and the nodes (also subscriptions) each port resides on. + +On the right, the Actions dropdown lists the workflows available to us for further managing the subscription's lifecycle. + +![Core Link subscription page](../img/at-a-glance/core_link_sub.png) + +The General tab provides more extensive information about the subscription. + +![Core Link subscription page general tab](../img/at-a-glance/core_link_sub_general.png) + +## Additional Actions +Above, we saw the Actions available to manage a Core Link subscription after it was created. + +`Modify core_link`: Users can define modify workflows to update the subscription database and any orchestrated external resources. These workflows facilitate changes to a subscription mid-lifecycle. + +`Validate core_link`: Users can define validation workflows to verify the data in the WFO database against the external systems it manages. An error in a validation workflow places the corresponding subscription in an Out of Sync state, which flags it for remediation and blocks execution of its workflows. For example, if WFO manages a resource in Netbox and that resource is then edited directly in Netbox, this could be detected with a validation workflow. + +`Terminate core_link`: Users can define terminate workflows in order to deprovision a subscription and any resources orchestrated on its behalf. Terminated subscriptions are still accessible for reference. + +Users aren't limited to these workflows. +For example, it can be helpful to create distinct modify workflows for the same product to make unrelated changes. +Users can also create Tasks, which are simply workflows that aren't tied to any subscription. +Tasks can be run automatically via WFO's [scheduling](../../orchestrator-core/guides/tasks/) features, which can be customized to run at any time or frequency. + +## Behind the Scenes + +So what's actually happening behind the scenes when we run these workflows? + +WFO provides none of the facilities for talking to Netbox, NSO, Ansible, etc. +These features are implemented by other packages or by the software team leveraging the framework. + +In particular, each workflow is created from a number of user-defined **steps**. +A step is just a Python function that can be re-used by developers across workflows. +Usually, each step is responsible for interacting with an external resource: an inventory system, a database, a network device, an HTTP API, etc. +When a workflow fails for some reason, it can be retried beginning at whichever step failed. + +If you want to see the code for yourself, the above example came from the [example-orchestrator][example-orchestrator] repo. +The Core Link producted is defined by a [Product][example-core-link-product-type] and its constituent [Product Blocks][example-core-link-product-blocks], along with its [workflows][example-core-link-workflows]. +The workflows update the inventory system, Netbox, via [services/netbox.py][example-core-netbox-service]. +By convention, user-defined modules for interacting with external services are organized under `services/`. + +## Next Steps +To deploy your own Workflow Orchestrator, you will need two services: + +* A web backend. You will develop a Python application that imports the [`orchestrator-core`][orchestrator-core] package as a dependency. +* A web frontend served over HTTP. You may develop a React application using the [`orchestrator-ui-components`][orchestrator-ui-library] NPM package, but it's easiest to start by modifying a copy of the [`example-orchestrator-ui`][example-orchestrator-ui] application for your own use. + +Next, see [The Framework](./framework.md) for an architectural walkthrough of the software stack. + +[nren-wikipedia]: https://en.wikipedia.org/wiki/National_research_and_education_network +[pydantic-forms]: https://workfloworchestrator.org/pydantic-forms/ +[orchestrator-core]: https://github.com/workfloworchestrator/orchestrator-core +[orchestrator-ui-library]: https://github.com/workfloworchestrator/orchestrator-ui-library +[example-orchestrator]: https://github.com/workfloworchestrator/example-orchestrator +[example-orchestrator-ui]: https://github.com/workfloworchestrator/example-orchestrator-ui +[example-core-link-product-type]: https://github.com/workfloworchestrator/example-orchestrator/blob/main/products/product_types/core_link.py +[example-core-link-product-blocks]: https://github.com/workfloworchestrator/example-orchestrator/blob/main/products/product_blocks/core_link.py +[example-core-link-workflows]: https://github.com/workfloworchestrator/example-orchestrator/tree/main/workflows/core_link +[example-core-netbox-service]: https://github.com/workfloworchestrator/example-orchestrator/blob/main/services/netbox.py diff --git a/docs/css/style.css b/docs/css/style.css index 000e4699..4d1e59bf 100644 --- a/docs/css/style.css +++ b/docs/css/style.css @@ -35,6 +35,48 @@ img[alt='pypi-downloads'], img[alt='npm-downloads'] { filter: drop-shadow(1px 1px 5px white); } +/* + * Make it discoverable that images open in a lightbox (mkdocs-glightbox): hovering one shows a + * zoom-in cursor and a magnifier badge in its top right corner. The badge is drawn on the link + * glightbox wraps around the image, so it disappears together with the plugin if it is ever + * removed, and never covers an image that is not clickable. + */ +:root { + --wfo-magnify-icon: url('data:image/svg+xml;charset=utf-8,'); +} + +.md-typeset a.glightbox { + cursor: zoom-in; + display: inline-block; + line-height: 0; + position: relative; +} + +.md-typeset a.glightbox::after { + background: rgba(0, 0, 0, .55) var(--wfo-magnify-icon) center / 1.1rem no-repeat; + border-radius: .2rem; + content: ""; + height: 1.7rem; + opacity: 0; + pointer-events: none; + position: absolute; + right: .5rem; + top: .5rem; + transition: opacity 125ms; + width: 1.7rem; +} + +.md-typeset a.glightbox:hover::after, +.md-typeset a.glightbox:focus-visible::after { + opacity: 1; +} + +/* Dim the page behind the lightbox rather than blacking it out (glightbox defaults to .92 alpha). */ +.goverlay { + background: rgba(0, 0, 0, .6); + backdrop-filter: blur(2px); +} + /* * Label search results with the project they belong to. * diff --git a/docs/img/at-a-glance/core_link_sub.png b/docs/img/at-a-glance/core_link_sub.png new file mode 100644 index 00000000..28f7cc9a Binary files /dev/null and b/docs/img/at-a-glance/core_link_sub.png differ diff --git a/docs/img/at-a-glance/core_link_sub_general.png b/docs/img/at-a-glance/core_link_sub_general.png new file mode 100644 index 00000000..1feb2a9e Binary files /dev/null and b/docs/img/at-a-glance/core_link_sub_general.png differ diff --git a/docs/img/at-a-glance/create_core_link_dropdown.png b/docs/img/at-a-glance/create_core_link_dropdown.png new file mode 100644 index 00000000..7fdbbd07 Binary files /dev/null and b/docs/img/at-a-glance/create_core_link_dropdown.png differ diff --git a/docs/img/at-a-glance/create_core_link_form_a.png b/docs/img/at-a-glance/create_core_link_form_a.png new file mode 100644 index 00000000..e46cc8c9 Binary files /dev/null and b/docs/img/at-a-glance/create_core_link_form_a.png differ diff --git a/docs/img/at-a-glance/create_core_link_form_b.png b/docs/img/at-a-glance/create_core_link_form_b.png new file mode 100644 index 00000000..a461d8e5 Binary files /dev/null and b/docs/img/at-a-glance/create_core_link_form_b.png differ diff --git a/docs/img/at-a-glance/create_core_link_output.png b/docs/img/at-a-glance/create_core_link_output.png new file mode 100644 index 00000000..05860ec9 Binary files /dev/null and b/docs/img/at-a-glance/create_core_link_output.png differ diff --git a/docs/js/glightbox-instant.js b/docs/js/glightbox-instant.js new file mode 100644 index 00000000..72658837 --- /dev/null +++ b/docs/js/glightbox-instant.js @@ -0,0 +1,21 @@ +/* + * Re-apply the lightbox image links after an instant navigation. + * + * With the privacy plugin enabled, mkdocs-glightbox emits the image anchors + * without an href and fills it in from the img afterwards, using a one-shot + * inline script. navigation.instant (inherited from orchestrator-core) swaps + * in a new page without re-running that script, leaving the anchors empty: + * the lightbox then opens on nothing and shows a spinner forever. + * + * Loaded via extra_javascript, so this subscriber is registered before the + * lightbox.reload() one that mkdocs-glightbox appends to the body, and thus + * runs first on every page load. + */ +document$.subscribe(function () { + document.querySelectorAll("a.glightbox:not([href])").forEach(function (el) { + const img = el.querySelector("img") + if (img && img.src) { + el.setAttribute("href", img.src) + } + }) +}) diff --git a/mkdocs.yml b/mkdocs.yml index 31d30c23..7f6e31ac 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -4,6 +4,7 @@ nav: - Home: index.md - Learn: - Concepts: + - At A Glance: architecture/at-a-glance.md # TODO (https://github.com/workfloworchestrator/workfloworchestrator.github.io/issues/63): # The Orchestrator UI and Input Forms pages contain lots of project specific documentation and # implementation detail. @@ -90,6 +91,7 @@ extra_css: extra_javascript: - js/nav-persistence.js - js/repo-source.js + - js/glightbox-instant.js repo_name: workfloworchestrator repo_url: https://github.com/workfloworchestrator copyright: Copyright © 2018 - 2026 Workflow Orchestrator Programme @@ -122,10 +124,14 @@ plugins: python: inventories: - https://docs.python.org/3/objects.inv + # Read every sub-project from its checkout rather than from the copy installed in the + # venv, so that docstrings cannot go stale: the checkouts track their main branch, while + # the installed versions only move when pyproject.toml is bumped. A page documenting a + # symbol added since the last bump fails the build ("Could not collect ...") otherwise. + # Each entry is the directory *containing* the package, not the package itself. paths: - - lso/lso - # Read pydantic-forms from the checkout rather than the installed copy, so that - # attribute docstrings cannot go stale between edits and a re-install. + - orchestrator-core + - lso - pydantic-forms options: show_source: true @@ -185,5 +191,14 @@ plugins: # orchestrator-ui-library plugins - termynal - awesome-pages + # Click a screenshot to open it full size in a lightbox. Listed last so it runs after the plugins + # that inject HTML, and thus also covers images pulled in from the sub-projects. Images already + # wrapped in a link (badges, for instance) are left alone; add the "off-glb" class to opt an + # individual image out. + - glightbox: + draggable: false + zoomable: true + skip_classes: + - off-glb # workfloworchestrator.org general plugin that has to be loaded after awesome-pages - macros diff --git a/pyproject.toml b/pyproject.toml index 8478cc32..36a8e763 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,6 +5,7 @@ requires-python = ">=3.13,<3.15" dependencies = [ "mkdocs>=1.6.1", "mkdocs-drawio>=1.12.0", + "mkdocs-glightbox>=0.5.2", "mkdocs-material[imaging]>=9.7.0", "mkdocs-monorepo-plugin", # orchestrator-core docs plugins diff --git a/uv.lock b/uv.lock index 781d162b..1311f84d 100644 --- a/uv.lock +++ b/uv.lock @@ -1528,6 +1528,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl", hash = "sha256:e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650", size = 9555, upload-time = "2026-03-10T02:46:32.256Z" }, ] +[[package]] +name = "mkdocs-glightbox" +version = "0.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "selectolax" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8d/26/c793459622da8e31f954c6f5fb51e8f098143fdfc147b1e3c25bf686f4aa/mkdocs_glightbox-0.5.2.tar.gz", hash = "sha256:c7622799347c32310878e01ccf14f70648445561010911c80590cec0353370ac", size = 510586, upload-time = "2025-10-23T14:55:18.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/ca/03624e017e5ee2d7ce8a08d89f81c1e535eb3c30d7b2dc4a435ea3fbbeae/mkdocs_glightbox-0.5.2-py3-none-any.whl", hash = "sha256:23a431ea802b60b1030c73323db2eed6ba859df1a0822ce575afa43e0ea3f47e", size = 26458, upload-time = "2025-10-23T14:55:17.43Z" }, +] + [[package]] name = "mkdocs-include-markdown-plugin" version = "7.3.0" @@ -2769,6 +2781,41 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/72/bfa4e61ab8e7dc1c8adf397e05e6cbdd4239357bd72b248d3de662f23915/rpds_py-2026.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c93c629be4636cf54337bd5f06c104d55e42ced54d681f6fe21ae510a65116f6", size = 225422, upload-time = "2026-05-28T12:01:05.194Z" }, ] +[[package]] +name = "selectolax" +version = "0.4.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/39/6c/aec38dfee314a38cb7c0940fe055b22f22627b3e0a216772c24372eef3a9/selectolax-0.4.11.tar.gz", hash = "sha256:2b565ddabce6c9a7b73fa28a39acf8f411a084fa2f169234ec2470f552d4421d", size = 4883455, upload-time = "2026-07-15T07:25:30.588Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/b5/6e0653d45b8d138b3fc37b37780b989761fb486e7c002aa413eb89d3ad64/selectolax-0.4.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5153157ed60d968ed303acbfd2c8762fa0c0462e2663bd04466471c565deb88a", size = 2250779, upload-time = "2026-07-15T07:24:26.769Z" }, + { url = "https://files.pythonhosted.org/packages/53/c5/c367cf0583799d8c32555c4fa3b900b1e8de1aef07fb009c488a615b6ed0/selectolax-0.4.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:386494779e5464e587ed4dc076e1c48c24ebaf2da1e3a249690551d1f97fe8ed", size = 2300206, upload-time = "2026-07-15T07:24:28.321Z" }, + { url = "https://files.pythonhosted.org/packages/a7/61/956974dc429e3df99814d1ba5629a324eef366e2116b030fdd5354713402/selectolax-0.4.11-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47be0e591125484de14ff0c9aaaa814dd4a4019de35eabe360e88169a263a2b5", size = 2382455, upload-time = "2026-07-15T07:24:29.907Z" }, + { url = "https://files.pythonhosted.org/packages/51/f6/626716e2730f396bd81b853b37e9eeddd3a847730efff7548ad6d695c6e8/selectolax-0.4.11-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f8f014f328f6f79a364293bca54d43af1cec776dc10a5302054a54b5fb2d8675", size = 2431069, upload-time = "2026-07-15T07:24:31.55Z" }, + { url = "https://files.pythonhosted.org/packages/48/f6/acb03eb9e468f74fab17c655761179022fed57bfb1b25ff741e8c0c6a06c/selectolax-0.4.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c3f6fac361b5f415c409dfd1a07dd0e9a5899d10daba8d88ce16bd552b0e06f2", size = 2387626, upload-time = "2026-07-15T07:24:33.029Z" }, + { url = "https://files.pythonhosted.org/packages/5a/08/e242e5785e049499771ac5e560112396d244e6142348eaf1c70849f83a66/selectolax-0.4.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8121f4cbfe870d9ad24ae418a735d918d55844e905c3270077e97f4e579770f", size = 2449451, upload-time = "2026-07-15T07:24:34.648Z" }, + { url = "https://files.pythonhosted.org/packages/15/5d/b5dfbde64d622cc94d2136edb0caaafb5779ebbd884f6ae9c041d8dfa669/selectolax-0.4.11-cp313-cp313-win32.whl", hash = "sha256:bde121202b33e6041e9d2db1d62e7466b5883fd1c441eb96ff68d3ea015cfcbd", size = 1763387, upload-time = "2026-07-15T07:24:36.239Z" }, + { url = "https://files.pythonhosted.org/packages/6e/90/2888c831ebd473b6c17486d805a16925187c743964bbf895ec421c1cf2ab/selectolax-0.4.11-cp313-cp313-win_amd64.whl", hash = "sha256:5c7a91fbe1a94849d85228897c416ab9b4518bea6b04dce8ef8acd825ec80e9d", size = 1882102, upload-time = "2026-07-15T07:24:37.847Z" }, + { url = "https://files.pythonhosted.org/packages/83/ea/e78be8710bf162b43d6336ee354fbe21ea712284bd0bf58c67e15264862d/selectolax-0.4.11-cp313-cp313-win_arm64.whl", hash = "sha256:597b8e065978be200c598ae6d682496d96fbce14d34b5d519e93cf5b6be5fb60", size = 1817155, upload-time = "2026-07-15T07:24:39.354Z" }, + { url = "https://files.pythonhosted.org/packages/08/5a/ba94f50ca5a6a0af65e8d47147bbe9f6ad11c408fd03c832ea737836d3eb/selectolax-0.4.11-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:663ff792f92ed749cfcf452ac19aff5da74b05521e7daacb3b74388deb14d117", size = 2266464, upload-time = "2026-07-15T07:24:41.038Z" }, + { url = "https://files.pythonhosted.org/packages/12/fe/f4d7d554cd7db415c831c8fb5a2b6bbbe3bdf5a49c8f417a6093d4618d6c/selectolax-0.4.11-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d5ce592a92fceeca2694b369a83ad72891a9c356f668718fe7e1c83eea407bb4", size = 2317609, upload-time = "2026-07-15T07:24:42.682Z" }, + { url = "https://files.pythonhosted.org/packages/96/d6/9d702075634c1a38517a8af4242346bf0e65f206703037b56cf8da114eec/selectolax-0.4.11-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0f56c49161b18621ac452e42e02b0c5c61ba4c21095cfff3990e040bd9a043c", size = 2382277, upload-time = "2026-07-15T07:24:44.331Z" }, + { url = "https://files.pythonhosted.org/packages/84/c3/f541806ec7bdd0ce8ec69351572d2f2b3919264818cd5bb792482684d492/selectolax-0.4.11-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:224682039ca13eb822be626e49a03592ee2b8557bcdc6381e49417a995170c94", size = 2430423, upload-time = "2026-07-15T07:24:45.937Z" }, + { url = "https://files.pythonhosted.org/packages/70/81/533fa254be8e63b1c0fbe261ba4e2c1ca86357a4844b0830a0d7ae0985f9/selectolax-0.4.11-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bd843540a197a33049a08fd80e59bfeafbaa688e632d53a05a9b65af5e88296f", size = 2404012, upload-time = "2026-07-15T07:24:47.774Z" }, + { url = "https://files.pythonhosted.org/packages/25/5a/3fc3de5bfdc70af07d55bdc17837b5fd4ae6229444868f057085addd9a18/selectolax-0.4.11-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2b842c829f916fecb51f0f55882eca3e2ad49e85388178f14ae6fe0912be0a57", size = 2466775, upload-time = "2026-07-15T07:24:49.387Z" }, + { url = "https://files.pythonhosted.org/packages/f2/42/62c66067cbd3c360f762ac6964793091ea0371b3527ca2bf90955fb0b6f3/selectolax-0.4.11-cp314-cp314-win32.whl", hash = "sha256:d33e2ed75cc33e7af3fd50521c33e7d8634fae23bc197a6cee6a5015e056eef6", size = 1875717, upload-time = "2026-07-15T07:24:50.996Z" }, + { url = "https://files.pythonhosted.org/packages/14/b5/6d9ed39e909752645798c1469fb9443c0880ede999e63241ee89e91c7a54/selectolax-0.4.11-cp314-cp314-win_amd64.whl", hash = "sha256:e5929cbe3eedfaf51a09ec89642ab5355b703486d43bcf3c8f0c27d6043a488d", size = 1994595, upload-time = "2026-07-15T07:24:53.143Z" }, + { url = "https://files.pythonhosted.org/packages/49/f9/f172cfe8c29e295b9d7bc79e5b071937470f74311cd04dc3090d4166520a/selectolax-0.4.11-cp314-cp314-win_arm64.whl", hash = "sha256:466daca0599408c9d2cad7658a68490facc5c9b8d0f41ac5d17948914f57306f", size = 1928531, upload-time = "2026-07-15T07:24:55.539Z" }, + { url = "https://files.pythonhosted.org/packages/97/e9/6289d23fa4e5ccd5570a31c9180616a2e3c87ec565f7887bcfbca6204b6d/selectolax-0.4.11-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:086ca6f7e4c475bfff871ec1448ae5d342d43d6a2ca2cea65160d01b3a6a75ec", size = 2281363, upload-time = "2026-07-15T07:24:57.054Z" }, + { url = "https://files.pythonhosted.org/packages/06/c4/1fbf3624f9e52dadda8471dfb68eaf6021e819b827cdb62ce878fa28f469/selectolax-0.4.11-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b530a2c4fad7400af27b2b7e0333c1318ecb5f5dc38e8a141dbe3bd81b398fdf", size = 2325491, upload-time = "2026-07-15T07:24:58.969Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ba/25710a259ecb2b66b9168956b768a2651533c8ea813da9decb0e0f3ee39a/selectolax-0.4.11-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3637d21f7fe60fbd6ca3dbc67a1747f6a55a9389114d72f06b5d69ba2beddf01", size = 2387575, upload-time = "2026-07-15T07:25:00.788Z" }, + { url = "https://files.pythonhosted.org/packages/bc/73/331f83e64e3a17478e832308248345d5224957eb7a62dad2e7fc5daa15b3/selectolax-0.4.11-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fad5b1065f73eeaa07ea343cbc548aaa9f9a5c359c3bdd8d98f5d80b61550d1c", size = 2439126, upload-time = "2026-07-15T07:25:02.574Z" }, + { url = "https://files.pythonhosted.org/packages/d0/33/ab29a558dc65d3a1e28c217b62605b5135123ad89f1f825c8b741366e0fc/selectolax-0.4.11-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1da54e42ab99b9191269306e13c0fd67ada1c6654e8dc8d74fac615931dd3c62", size = 2412927, upload-time = "2026-07-15T07:25:04.375Z" }, + { url = "https://files.pythonhosted.org/packages/ae/b6/e774ec9179d7524adf47d7187b3e4e630104e149b2fbcbfe06088a3f4847/selectolax-0.4.11-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:28915b8fa90c1c3cb585858a6d24d433a3f38ea514aea59013bdb0930d9f6025", size = 2475264, upload-time = "2026-07-15T07:25:05.996Z" }, + { url = "https://files.pythonhosted.org/packages/97/14/0b4865125e777c9d852c9e388c1165e2ef4d7f1fb46596b13a1c02153fe7/selectolax-0.4.11-cp314-cp314t-win32.whl", hash = "sha256:1a6deb4464198ac67f32e56c4463aedf3e1d834b458eaac5b5b5b1ef02dcf15e", size = 1898010, upload-time = "2026-07-15T07:25:07.859Z" }, + { url = "https://files.pythonhosted.org/packages/40/1a/88db3237f2fb357119164c4f5a33a659615e3d10dd0f773d092341ee0cc4/selectolax-0.4.11-cp314-cp314t-win_amd64.whl", hash = "sha256:41f388c26304c1d840f5ee5e07c06bb9388ec834d10fec60dc148f22f98efd38", size = 2019721, upload-time = "2026-07-15T07:25:09.471Z" }, + { url = "https://files.pythonhosted.org/packages/37/03/193913c0f3d37c1e8d66ebfa0f139b2f286f70ec285907aa98b44a620447/selectolax-0.4.11-cp314-cp314t-win_arm64.whl", hash = "sha256:9077fa36e99ef4bb801194ff8f492f67279c0562e7cdfa9b4d06f5c010131969", size = 1950774, upload-time = "2026-07-15T07:25:11.533Z" }, +] + [[package]] name = "semver" version = "3.0.4" @@ -3362,6 +3409,7 @@ dependencies = [ { name = "mkdocs-awesome-pages-plugin" }, { name = "mkdocs-drawio" }, { name = "mkdocs-embed-external-markdown" }, + { name = "mkdocs-glightbox" }, { name = "mkdocs-include-markdown-plugin" }, { name = "mkdocs-macros-plugin" }, { name = "mkdocs-material", extra = ["imaging"] }, @@ -3383,6 +3431,7 @@ requires-dist = [ { name = "mkdocs-awesome-pages-plugin" }, { name = "mkdocs-drawio", specifier = ">=1.12.0" }, { name = "mkdocs-embed-external-markdown", specifier = ">=3.0.2" }, + { name = "mkdocs-glightbox", specifier = ">=0.5.2" }, { name = "mkdocs-include-markdown-plugin", specifier = ">=7.1.6" }, { name = "mkdocs-macros-plugin", specifier = ">=1.3.7" }, { name = "mkdocs-material", extras = ["imaging"], specifier = ">=9.7.0" },