Skip to content

Enhance export features, refactor capabilities, and improve auth pages - #975

Open
qianmoQ wants to merge 166 commits into
devlive-community:devfrom
qianmoQ:2026.0.0
Open

qianmoQ wants to merge 166 commits into
devlive-community:devfrom
qianmoQ:2026.0.0

Conversation

@qianmoQ

@qianmoQ qianmoQ commented Nov 1, 2026

Copy link
Copy Markdown
Member

Changelog category (leave one)

  • New Feature
  • Bug Fix
  • Documentation (changelog entry is not required)
  • Other

Changelog entry (Details of this change)

If there is an issue connection, write it to the end of the question
e.g: issue-7
Please delete this information when submitting

  • e.g: Support xxxx

Affected version

  • e.g: latest version

The tooltip around a database ER card's header kept the header as wide as the table name; it
now spans the card, a long name ends in an ellipsis and the tooltip shows the full name with
the table comment.
Each plugin declares its capabilities in src/main/resources/org/devlive/metaforge/plugin/
capabilities.json instead of <ServiceClass>.capabilities.json. As every plugin uses the same
path, the SPI reads the file from the jar or classes directory of the plugin's service class
rather than by resource name, so plugins on one class path never read each other's; a
subclass whose jar has no file still uses its parent's. The tests load classes from
throwaway directories, and the plugin guide shows the path.
ConvertService gains options() and extension(): a plugin lists the settings its writer takes,
with bilingual labels and choices, and reads the values from ConvertRequest.options.
- JSON: array or JSON Lines, indentation, date format (ISO 8601, yyyy-MM-dd HH:mm:ss or epoch
  milliseconds) and whether NULL fields are kept
- CSV: delimiter, header row and NULL text; cells holding the delimiter, quotes or line breaks
  are now quoted
- TXT: delimiter, header row and NULL text
- XML: root and row element names and whether NULL attributes are kept
Export now follows the prototype in the workbench: choose objects, set
content per table, pick a format plugin with its declared settings and a
live sample, set the output and execution, then check the plan and run
it as a job whose file is downloaded when it is done. The page fits the
viewport and only the step content scrolls.
The export API lists installed formats with their declared settings,
writes a sample of a table with the chosen format, checks a plan per
table, and runs it as a job that reads tables in batches, writes one
file per table plus schema.sql, and packs them into a ZIP to download.
Nothing read the flag since plugins declare their capabilities, so the
SPI default, the four overrides and the source list field go away.
Sign-in, sign-up, setup, home, 404 and 403 had no route title, so the
browser tab only showed MetaForge. They now show their page name, and
the title is rewritten when the language changes.
Only blank strings counted as empty, so a custom configures array sent
as null reached getMapValue and testing the connection failed with an
NPE. Any field without a value is now left out, and a null array gives
no environment.
Entry pages were never kept as a return address, so a network drop on
sign-in, sign-up or setup sent the user to home after reconnecting and
sign-up was lost. The network page now keeps them with their own
redirect; signing in and the other interim pages still never return to
an entry page.
Downloads only logged a lost connection, so exporting a file while the
server was unreachable left the page in place. They now go through the
same failure handling as other requests.
System settings (stored under the SYSTEM configure category) switch on
registration, the captcha, remembered sign-ins, email sign-in, email
verification on sign-up and password reset, and hold the SMTP server
and the terms and privacy links. Email features only take effect once
mail can be sent. The sign-in page reads what is on from
/api/auth/options; codes are mailed by /api/auth/code and passwords
reset by /api/auth/reset. Users get an email column, and administrators
a System settings menu on fresh and existing installs.
Administrators turn sign-in and sign-up features on and off, set the
agreement links and the SMTP server, and send a test mail with the
server as it is in the form. Features that send mail are marked until a
mail server is set.
The auth pages share a brand panel and a card with a language menu and
sign-in / sign-up tabs. Each optional part appears only when system
settings turn it on: the captcha, remembered sign-in, sign-in by email,
the forgot password link and its reset page, email verification with a
code button that counts down, and the agreement box. Sign-up shows that
it is closed when registration is off.
…lled

