@bluedynamics/cdk8s-monitoring 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/README.md +8 -1
  2. package/documentation/Makefile +357 -0
  3. package/documentation/README.md +72 -0
  4. package/documentation/mx.ini +3 -0
  5. package/documentation/sources/_build/html/.buildinfo +4 -0
  6. package/documentation/sources/_build/html/.doctrees/environment.pickle +0 -0
  7. package/documentation/sources/_build/html/.doctrees/explanation/architecture.doctree +0 -0
  8. package/documentation/sources/_build/html/.doctrees/explanation/design-philosophy.doctree +0 -0
  9. package/documentation/sources/_build/html/.doctrees/explanation/index.doctree +0 -0
  10. package/documentation/sources/_build/html/.doctrees/explanation/retention-and-downsampling.doctree +0 -0
  11. package/documentation/sources/_build/html/.doctrees/how-to/add-app-dashboards.doctree +0 -0
  12. package/documentation/sources/_build/html/.doctrees/how-to/configure-s3-credentials.doctree +0 -0
  13. package/documentation/sources/_build/html/.doctrees/how-to/configure-smtp-alerting.doctree +0 -0
  14. package/documentation/sources/_build/html/.doctrees/how-to/index.doctree +0 -0
  15. package/documentation/sources/_build/html/.doctrees/how-to/override-defaults.doctree +0 -0
  16. package/documentation/sources/_build/html/.doctrees/how-to/provide-config.doctree +0 -0
  17. package/documentation/sources/_build/html/.doctrees/how-to/setup-prerequisites.doctree +0 -0
  18. package/documentation/sources/_build/html/.doctrees/index.doctree +0 -0
  19. package/documentation/sources/_build/html/.doctrees/reference/api/index.doctree +0 -0
  20. package/documentation/sources/_build/html/.doctrees/reference/configuration-options.doctree +0 -0
  21. package/documentation/sources/_build/html/.doctrees/reference/index.doctree +0 -0
  22. package/documentation/sources/_build/html/.doctrees/reference/sync-waves.doctree +0 -0
  23. package/documentation/sources/_build/html/.doctrees/tutorials/01-quick-start.doctree +0 -0
  24. package/documentation/sources/_build/html/.doctrees/tutorials/index.doctree +0 -0
  25. package/documentation/sources/_build/html/_images/kup6s-icon-explanation.svg +32 -0
  26. package/documentation/sources/_build/html/_images/kup6s-icon-howto.svg +34 -0
  27. package/documentation/sources/_build/html/_images/kup6s-icon-monitoring.svg +79 -0
  28. package/documentation/sources/_build/html/_images/kup6s-icon-reference.svg +34 -0
  29. package/documentation/sources/_build/html/_images/kup6s-icon-tutorials.svg +30 -0
  30. package/documentation/sources/_build/html/_sources/explanation/architecture.md.txt +76 -0
  31. package/documentation/sources/_build/html/_sources/explanation/design-philosophy.md.txt +64 -0
  32. package/documentation/sources/_build/html/_sources/explanation/index.md.txt +39 -0
  33. package/documentation/sources/_build/html/_sources/explanation/retention-and-downsampling.md.txt +59 -0
  34. package/documentation/sources/_build/html/_sources/how-to/add-app-dashboards.md.txt +101 -0
  35. package/documentation/sources/_build/html/_sources/how-to/configure-s3-credentials.md.txt +96 -0
  36. package/documentation/sources/_build/html/_sources/how-to/configure-smtp-alerting.md.txt +61 -0
  37. package/documentation/sources/_build/html/_sources/how-to/index.md.txt +60 -0
  38. package/documentation/sources/_build/html/_sources/how-to/override-defaults.md.txt +98 -0
  39. package/documentation/sources/_build/html/_sources/how-to/provide-config.md.txt +116 -0
  40. package/documentation/sources/_build/html/_sources/how-to/setup-prerequisites.md.txt +105 -0
  41. package/documentation/sources/_build/html/_sources/index.md.txt +116 -0
  42. package/documentation/sources/_build/html/_sources/reference/api/index.md.txt +110 -0
  43. package/documentation/sources/_build/html/_sources/reference/configuration-options.md.txt +161 -0
  44. package/documentation/sources/_build/html/_sources/reference/index.md.txt +50 -0
  45. package/documentation/sources/_build/html/_sources/reference/sync-waves.md.txt +66 -0
  46. package/documentation/sources/_build/html/_sources/tutorials/01-quick-start.md.txt +135 -0
  47. package/documentation/sources/_build/html/_sources/tutorials/index.md.txt +37 -0
  48. package/documentation/sources/_build/html/_sphinx_design_static/design-tabs.js +101 -0
  49. package/documentation/sources/_build/html/_sphinx_design_static/sphinx-design.min.css +1 -0
  50. package/documentation/sources/_build/html/_static/base-stemmer.js +476 -0
  51. package/documentation/sources/_build/html/_static/basic.css +906 -0
  52. package/documentation/sources/_build/html/_static/brand-theme.css +685 -0
  53. package/documentation/sources/_build/html/_static/check-solid.svg +4 -0
  54. package/documentation/sources/_build/html/_static/clipboard.min.js +7 -0
  55. package/documentation/sources/_build/html/_static/copy-button.svg +5 -0
  56. package/documentation/sources/_build/html/_static/copybutton.css +94 -0
  57. package/documentation/sources/_build/html/_static/copybutton.js +248 -0
  58. package/documentation/sources/_build/html/_static/copybutton_funcs.js +73 -0
  59. package/documentation/sources/_build/html/_static/custom-icons.css +123 -0
  60. package/documentation/sources/_build/html/_static/design-tabs.js +101 -0
  61. package/documentation/sources/_build/html/_static/doctools.js +150 -0
  62. package/documentation/sources/_build/html/_static/documentation_options.js +13 -0
  63. package/documentation/sources/_build/html/_static/english-stemmer.js +1066 -0
  64. package/documentation/sources/_build/html/_static/file.png +0 -0
  65. package/documentation/sources/_build/html/_static/fonts/hack/Hack-Regular.woff2 +0 -0
  66. package/documentation/sources/_build/html/_static/fonts/orbitron/Orbitron-Black.woff2 +11 -0
  67. package/documentation/sources/_build/html/_static/fonts/orbitron/Orbitron-Bold.woff2 +11 -0
  68. package/documentation/sources/_build/html/_static/fonts/orbitron/Orbitron-Regular.woff2 +0 -0
  69. package/documentation/sources/_build/html/_static/fonts/rajdhani/Rajdhani-Bold.woff2 +11 -0
  70. package/documentation/sources/_build/html/_static/fonts/rajdhani/Rajdhani-Medium.woff2 +11 -0
  71. package/documentation/sources/_build/html/_static/fonts/rajdhani/Rajdhani-Regular.woff2 +11 -0
  72. package/documentation/sources/_build/html/_static/fonts/rajdhani/Rajdhani-SemiBold.woff2 +11 -0
  73. package/documentation/sources/_build/html/_static/kup6s-icon-explanation.svg +32 -0
  74. package/documentation/sources/_build/html/_static/kup6s-icon-howto.svg +34 -0
  75. package/documentation/sources/_build/html/_static/kup6s-icon-monitoring.svg +79 -0
  76. package/documentation/sources/_build/html/_static/kup6s-icon-reference.svg +34 -0
  77. package/documentation/sources/_build/html/_static/kup6s-icon-tutorials.svg +30 -0
  78. package/documentation/sources/_build/html/_static/language_data.js +13 -0
  79. package/documentation/sources/_build/html/_static/logo-fix.js +12 -0
  80. package/documentation/sources/_build/html/_static/minus.png +0 -0
  81. package/documentation/sources/_build/html/_static/plus.png +0 -0
  82. package/documentation/sources/_build/html/_static/print.css +42 -0
  83. package/documentation/sources/_build/html/_static/pygments.css +278 -0
  84. package/documentation/sources/_build/html/_static/searchtools.js +693 -0
  85. package/documentation/sources/_build/html/_static/shibuya.css +2 -0
  86. package/documentation/sources/_build/html/_static/shibuya.js +43 -0
  87. package/documentation/sources/_build/html/_static/sphinx-design.min.css +1 -0
  88. package/documentation/sources/_build/html/_static/sphinx_highlight.js +159 -0
  89. package/documentation/sources/_build/html/explanation/architecture.html +726 -0
  90. package/documentation/sources/_build/html/explanation/design-philosophy.html +277 -0
  91. package/documentation/sources/_build/html/explanation/index.html +240 -0
  92. package/documentation/sources/_build/html/explanation/retention-and-downsampling.html +268 -0
  93. package/documentation/sources/_build/html/genindex.html +531 -0
  94. package/documentation/sources/_build/html/how-to/add-app-dashboards.html +320 -0
  95. package/documentation/sources/_build/html/how-to/configure-s3-credentials.html +314 -0
  96. package/documentation/sources/_build/html/how-to/configure-smtp-alerting.html +277 -0
  97. package/documentation/sources/_build/html/how-to/index.html +259 -0
  98. package/documentation/sources/_build/html/how-to/override-defaults.html +316 -0
  99. package/documentation/sources/_build/html/how-to/provide-config.html +331 -0
  100. package/documentation/sources/_build/html/how-to/setup-prerequisites.html +318 -0
  101. package/documentation/sources/_build/html/index.html +353 -0
  102. package/documentation/sources/_build/html/objects.inv +6 -0
  103. package/documentation/sources/_build/html/reference/api/index.html +322 -0
  104. package/documentation/sources/_build/html/reference/configuration-options.html +654 -0
  105. package/documentation/sources/_build/html/reference/index.html +249 -0
  106. package/documentation/sources/_build/html/reference/sync-waves.html +346 -0
  107. package/documentation/sources/_build/html/search.html +555 -0
  108. package/documentation/sources/_build/html/searchindex.js +1 -0
  109. package/documentation/sources/_build/html/tutorials/01-quick-start.html +346 -0
  110. package/documentation/sources/_build/html/tutorials/index.html +238 -0
  111. package/documentation/sources/_static/brand-theme.css +685 -0
  112. package/documentation/sources/_static/custom-icons.css +123 -0
  113. package/documentation/sources/_static/fonts/hack/Hack-Regular.woff2 +0 -0
  114. package/documentation/sources/_static/fonts/orbitron/Orbitron-Black.woff2 +11 -0
  115. package/documentation/sources/_static/fonts/orbitron/Orbitron-Bold.woff2 +11 -0
  116. package/documentation/sources/_static/fonts/orbitron/Orbitron-Regular.woff2 +0 -0
  117. package/documentation/sources/_static/fonts/rajdhani/Rajdhani-Bold.woff2 +11 -0
  118. package/documentation/sources/_static/fonts/rajdhani/Rajdhani-Medium.woff2 +11 -0
  119. package/documentation/sources/_static/fonts/rajdhani/Rajdhani-Regular.woff2 +11 -0
  120. package/documentation/sources/_static/fonts/rajdhani/Rajdhani-SemiBold.woff2 +11 -0
  121. package/documentation/sources/_static/kup6s-icon-explanation.svg +32 -0
  122. package/documentation/sources/_static/kup6s-icon-howto.svg +34 -0
  123. package/documentation/sources/_static/kup6s-icon-monitoring.svg +79 -0
  124. package/documentation/sources/_static/kup6s-icon-reference.svg +34 -0
  125. package/documentation/sources/_static/kup6s-icon-tutorials.svg +30 -0
  126. package/documentation/sources/_static/logo-fix.js +12 -0
  127. package/documentation/sources/conf.py +100 -0
  128. package/documentation/sources/explanation/architecture.md +76 -0
  129. package/documentation/sources/explanation/design-philosophy.md +64 -0
  130. package/documentation/sources/explanation/index.md +39 -0
  131. package/documentation/sources/explanation/retention-and-downsampling.md +59 -0
  132. package/documentation/sources/how-to/add-app-dashboards.md +101 -0
  133. package/documentation/sources/how-to/configure-s3-credentials.md +96 -0
  134. package/documentation/sources/how-to/configure-smtp-alerting.md +61 -0
  135. package/documentation/sources/how-to/index.md +60 -0
  136. package/documentation/sources/how-to/override-defaults.md +98 -0
  137. package/documentation/sources/how-to/provide-config.md +116 -0
  138. package/documentation/sources/how-to/setup-prerequisites.md +105 -0
  139. package/documentation/sources/index.md +116 -0
  140. package/documentation/sources/reference/api/index.md +110 -0
  141. package/documentation/sources/reference/configuration-options.md +161 -0
  142. package/documentation/sources/reference/index.md +50 -0
  143. package/documentation/sources/reference/sync-waves.md +66 -0
  144. package/documentation/sources/tutorials/01-quick-start.md +135 -0
  145. package/documentation/sources/tutorials/index.md +37 -0
  146. package/examples/synth.ts +7 -0
  147. package/lib/constructs/grafana-password-secret.d.ts +4 -2
  148. package/lib/constructs/grafana-password-secret.js +6 -6
  149. package/lib/constructs/loki-s3-bucket.d.ts +1 -1
  150. package/lib/constructs/loki-s3-bucket.js +5 -5
  151. package/lib/constructs/loki-s3-credentials.d.ts +2 -2
  152. package/lib/constructs/loki-s3-credentials.js +7 -7
  153. package/lib/constructs/thanos-s3-bucket.d.ts +1 -1
  154. package/lib/constructs/thanos-s3-bucket.js +5 -5
  155. package/lib/constructs/thanos-s3-credentials.d.ts +2 -2
  156. package/lib/constructs/thanos-s3-credentials.js +6 -6
  157. package/lib/default-config.js +2 -1
  158. package/lib/monitoring-chart.js +2 -1
  159. package/lib/types.d.ts +13 -0
  160. package/lib/types.js +1 -1
  161. package/package.json +1 -1
