This release is a pre-release and may not be stable for production use.
ECMind blue Studio
A admin interface for the DMS enaio(r) (OPTIMAL SYSTEMS GmbH), exposing the ecmind-blue-client features. Query folders, registers and documents with a simple query language in a Monaco editor, inspect results and download files. Works fully offline against your DMS infrastructure; all frontend assets are vendored.
Features (work in progress)
- Browser-like vertical tabs, each with a query editor and a result view
- Object definition explorer: every cabinet, type and index field with its full configuration, a filter over all of it, prefilled statements per object type and the native asobjdef.xml as download
- Job call monitor: the live server job stream with filters per server, job, computer, application and user, and the full parameters of a single call with base64 payloads decoded; every value in the detail header filters the stream to itself, and any captured call can be sent to a new tab as a ready to run statement
- Raw job calls from the editor:
execute krn.GetServerInfo set Flags = 0runs any server job with typed parameters, input files and its response files previewable and downloadable - Simple query language for folders, registers and documents with subqueries, Klausel-style conditions, code completion and diagnostics
- Security statements for users, groups, roles, membership and the rights export
- Result tables per object type with file download
- English/German localization with automatic detection
- Per-login configuration in the DMS: theme, language and saved queries follow the user to any browser, with a storage dialog to inspect, export, import or delete the stored JSON
Requirements
- Python >= 3.13 and uv
- A DMS application server (default: 127.0.0.1:4000)
- A technical account with the system role "Kontextwechsel" (switch context)
Setup
uv sync
copy .env.example .env
# edit .env: servers, technical account, session secret
Run
uv run uvicorn ecmind_blue_studio.main:app --reload --host 127.0.0.1 --port 8099
Then open http://127.0.0.1:8099 and log in with a DMS user.
Object definition
The first tab cannot be closed and shows what the DMS server knows about its
own data model: cabinets with their folders, registers and documents, the index
mask fields of every type with data type, database column and names in the
configured languages, and the complete configuration of a single field. The
filter box searches all of it at once, six buttons per object type open a
prefilled select, count, insert, update, upsert or delete statement
in a new tab, and the download button hands out the native asobjdef.xml.
It needs the system role "DMS: Supervisor" (18) or "Editor: Starten" (11); without one of them the tab is not shown.
Job calls
The second fixed tab shows the live job stream of the configured servers, the
same feed the Enterprise Manager displays. Press Listen and every finished job
appears as one line with its timestamp, computer, user, job name, application
and file counts; a green check for success, a red warning for a failure, and
optionally a blue play line when the job starts. Filter by server, by job (a
popup over every namespace the server offers), by computer, application or user,
or reduce the stream to failures only. Clicking a line shows the call the way
the Enterprise Manager prints it, with base64 payloads decoded and XML indented.
Every value in that header is a link: the job and the server narrow the stream
itself, the computer, application and user filter the lines already on screen,
and the last link opens the call in a new tab as a ready to run execute
statement, with every parameter filled in and the user it originally ran for.
It needs the system role "DMS: Supervisor" (18) or "Administrator: Starten" (1). The monitor runs on the technical connection, which is why the roles are checked on the server and not only in the browser. Job parameters are shown unmasked.
Query language
Each tab has a Monaco editor with completion, hover and live diagnostics for a simple query language (see SPECS.md section 4). Ctrl+Enter runs the query, Shift+Alt+F formats it, Alt+N opens a tab and Alt+W closes it. Internal field names work without brackets:
select document Invoice, DeliveryNote where Status = 'open' order by InvoiceDate desc limit 100
select document Invoice in (register Correspondence where Subject = 'Dunning*') where InvoiceNo != ''
select document Invoice where InvoiceNo != '' and Customer.CustomerNo = 'C-10*'
select folder Customer contains (document Invoice where Status = 'overdue')
select folder Customer where Invoice.Status = 'overdue' and Blocked = false
select document Invoice where Clerk = #USER# and InvoiceDate >= #DATE#-1y6m
select document Invoice columns InvoiceNo, Amount, Customer.CompanyName
select document Invoice columns InvoiceNo, {OBJECT_FILESIZE}, {OBJECT_MODIFYTIME}, {OBJECT_DOCPAGECOUNT}
count document Invoice where Status = 'open'
System fields (OBJECT_ID, OBJECT_LINKS, OBJECT_FILESIZE, OBJECT_COUNT,
OBJECT_MODIFYUSER and more, see SPECS.md) work in where, order by and
columns; document-only fields are rejected on folder and register queries
with a live editor marker.
The editor also flags object types the signed-in user cannot see, and completion notes them in its list. The DMS answers a query on such a type with an empty result and no error at all, so the hint is shown before the run. It stays a warning and never blocks the query: a type you can see may return no rows as well.
Data statements update, create and delete objects, always with the rights of the signed-in user:
update document Invoice 4711 set Status = 'paid'
update document Invoice 4711, 4712 set Status = 'paid'
delete document Invoice 6524, 6522 purge
insert document Invoice into 55 set InvoiceNo = 'R-9' file 'scan.pdf'
upsert document Invoice into 55 set InvoiceNo = 'R-9', Amount = 12.5 search InvoiceNo
variant 4711 set Status = 'draft' file 'v2.pdf'
delete document Invoice where Status = 'void' purge
insert document Invoice into 55 set InvoiceNo = 'R-9', Positions = (Article = 'A-100', Qty = 5), (Article = 'B-200', Qty = 3)
update document Invoice 4711 set Positions = (Article = 'A-100', Qty = 7) replacetables
A single insert, update or variant shows what it wrote: the objects are
read back from the server and rendered as a result table below the
confirmation, with the usual row actions. A large update (more objects than
one result page) and statement batches keep the plain confirmation, because
reading every object back costs a roundtrip each.
Table fields take a comma separated list of rows, each row a list of
Column = value pairs. On update and upsert the statement has to say whether
the rows replace the table (replacetables) or extend it (appendtables) -
the mode belongs to the whole statement, so replacing one table and appending
to another needs two statements. The table popup in a result row carries an
"Edit table" button that opens a new tab with exactly such a statement, filled
with the rows currently stored.
Files referenced by file '...' are attached to the tab with the paperclip
button; file #BROWSE# instead opens the file dialog every time the statement
update and delete also take a list of object ids, and every listed object
gets its own copy of the file; a failing object does not stop the others, and
the result lists what was written and what failed. On an id based update the
set clause is optional, so
update document Invoice 6523 file 'scan.pdf' replaces only the file and
update document Invoice 6523 file #NULL# only removes it. Prefilled
document statements (edit button, object definition page) end with
file #BROWSE# as a reminder; delete the line for metadata-only changes.
File content can also be written inline, with an optional extension
after the closing quotes so the DMS knows the file type: file '''<data> </data>'''.xml stores the text, file b64'''iVBOR...'''.png decodes base64
(line breaks and spaces are ignored), and file #NULL# removes the document
file on an update. Without an extension, text becomes a txt and base64 a bin
file:
insert document Invoice into 5693
set InvoiceNo = 'R-9'
file b64'''
iVBORw0KGgoAAAANSUhEUgAAABAAAAARCAYAAADUryzE...
'''.png;
select document Invoice
Security statements administer the DMS users, groups and roles in the same language, again with the rights of the signed-in user, so the server enforces the role for configuring the security system:
select user where locked = false order by username limit 100
select user where group = 'Sales'
select group where user = #USER#
insert user KOL set display_name = 'Roland Koller', password = 'Start.2026', change_pwd = true
add user KOL to group Sales
export security group Sales, Marketing
select user KOL shows one account with every attribute, count group the
number alone, and delete user KOL forward to MEY hands the portfolios and
mails over before the account goes. User and group rows carry the same edit
button as DMS rows: it opens a new tab with an update user or update group
statement holding the current values. The password is never part of it, and
never part of a result or a log.
Free SQL with DMS names
sql passes everything after the verb to the server database, with one
exception: a {...} reference is resolved against the object definition first,
so a statement can name types and fields instead of object48 and feld4.
sql SELECT {original_filename} FROM {PostDoc} WHERE {Status} = 'done'
goes to the database as SELECT feld1 FROM object48 WHERE feld4 = 'done'. The
tables the statement names decide what a bare field means, and either spelling
does: sql SELECT {Status} FROM object48 works too. Where that is not enough,
name the type: {PostDoc.Status}. A SQL alias narrows it as well
(p.{Status} ... FROM {PostDoc} p).
System fields resolve to their own columns ({OBJECT_CRDATE} becomes
angelegt), and a table field resolves to its row table, so its rows can be
joined in:
sql SELECT i.{InvoiceNo}, p.{Qty}
FROM {Invoice} i JOIN {Invoice.Positions} p ON p.id = i.id
This is a name substitution and never a rewrite. Only the text inside the braces
is replaced, and only ever by an identifier out of the definition, so the join,
the clauses and the literals are exactly what you wrote, and a reference cannot
smuggle SQL in. Braces inside a string, a quoted identifier or a comment stay
the database's, and so does anything that is not identifier shaped, which is why
ODBC escapes such as {fn concat(a,b)} still pass through.
Completion offers both spellings side by side, so the physical schema and the DMS
names are one list: object48 next to {PostDoc}, feld4 next to
{PostDoc.Status}, and object48list1 labelled as the table field it holds.
Either spelling finds both, and each shows what the other one is. Hover tells
which column a reference stands for, and an unknown name is an editor marker
rather than a server error. Everything outside the braces is still unvalidated
and never reformatted.
Job calls from the editor
execute calls any server job directly, with a typed parameter list:
execute krn.GetServerInfo set Flags = 0, Info = 3
execute dms.GetResultList
set Flags = 16,
XML = '''<DMSQuery requesttype="LOL" pagesize="10">
<Archive>
<ObjectType internal_name="Invoice">
<Fields field_schema="MIN"/>
</ObjectType>
</Archive>
</DMSQuery>''',
Encoding = 'UTF-8'
The type of a parameter comes from the literal, so nothing has to be declared:
'text' is a string, 42 an integer, 12.5 a double, true a boolean,
datetime'...' a timestamp. A base64 parameter is written in plain text
between ''' quotes, which is what keeps an XML payload readable in the editor;
b64'''...''' carries real binary. The suffix after a text block names its
encoding when the job wants something other than UTF-8: '''...'''.utf16. Where
the literal cannot say it, a cast does: bigint 4294967296, string '4711'.
Input files come from the tab's attachments, a #BROWSE# pick or inline content,
the same way a write statement takes them, except that a job takes a list:
file 'a.pdf', 'b.pdf'. Response files are listed under the result with a
preview for text, PDF and image formats and a download for anything.
The result is the block the job call monitor shows for a call, so the two read the same. JSON gives the whole call as one record with text buffers decoded, XML gives the raw payload the job returned, and the delimited formats flatten the output parameters. A non-zero return code is not an error but the answer: it is rendered with the server's own message, exactly as the monitor renders a failed call.
Completion knows the jobs the server actually implements: execute offers the
engines, execute krn. their jobs, and picking one writes the whole statement
with every required parameter and a tab stop on each value. The parameter names,
types and descriptions come from the enaio Server-API reference, so they are
documentation rather than gospel: a job the reference has never heard of still
runs, with a warning and no help.
Unlike every other statement this one does not run as the signed-in user. It goes out on the pool's own connection, like the job call monitor, because writing the caller's identity into the parameter list would aim a job meant for a named user at whoever happens to be signed in. A statement that wants a user context says so itself:
execute dms.GetResultList
set {$$$SwitchContextUserName$$$} = 'administrator',
Flags = 16,
XML = '''<DMSQuery/>'''
The braces are needed because $ is not part of a name; the GUID and ID forms
({$$$SwitchContextUserGUID$$$}, {$$$SwitchContextUserID$$$}) work the same
way. The role gate is therefore the whole access control: "DMS: Supervisor" (18)
or "Administrator: Starten" (1), checked on the server, not just hidden.
Results render as a table by default or as JSON, JSONL, XML, CSV, TSV or TSV
quoted, sql results included. Every text format opens in an editable editor, so
the output can be trimmed before copy or download hands it to the next step.
TSV sanitizes tabs and line breaks inside values to keep one record per line,
TSV quoted keeps them and quotes like CSV instead. Every result
row offers details (metadata, variants and versions), an edit button that opens
a prefilled update statement, and file download.
Statement batches (separated by ;) stream their results statement by
statement, report the executed command and the upsert action per row, and can
be cancelled mid run; the stop button in the toolbar also aborts long running
queries. A query starting with a comment line names its tab, the question
button in the toolbar lists all keyboard shortcuts, the info button next to it
opens the about dialog, and the tab rail shows the connected DMS servers
above the user menu.
Per-login configuration
Theme, language, table lines and saved queries live in one JSON document in the
DMS, in the user data record ecmind_blue_studio of the signed-in user (slot
EBS_USERDATA_SLOT, default 80). They are not kept in the browser, so a second
machine shows the same setup, and a browser reset loses nothing. If the DMS
refuses user data for a login, the app runs on the defaults and every write is
blocked with an error rather than falling back to storage that can vanish.
"Settings storage" in the user menu shows the stored document in an editor and can download it, load a JSON file into the editor, save manual changes or delete the whole record from the DMS.
Open tabs are the exception: they stay in browser storage, shared by all windows of the browser, so closing and reopening the browser brings back the last set of queries. Which tab is in front stays per window.
Tests
uv run pytest # unit and endpoint tests
$env:EBS_LIVE_TESTS = "1"; uv run pytest -m live # against a local DMS (PowerShell)
Documentation
- SPECS.md - full specification and decision log
- TODO.md - backlog
- CHANGELOG.md - notable changes
License
MIT, see LICENSE. Bundled third-party libraries, fonts, icons and tools are acknowledged in THIRD-PARTY-NOTICES.md.
The MIT license covers this software only and grants no rights to the DMS itself. Operators must make sure their use of the DMS is covered by their own license agreement with the DMS vendor or reseller. See the DMS licensing section of THIRD-PARTY-NOTICES.md.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ecmind_blue_studio-1.0.0a6.tar.gz.
File metadata
- Download URL: ecmind_blue_studio-1.0.0a6.tar.gz
- Upload date:
- Size: 3.9 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f3ae71de2a6a7f9e57695959ae1b5317b1539fe16197f188d3d193e23d2a0822
|
|
| MD5 |
948e5a0e55bf5c6c546ba9a8f45dab27
|
|
| BLAKE2b-256 |
5002df3cfb236dc555fad3884987596935e2ec55a401b99f9067ccc9dfb87f34
|
File details
Details for the file ecmind_blue_studio-1.0.0a6-py3-none-any.whl.
File metadata
- Download URL: ecmind_blue_studio-1.0.0a6-py3-none-any.whl
- Upload date:
- Size: 4.1 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
529a1b5c79ac563508b8c88679be1c416b946d24e80a42b8bf42c2103407a128
|
|
| MD5 |
b8f30447c286ef067a911a240146bd7b
|
|
| BLAKE2b-256 |
3714f80b2a0104ba58e03d2b8fa7f1c7c571ad6ee089bb2983485cd314620383
|