A new Database shell at the application layer: a Harmonia/Alpine re-imagining of the IDE's Database perspective, scoped to support essentials only — browse the schema, look at data, run a SQL fix. It gives operators and support engineers a way to touch the database without opening the IDE, exactly the way the Monitoring shell answers "is the system healthy?" without the IDE.
Status: in delivery. PR 0 is the Harmonia bump (#6656), PR 1 the URL-layer gate (#6657), PR 2
this shell. Prior art: the Monitoring shell (components/resources/resources-monitoring,
PRs #6613–#6615) — the only existing single-purpose operations shell and the construction template
this plan clones.
- The bump is to 2.9.0, not 2.10.0. 2.10.0 is published on npm but has no webjar on Maven
Central, and 2.9.0 carries every breaking change that mattered (the tree rewrite included). The
2.10.0 delta — a cross-origin iframe crash fix and an inverted
getBreakpointListenerthird argument — is a follow-up; when its webjar lands, the six shells calling that listener must passtrue. - The
data-disabledsweep was a bug fix, not a rename.data-disabledon a rootx-h-selectnever did anything, even on 2.6.0: it was always a per-option attribute, and the whole control is disabled through nativedisabledonx-h-select-input. Preview-mode and read-only dropdowns in the generated apps were never actually disabled. They are now. - Two breaking changes the plan did not list turned out to matter: the 2.7.0 slot-picker stopped
collapsing its day columns on narrow screens by itself (
.responsiverestores it), and 2.7.0 moved an option's label into a text column, which broke one IT assertion that demanded exactly one<span>per label. - Lazy tree loading must not own the expanded state. A 2.9 tree item detects its own children
and owns whether it is expanded, so a second owner in the store fights it. The shell instead
renders one placeholder child under an unloaded node — which is what gives the row its chevron
before anything has been fetched — and the
tree-item-clickhandler only loads.
- A standalone Harmonia shell served at
/services/web/database/, registered onplatform-shells(shell iddatabaseShell, icondatabase, order 38 — between Monitoring 37 and the IDE 40). - Two pages: an Explorer (datasource → schema → tables/views/procedures → columns tree, "show contents" grid, DDL view) and a SQL Console (statement area, results grid, messages strip).
- Zero new backend. Every read and execute goes through endpoints that already ship in
components/data/*, all method-gated@RolesAllowed({ADMINISTRATOR, DEVELOPER, OPERATOR}). - Role gate:
database.accesson/database/for ADMINISTRATOR / OPERATOR / DEVELOPER — the same trio the endpoints declare, so the shell never renders a page its user cannot use.
Highly feasible, low risk. The backend surface exists 1:1; the shell skeleton, shared runtime,
page pattern, registration plumbing and IT pattern are all proven by the Monitoring shell; Harmonia
(as of 2.10.0) carries every component the UI needs (tree, table, split, tabs, select,
menu/popover). Estimated cost: ~2.5–3k LOC across 2–3 PRs, roughly 1–1.5 weeks of focused
effort, of which the single real prerequisite — the Harmonia webjar bump 2.6.0 → 2.10.0 — carries
most of the risk.
- Datasource picker (names from
GET /services/data/metadata), current selection in an Alpine store, persisted to localStorage (the IDE does the same via${brand.prefix}.view-db-explorer.database). - Lazy metadata tree: schema → tables / views / procedures → columns. Fixed depth, so plain nested
x-for— no recursive templates needed. - "Show contents" — a generated
SELECTexecuted through/query, rendered in anx-h-tablewith dynamic columns and client-side pagination. - Object DDL via
GET /services/data/definition/{ds}/{schema}/{structure}. - SQL Console: monospace textarea (NOT Monaco — see decisions), Ctrl/Cmd+Enter to execute, results grid, update counts / errors in a messages strip, statement text persisted in localStorage.
- Explorer row actions: Show contents, Copy name, Generate SELECT into the console.
- Metadata cache refresh (
GET /services/data/metadata/invalidate-cache). - i18n en-US + bg-BG, following the
i18n/<locale>/<project>.jsonaggregator convention.
- Import / export (sync + async), anonymization, data transfer (the WebSocket), schema export/import processes — not support essentials; also exactly the endpoints restricted to ADMINISTRATOR/OPERATOR-only, so cutting them keeps the role story uniform.
- Datasource CRUD (
/services/data/sources) — the shell consumes datasources, it does not manage them. - Row-edit dialogs (the IDE's
result-view-crud+ the server-side JSdatabaseTable.jspath) — the SQL console covers support writes; optional later phase. - Monaco — a support console does not justify embedding a multi-MB editor in an app-layer shell.
- Re-implementing anything the Workbench Database perspective does deeply (script generation beyond a SELECT, project exports, topology). Each page links to the IDE instead — the Monitoring shell's scope line, applied here.
Prefix from BaseEndpoint: /services/data/. Datasource/schema are always path variables —
no server-side "current datasource" session state; every request is self-describing.
| Need | Endpoint | Roles |
|---|---|---|
| List datasources | GET /services/data/metadata |
ADMIN, DEV, OPERATOR |
| Datasource → schemas | GET /services/data/metadata/{ds} |
ADMIN, DEV, OPERATOR |
| Schema → objects | GET /services/data/metadata/{ds}/{schema} |
ADMIN, DEV, OPERATOR |
| Object metadata (columns…) | GET /services/data/metadata/{ds}/{schema}/{structure}?kind= — structure name Base64-encoded in the path |
ADMIN, DEV, OPERATOR |
| Object DDL | GET /services/data/definition/{ds}/{schema}/{structure} |
ADMIN, DEV, OPERATOR |
| Run SELECT | POST /services/data/{ds}/query — body raw SQL, Content-Type: text/plain |
ADMIN, DEV, OPERATOR |
| Run UPDATE / DDL | POST /services/data/{ds}/update |
ADMIN, DEV, OPERATOR |
| Call procedure | POST /services/data/{ds}/procedure |
ADMIN, DEV, OPERATOR |
| Refresh metadata cache | GET /services/data/metadata/invalidate-cache |
ADMIN, DEV, OPERATOR |
These are de-facto contracts of DatabaseExecutionEndpoint / DatabaseExecutionService, not bugs
to fix in this effort:
- Statement dispatch is a prefix sniff, not parsing (the IDE's
result.jsdoes the same):select …→/query,call …→/procedure,query: …→/querywith the prefix stripped (NoSQL, e.g. MongoDB find),update: …→/updatestripped, everything else →/update. Replicate exactly. - Response format branches on an exact
Acceptmatch (text/plain→ monospaced table,text/csv→ CSV, anything else → JSON). Send a plainAccept: application/jsonand always take the JSON branch. - Results are silently capped server-side at
DIRIGIBLE_DATABASE_DEFAULT_QUERY_LIMIT(default 1000) with no truncation marker in the payload. The grid must label honestly: "showing N rows — the server may cap results" (at least when N looks like a round cap). The IDE never surfaced this; the support shell should. - Procedure results are double-encoded: a JSON array whose elements are JSON strings —
JSON.parseeach element. - The service splits multi-statement bodies on
;(on--when the body containsCREATE PROCEDURE/CREATE TRIGGER) and streams results per statement; an error mid-stream surfaces as a 500 on an already-committed response — render whatever arrived plus the error. - Encode names consistently from day one. Metadata/definition paths take the structure name
Base64-encoded; anywhere else, encode per path segment. The IDE breaks on schema/table names
containing
.(it splits"schema.table"on the dot); greenfield code must not copy that.
/services/data/** has no URL-layer gate in HttpSecurityURIConfigurator — it falls through to
AUTHENTICATED_PATTERNS (/services/** → authenticated()), so method-level @RolesAllowed is
the only rejection. Mirror what the Monitoring shell did in #6613: add /services/data/** to the
operations RoleGate (ADMINISTRATOR/DEVELOPER/OPERATOR) ahead of the catch-all, and extend
HttpSecurityURIConfiguratorTest's matrix. ~60 LOC of defense-in-depth. Note the endpoints are
mixed-role (export/import/anonymize are ADMIN/OPERATOR-only, stricter than the gate) — the URL gate
must use the widest legitimate set and leave the finer method-level checks in place.
Independent platform gap found while mapping — report it privately, do NOT open a public issue.
The /websockets/data/transfer handler (DataTransferWebsocketHandler, data-transfer) carries no
role check at all and /websockets/** is merely authenticated() — any authenticated user can
drive data transfer, unlike every REST sibling in components/data/ (all ADMINISTRATOR + OPERATOR).
SECURITY.md routes undisclosed vulnerabilities to the Eclipse Security Team or a committers-only
Bugzilla entry, so this belongs there rather than in the issue tracker. It is deliberately kept out
of PR 1: that PR is a strict no-op for every existing caller, while fixing this changes who may use
a feature, and the right role set is a policy call (DEVELOPER opens the IDE's Transfer view).
New module components/resources/resources-database (dirigible-components-resources-database),
web assets under src/main/resources/META-INF/dirigible/database/. A straight clone of the
resources-monitoring skeleton — the only live single-purpose operations shell (admin / personal /
application are host shells whose large appShell.js exists to mount extension-contributed
perspectives; not the right template).
pom.xml (no webjar deps needed — unlike monitoring's bpmn-visualization)
META-INF/dirigible/database/
├── index.html sidebar + toolbar chrome, Pinecone routes into #app
├── database.access /database/ → ADMINISTRATOR, OPERATOR, DEVELOPER
├── configs/shell.js id 'databaseShell', path, label, icon 'database', order 38
├── extensions/shell.extension platform-shells registration
├── css/app.css shell-local styles only
├── i18n/en-US/database.json + bg-BG/ namespace = file basename
├── js/
│ ├── config.js window.App.config { projectName, basePath, restBase: '' }
│ ├── appShell.js thin: currentPath tracking + Harmonia breakpoint drawer
│ ├── services/dbops.js one wrapper per endpoint + soft(); owns the dispatch sniff,
│ │ Base64/segment encoding, procedure double-decode
│ ├── stores/
│ │ ├── explorer.js datasource list + selection (localStorage), lazy tree state,
│ │ │ contents grid state, DDL state
│ │ └── sql.js statement text (localStorage), execute, results, messages
│ └── components/pages/
│ ├── explorerPage.js
│ └── sqlPage.js
└── views/
├── _explorer.html x-h-split: tree (x-h-tree, nested x-for) | contents/DDL tabs
└── _sql.html textarea + run toolbar + results x-h-table + messages
Everything cross-cutting comes from the shared runtime at
/services/web/application-core/shell/ by absolute URL (app.js, i18n/api/apiError/format/branding
services, theme/locale/currentUser/shells stores, base css) — no per-project copies. Script order in
index.html is load-bearing (Pinecone → shared runtime → config → local services/stores/pages →
appShell → Alpine → Harmonia bundle, which auto-starts Alpine → Lucide last).
Registration edits outside the module: components/pom.xml (module + dependencyManagement),
components/group/group-ui/pom.xml (assembly dependency), resources-home's home.js (icon map,
description, SECONDARY list — a support tool is a secondary destination, like Monitoring).
- Per page: an Alpine store (data + async actions, never touches DOM), an Alpine page
component (
Alpine.data('explorerPage')— formatting, derived getters, calls$store.explorer.load()ininit()), and a view fragment. - Every endpoint read goes through
dbops.js; aggregate reads wrap insoft()so an unavailable datasource degrades to a marked tile, not an empty screen. - Stable ids for the IT: sidebar buttons
database-nav-<section>, page rootsdatabase-<section>-page. - Alpine/Lucide trap: a bound
:data-lucidegoes on<svg x-h-lucide>, never<i x-h-lucide>— the plugin replaces the element and the throw silently aborts Alpine's walk (the failure modeMonitoringShellITexists to catch). - Tree: Harmonia ≥2.9 API (
x-h-tree-row/x-h-tree-label/x-h-tree-actions, selection driven by thetree-item-clickevent — the tree keeps no selection model of its own). Lazy loading = fetch children on first expand, cache in the store.
The root pom pins harmonia.version 2.6.0; the tree was rebuilt with breaking changes in 2.9.0
(x-h-tree-button → row/label/actions/indicator, new tree-item-click event). Building the
explorer on the 2.6.0 tree API would be instant legacy — bump first. Measured blast radius:
- Old tree API: one file —
template-application-ui-harmonia-java'sui/perspective/manage/list-view.html.template(the tree-list layout). - Removed
data-disabled/data-label: three templates (admin-index.html.template,document/document-view.html.template,manage/form-view.html.template). Theresources-karavan-libsgrep hit is a bundled third-party React app — not Harmonia markup. - 2.9.0 also reworked sidebar group actions and the
aria-disabledkeyboard behaviour — verification pass over the five existing shells'index.html(application, monitoring, admin, personal, partner) plus the Harmonia templates. - Per the established bump procedure: read the upstream CHANGELOG (2.7 → 2.10), regenerate the sample apps after the template migrations, full IT run.
| PR | Content | Size | Status |
|---|---|---|---|
| 0 | Harmonia bump 2.6.0 → 2.9.0: pom pin, tree-template migration, slot-picker .responsive, data-disabled fix, one IT assertion |
7 files | #6656 open, 6 UI journeys green |
| 1 | /services/data/** URL-layer gate + HttpSecurityURIConfiguratorTest matrix rows |
3 files | #6657 open, 30 unit tests green |
| 2 | The shell: module skeleton, dbops.js, two store/page/view triples, registration edits, i18n, DatabaseShellIT |
~1,300 LOC, 18 files | in progress |
Calibration: the Monitoring shell landed as three PRs totalling +3,932 LOC over 46 files, six pages, zero Java in the module. This shell has fewer pages (2 vs 6) but denser ones — the lazy tree and the arbitrary-columns results grid are the two components Monitoring never needed. Total: ~1–1.5 weeks end to end including the bump — materially less if the bump happens anyway for other reasons.
DatabaseShellIT(@Tag("ui"),UserInterfaceIntegrationTest) — the MonitoringShellIT pattern, ~100 LOC: (1) the Explorer renders shell chrome, the tree showsDefaultDBschemas, and a store poll resolved (catches the Alpine-bootstrap abort, which has no DOM or server symptom); (2) every section reachable bydatabase-nav-<x>id, asserted ondatabase-<x>-pagewith deployment-independent text; (3) the SQL console executes aSELECT 1-class statement against DefaultDB and the grid renders a row — the one end-to-end journey worth the browser.- The backend needs no new tests (unchanged); PR 1's gate change extends the existing
HttpSecurityURIConfiguratorTest.
- Textarea, not Monaco (decision). Support usage doesn't justify a multi-MB editor; localStorage persistence and Ctrl/Cmd+Enter match the IDE's muscle memory. Revisit only on real demand (CodeMirror-class, lazy-loaded like Monitoring's bpmn-visualization, would be the shape).
- The Harmonia bump is the main risk — a platform-wide change riding ahead of a feature. Mitigated by its own PR, the small measured breaking-change inventory above, and the full IT suite.
- Silent row cap UX — inherent to the backend; label it, don't fix it here.
- Write access is the point, and the gate is roles, not the shell. The shell deliberately
allows UPDATE/DDL (a support tool that can't fix data is a dashboard). Safety comes from the
.accessfile + the endpoints'@RolesAllowed(+ PR 1's URL gate), not from hiding buttons. No confirmation theatre beyond a destructive-statement confirm on non-selectexecution. - Don't port the IDE's bugs. Known in the AngularJS stack and out of scope to copy: a dead
deletebranch inresult.jsrow CRUD, a$destroythrow on an undeclared listener, the dot-splitting ofschema.table, and the never-wireddatabase.database.selection.changed(database-kind) topic. Greenfield code replaces the whole MessageHub topic web with two Alpine stores.
Import/export (sync/async), anonymization, data transfer, schema export/import processes, datasource CRUD, row-edit dialogs, Monaco, procedure/script generation beyond SELECT, project exports, topology — the Workbench Database perspective remains the deep tool; this shell links to it.