MfInput passes autocomplete (still off by default) and name to its
input, so forms can mark usernames, passwords and one-time codes.
Browser autofill no longer paints its own background on the bare input
inside the field.
The auth background used min-h-screen, which style.css redefines for the
app layout, so the page stopped about 360px short of the bottom. The
username rule now sits under the field, the captcha row fills the
width, and the captcha comes after the passwords on sign-up with each
field carrying its autofill hint, so browsers stop filling the username
into it.
The released class names load the drivers again:
io.edurt.datacap.driver.redis.RedisDriver,
io.edurt.datacap.driver.cluster.RedisClusterDriver,
io.edurt.datacap.driver.MongoJdbcDriver and com.dbschema.MongoJdbcDriver.
The Redis driver takes jdbc:redis-cluster://host:port,host:port again,
reads the database from the URL path, and runs plain Redis commands as
before, answering with one RESULTS column; execute() now returns that
result. SELECT n is refused because connections are pooled.
replace the badge-only page with a complete guide: project introduction,
feature list, requirements, development and production build
instructions that match the actual build (IDE launch, dist tarball),
docker deployment and a project structure map; keep the connector
gallery, community sections and badges
Each provider is stored under the SYSTEM configure category and is offered
on the sign-in page only once an administrator turns it on and sets its
client; OpenID Connect also needs its endpoints. Client secrets are never
sent back and are kept when saved empty, and the fixed GitHub and Google
endpoints can not be changed.

/api/auth/oauth/{provider}/authorize starts the authorization code flow
with a state kept in the browser and PKCE. The callback finds the user by
the linked account (metaforge_user_oauth), then by a verified email, or
creates one while registration is open, and hands the token to the web
page through a one-time ticket. An optional site address fixes the
callback URL behind a proxy.
The development loader read target/dependency, but plugin builds copy
their jars to target/dependencies, so a plugin loaded from its module
saw no driver jar at all. Drivers on the server's own class path still
worked; the MetaForge drivers (Redis, MongoDB, Kafka, ZooKeeper, HDFS,
Aliyun OSS) failed with ClassNotFoundException. Both folders are read
now.
A class in a parent-first package that the parent loader does not have,
such as org.devlive.metaforge.RedisDriver inside a plugin's driver jar,
failed with ClassNotFoundException. The plugin's own jars are tried
before giving up.
System settings get a third-party sign-in section for each provider's
client and endpoints. The sign-in page shows the prototype's other
sign-in buttons only for providers an administrator turned on, and a
callback page signs in with the returned ticket and goes back to where
the user was.
A label around each provider field also caught clicks on the client
secret's show button; the fields are plain blocks with accessible names
now, and the read-only callback address gets one too.
Sign-in and sign-up, the mail server and third-party sign-in are tabs on
the left, each with how much of it is on; the chosen group fills the
right with its own heading and the save button. The tab is kept in the
address, and a save that fails a check opens the tab with the error.
The driver read values with Redisson's binary codec, so keys set by
redis-cli or applications failed to decode. Values are plain strings
now. SELECT takes quoted keys (`test:list`), reads an exact key, a
pattern or, as before, the keys starting with the name, keeps only the
selected columns, and returns its columns even when nothing matches.
SELECT version() reports the server's redis_version.
SpotBugs failed the test module on unclosed result sets and instanceof
assertions in the Redis compatibility tests.
RedisE2ETest writes keys with plain commands, reads them back with SQL
and browses them as the source tree does. The connector page lists the
verified versions and the SQL the driver understands.
…he source

execute() always answered false, so MetaForge, which runs every
statement through it, got an update count of 0 instead of rows. It now
leaves the result set, and an empty result still has metadata. The
database comes from the property or the URL path (admin otherwise), and
getMetaData() lists databases, collections and the fields of their
first documents through the SQL driver foundation.
The plugin passes the configured database in the URL, which it built
and then dropped, lists metadata through the driver's JDBC metadata as
the other MetaForge drivers do, and declares database:list, table:list
and column:read, so the query page and the source workbench show a
tree.
A bodyClass prop (default px-5 py-4) lets drawers such as the plugin detail lay
out full-width tabs and scroll inside their own sections.
The store keeps the site header and splits into market, installed, update
center and history views with dynamic categories. Plugin details show the
built size, the MetaForge version required, features from declared capabilities
and confirmed download counts; unknown counts show a dash, never zero.

- install, update, enable, disable and uninstall are admin only and succeed only
  when the server returns true; uninstall is blocked while resources use a plugin