package/README.md CHANGED
@@ -41,6 +41,13 @@ const config = mergeConfig({
41
41
  buckets: { thanos: 'metrics-thanos', loki: 'logs-loki' },
42
42
  },
43
43
  smtp: { host: 'mail.example.com', port: 587, from: 'monitoring@example.com', requireTls: true },
44
+ integrations: {
45
+ s3ProviderConfig: 'my-s3-provider', // Crossplane ProviderConfig (buckets)
46
+ s3SecretStore: 'my-s3-secret-store', // ESO ClusterSecretStore (S3 credentials)
47
+ s3CredentialsKey: 'my-s3-credentials', // remote key holding AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY
48
+ grafanaSecretStore: 'my-app-secret-store', // ESO ClusterSecretStore (Grafana admin)
49
+ grafanaCredentialsKey: 'my-grafana-admin', // remote key holding admin-user / admin-password
50
+ },
44
51
  // Override any default, e.g. storage: { prometheus: '30Gi' }
45
52
  });
46
53
 
@@ -50,7 +57,7 @@ app.synth();
50
57
 
51
58
  ### Required vs. defaulted configuration
52
59
 
53
- - **Required (`RequiredClusterConfig`)**: `namespace`, `domains`, `s3`, `smtp`. No universal default exists, so you must provide them.
60
+ - **Required (`RequiredClusterConfig`)**: `namespace`, `domains`, `s3`, `smtp`, `integrations`. No universal default exists, so you must provide them. `integrations` names the external Crossplane ProviderConfig and ESO ClusterSecretStores/keys this stack references (nothing is hardcoded — wire your own).
54
61
  - **Defaulted (`DefaultableConfig`)**: `versions`, `retention`, `storage`, `replicas`, `resources`. Shipped in `DEFAULT_CONFIG`; override any subset via `mergeConfig` (deep-merged).
