A single web page that uploads cycler data files from folders on your computer to a datalab instance.
It does the job of datalab-beholder on machines where you cannot run its .exe in the background, such as a university-managed PC.
Nothing is installed.
The page runs in Chrome or Edge and syncs while its tab is open.
beholder-sync.html from disk also loads, but folder access from a local file has not been tested.config.yaml into the top box and click Load config.
Each watched path in the config gets a card of its own.
Without a config, fill in the boxes by hand and click Add watched path for each folder.
Get an API key from <your datalab URL>/get-api-key if the config has none.
If a line does not parse, the rest of the config still loads, and a banner names the line.
Write Windows paths in single quotes, such as path: 'C:\Users\me\data'.The page syncs once when you pick a folder, then every sync.metadata_interval seconds from the config (20 minutes by default).
Sync now runs a pass immediately and retries files that are waiting out a failure.
A watched path is one entry of the config’s watched_paths: a folder, and the patterns that pick and file its data.
Several paths can share a machine, such as two cyclers and an EIS potentiostat.
They all upload to the same datalab.
Each pass walks every chosen folder and compares every file’s size and modified time with the last version that reached datalab. For each new or changed file it:
id_patterns and item_id_template.item_type and the collection from collection_id_template.block_patterns (for example cycle for *.ndax) if the item has none wired to that file.These are the same API calls, in the same order, that datalab-beholder makes.
Patterns follow beholder’s rules: globs match the file name without regard to case, and ID regexes can use Python’s (?P<name>...) syntax.
Each watched path has a card, as on beholder’s own dashboard. The card lists the path’s rules, and counts its files that are synced, pending, stuck, or have no item ID. Below the counts are its failing files with their errors and its ten most recent uploads. Edit patterns on the card changes that path’s rules. The pill at the top sums the cards and shows the worst state. A failed upload is retried after 1, 2, 4 and so on up to 60 minutes. A pass where datalab does not answer at all leaves files pending without counting a failure.
lightningtree-ai.github.io shares that storage. Do not turn on Pages for another repo in this organisation until the page has a domain of its own.group_id. The page logs a warning instead.Access-Control-Allow-Origin for the page’s origin. datalab’s API sends *.cdnjs.cloudflare.com, which serves the YAML parser. Syncing works without it. Only Load config needs it.test_sync.py drives the page in headless Chromium against a mock datalab served from a second port, so every request is cross-origin.
The watched folder is built in the browser’s origin-private file system, whose handles behave like a folder the user picked.
uv run --with playwright python test_sync.py
Run playwright install chromium once first, or set CHROMIUM_PATH to a Chromium binary already on disk.
The last lines of a passing run:
PASS matching folder name, any case, gives no warning
PASS wrong folder name warns
PASS no config path gives no warning
PASS multi-datalab config defaults to datalab.lightningtree.ai
PASS an explicit datalab choice wins
PASS an unknown datalab name falls back to the default
PASS blank max depth is unlimited
PASS version 1 rows and config path carry over
PASS version 1 files do not upload again
PASS no page errors
ALL PASSED
The test cannot open the native folder picker, so choosing a folder and the permission prompt are only checked by hand.
Written with Claude Code.