- installed plugins missing from the catalogue describe themselves from the
  plugin.json shipped in their package
The owner turns on public access per report and gets a random 192-bit link,
which can be rotated at any time. Visitors read the saved definition only: the
query chart snapshot, its read-only refresh, or the saved dataset query, run as
the owner with a 30 second cache. They never send SQL and never see the SQL,
data source, owner or internal errors.

- /embed/report/{token} serves the web UI with CSP frame-ancestors from the
  owner's allowed sites (any site when empty) and no X-Frame-Options; every
  other page stays same-origin
- saving a report never changes its sharing; new reports start private
- reports from the earlier chart editor cannot be shared, they ran SQL in the
  browser
The report list gets a Share action: public access with a copyable link and a
link reset, iframe embedding with allowed sites and ready-to-paste embed code,
and a Public tag on shared reports. Share and embed addresses open a bare public
page without sign-in; embedded pages are compact and ?title=0 hides the name.
Owners set an expiry for a share link and decide whether the public page shows
the report title and description. Expired links stop answering and refuse
framing at once; a hidden description is not sent at all. Expiry travels as
epoch milliseconds, and a report save keeps these settings unchanged.
Share moves out of the settings menu into its own button, highlighted while a
report is shared. The dialog sets the validity (forever, 1, 7 or 30 days, or
until a date) and whether the page shows the title and description, and tells
when the link expires. Expired shares are tagged in the list. The public page
follows the owner's display settings instead of a ?title=0 parameter.
The hash router captures the address when its module is evaluated, and main.ts
mapped /share and /embed paths only after its imports had already created the
router, so a share link started at / and was sent to the sign-in page. The
mapping now runs from the first import.

- the link sits on its own line with copy, preview and new link below it
- switch rows span the dialog width, and expiry times read YYYY-MM-DD HH:mm:ss
Owners decide per report whether the public page offers the data as a table
and as a CSV download; both are off by default and the server refuses either
unless allowed. Both serve the rows the chart shows (refreshed or saved chart
rows, or the dataset result), at most 10000. The CSV carries a byte order mark
for spreadsheet apps and neutralises cells that would start a formula.
A shared address now mounts a standalone public report app instead of the main
app, so the hash router never runs and no #/public/report/... is appended to
the address. The public page switches between the chart and a data table and
downloads the CSV when the owner allows it, and the share dialog adds the data
access switches.
Browser requests for page addresses (GET, accepting HTML, not under /api, not a
static file or other path with an extension) now receive the UI document, so
addresses such as /query or /admin/user open directly and survive a reload.
Those pages need no sign-in; the APIs they call still do. Third-party sign-in
returns to /auth/oauth instead of /#/auth/oauth.
Addresses read /query instead of /#/query. Creating the hash router rewrote the
address with #/ even on shared report pages that never use it. Old hash
addresses from bookmarks and links still open: a first import turns /#/path
into /path before the router is created, and plain anchors stay untouched.
The index page ran the MySQL default against information_schema.STATISTICS,
whose IF branches return UInt64 and Float64 and fail on ClickHouse with "no
supertype". The primary key, or the sorting key without one, now comes from
system.tables, split only at top-level commas, and data skipping indexes from
system.data_skipping_indices; servers without that table still list the key.
Opening a source showed blank names, an empty database selector and "no data"
while the databases were still loading. The header, sidebar and content now
share the database list and show its loading state, a source opened without a
database opens its first one, and an empty or failed list says so.

- the tree reloads only for another source or database: its watcher compared a
  fresh array of the rebuilt route params, so opening any table reloaded it
- structural changes (tables or columns created, changed or dropped) refresh
  it; closing a dialog no longer does, and the tree no longer expands every
  table, which loaded the columns of each one
NotifyService.fields() lists a channel's settings (text, URL or write-only
secret, required or not), and NotifyRequest.configure carries the user's values
to send with. DingTalk declares its robot token, which also accepts the whole
webhook address, and its optional signing secret.

- DingTalk no longer logs the access token or the signature, only the outcome
- a missing token fails before any request, and titled messages use markdown
- request building is separate from sending, so it is tested without network
- group notifications into scene categories; old operation subscriptions
  (CREATED, DYNAMIC, SYNCDATA, ...) are read as the matching dataset scenes