55
62
 
56
63
  ### Adding application dashboards and alerts
@@ -0,0 +1,357 @@
1
+ ##############################################################################
2
+ # THIS FILE IS GENERATED BY MXMAKE
3
+ #
4
+ # DOMAINS:
5
+ #: core.base
6
+ #: core.mxenv
7
+ #: docs.sphinx
8
+ #
9
+ # SETTINGS (ALL CHANGES MADE BELOW SETTINGS WILL BE LOST)
10
+ ##############################################################################
11
+
12
+ ## core.base
13
+
14
+ # `deploy` target dependencies.
15
+ # No default value.
16
+ DEPLOY_TARGETS?=
17
+
18
+ # target to be executed when calling `make run`
19
+ # No default value.
20
+ RUN_TARGET?=
21
+
22
+ # Additional files and folders to remove when running clean target
23
+ # No default value.
24
+ CLEAN_FS?=
25
+
26
+ # Optional makefile to include before default targets. This can
27
+ # be used to provide custom targets or hook up to existing targets.
28
+ # Default: include.mk
29
+ INCLUDE_MAKEFILE?=include.mk
30
+
31
+ # Optional additional directories to be added to PATH in format
32
+ # `/path/to/dir/:/path/to/other/dir`. Gets inserted first, thus gets searched
33
+ # first.
34
+ # No default value.
35
+ EXTRA_PATH?=
36
+
37
+ # Path to Python project relative to Makefile (repository root).
38
+ # Leave empty if Python project is in the same directory as Makefile.
39
+ # For monorepo setups, set to subdirectory name (e.g., `backend`).
40
+ # Future-proofed for multi-language monorepos (e.g., PROJECT_PATH_NODEJS).
41
+ # No default value.
42
+ PROJECT_PATH_PYTHON?=
43
+
44
+ ## core.mxenv
45
+
46
+ # Primary Python interpreter to use. It is used to create the
47
+ # virtual environment if `VENV_ENABLED` and `VENV_CREATE` are set to `true`.
48
+ # If global `uv` is used, this value is passed as `--python VALUE` to the venv creation.
49
+ # uv then downloads the Python interpreter if it is not available.
50
+ # for more on this feature read the [uv python documentation](https://docs.astral.sh/uv/concepts/python-versions/)
51
+ # Default: python3
52
+ PRIMARY_PYTHON?=3.13
53
+
54
+ # Minimum required Python version.
55
+ # Default: 3.10
56
+ PYTHON_MIN_VERSION?=3.13
57
+
58
+ # Install packages using the given package installer method.
59
+ # Supported are `pip` and `uv`. When `uv` is selected, a global installation
60
+ # is auto-detected and used if available. Otherwise, uv is installed in the
61
+ # virtual environment or using `PRIMARY_PYTHON`, depending on the
62
+ # `VENV_ENABLED` setting.
63
+ # Default: pip
64
+ PYTHON_PACKAGE_INSTALLER?=uv
65
+
66
+ # Python version for UV to install/use when creating virtual
67
+ # environments with global UV. Passed to `uv venv -p VALUE`. Supports version
68
+ # specs like `3.11`, `3.14`, `cpython@3.14`. Defaults to PRIMARY_PYTHON value
69
+ # for backward compatibility.
70
+ # Default: $(PRIMARY_PYTHON)
71
+ UV_PYTHON?=$(PRIMARY_PYTHON)
72
+
73
+ # Flag whether to use virtual environment. If `false`, the
74
+ # interpreter according to `PRIMARY_PYTHON` found in `PATH` is used.
75
+ # Default: true
76
+ VENV_ENABLED?=true
77
+
78
+ # Flag whether to create a virtual environment. If set to `false`
79
+ # and `VENV_ENABLED` is `true`, `VENV_FOLDER` is expected to point to an
80
+ # existing virtual environment.
81
+ # Default: true
82
+ VENV_CREATE?=true
83
+
84
+ # The folder of the virtual environment.
85
+ # If `VENV_ENABLED` is `true` and `VENV_CREATE` is true it is used as the
86
+ # target folder for the virtual environment. If `VENV_ENABLED` is `true` and
87
+ # `VENV_CREATE` is false it is expected to point to an existing virtual
88
+ # environment. If `VENV_ENABLED` is `false` it is ignored.
89
+ # Default: .venv
90
+ VENV_FOLDER?=.venv
91
+
92
+ # mxdev to install in virtual environment.
93
+ # Default: mxdev
94
+ MXDEV?=mxdev
95
+
96
+ # mxmake to install in virtual environment.
97
+ # Default: mxmake
98
+ MXMAKE?=mxmake
99
+
100
+ ## docs.sphinx
101
+
102
+ # Documentation source folder.
103
+ # Default: docs/source
104
+ DOCS_SOURCE_FOLDER?=sources
105
+
106
+ # Documentation generation target folder.
107
+ # Default: docs/html
108
+ DOCS_TARGET_FOLDER?=html
109
+
110
+ # Documentation linkcheck output folder.
111
+ # Default: docs/linkcheck
112
+ DOCS_LINKCHECK_FOLDER?=docs/linkcheck
113
+
114
+ # Documentation Python requirements to be installed (via pip).
115
+ # No default value.
116
+ DOCS_REQUIREMENTS?=myst_parser sphinxcontrib.mermaid shibuya sphinx-design sphinx-copybutton
117
+
118
+ ##############################################################################
119
+ # END SETTINGS - DO NOT EDIT BELOW THIS LINE
120
+ ##############################################################################
121
+
122
+ INSTALL_TARGETS?=
123
+ DIRTY_TARGETS?=
124
+ CLEAN_TARGETS?=
125
+ PURGE_TARGETS?=
126
+
127
+ export PATH:=$(if $(EXTRA_PATH),$(EXTRA_PATH):,)$(PATH)
128
+
129
+ # Helper variable: adds trailing slash to PROJECT_PATH_PYTHON only if non-empty
130
+ PYTHON_PROJECT_PREFIX=$(if $(PROJECT_PATH_PYTHON),$(PROJECT_PATH_PYTHON)/,)
131
+
132
+ # Defensive settings for make: https://tech.davis-hansson.com/p/make/
133
+ SHELL:=bash
134
+ .ONESHELL:
135
+ # for Makefile debugging purposes add -x to the .SHELLFLAGS
136
+ .SHELLFLAGS:=-eu -o pipefail -O inherit_errexit -c
137
+ .SILENT:
138
+ .DELETE_ON_ERROR:
139
+ MAKEFLAGS+=--warn-undefined-variables
140
+ MAKEFLAGS+=--no-builtin-rules
141
+
142
+ # mxmake folder
143
+ MXMAKE_FOLDER?=.mxmake
144
+
145
+ # Sentinel files
146
+ SENTINEL_FOLDER?=$(MXMAKE_FOLDER)/sentinels
147
+ SENTINEL?=$(SENTINEL_FOLDER)/about.txt
148
+ $(SENTINEL): $(firstword $(MAKEFILE_LIST))
149
+ @mkdir -p $(SENTINEL_FOLDER)
150
+ @echo "Sentinels for the Makefile process." > $(SENTINEL)
151
+
152
+ ##############################################################################
153
+ # mxenv
154
+ ##############################################################################
155
+
156
+ OS?=
157
+
158
+ # Determine the executable path
159
+ ifeq ("$(VENV_ENABLED)", "true")
160
+ export VIRTUAL_ENV=$(abspath $(VENV_FOLDER))
161
+ ifeq ("$(OS)", "Windows_NT")
162
+ VENV_EXECUTABLE_FOLDER=$(VIRTUAL_ENV)/Scripts
163
+ else
164
+ VENV_EXECUTABLE_FOLDER=$(VIRTUAL_ENV)/bin
165
+ endif
166
+ export PATH:=$(VENV_EXECUTABLE_FOLDER):$(PATH)
167
+ MXENV_PYTHON=python
168
+ else
169
+ MXENV_PYTHON=$(PRIMARY_PYTHON)
170
+ endif
171
+
172
+ # Determine the package installer with non-interactive flags
173
+ ifeq ("$(PYTHON_PACKAGE_INSTALLER)","uv")
174
+ PYTHON_PACKAGE_COMMAND=uv pip --no-progress
175
+ else
176
+ PYTHON_PACKAGE_COMMAND=$(MXENV_PYTHON) -m pip
177
+ endif
178
+
179
+ # Auto-detect global uv availability (simple existence check)
180
+ ifeq ("$(PYTHON_PACKAGE_INSTALLER)","uv")
181
+ UV_AVAILABLE:=$(shell command -v uv >/dev/null 2>&1 && echo "true" || echo "false")
182
+ else
183
+ UV_AVAILABLE:=false
184
+ endif
185
+
186
+ # Determine installation strategy
187
+ # depending on the PYTHON_PACKAGE_INSTALLER and UV_AVAILABLE
188
+ # - both vars can be false or
189
+ # - one of them can be true,
190
+ # - but never boths.
191
+ USE_GLOBAL_UV:=$(shell [[ "$(PYTHON_PACKAGE_INSTALLER)" == "uv" && "$(UV_AVAILABLE)" == "true" ]] && echo "true" || echo "false")
192
+ USE_LOCAL_UV:=$(shell [[ "$(PYTHON_PACKAGE_INSTALLER)" == "uv" && "$(UV_AVAILABLE)" == "false" ]] && echo "true" || echo "false")
193
+
194
+ # Check if global UV is outdated (non-blocking warning)
195
+ ifeq ("$(USE_GLOBAL_UV)","true")
196
+ UV_OUTDATED:=$(shell uv self update --dry-run 2>&1 | grep -q "Would update" && echo "true" || echo "false")
197
+ else
198
+ UV_OUTDATED:=false
199
+ endif
200
+
201
+ MXENV_TARGET:=$(SENTINEL_FOLDER)/mxenv.sentinel
202
+ $(MXENV_TARGET): $(SENTINEL)
203
+ # Validation: Check Python version if not using global uv
204
+ ifneq ("$(USE_GLOBAL_UV)","true")
205
+ @$(PRIMARY_PYTHON) -c "import sys; vi = sys.version_info; sys.exit(1 if (int(vi[0]), int(vi[1])) >= tuple(map(int, '$(PYTHON_MIN_VERSION)'.split('.'))) else 0)" \
206
+ && echo "Need Python >= $(PYTHON_MIN_VERSION)" && exit 1 || :
207
+ else
208
+ @echo "Using global uv for Python $(UV_PYTHON)"
209
+ endif
210
+ # Validation: Check VENV_FOLDER is set if venv enabled
211
+ @[[ "$(VENV_ENABLED)" == "true" && "$(VENV_FOLDER)" == "" ]] \
212
+ && echo "VENV_FOLDER must be configured if VENV_ENABLED is true" && exit 1 || :
213
+ # Validation: Check uv not used with system Python
214
+ @[[ "$(VENV_ENABLED)" == "false" && "$(PYTHON_PACKAGE_INSTALLER)" == "uv" ]] \
215
+ && echo "Package installer uv does not work with a global Python interpreter." && exit 1 || :
216
+ # Warning: Notify if global UV is outdated
217
+ ifeq ("$(UV_OUTDATED)","true")
218
+ @echo "WARNING: A newer version of uv is available. Run 'uv self update' to upgrade."
219
+ endif
220
+
221
+ # Create virtual environment
222
+ ifeq ("$(VENV_ENABLED)", "true")
223
+ ifeq ("$(VENV_CREATE)", "true")
224
+ ifeq ("$(USE_GLOBAL_UV)","true")
225
+ @echo "Setup Python Virtual Environment using global uv at '$(VENV_FOLDER)'"
226
+ @uv venv --allow-existing --no-progress -p $(UV_PYTHON) --seed $(VENV_FOLDER)
227
+ else
228
+ @echo "Setup Python Virtual Environment using module 'venv' at '$(VENV_FOLDER)'"
229
+ @$(PRIMARY_PYTHON) -m venv $(VENV_FOLDER)
230
+ @$(MXENV_PYTHON) -m ensurepip -U
231
+ endif
232
+ endif
233
+ else
234
+ @echo "Using system Python interpreter"
235
+ endif
236
+
237
+ # Install uv locally if needed
238
+ ifeq ("$(USE_LOCAL_UV)","true")
239
+ @echo "Install uv in virtual environment"
240
+ @$(MXENV_PYTHON) -m pip install uv
241
+ endif
242
+
243
+ # Install/upgrade core packages
244
+ @$(PYTHON_PACKAGE_COMMAND) install -U pip setuptools wheel
245
+ @echo "Install/Update MXStack Python packages"
246
+ @$(PYTHON_PACKAGE_COMMAND) install -U $(MXDEV) $(MXMAKE)
247
+ @touch $(MXENV_TARGET)
248
+
249
+ .PHONY: mxenv
250
+ mxenv: $(MXENV_TARGET)
251
+
252
+ .PHONY: mxenv-dirty
253
+ mxenv-dirty:
254
+ @rm -f $(MXENV_TARGET)
255
+
256
+ .PHONY: mxenv-clean
257
+ mxenv-clean: mxenv-dirty
258
+ ifeq ("$(VENV_ENABLED)", "true")
259
+ ifeq ("$(VENV_CREATE)", "true")
260
+ @rm -rf $(VENV_FOLDER)
261
+ endif
262
+ else
263
+ @$(PYTHON_PACKAGE_COMMAND) uninstall -y $(MXDEV)
264
+ @$(PYTHON_PACKAGE_COMMAND) uninstall -y $(MXMAKE)
265
+ endif
266
+
267
+ INSTALL_TARGETS+=mxenv
268
+ DIRTY_TARGETS+=mxenv-dirty
269
+ CLEAN_TARGETS+=mxenv-clean
270
+
271
+ ##############################################################################
272
+ # sphinx
273
+ ##############################################################################
274
+
275
+ # additional targets required for building docs.
276
+ DOCS_TARGETS+=
277
+
278
+ SPHINX_BIN=sphinx-build
279
+ SPHINX_AUTOBUILD_BIN=sphinx-autobuild
280
+
281
+ DOCS_TARGET:=$(SENTINEL_FOLDER)/sphinx.sentinel
282
+ $(DOCS_TARGET): $(MXENV_TARGET)
283
+ @echo "Install Sphinx"
284
+ @$(PYTHON_PACKAGE_COMMAND) install -U sphinx sphinx-autobuild $(DOCS_REQUIREMENTS)
285
+ @touch $(DOCS_TARGET)
286
+
287
+ .PHONY: docs
288
+ docs: $(DOCS_TARGET) $(DOCS_TARGETS)
289
+ @echo "Build sphinx docs"
290
+ @$(SPHINX_BIN) $(DOCS_SOURCE_FOLDER) $(DOCS_TARGET_FOLDER)
291
+
292
+ .PHONY: docs-live
293
+ docs-live: $(DOCS_TARGET) $(DOCS_TARGETS)
294
+ @echo "Rebuild Sphinx documentation on changes, with live-reload in the browser"
295
+ @$(SPHINX_AUTOBUILD_BIN) $(DOCS_SOURCE_FOLDER) $(DOCS_TARGET_FOLDER)
296
+
297
+ .PHONY: docs-linkcheck
298
+ docs-linkcheck: $(DOCS_TARGET) $(DOCS_TARGETS)
299
+ @echo "Run Sphinx linkcheck"
300
+ @$(SPHINX_BIN) -b linkcheck $(DOCS_SOURCE_FOLDER) $(DOCS_LINKCHECK_FOLDER)
301
+
302
+ .PHONY: docs-dirty
303
+ docs-dirty:
304
+ @rm -f $(DOCS_TARGET)
305
+
306
+ .PHONY: docs-clean
307
+ docs-clean: docs-dirty
308
+ @test -e $(MXENV_PYTHON) && $(MXENV_PYTHON) -m pip uninstall -y \
309
+ sphinx sphinx-autobuild $(DOCS_REQUIREMENTS) || :
310
+ @rm -rf $(DOCS_TARGET_FOLDER)
311
+
312
+ INSTALL_TARGETS+=$(DOCS_TARGET)
313
+ DIRTY_TARGETS+=docs-dirty
314
+ CLEAN_TARGETS+=docs-clean
315
+
316
+ ##############################################################################
317
+ # Custom includes
318
+ ##############################################################################
319
+
320
+ -include $(INCLUDE_MAKEFILE)
321
+
322
+ ##############################################################################
323
+ # Default targets
324
+ ##############################################################################
325
+
326
+ INSTALL_TARGET:=$(SENTINEL_FOLDER)/install.sentinel
327
+ $(INSTALL_TARGET): $(INSTALL_TARGETS)
328
+ @touch $(INSTALL_TARGET)
329
+
330
+ .PHONY: install
331
+ install: $(INSTALL_TARGET)
332
+ @touch $(INSTALL_TARGET)
333
+
334
+ .PHONY: run
335
+ run: $(RUN_TARGET)
336
+
337
+ .PHONY: deploy
338
+ deploy: $(DEPLOY_TARGETS)
339
+
340
+ .PHONY: dirty
341
+ dirty: $(DIRTY_TARGETS)
342
+ @rm -f $(INSTALL_TARGET)
343
+
344
+ .PHONY: clean
345
+ clean: dirty $(CLEAN_TARGETS)
346
+ @rm -rf $(CLEAN_TARGETS) $(MXMAKE_FOLDER) $(CLEAN_FS)
347
+
348
+ .PHONY: purge
349
+ purge: clean $(PURGE_TARGETS)
350
+
351
+ .PHONY: runtime-clean
352
+ runtime-clean:
353
+ @echo "Remove runtime artifacts, like byte-code and caches."
354
+ @find . -name '*.py[c|o]' -delete
355
+ @find . -name '*~' -exec rm -f {} +
356
+ @find . -name '__pycache__' -exec rm -fr {} +
357
+
@@ -0,0 +1,72 @@
1
+ # cdk8s-monitoring documentation
2
+
3
+ This directory contains the documentation for `@bluedynamics/cdk8s-monitoring`, built with [Sphinx](https://www.sphinx-doc.org/), [MyST Parser](https://myst-parser.readthedocs.io/), and organized with the [Diátaxis framework](https://diataxis.fr/).
4
+
5
+ ## Published documentation
6
+
7
+ <https://bluedynamics.github.io/cdk8s-monitoring/>
8
+
9
+ ## Build locally
10
+
11
+ ### Prerequisites
12
+
13
+ - Python 3.13 (or 3.10+ with manual configuration)
14
+ - The `make` command
15
+ - `uv` (optional, but recommended for faster installs)
16
+
17
+ ### Quick start
18
+
19
+ ```shell
20
+ cd documentation
21
+ make docs
22
+ ```
23
+
24
+ Open `html/index.html` in your browser.
25
+
26
+ The first build creates a virtual environment, installs Sphinx and the required extensions, and generates the HTML.
27
+ Subsequent builds reuse the environment and are much faster.
28
+
29
+ ### Live reload
30
+
31
+ ```shell
32
+ cd documentation
33
+ make docs-live
34
+ ```
35
+
36
+ This starts a development server that rebuilds and refreshes the browser whenever you save a file.
37
+
38
+ ### Clean build
39
+
40
+ ```shell
41
+ make clean
42
+ make docs
43
+ ```
44
+
45
+ ## Structure
46
+
47
+ The documentation follows the four Diátaxis quadrants.
48
+
49
+ ```
50
+ sources/
51
+ ├── index.md # Landing page with grid cards
52
+ ├── tutorials/ # Learning-oriented lessons
53
+ ├── how-to/ # Goal-oriented recipes
54
+ ├── reference/ # Information-oriented specifications
55
+ │ └── api/ # Construct and interface overview
56
+ ├── explanation/ # Understanding-oriented background
57
+ └── _static/ # CSS, icons, fonts, scripts
58
+ ```
59
+
60
+ ## Style
61
+
62
+ Documentation is authored in plone-doc-style: American English, one sentence per line, sentence-case headings, dash-separated filenames, and sparse admonitions.
63
+
64
+ ## Theme and extensions
65
+
66
+ - **Theme:** [Shibuya](https://shibuya.lepture.com/)
67
+ - **Parser:** [MyST](https://myst-parser.readthedocs.io/)
68
+ - **Extensions:** `myst_parser`, `sphinxcontrib.mermaid`, `sphinx_design`, `sphinx_copybutton`
69
+
70
+ ## Deployment
71
+
72
+ A push to `main` that touches `documentation/` triggers the `documentation.yml` GitHub Actions workflow, which builds the site and deploys it to GitHub Pages.
@@ -0,0 +1,3 @@
1
+ [settings]
2
+ threads = 5
3
+ version-overrides =
@@ -0,0 +1,4 @@
1
+ # Sphinx build info version 1
2
+ # This file records the configuration used when building these files. When it is not found, a full rebuild will be done.
3
+ config: c6f4b154db9f6c9190f85eed1c9807ae
4
+ tags: 645f666f9bcd5a90fca523b33c5a78b7
@@ -0,0 +1,32 @@
1
+ <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
2
+ <g transform="translate(50, 50)">
3
+ <path d="M -35,-35 L -30,-35 L -30,-30" stroke="#00d4ff" stroke-width="2.5" fill="none"/>
4
+ <path d="M -35,35 L -30,35 L -30,30" stroke="#00d4ff" stroke-width="2.5" fill="none"/>
5
+ <path d="M 35,-35 L 30,-35 L 30,-30" stroke="#ff00ff" stroke-width="2.5" fill="none"/>
6
+ <path d="M 35,35 L 30,35 L 30,30" stroke="#ff00ff" stroke-width="2.5" fill="none"/>
7
+
8
+ <line x1="-25" y1="-15" x2="25" y2="-15" stroke="#00d4ff" stroke-width="1" opacity="0.4"/>
9
+ <line x1="-25" y1="15" x2="25" y2="15" stroke="#ff00ff" stroke-width="1" opacity="0.4"/>
10
+
11
+ <!-- Explanation: Document with lightbulb/understanding symbol -->
12
+ <rect x="-12" y="-16" width="24" height="32" fill="none" stroke="#00d4ff" stroke-width="2.5"/>
13
+ <path d="M 12,-16 L 12,-10 L 6,-10" fill="none" stroke="#ff00ff" stroke-width="2"/>
14
+ <path d="M 6,-10 L 12,-10 L 12,-16" fill="#ff00ff" opacity="0.3"/>
15
+
16
+ <!-- Lightbulb for understanding -->
17
+ <circle cx="0" cy="-2" r="6" fill="none" stroke="#ff00ff" stroke-width="2"/>
18
+ <path d="M -3,4 L -3,7 L 3,7 L 3,4" fill="none" stroke="#00d4ff" stroke-width="1.5"/>
19
+ <line x1="-1" y1="8" x2="1" y2="8" stroke="#00d4ff" stroke-width="1.5"/>
20
+
21
+ <!-- Light rays -->
22
+ <line x1="-8" y1="-2" x2="-10" y2="-2" stroke="#ff00ff" stroke-width="1.5" opacity="0.6"/>
23
+ <line x1="8" y1="-2" x2="10" y2="-2" stroke="#ff00ff" stroke-width="1.5" opacity="0.6"/>
24
+ <line x1="-5" y1="-7" x2="-7" y2="-9" stroke="#ff00ff" stroke-width="1.5" opacity="0.6"/>
25
+ <line x1="5" y1="-7" x2="7" y2="-9" stroke="#ff00ff" stroke-width="1.5" opacity="0.6"/>
26
+
27
+ <line x1="-12" y1="-10" x2="-12" y2="16" stroke="#ff00ff" stroke-width="3" opacity="0.4"/>
28
+
29
+ <circle cx="-28" cy="0" r="2" fill="#00d4ff" opacity="0.8"/>
30
+ <circle cx="28" cy="0" r="2" fill="#ff00ff" opacity="0.8"/>
31
+ </g>
32
+ </svg>
@@ -0,0 +1,34 @@
1
+ <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
2
+ <g transform="translate(50, 50)">
3
+ <path d="M -35,-35 L -30,-35 L -30,-30" stroke="#00d4ff" stroke-width="2.5" fill="none"/>
4
+ <path d="M -35,35 L -30,35 L -30,30" stroke="#00d4ff" stroke-width="2.5" fill="none"/>
5
+ <path d="M 35,-35 L 30,-35 L 30,-30" stroke="#ff00ff" stroke-width="2.5" fill="none"/>
6
+ <path d="M 35,35 L 30,35 L 30,30" stroke="#ff00ff" stroke-width="2.5" fill="none"/>
7
+
8
+ <line x1="-25" y1="-15" x2="25" y2="-15" stroke="#00d4ff" stroke-width="1" opacity="0.4"/>
9
+ <line x1="-25" y1="15" x2="25" y2="15" stroke="#ff00ff" stroke-width="1" opacity="0.4"/>
10
+
11
+ <!-- How-to: Document with numbered steps/checklist -->
12
+ <rect x="-12" y="-16" width="24" height="32" fill="none" stroke="#00d4ff" stroke-width="2.5"/>
13
+ <path d="M 12,-16 L 12,-10 L 6,-10" fill="none" stroke="#ff00ff" stroke-width="2"/>
14
+ <path d="M 6,-10 L 12,-10 L 12,-16" fill="#ff00ff" opacity="0.3"/>
15
+
16
+ <!-- Numbered steps -->
17
+ <circle cx="-7" cy="-6" r="2" fill="none" stroke="#ff00ff" stroke-width="1.5"/>
18
+ <text x="-7" y="-4" font-family="Arial" font-size="6" fill="#ff00ff" text-anchor="middle" font-weight="bold">1</text>
19
+ <line x1="-3" y1="-6" x2="8" y2="-6" stroke="#ff00ff" stroke-width="1.5" opacity="0.8"/>
20
+
21
+ <circle cx="-7" cy="1" r="2" fill="none" stroke="#ff00ff" stroke-width="1.5"/>
22
+ <text x="-7" y="3" font-family="Arial" font-size="6" fill="#ff00ff" text-anchor="middle" font-weight="bold">2</text>
23
+ <line x1="-3" y1="1" x2="8" y2="1" stroke="#ff00ff" stroke-width="1.5" opacity="0.6"/>
24
+
25
+ <circle cx="-7" cy="8" r="2" fill="none" stroke="#00d4ff" stroke-width="1.5"/>
26
+ <text x="-7" y="10" font-family="Arial" font-size="6" fill="#00d4ff" text-anchor="middle" font-weight="bold">3</text>
27
+ <line x1="-3" y1="8" x2="6" y2="8" stroke="#00d4ff" stroke-width="1.5" opacity="0.4"/>
28
+
29
+ <line x1="-12" y1="-10" x2="-12" y2="16" stroke="#ff00ff" stroke-width="3" opacity="0.4"/>
30
+
31
+ <circle cx="-28" cy="0" r="2" fill="#00d4ff" opacity="0.8"/>
32
+ <circle cx="28" cy="0" r="2" fill="#ff00ff" opacity="0.8"/>
33
+ </g>
34
+ </svg>
@@ -0,0 +1,79 @@
1
+ <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
2
+ <g transform="translate(50, 50)">
3
+ <!-- Scanlines (CRT effect - reduced) -->
4
+ <line x1="-35" y1="-28" x2="35" y2="-28" stroke="#00d4ff" stroke-width="0.3" opacity="0.12"/>
5
+ <line x1="-35" y1="-20" x2="35" y2="-20" stroke="#00d4ff" stroke-width="0.3" opacity="0.12"/>
6
+ <line x1="-35" y1="-12" x2="35" y2="-12" stroke="#00d4ff" stroke-width="0.3" opacity="0.12"/>
7
+ <line x1="-35" y1="-4" x2="35" y2="-4" stroke="#00d4ff" stroke-width="0.3" opacity="0.12"/>
8
+ <line x1="-35" y1="4" x2="35" y2="4" stroke="#00d4ff" stroke-width="0.3" opacity="0.12"/>
9
+ <line x1="-35" y1="12" x2="35" y2="12" stroke="#00d4ff" stroke-width="0.3" opacity="0.12"/>
10
+ <line x1="-35" y1="20" x2="35" y2="20" stroke="#00d4ff" stroke-width="0.3" opacity="0.12"/>
11
+ <line x1="-35" y1="28" x2="35" y2="28" stroke="#00d4ff" stroke-width="0.3" opacity="0.12"/>
12
+
13
+ <!-- Corner brackets -->
14
+ <path d="M -35,-35 L -30,-35 L -30,-30" stroke="#00d4ff" stroke-width="2.5" fill="none"/>
15
+ <path d="M -35,35 L -30,35 L -30,30" stroke="#00d4ff" stroke-width="2.5" fill="none"/>
16
+ <path d="M 35,-35 L 30,-35 L 30,-30" stroke="#ff00ff" stroke-width="2.5" fill="none"/>
17
+ <path d="M 35,35 L 30,35 L 30,30" stroke="#ff00ff" stroke-width="2.5" fill="none"/>
18
+
19
+ <!-- Glitch lines (digital corruption effect - subtle) -->
20
+ <!-- Top left glitches -->
21
+ <line x1="-33" y1="-36" x2="-30" y2="-36" stroke="#ff00ff" stroke-width="1" opacity="0.3"/>
22
+ <!-- Top right glitches -->
23
+ <line x1="30" y1="-36" x2="33" y2="-36" stroke="#00d4ff" stroke-width="1" opacity="0.3"/>
24
+ <!-- Bottom left glitches -->
25
+ <line x1="-33" y1="36" x2="-30" y2="36" stroke="#00d4ff" stroke-width="1" opacity="0.3"/>
26
+ <!-- Bottom right glitches -->
27
+ <line x1="30" y1="36" x2="33" y2="36" stroke="#ff00ff" stroke-width="1" opacity="0.3"/>
28
+
29
+ <line x1="-25" y1="-15" x2="25" y2="-15" stroke="#00d4ff" stroke-width="1" opacity="0.4"/>
30
+ <line x1="-25" y1="15" x2="25" y2="15" stroke="#ff00ff" stroke-width="1" opacity="0.4"/>
31
+
32
+ <!-- Plone circular device with 4 dots pattern -->
33
+ <!-- Outer circle (centered) -->
34
+ <circle cx="0" cy="0" r="10" fill="none" stroke="#00d4ff" stroke-width="2.5"/>
35
+
36
+ <!-- Glitch circles (offset duplicates for digital corruption - subtle) -->
37
+ <circle cx="0.5" cy="0" r="10" fill="none" stroke="#ff00ff" stroke-width="0.8" opacity="0.15"/>
38
+ <circle cx="-0.3" cy="0.2" r="10" fill="none" stroke="#00d4ff" stroke-width="0.6" opacity="0.1"/>
39
+
40
+ <!-- Inner circle (subtle, magenta) -->
41
+ <circle cx="0" cy="0" r="8.5" fill="none" stroke="#ff00ff" stroke-width="1" opacity="0.3"/>
42
+
43
+ <!-- The distinctive Plone dot pattern -->
44
+ <!-- 2 dots on the left side (cyan, moved more to center) -->
45
+ <circle cx="-3" cy="-3" r="2" fill="#00d4ff" opacity="0.9"/>
46
+ <circle cx="-3" cy="3" r="2" fill="#00d4ff" opacity="0.9"/>
47
+
48
+ <!-- 1 dot on the right side (cyan) -->
49
+ <circle cx="4" cy="0" r="2" fill="#00d4ff" opacity="0.9"/>
50
+
51
+ <!-- CMS content elements positioned away from corner brackets -->
52
+ <!-- Document icon (top left, moved in from corner) -->
53
+ <rect x="-26" y="-26" width="12" height="16" fill="none" stroke="#ff00ff" stroke-width="1.5" rx="1"/>
54
+ <line x1="-24" y1="-22" x2="-16" y2="-22" stroke="#ff00ff" stroke-width="1" opacity="0.7"/>
55
+ <line x1="-24" y1="-19" x2="-16" y2="-19" stroke="#ff00ff" stroke-width="1" opacity="0.7"/>
56
+ <line x1="-24" y1="-16" x2="-16" y2="-16" stroke="#ff00ff" stroke-width="1" opacity="0.7"/>
57
+ <line x1="-24" y1="-13" x2="-18" y2="-13" stroke="#ff00ff" stroke-width="1" opacity="0.7"/>
58
+
59
+ <!-- Image icon (top right, moved in from corner) -->
60
+ <rect x="14" y="-26" width="12" height="16" fill="none" stroke="#00d4ff" stroke-width="1.5" rx="1"/>
61
+ <circle cx="18" cy="-21" r="2.5" fill="none" stroke="#00d4ff" stroke-width="1" opacity="0.7"/>
62
+ <path d="M 14,-12 L 18,-18 L 22,-15 L 26,-20" fill="none" stroke="#00d4ff" stroke-width="1.2" opacity="0.7"/>
63
+
64
+ <!-- File/Content icon (bottom left, moved in from corner) -->
65
+ <rect x="-26" y="10" width="12" height="16" fill="none" stroke="#00d4ff" stroke-width="1.5" rx="1"/>
66
+ <line x1="-24" y1="14" x2="-16" y2="14" stroke="#00d4ff" stroke-width="1" opacity="0.7"/>
67
+ <line x1="-24" y1="17" x2="-16" y2="17" stroke="#00d4ff" stroke-width="1" opacity="0.7"/>
68
+ <line x1="-24" y1="20" x2="-16" y2="20" stroke="#00d4ff" stroke-width="1" opacity="0.7"/>
69
+ <line x1="-24" y1="23" x2="-18" y2="23" stroke="#00d4ff" stroke-width="1" opacity="0.7"/>
70
+
71
+ <!-- Folder icon (bottom right, moved in from corner) -->
72
+ <path d="M 14,14 L 14,10 L 19,10 L 21,12 L 26,12 L 26,26 L 14,26 Z" fill="none" stroke="#ff00ff" stroke-width="1.5" opacity="0.8"/>
73
+ <line x1="16" y1="16" x2="24" y2="16" stroke="#ff00ff" stroke-width="1" opacity="0.5"/>
74
+ <line x1="16" y1="20" x2="24" y2="20" stroke="#ff00ff" stroke-width="1" opacity="0.5"/>
75
+
76
+ <circle cx="-28" cy="0" r="2" fill="#00d4ff" opacity="0.8"/>
77
+ <circle cx="28" cy="0" r="2" fill="#ff00ff" opacity="0.8"/>
78
+ </g>
79
+ </svg>