ecmind-blue-studio 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ecmind_blue_studio-1.0.0/LICENSE +21 -0
- ecmind_blue_studio-1.0.0/PKG-INFO +624 -0
- ecmind_blue_studio-1.0.0/README.md +604 -0
- ecmind_blue_studio-1.0.0/pyproject.toml +61 -0
- ecmind_blue_studio-1.0.0/pyproject.toml.orig +51 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/__init__.py +7 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/__main__.py +18 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/config.py +91 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/deps.py +62 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/downloads.py +22 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/errors.py +95 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/i18n.py +72 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/jobs/catalog.json +12335 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/locales/de.json +701 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/locales/en.json +701 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/main.py +305 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/querylang/__init__.py +370 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/querylang/ast.py +743 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/querylang/bindings.py +531 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/querylang/coercion.py +162 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/querylang/completion.py +2809 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/querylang/errors.py +277 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/querylang/executor.py +364 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/querylang/formatter.py +283 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/querylang/formatting.py +112 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/querylang/keywords.py +390 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/querylang/lexer.py +438 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/querylang/parser.py +2286 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/querylang/planner.py +348 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/querylang/registry.py +452 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/querylang/resolver.py +2614 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/querylang/security.py +887 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/querylang/snapshot.py +362 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/querylang/span.py +20 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/querylang/sql_completion.py +586 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/querylang/sql_names.py +835 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/querylang/statement_templates.py +162 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/querylang/threads.py +447 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/querylang/tokens.py +43 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/querylang/variables.py +206 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/rendering.py +53 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/routers/__init__.py +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/routers/auth.py +108 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/routers/definition.py +84 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/routers/execute.py +127 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/routers/files.py +233 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/routers/health.py +51 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/routers/icons.py +19 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/routers/jobcalls.py +72 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/routers/objects.py +470 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/routers/pages.py +81 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/routers/preview.py +85 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/routers/query.py +778 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/routers/settings.py +190 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/services/__init__.py +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/services/auth.py +88 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/services/commands.py +639 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/services/db_schema.py +79 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/services/definition_view.py +467 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/services/definitions.py +127 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/services/execute.py +410 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/services/execute_files.py +269 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/services/exports.py +418 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/services/groups.py +34 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/services/jobcalls.py +754 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/services/jobpairs.py +419 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/services/jobs.py +230 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/services/parent_hints.py +126 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/services/queries.py +1390 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/services/registry.py +505 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/services/registry_keys.py +85 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/services/roles.py +67 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/services/security_admin.py +750 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/services/security_names.py +99 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/services/server_fanout.py +218 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/services/startup_checks.py +50 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/services/textpreview.py +208 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/services/thread_info.py +289 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/services/threads.py +652 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/services/user_settings.py +272 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/services/value_match.py +68 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/services/visible_types.py +117 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/services/zips.py +121 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/sessions.py +199 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/app.css +1145 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/icons/favicon-blue.svg +1 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/icons/favicon-green.svg +1 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/icons/favicon-yellow.svg +1 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/js/app.js +419 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/js/attachments.js +70 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/js/definition.js +243 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/js/ecmql.js +289 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/js/editor.js +508 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/js/i18n.js +14 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/js/jobcalls.js +1102 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/js/output.js +199 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/js/preview.js +280 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/js/saved.js +193 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/js/settings.js +119 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/js/splitter.js +86 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/js/sync.js +57 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/js/tabs.js +459 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/js/userconfig.js +232 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/bootstrap/bootstrap.bundle.min.js +7 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/ecmind-css/ecmind.css +2 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/fonts/LICENSE-jetbrains-mono.txt +93 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/fonts/LICENSE-open-sans.txt +93 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/fonts/files/jetbrains-mono-latin-400-normal.woff2 +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/fonts/files/jetbrains-mono-latin-500-italic.woff2 +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/fonts/files/jetbrains-mono-latin-500-normal.woff2 +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/fonts/files/jetbrains-mono-latin-700-normal.woff2 +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/fonts/files/jetbrains-mono-latin-ext-400-normal.woff2 +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/fonts/files/jetbrains-mono-latin-ext-500-italic.woff2 +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/fonts/files/jetbrains-mono-latin-ext-500-normal.woff2 +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/fonts/files/jetbrains-mono-latin-ext-700-normal.woff2 +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/fonts/files/open-sans-latin-400-italic.woff2 +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/fonts/files/open-sans-latin-400-normal.woff2 +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/fonts/files/open-sans-latin-600-normal.woff2 +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/fonts/files/open-sans-latin-700-normal.woff2 +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/fonts/files/open-sans-latin-ext-400-italic.woff2 +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/fonts/files/open-sans-latin-ext-400-normal.woff2 +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/fonts/files/open-sans-latin-ext-600-normal.woff2 +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/fonts/files/open-sans-latin-ext-700-normal.woff2 +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/fonts/files/open-sans-math-400-italic.woff2 +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/fonts/files/open-sans-math-400-normal.woff2 +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/fonts/files/open-sans-math-600-normal.woff2 +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/fonts/files/open-sans-math-700-normal.woff2 +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/fonts/files/open-sans-symbols-400-italic.woff2 +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/fonts/files/open-sans-symbols-400-normal.woff2 +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/fonts/files/open-sans-symbols-600-normal.woff2 +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/fonts/files/open-sans-symbols-700-normal.woff2 +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/fonts/fonts.css +196 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/htmx/htmx.min.js +1 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/monaco/vs/base/browser/ui/codicons/codicon/codicon.ttf +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/monaco/vs/base/worker/workerMain.js +31 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/monaco/vs/editor/editor.main.css +8 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/monaco/vs/editor/editor.main.js +798 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/monaco/vs/loader.js +11 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/monaco/vs/nls.messages.de.js +21 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/phosphor/fill/Phosphor-Fill.ttf +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/phosphor/fill/Phosphor-Fill.woff +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/phosphor/fill/Phosphor-Fill.woff2 +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/phosphor/fill/style.css +4627 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/phosphor/regular/Phosphor.ttf +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/phosphor/regular/Phosphor.woff +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/phosphor/regular/Phosphor.woff2 +0 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/static/vendor/phosphor/regular/style.css +4627 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/app.html +333 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/base.html +36 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/login.html +32 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/partials/batch_result.html +52 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/partials/batch_row.html +68 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/partials/batch_shell.html +18 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/partials/count_result.html +20 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/partials/definition.html +173 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/partials/definition_field.html +101 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/partials/details.html +161 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/partials/dml_result.html +54 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/partials/execute_result.html +86 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/partials/history.html +27 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/partials/jobcalls.html +103 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/partials/registry_reload_result.html +24 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/partials/registry_result.html +94 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/partials/registry_write_result.html +81 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/partials/result_error.html +15 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/partials/result_section.html +116 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/partials/result_tabs.html +33 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/partials/result_text.html +22 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/partials/security_dml_result.html +65 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/partials/security_reload_result.html +26 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/partials/security_result.html +74 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/partials/settings_dialog.html +74 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/partials/settings_storage_dialog.html +53 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/partials/sql_result.html +40 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/partials/table_field.html +29 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/partials/thread_break_result.html +107 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/partials/thread_stack_result.html +76 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/partials/threads_result.html +168 -0
- ecmind_blue_studio-1.0.0/src/ecmind_blue_studio/templates/partials/values_result.html +33 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ECMind GmbH
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ecmind-blue-studio
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: ECMind blue Studio, admin interface for the ecmind-blue-client
|
|
5
|
+
Author: ECMind GmbH
|
|
6
|
+
Author-email: ECMind GmbH <info@ecmind.ch>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: fastapi
|
|
10
|
+
Requires-Dist: uvicorn
|
|
11
|
+
Requires-Dist: jinja2
|
|
12
|
+
Requires-Dist: pydantic-settings
|
|
13
|
+
Requires-Dist: itsdangerous
|
|
14
|
+
Requires-Dist: python-multipart
|
|
15
|
+
Requires-Dist: ecmind-blue-client==1.0.1
|
|
16
|
+
Requires-Python: >=3.13
|
|
17
|
+
Project-URL: Homepage, https://ecmind.ch/
|
|
18
|
+
Project-URL: Repository, https://gitlab.ecmind.ch/open/ecmind_blue_studio
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# ECMind blue Studio
|
|
22
|
+
|
|
23
|
+
A admin interface for the DMS enaio(r) (OPTIMAL SYSTEMS GmbH), exposing the
|
|
24
|
+
ecmind-blue-client features.
|
|
25
|
+
Query folders, registers and documents with a simple query language in a Monaco
|
|
26
|
+
editor, inspect results and download files. Works fully offline against your
|
|
27
|
+
DMS infrastructure; all frontend assets are vendored.
|
|
28
|
+
|
|
29
|
+
## Features (work in progress)
|
|
30
|
+
|
|
31
|
+
- Browser-like vertical tabs, each with a query editor and a result view
|
|
32
|
+
- Object definition explorer: every cabinet, type and index field with its full
|
|
33
|
+
configuration, a filter over all of it, prefilled statements per object type
|
|
34
|
+
and the native asobjdef.xml as download
|
|
35
|
+
- Job call monitor: the live server job stream with filters per server, job,
|
|
36
|
+
computer, application and user, and the full parameters of a single call with
|
|
37
|
+
base64 payloads decoded; every value in the detail header filters the stream
|
|
38
|
+
to itself, and any captured call can be sent to a new tab as a ready to run
|
|
39
|
+
statement
|
|
40
|
+
- Raw job calls from the editor: `execute krn.GetServerInfo set Flags = 0` runs
|
|
41
|
+
any server job with typed parameters, input files and its response files
|
|
42
|
+
previewable and downloadable
|
|
43
|
+
- Simple query language for folders, registers and documents with subqueries,
|
|
44
|
+
Klausel-style conditions, code completion and diagnostics
|
|
45
|
+
- Batch values: `let id = #UUID7#` names a value for the whole batch and
|
|
46
|
+
`print @res.Count, @res.TotalHits` shows what a statement answered
|
|
47
|
+
- Security statements for users, groups, roles, membership and the rights export
|
|
48
|
+
- Registry statements that read and write the servers' own configuration, all
|
|
49
|
+
configured servers at once or one by name, with the server in every row
|
|
50
|
+
- Result tables per object type with file download and a document preview:
|
|
51
|
+
text formats in a Monaco editor with syntax colouring, find, formatting
|
|
52
|
+
and copy, PDF and images in the browser's own viewer; a save button turns the
|
|
53
|
+
edited text into an update statement in a new tab
|
|
54
|
+
- English/German localization with automatic detection
|
|
55
|
+
- Per-login configuration in the DMS: theme, language and saved queries follow
|
|
56
|
+
the user to any browser, with a storage dialog to inspect, export, import or
|
|
57
|
+
delete the stored JSON
|
|
58
|
+
|
|
59
|
+
## Requirements
|
|
60
|
+
|
|
61
|
+
- Python >= 3.13 and [uv](https://docs.astral.sh/uv/)
|
|
62
|
+
- A DMS application server (default: 127.0.0.1:4000)
|
|
63
|
+
- A technical account with the system role "Kontextwechsel" (switch context)
|
|
64
|
+
|
|
65
|
+
## Setup
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
uv sync
|
|
69
|
+
copy .env.example .env
|
|
70
|
+
# edit .env: servers, technical account, session secret
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Run
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
uv run uvicorn ecmind_blue_studio.main:app --reload --host 127.0.0.1 --port 8099
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Then open http://127.0.0.1:8099 and log in with a DMS user.
|
|
80
|
+
|
|
81
|
+
### Deployment notes
|
|
82
|
+
|
|
83
|
+
- Keep `.env` private (`chmod 600 .env`): it holds the DMS password and the
|
|
84
|
+
cookie-signing secret. The session file (`EBS_SESSION_FILE`) is created 0600
|
|
85
|
+
by the application itself and holds only hashed session ids.
|
|
86
|
+
- Behind TLS, set `EBS_COOKIE_SECURE=true` so the session cookie carries the
|
|
87
|
+
Secure flag. The login form additionally refuses cross-origin posts: the
|
|
88
|
+
`Origin` host must match `Host` or `X-Forwarded-Host`, so a reverse proxy
|
|
89
|
+
must forward one of the two (nginx: `proxy_set_header Host $host;`).
|
|
90
|
+
- `GET /health` is open for liveness probes and names the exact version only
|
|
91
|
+
to signed-in users. `GET /health/ready` performs throwaway logins and is
|
|
92
|
+
restricted to localhost by the client address; do not route it through a
|
|
93
|
+
reverse proxy, where every caller looks local.
|
|
94
|
+
- Spilled response files (answers above `EBS_RESPONSE_SPILL_MB`) land in a
|
|
95
|
+
0700 directory per service account under the system temp directory and are
|
|
96
|
+
removed on expiry, logout and shutdown.
|
|
97
|
+
|
|
98
|
+
## Object definition
|
|
99
|
+
|
|
100
|
+
The first tab cannot be closed and shows what the DMS server knows about its
|
|
101
|
+
own data model: cabinets with their folders, registers and documents, the index
|
|
102
|
+
mask fields of every type with data type, database column and names in the
|
|
103
|
+
configured languages, and the complete configuration of a single field. The
|
|
104
|
+
filter box searches all of it at once, six buttons per object type open a
|
|
105
|
+
prefilled `select`, `count`, `insert`, `update`, `upsert` or `delete` statement
|
|
106
|
+
in a new tab, and the download button hands out the native `asobjdef.xml`.
|
|
107
|
+
|
|
108
|
+
It needs the system role "DMS: Supervisor" (18) or "Editor: Starten" (11);
|
|
109
|
+
without one of them the tab is not shown.
|
|
110
|
+
|
|
111
|
+
## Job calls
|
|
112
|
+
|
|
113
|
+
The second fixed tab shows the live job stream of the configured servers, the
|
|
114
|
+
same feed the Enterprise Manager displays. Press Listen and every finished job
|
|
115
|
+
appears as one line with its timestamp, computer, user, job name, application
|
|
116
|
+
and file counts; a green check for success, a red warning for a failure, and
|
|
117
|
+
optionally a blue play line when the job starts. Filter by server, by job (a
|
|
118
|
+
popup over every namespace the server offers), by computer, application or user,
|
|
119
|
+
or reduce the stream to failures only. Clicking a line shows the call the way
|
|
120
|
+
the Enterprise Manager prints it, with base64 payloads decoded and XML indented.
|
|
121
|
+
Every value in that header is a link: the job and the server narrow the stream
|
|
122
|
+
itself, the computer, application and user filter the lines already on screen,
|
|
123
|
+
and the last link opens the call in a new tab as a ready to run `execute`
|
|
124
|
+
statement, with every parameter filled in and the user it originally ran for.
|
|
125
|
+
|
|
126
|
+
It needs the system role "DMS: Supervisor" (18) or "Administrator: Starten" (1).
|
|
127
|
+
The monitor runs on the technical connection, which is why the roles are checked
|
|
128
|
+
on the server and not only in the browser. Job parameters are shown unmasked.
|
|
129
|
+
|
|
130
|
+
## Query language
|
|
131
|
+
|
|
132
|
+
Each tab has a Monaco editor with completion, hover and live diagnostics for a
|
|
133
|
+
simple query language (see [SPECS.md](SPECS.md) section 4). Ctrl+Enter runs the
|
|
134
|
+
query, Shift+Alt+F formats it, Alt+N opens a tab and Alt+W closes it. Internal
|
|
135
|
+
field names work without brackets:
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
select document Invoice, DeliveryNote where Status = 'open' order by InvoiceDate desc limit 100
|
|
139
|
+
select document Invoice in (register Correspondence where Subject = 'Dunning*') where InvoiceNo != ''
|
|
140
|
+
select document Invoice where InvoiceNo != '' and Customer.CustomerNo = 'C-10*'
|
|
141
|
+
select folder Customer contains (document Invoice where Status = 'overdue')
|
|
142
|
+
select folder Customer where Invoice.Status = 'overdue' and Blocked = false
|
|
143
|
+
select document Invoice where Clerk = #USER# and InvoiceDate >= #DATE#-1y6m
|
|
144
|
+
select document Invoice columns InvoiceNo, Amount, Customer.CompanyName
|
|
145
|
+
select document Invoice columns InvoiceNo, {OBJECT_FILESIZE}, {OBJECT_MODIFYTIME}, {OBJECT_DOCPAGECOUNT}
|
|
146
|
+
count document Invoice where Status = 'open'
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
System fields (`OBJECT_ID`, `OBJECT_LINKS`, `OBJECT_FILESIZE`, `OBJECT_COUNT`,
|
|
150
|
+
`OBJECT_MODIFYUSER` and more, see SPECS.md) work in `where`, `order by` and
|
|
151
|
+
`columns`; document-only fields are rejected on folder and register queries
|
|
152
|
+
with a live editor marker.
|
|
153
|
+
|
|
154
|
+
The editor also flags object types the signed-in user cannot see, and completion
|
|
155
|
+
notes them in its list. The DMS answers a query on such a type with an empty result
|
|
156
|
+
and no error at all, so the hint is shown before the run. It stays a warning and
|
|
157
|
+
never blocks the query: a type you can see may return no rows as well.
|
|
158
|
+
|
|
159
|
+
Data statements update, create and delete objects, always with the rights of the
|
|
160
|
+
signed-in user:
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
update document Invoice 4711 set Status = 'paid'
|
|
164
|
+
update document Invoice 4711, 4712 set Status = 'paid'
|
|
165
|
+
delete document Invoice 6524, 6522 purge
|
|
166
|
+
insert document Invoice into 55 set InvoiceNo = 'R-9' file 'scan.pdf'
|
|
167
|
+
upsert document Invoice into 55 set InvoiceNo = 'R-9', Amount = 12.5 search InvoiceNo
|
|
168
|
+
variant 4711 set Status = 'draft' file 'v2.pdf'
|
|
169
|
+
delete document Invoice where Status = 'void' purge
|
|
170
|
+
insert document Invoice into 55 set InvoiceNo = 'R-9', Positions = (Article = 'A-100', Qty = 5), (Article = 'B-200', Qty = 3)
|
|
171
|
+
update document Invoice 4711 set Positions = (Article = 'A-100', Qty = 7) replacetables
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
A single `insert`, `update` or `variant` shows what it wrote: the objects are
|
|
175
|
+
read back from the server and rendered as a result table below the
|
|
176
|
+
confirmation, with the usual row actions. A large `update` (more objects than
|
|
177
|
+
one result page) and statement batches keep the plain confirmation, because
|
|
178
|
+
reading every object back costs a roundtrip each.
|
|
179
|
+
|
|
180
|
+
Table fields take a comma separated list of rows, each row a list of
|
|
181
|
+
`Column = value` pairs. On update and upsert the statement has to say whether
|
|
182
|
+
the rows replace the table (`replacetables`) or extend it (`appendtables`) -
|
|
183
|
+
the mode belongs to the whole statement, so replacing one table and appending
|
|
184
|
+
to another needs two statements. The table popup in a result row carries an
|
|
185
|
+
"Edit table" button that opens a new tab with exactly such a statement, filled
|
|
186
|
+
with the rows currently stored.
|
|
187
|
+
|
|
188
|
+
Files referenced by `file '...'` are attached to the tab with the paperclip
|
|
189
|
+
button; `file #BROWSE#` instead opens the file dialog every time the statement
|
|
190
|
+
`update` and `delete` also take a list of object ids, and every listed object
|
|
191
|
+
gets its own copy of the file; a failing object does not stop the others, and
|
|
192
|
+
the result lists what was written and what failed. On an id based update the
|
|
193
|
+
`set` clause is optional, so
|
|
194
|
+
`update document Invoice 6523 file 'scan.pdf'` replaces only the file and
|
|
195
|
+
`update document Invoice 6523 file #NULL#` only removes it. Prefilled
|
|
196
|
+
document statements (edit button, object definition page) end with
|
|
197
|
+
`file #BROWSE#` as a reminder; delete the line for metadata-only changes.
|
|
198
|
+
File content can also be written inline, with an optional extension
|
|
199
|
+
after the closing quotes so the DMS knows the file type: `file '''<data>
|
|
200
|
+
</data>'''.xml` stores the text, `file b64'''iVBOR...'''.png` decodes base64
|
|
201
|
+
(line breaks and spaces are ignored), and `file #NULL#` removes the document
|
|
202
|
+
file on an update. Without an extension, text becomes a txt and base64 a bin
|
|
203
|
+
file:
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
insert document Invoice into 5693
|
|
207
|
+
set InvoiceNo = 'R-9'
|
|
208
|
+
file b64'''
|
|
209
|
+
iVBORw0KGgoAAAANSUhEUgAAABAAAAARCAYAAADUryzE...
|
|
210
|
+
'''.png;
|
|
211
|
+
select document Invoice
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
`let` names a value for the rest of a batch and `print` shows one row of values,
|
|
215
|
+
which is what makes a batch readable and re-usable:
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
let id = #UUID7#;
|
|
219
|
+
insert document Invoice into 55 set InvoiceNo = 'R-9', Ident = @id;
|
|
220
|
+
insert document Note into 55 set Text = @id;
|
|
221
|
+
print 'filed as', @id, #DATETIME#
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
```
|
|
225
|
+
execute dms.GetResultList set Flags = 0, XML = '''<DMSQuery/>''' as res;
|
|
226
|
+
print @res.Count, @res.TotalHits
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
A declaration also names a file: `let f = #BROWSE#` or an inline block. Such a
|
|
230
|
+
name stands in a `file` clause, of a write statement and of a job call alike, and
|
|
231
|
+
carries the file's attributes as members, so one picked file or one inline block
|
|
232
|
+
reaches several statements and can be read without being carried:
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
let f = #BROWSE#;
|
|
236
|
+
insert document Invoice into 55 set InvoiceNo = #FILEBASE#, Size = @f.filesize file @f;
|
|
237
|
+
update document Invoice 4711 set Scan = @f.filesha256 file @f
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
An attachment name stays a plain value on purpose: `'scan.pdf'` already means the
|
|
241
|
+
same file in every statement, so there is nothing to declare.
|
|
242
|
+
|
|
243
|
+
`#UUID4#` and `#UUID7#` generate an identifier at every use, so an insert gets a
|
|
244
|
+
fresh one and an id list gets one per object; declaring it with `let` is how one
|
|
245
|
+
value reaches several statements. `#UUID7#` is ordered by the millisecond it was
|
|
246
|
+
made in, `#UUID4#` is random. A print takes literals, variables and bindings,
|
|
247
|
+
and `@res.*` prints every member the statement it names reported, which is how
|
|
248
|
+
one finds out what a job really answers. Both statements may stand anywhere in a
|
|
249
|
+
batch, where they render as one line each; on their own they render a one-row
|
|
250
|
+
table that exports like any other result.
|
|
251
|
+
|
|
252
|
+
Security statements administer the DMS users, groups and roles in the same
|
|
253
|
+
language, again with the rights of the signed-in user, so the server enforces
|
|
254
|
+
the role for configuring the security system:
|
|
255
|
+
|
|
256
|
+
```
|
|
257
|
+
select user where locked = false order by username limit 100
|
|
258
|
+
select user where group = 'Sales'
|
|
259
|
+
select group where user = #USER#
|
|
260
|
+
insert user KOL set display_name = 'Roland Koller', password = 'Start.2026', change_pwd = true
|
|
261
|
+
add user KOL to group Sales
|
|
262
|
+
export security group Sales, Marketing
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
`select user KOL` shows one account with every attribute, `count group` the
|
|
266
|
+
number alone, and `delete user KOL forward to MEY` hands the portfolios and
|
|
267
|
+
mails over before the account goes. User and group rows carry the same edit
|
|
268
|
+
button as DMS rows: it opens a new tab with an `update user` or `update group`
|
|
269
|
+
statement holding the current values. The password is never part of it, and
|
|
270
|
+
never part of a result or a log.
|
|
271
|
+
|
|
272
|
+
#### A rights change is not in effect until it is reloaded
|
|
273
|
+
|
|
274
|
+
A membership or account write lands in the database at once, but the server
|
|
275
|
+
answers every rights decision from the model it holds in memory. Until that is
|
|
276
|
+
re-read, the change is invisible: a search runs with the old rights, the object
|
|
277
|
+
type the new group grants is simply absent from the answer, and **nothing
|
|
278
|
+
reports an error** -- the hit list is empty and the result document does not
|
|
279
|
+
even mention the type. So the write is done and not yet effective, the way a
|
|
280
|
+
registry write is set and not yet saved:
|
|
281
|
+
|
|
282
|
+
```
|
|
283
|
+
add user KOL to group Sales reload
|
|
284
|
+
reload security
|
|
285
|
+
reload security on 'srv1'
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
`reload security` runs `mng.UpdateSecuritySystem`, and a write that ends in
|
|
289
|
+
`reload` runs it for that statement alone. Which to use is a question of how
|
|
290
|
+
many changes there are: the job re-reads the whole model, so twenty writes want
|
|
291
|
+
one reload after them and not twenty. A write without the flag says in its
|
|
292
|
+
result that it is not in effect yet; a batch that changes rights and then
|
|
293
|
+
**reads** warns, naming the statement that reads too early.
|
|
294
|
+
|
|
295
|
+
The job takes no server parameter, so a single call would land on whichever
|
|
296
|
+
machine the pool picked and leave the rest of a cluster answering from a stale
|
|
297
|
+
model. Both forms therefore address every configured server unless an `on`
|
|
298
|
+
clause names one, and the result carries a line per server. The reload is
|
|
299
|
+
idempotent, so asking a machine that was already current costs a re-read and
|
|
300
|
+
nothing else.
|
|
301
|
+
|
|
302
|
+
Behind the system role "Administrator: Sicherheitssystem konfigurieren" (4), or
|
|
303
|
+
the two roles the `execute` verb needs, since those can call the job directly
|
|
304
|
+
anyway. The call carries the signed-in user in `$$$SwitchContextUserName$$$`, so
|
|
305
|
+
the server checks as well: a user without role 4 gets `-1040906164` from the job
|
|
306
|
+
itself, verified against 12.0.801.
|
|
307
|
+
|
|
308
|
+
### Free SQL with DMS names
|
|
309
|
+
|
|
310
|
+
`sql` passes everything after the verb to the server database, with one
|
|
311
|
+
exception: a `{...}` reference is resolved against the object definition first,
|
|
312
|
+
so a statement can name types and fields instead of `object48` and `feld4`.
|
|
313
|
+
|
|
314
|
+
```
|
|
315
|
+
sql SELECT {original_filename} FROM {PostDoc} WHERE {Status} = 'done'
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
goes to the database as `SELECT feld1 FROM object48 WHERE feld4 = 'done'`. The
|
|
319
|
+
tables the statement names decide what a bare field means, and either spelling
|
|
320
|
+
does: `sql SELECT {Status} FROM object48` works too. Where that is not enough,
|
|
321
|
+
name the type: `{PostDoc.Status}`. A SQL alias narrows it as well
|
|
322
|
+
(`p.{Status} ... FROM {PostDoc} p`).
|
|
323
|
+
|
|
324
|
+
System fields resolve to their own columns (`{OBJECT_CRDATE}` becomes
|
|
325
|
+
`angelegt`), and a table field resolves to its row table, so its rows can be
|
|
326
|
+
joined in:
|
|
327
|
+
|
|
328
|
+
```
|
|
329
|
+
sql SELECT i.{InvoiceNo}, p.{Qty}
|
|
330
|
+
FROM {Invoice} i JOIN {Invoice.Positions} p ON p.id = i.id
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
This is a name substitution and never a rewrite. Only the text inside the braces
|
|
334
|
+
is replaced, and only ever by an identifier out of the definition, so the join,
|
|
335
|
+
the clauses and the literals are exactly what you wrote, and a reference cannot
|
|
336
|
+
smuggle SQL in. Braces inside a string, a quoted identifier or a comment stay
|
|
337
|
+
the database's, and so does anything that is not identifier shaped, which is why
|
|
338
|
+
ODBC escapes such as `{fn concat(a,b)}` still pass through.
|
|
339
|
+
|
|
340
|
+
Completion offers both spellings side by side, so the physical schema and the DMS
|
|
341
|
+
names are one list: `object48` next to `{PostDoc}`, `feld4` next to
|
|
342
|
+
`{PostDoc.Status}`, and `object48list1` labelled as the table field it holds.
|
|
343
|
+
Either spelling finds both, and each shows what the other one is. Hover tells
|
|
344
|
+
which column a reference stands for, and an unknown name is an editor marker
|
|
345
|
+
rather than a server error. Everything outside the braces is still unvalidated
|
|
346
|
+
and never reformatted.
|
|
347
|
+
|
|
348
|
+
### Server registry
|
|
349
|
+
|
|
350
|
+
The enaio registry lives per server machine, so two application servers of one
|
|
351
|
+
pool can disagree about a setting and nothing in the studio used to show that.
|
|
352
|
+
Registry statements read and write it with the **server as a column of every
|
|
353
|
+
row**; leaving the `on` clause out addresses every configured server, which is
|
|
354
|
+
what turns "is this the same everywhere?" into one statement:
|
|
355
|
+
|
|
356
|
+
```
|
|
357
|
+
select registry
|
|
358
|
+
select registry where path = 'Security\*'
|
|
359
|
+
select registry on 'srv1' where key = 'Services\Fulltext'
|
|
360
|
+
select registry where name = 'HelpURL' order by server
|
|
361
|
+
count registry where path = 'Services\*'
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
The columns are `server`, `key`, `name`, `path` and `value`, all of them text,
|
|
365
|
+
with the usual `*` and `?` wildcards. Both path separators work: `Security/*` is
|
|
366
|
+
translated to the backslash the server insists on. An entry whose name ends in
|
|
367
|
+
`Pwd`, `Password` or `Secret` shows `***` and never its value, in a result, an
|
|
368
|
+
export or a log alike; `PwdDecryption` and `PasswordExpirationInterval` are
|
|
369
|
+
settings rather than secrets and stay readable.
|
|
370
|
+
|
|
371
|
+
A write names one entry by its absolute path, never a wildcard, and reports the
|
|
372
|
+
previous value beside the new one for every server it reached:
|
|
373
|
+
|
|
374
|
+
```
|
|
375
|
+
update registry on 'srv1' set 'Security\HelpURL' = 'Test'
|
|
376
|
+
update registry create set 'Security\NewEntry' = 'x'
|
|
377
|
+
update registry set 'HelpURL' = 'https://help.example' save
|
|
378
|
+
reload registry on 'srv1'
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
`krn.RESetRegValue` changes the running server's memory and nothing else, so
|
|
382
|
+
**a write is not persisted unless the statement ends in `save`** - the result
|
|
383
|
+
says so, and `reload registry` throws an unsaved change away right now. A path
|
|
384
|
+
the server does not know is refused unless the statement says `create`, and one
|
|
385
|
+
failing entry does not stop the others. One unreachable server never ends the
|
|
386
|
+
run: it is reported beside the rows the others returned.
|
|
387
|
+
|
|
388
|
+
There is no delete: the server API has no job that removes a registry entry.
|
|
389
|
+
|
|
390
|
+
Behind the same system roles as the job call monitor, "DMS: Supervisor" (18) or
|
|
391
|
+
"Administrator: Starten" (1), enforced on the server. Like `execute`, these
|
|
392
|
+
statements run on the technical connection, because the `krn.RE*` jobs have no
|
|
393
|
+
rights model of their own; a read carries the signed-in user into the server's
|
|
394
|
+
job log so it records who looked.
|
|
395
|
+
|
|
396
|
+
### Server threads and queues
|
|
397
|
+
|
|
398
|
+
A thread pool belongs to one kernel process, so this is the registry's problem
|
|
399
|
+
again: `krn.JobThreadGetInfo` takes no server parameter, and the pool would
|
|
400
|
+
answer a different machine on every run. So the **server is a column of every
|
|
401
|
+
row** here too, and leaving the `on` clause out asks all of them:
|
|
402
|
+
|
|
403
|
+
```
|
|
404
|
+
select threads
|
|
405
|
+
select threads where running = true
|
|
406
|
+
select threads where idle = false order by avgjob desc
|
|
407
|
+
select threads on 'srv1' where queue = 'dms'
|
|
408
|
+
select threads where jobname = 'wfm.*' columns queue, number, jobname, username
|
|
409
|
+
count threads where running = true
|
|
410
|
+
|
|
411
|
+
select queues
|
|
412
|
+
select queues where jobswaiting > 0
|
|
413
|
+
select queues columns queue, numthreads, priority, jobsposted, namespaces
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
Unlike the registry's text columns these are **typed**, which is what makes
|
|
417
|
+
`order by avgjob desc` sort numerically and `where start > datetime'...'` mean
|
|
418
|
+
what it looks like. Beside the twenty fields the server reports there are five
|
|
419
|
+
derived ones, because the raw fields do not answer what you actually ask:
|
|
420
|
+
`running` (a job on the thread right now), `idle` (a thread never used),
|
|
421
|
+
`avgjob` (the mean job in milliseconds), and `duration` and `queuewait` for the
|
|
422
|
+
last job, both empty for as long as it is still running.
|
|
423
|
+
|
|
424
|
+
Two of the server's own fields read differently than the vendor documentation
|
|
425
|
+
suggests, and the studio follows the measurement rather than the document:
|
|
426
|
+
`jobsummary` is the **total** time that thread has ever worked, not the last
|
|
427
|
+
job's runtime, which is why `avgjob` exists; and `sql` is documented but was
|
|
428
|
+
empty on every server probed, so nothing relies on it.
|
|
429
|
+
|
|
430
|
+
The call stack of one thread reads as the server writes one in its own error
|
|
431
|
+
messages, innermost frame first:
|
|
432
|
+
|
|
433
|
+
```
|
|
434
|
+
select callstack of thread 4940 on 'srv1'
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
And one running job can be interrupted:
|
|
438
|
+
|
|
439
|
+
```
|
|
440
|
+
break thread 4940 on 'srv1'
|
|
441
|
+
break thread 4940 job 1057423 on 'srv1'
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
**A break is a request, not a result.** The server accepts one for any job and
|
|
445
|
+
only interrupts jobs that support being interrupted, so the statement reads the
|
|
446
|
+
thread again afterwards and says which of the two happened: *stopped*, or
|
|
447
|
+
*accepted and still running*. In a batch the second counts as a failed
|
|
448
|
+
statement. A thread with no job is not asked at all. `on` is required here and
|
|
449
|
+
names exactly one server, because a thread id means nothing on another machine.
|
|
450
|
+
|
|
451
|
+
Behind the same system roles as the registry family, "DMS: Supervisor" (18) or
|
|
452
|
+
"Administrator: Starten" (1). That gate matters for a plain read: the snapshot
|
|
453
|
+
carries session GUIDs, client station names and the user every job runs as. A
|
|
454
|
+
read carries the signed-in user into the server's job log so it records who
|
|
455
|
+
looked.
|
|
456
|
+
|
|
457
|
+
### Job calls from the editor
|
|
458
|
+
|
|
459
|
+
`execute` calls any server job directly, with a typed parameter list:
|
|
460
|
+
|
|
461
|
+
```
|
|
462
|
+
execute krn.GetServerInfo set Flags = 0, Info = 3
|
|
463
|
+
|
|
464
|
+
execute dms.GetResultList
|
|
465
|
+
set Flags = 16,
|
|
466
|
+
XML = '''<DMSQuery requesttype="LOL" pagesize="10">
|
|
467
|
+
<Archive>
|
|
468
|
+
<ObjectType internal_name="Invoice">
|
|
469
|
+
<Fields field_schema="MIN"/>
|
|
470
|
+
</ObjectType>
|
|
471
|
+
</Archive>
|
|
472
|
+
</DMSQuery>''',
|
|
473
|
+
Encoding = 'UTF-8'
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
The type of a parameter comes from the literal, so nothing has to be declared:
|
|
477
|
+
`'text'` is a string, `42` an integer, `12.5` a double, `true` a boolean,
|
|
478
|
+
`datetime'...'` a timestamp. A base64 parameter is written **in plain text**
|
|
479
|
+
between `'''` quotes, which is what keeps an XML payload readable in the editor;
|
|
480
|
+
`b64'''...'''` carries real binary. The suffix after a text block names its
|
|
481
|
+
encoding when the job wants something other than UTF-8: `'''...'''.utf16`. Where
|
|
482
|
+
the literal cannot say it, a cast does: `bigint 4294967296`, `string '4711'`.
|
|
483
|
+
|
|
484
|
+
A job call hands what it answered to the next statement of a batch. `as <name>`
|
|
485
|
+
binds the call, and its members are the output parameters, by their own names,
|
|
486
|
+
next to `returncode`:
|
|
487
|
+
|
|
488
|
+
```
|
|
489
|
+
// the members of a job call binding are whatever the job answered
|
|
490
|
+
execute krn.SessionLogin set UserName = 'kol', Password = 'secret' as login;
|
|
491
|
+
insert document Note into 4711 set Text = @login.UserGUID
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
The braces form takes a name no identifier can spell, such as krn.RunScript's
|
|
495
|
+
`$ScriptResult$`: `@run.{$ScriptResult$}`. Nothing declares what a job answers,
|
|
496
|
+
so no type travels with the value: it is made to fit where it lands, text that
|
|
497
|
+
spells a number reaching a number field and a number reaching a text field, and
|
|
498
|
+
only what cannot be converted fails that one statement. Completion offers the
|
|
499
|
+
outputs the reference documents for that job; a name it does not document is a
|
|
500
|
+
warning and still runs, because the server is what decides.
|
|
501
|
+
|
|
502
|
+
Input files come from the tab's attachments, a `#BROWSE#` pick or inline content,
|
|
503
|
+
the same way a write statement takes them, except that a job takes a list:
|
|
504
|
+
`file 'a.pdf', 'b.pdf'`. Response files are listed under the result with a
|
|
505
|
+
preview for text, PDF and image formats and a download for anything; a text
|
|
506
|
+
file opens in the same editor the result rows use.
|
|
507
|
+
|
|
508
|
+
The result is the block the job call monitor shows for a call, so the two read
|
|
509
|
+
the same. JSON gives the whole call as one record with text buffers decoded, XML
|
|
510
|
+
gives the raw payload the job returned, and the delimited formats flatten the
|
|
511
|
+
output parameters. A non-zero return code is not an error but the answer: it is
|
|
512
|
+
rendered with the server's own message, exactly as the monitor renders a failed
|
|
513
|
+
call.
|
|
514
|
+
|
|
515
|
+
Completion knows the jobs the server actually implements: `execute ` offers the
|
|
516
|
+
engines, `execute krn.` their jobs, and picking one writes the whole statement
|
|
517
|
+
with every required parameter and a tab stop on each value. The parameter names,
|
|
518
|
+
types and descriptions come from the enaio Server-API reference, so they are
|
|
519
|
+
documentation rather than gospel: a job the reference has never heard of still
|
|
520
|
+
runs, with a warning and no help.
|
|
521
|
+
|
|
522
|
+
Unlike every other statement this one does **not** run as the signed-in user. It
|
|
523
|
+
goes out on the pool's own connection, like the job call monitor, because writing
|
|
524
|
+
the caller's identity into the parameter list would aim a job meant for a named
|
|
525
|
+
user at whoever happens to be signed in. A statement that wants a user context
|
|
526
|
+
says so itself:
|
|
527
|
+
|
|
528
|
+
```
|
|
529
|
+
execute dms.GetResultList
|
|
530
|
+
set {$$$SwitchContextUserName$$$} = 'administrator',
|
|
531
|
+
Flags = 16,
|
|
532
|
+
XML = '''<DMSQuery/>'''
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
The braces are needed because `$` is not part of a name; the GUID and ID forms
|
|
536
|
+
(`{$$$SwitchContextUserGUID$$$}`, `{$$$SwitchContextUserID$$$}`) work the same
|
|
537
|
+
way. The role gate is therefore the whole access control: "DMS: Supervisor" (18)
|
|
538
|
+
or "Administrator: Starten" (1), checked on the server, not just hidden.
|
|
539
|
+
|
|
540
|
+
Results render as a table by default or as JSON, JSONL, XML, CSV, TSV or TSV
|
|
541
|
+
quoted, `sql` results included. Every text format opens in an editable editor, so
|
|
542
|
+
the output can be trimmed before copy or download hands it to the next step.
|
|
543
|
+
TSV sanitizes tabs and line breaks inside values to keep one record per line,
|
|
544
|
+
TSV quoted keeps them and quotes like CSV instead. Every result
|
|
545
|
+
row offers details (metadata, variants and versions), an edit button that opens
|
|
546
|
+
a prefilled update statement, file download and, where the format allows it, a
|
|
547
|
+
preview. A text document (txt, log, md, csv, tsv, json, xml, html, svg, yaml,
|
|
548
|
+
ini, sql, js, ts, css, bat, ps1, py) opens in a Monaco editor with the colouring
|
|
549
|
+
of its format, find, a format button for JSON and XML and a copy button for the
|
|
550
|
+
buffer; PDF and images keep the browser's own viewer. The download button always
|
|
551
|
+
hands out the archived file, never what was edited on screen. Text is decoded
|
|
552
|
+
from UTF-8, a BOM, BOM-less UTF-16 or Windows-1252, and a file above
|
|
553
|
+
`EBS_PREVIEW_MAX_BYTES` (20 MB) shows its first bytes and says so.
|
|
554
|
+
|
|
555
|
+
The save button writes the edited buffer back: the modal closes and a new tab
|
|
556
|
+
opens with the statement prefilled and the caret in it. It never runs by itself
|
|
557
|
+
- the write is the Ctrl+Enter you press.
|
|
558
|
+
|
|
559
|
+
```
|
|
560
|
+
// PostDoc #4711 (.xml)
|
|
561
|
+
update document PostDoc 4711
|
|
562
|
+
file '''<Root>
|
|
563
|
+
<Line>edited</Line>
|
|
564
|
+
</Root>'''.xml
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
No `set` clause, so a save touches the file and no index field. A comment line
|
|
568
|
+
above the statement explains anything the shape does not say by itself: a text
|
|
569
|
+
block goes out as UTF-8, so a file stored as Windows-1252 or UTF-16 comes back
|
|
570
|
+
re-encoded; a text carrying `'''`, which a text block has no escape for, goes out
|
|
571
|
+
as base64 instead; and a text too long for `EBS_QUERY_MAX_CHARS` is attached to
|
|
572
|
+
the new tab as a file, with the statement naming it. A preview cut short by
|
|
573
|
+
`EBS_PREVIEW_MAX_BYTES` cannot be saved at all - the button is disabled and says
|
|
574
|
+
why, because writing a prefix back would overwrite the document with part of
|
|
575
|
+
itself. Response files of an `execute` run have no object to update, so they have
|
|
576
|
+
no save button.
|
|
577
|
+
|
|
578
|
+
Statement batches (separated by `;`) stream their results statement by
|
|
579
|
+
statement, report the executed command and the upsert action per row, and can
|
|
580
|
+
be cancelled mid run; the stop button in the toolbar also aborts long running
|
|
581
|
+
queries. A query starting with a comment line names its tab, the question
|
|
582
|
+
button in the toolbar lists all keyboard shortcuts, the info button next to it
|
|
583
|
+
opens the about dialog, and the tab rail shows the connected DMS servers
|
|
584
|
+
above the user menu.
|
|
585
|
+
|
|
586
|
+
## Per-login configuration
|
|
587
|
+
|
|
588
|
+
Theme, language, table lines and saved queries live in one JSON document in the
|
|
589
|
+
DMS, in the user data record `ecmind_blue_studio` of the signed-in user (slot
|
|
590
|
+
`EBS_USERDATA_SLOT`, default 80). They are not kept in the browser, so a second
|
|
591
|
+
machine shows the same setup, and a browser reset loses nothing. If the DMS
|
|
592
|
+
refuses user data for a login, the app runs on the defaults and every write is
|
|
593
|
+
blocked with an error rather than falling back to storage that can vanish.
|
|
594
|
+
|
|
595
|
+
"Settings storage" in the user menu shows the stored document in an editor and
|
|
596
|
+
can download it, load a JSON file into the editor, save manual changes or delete
|
|
597
|
+
the whole record from the DMS.
|
|
598
|
+
|
|
599
|
+
Open tabs are the exception: they stay in browser storage, shared by all windows
|
|
600
|
+
of the browser, so closing and reopening the browser brings back the last set of
|
|
601
|
+
queries. Which tab is in front stays per window.
|
|
602
|
+
|
|
603
|
+
## Tests
|
|
604
|
+
|
|
605
|
+
```
|
|
606
|
+
uv run pytest # unit and endpoint tests
|
|
607
|
+
$env:EBS_LIVE_TESTS = "1"; uv run pytest -m live # against a local DMS (PowerShell)
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
## Documentation
|
|
611
|
+
|
|
612
|
+
- [SPECS.md](SPECS.md) - full specification and decision log
|
|
613
|
+
- [TODO.md](TODO.md) - backlog
|
|
614
|
+
- [CHANGELOG.md](CHANGELOG.md) - notable changes
|
|
615
|
+
|
|
616
|
+
## License
|
|
617
|
+
|
|
618
|
+
MIT, see [LICENSE](LICENSE). Bundled third-party libraries, fonts, icons and tools are
|
|
619
|
+
acknowledged in [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md).
|
|
620
|
+
|
|
621
|
+
The MIT license covers this software only and grants no rights to the DMS itself. Operators must
|
|
622
|
+
make sure their use of the DMS is covered by their own license agreement with the DMS vendor or
|
|
623
|
+
reseller. See the DMS licensing section of
|
|
624
|
+
[THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md).
|