- add /api/v1/user/notify to read, save and test a user's channels: only
  enabled channels hold subscriptions, required settings are enforced, and
  secrets are write-only (masked on read, an empty value keeps them)
- replace the notification aspect with one dispatcher that runs after
  commit and passes each user's settings to plugin channels, which never
  received credentials before
- tell new datasets apart from updates before the build assigns an id, and
  notify when a sync fails as well as when it succeeds
- stop exposing channel settings in the user payload
- each channel has its own switch; only enabled channels show their
  settings form, get a test button and appear as scene columns
- secret settings are never shown: a stored one reads as set and stays
  when left empty
- scenes are grouped by category with per-channel checkboxes, and a
  category checkbox selects the whole group
- changes are saved together after validation instead of on every click
- both headers show notifications by scene name and link to the entity
- sources: created, updated and deleted; deleting by code now also
  removes owned rows and config files, as deleting by id did
- reports: created, deleted, and sharing turned on or off (rotating the
  link is not a new share); dashboards: created
- import and database export jobs: succeeded or failed, sent to the user
  who started them once the background job ends
- account: password changed or reset by an administrator, and an OAuth
  account linked to an existing user on first sign-in
- plugins: installed, uninstalled, enabled or disabled (administrators)
- dashboards have no publish step and accounts cannot be unlinked, so
  those scenes become dashboard created and are dropped respectively;
  workflow scenes wait for the workflow run rewrite
Full-width channel rows with inline forms grew long and uneven as
channels were added. Channels are now compact cards in a grid that only
gains rows, settings open in a drawer, and channel changes save at once.

- turning on a channel that lacks required settings opens the drawer
  first and saves it as on
- each card shows its state and how many scenes it receives
- scene ticks keep their own save and reset
Headers loaded the notification list and unread count once on mount, so
a test notice on the site channel, or any notice raised later, showed
only after a reload. A test notice now signals the headers to reload,
and opening the notification list always fetches it again.
The notification endpoints were the generic CRUD controller: updates and
deletes went by code without checking the owner, and the list had no
read, category or keyword filters. Every inbox statement is now limited
to the signed-in user.

- list with read state, category and keyword filters, newest first
- summary with total, unread and per-category counts
- mark one read or unread, mark all read, delete one or all read
- widen notification content to 2000 characters so failure reasons
  are kept, and index notifications by user and read state
- one bell for both headers: a panel with all and unread tabs, today and
  earlier groups, mark all read, a shortcut to notification settings and
  a link to the full center
- rows read or unread and delete on hover; deleting always asks first
- a detail drawer shows the whole content, its category and time, opens
  the related dataset, source or dashboard, and reads the notice on open
- a notification center page filters by read state, category and words,
  loads more on demand, clears read notices and shows an overview
- the unread count refreshes every minute while the tab is visible, and
  any change reloads every open list
The page added its own 1200px width and padding on top of the layout's
8px sides, so it sat narrower and further in than every other page. It
now fills the content area and uses the same heading as list pages.
Separate draft saves, publication and execution. Return each executor's
components and validate complete input-to-output pipelines on the server.

Add a full-screen editor with resizable panels, logs, bounded output
samples and runtime metrics. Persist execution records and enforce ownership.

Validate with workflow persistence tests, executor regression tests,
frontend type checks and builds, and backend static analysis.
Each executor plugin now ships a workflow.yaml in its own jar declaring its
capabilities (stop mode, row counts, metrics, output samples), graph limits,
per-workflow job settings and node plugins, and implements compile() to turn
an ordered pipeline into its own request.

The server discovers executors from installed executor plugins, validates
graphs against their declarations and resolves saved data sources before
handing plans over, so no workflow code branches on an executor name. The
MetaForge column mapping and the SeaTunnel table wiring move into their
executors; SeaTunnel gains ordered transform chains and env.parallelism.

The global configure/etc/conf/executor directory and the unused
/api/v1/configure/executor endpoint are removed.
The palette, node limits and graph checks follow the limits each executor
plugin declares instead of a single transform count, and the run console
explains when an executor does not report metrics or output samples.

Job settings declared by the executor appear in the workflow settings with
the executor description and capabilities. One field renderer now serves
node parameters and job settings, and declared field groups choose the
inspector tab.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant