Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
c22f4c6
Add routes listing existing compounds and storage places
rtg-1409 Oct 1, 2026
92fa382
Import compounds from PubChem without overwriting existing ones
rtg-1409 Oct 1, 2026
2b3eca0
Remove autofill
rtg-1409 Oct 1, 2026
a187838
Read compounds and their hazard pictograms straight from PubChem
rtg-1409 Oct 5, 2026
d2f3562
Offer to restore a deleted compound instead of refusing it
rtg-1409 Oct 5, 2026
cd9b648
Add storage places from the app without near-duplicates
rtg-1409 Oct 5, 2026
d0f4d24
Add bottles linked to their compound and storage place
rtg-1409 Oct 5, 2026
a0ee261
Tag new bottles with their compounds' hazards and peroxide class
rtg-1409 Oct 5, 2026
2dcf155
Warn before adding a bottle that is already in the ELN
rtg-1409 Oct 5, 2026
51b18de
Ask about the label instead of blocking same-lot bottles; add several…
rtg-1409 Oct 5, 2026
5088935
New add-bottle page, part 1: category, compounds and the bottle's own…
rtg-1409 Oct 5, 2026
7b71ec0
Add-bottle page, part 2: room and place dropdowns, add a new place
rtg-1409 Oct 5, 2026
7fb119f
Add-bottle page, part 3: amount, unit and number of bottles
rtg-1409 Oct 5, 2026
5605e84
Add-bottle page, part 4: the Create button
rtg-1409 Oct 5, 2026
4b5b1ee
Add-bottle page, part 5: find a missing compound in PubChem
rtg-1409 Oct 5, 2026
6cb44a0
Choose hazard pictograms the way an SDS would, not from any source
rtg-1409 Oct 5, 2026
e0543d8
Add-bottle page, part 6: print the new bottles' labels
rtg-1409 Oct 5, 2026
1b8e04b
Remove the old add-resource form and the code only it used
rtg-1409 Oct 5, 2026
09ea181
Scanner: change location moves the bottle's storage entry
rtg-1409 Oct 5, 2026
ffd0ad0
Scanner: mark empty also sets the amount to 0
rtg-1409 Oct 5, 2026
786f1f8
Scanner page: "Resource Manager" heading
rtg-1409 Oct 6, 2026
5c2530b
Scanner: routine-check steps on Mark Open, Tested and Mark Empty
rtg-1409 Oct 6, 2026
c3fb02e
Scanner page: let the command buttons scroll
rtg-1409 Oct 6, 2026
2b0d91b
Name the app "Toolkit" and add page tabs in the left column
rtg-1409 Oct 6, 2026
14a3adc
Toolkit tabs: Registry shows every command, Resources the resource ac…
rtg-1409 Oct 6, 2026
3bbe133
Name the app "ELN Toolkit"
rtg-1409 Oct 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,5 @@ HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
CMD python -c "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:5000/ping', timeout=4).status==200 else 1)"

# 1 worker, as on the old server (~160 MB each). --timeout matches the timer client's
# 600 s wait, so a long autofill or peroxide check isn't killed halfway.
# 600 s wait, so a long peroxide check isn't killed halfway.
CMD ["gunicorn", "-w", "1", "-b", "0.0.0.0:5000", "--timeout", "600", "--access-logfile", "-", "app:app"]
30 changes: 11 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Unified server for the DDOM Lab's custom ELN (eLabFTW) tooling. Consolidates the
former `eln_web_backend`, `eln_packages_backend`, and `eln_packages_common`
repositories into one Flask application: the web interface, the eLab API
wrapper, and the automations (autofill, label generation, RDKit images,
wrapper, and the automations (label generation, RDKit images,
peroxide-former Slack reminders) all live here and run through the server.

To install an eLabFTW instance, follow the eLabFTW installation instructions at https://doc.elabftw.net/docs/category/installation/
Expand All @@ -21,7 +21,6 @@ To install an eLabFTW instance, follow the eLabFTW installation instructions at
- Generating custom labels
- Performing 'batch actions' (above actions taken on multiple items at once)
- ELN Automations
- Autofilling Resource information from CAS (hazards, molecular weight, RDKit image, SMILES, etc.)
- Reminders to check peroxide formers with formatted list of peroxide formers


Expand All @@ -30,8 +29,8 @@ To install an eLabFTW instance, follow the eLabFTW installation instructions at
| Directory | Contents |
|---|---|
| `app.py` | Flask entry point; registers blueprints (gunicorn target `app:app`) |
| `eln_common/` | eLab API wrapper (`Resource_Manager`, `config`, `fill_info`) — formerly `eln_packages_common` |
| `automations/` | autofill, label generation, RDKit images, peroxide checks, Slack bot — formerly `eln_packages_backend` |
| `eln_common/` | eLab API wrapper (`Resource_Manager`, `config`, `pubchem`, `compound_import`, `add_bottle`...) — formerly `eln_packages_common` |
| `automations/` | label generation, RDKit images, peroxide checks, Slack bot — formerly `eln_packages_backend` |
| `web/` | Flask blueprints: `interface` (the UI routes) and `automation_api` (`/api/...`) |
| `static/` | Web UI pages and label templates |
| `scripts/` | One-off maintenance scripts (inventory dumps, compound linking) |
Expand All @@ -42,7 +41,7 @@ To install an eLabFTW instance, follow the eLabFTW installation instructions at

Server settings live in `config.yaml` at the repo root (relative paths resolve
from there): the instance URLs (`eln_url`, `eln_web_url` — required),
`printer_path`, `auto_upload_labels`, and the team-specific status/category
`printer_path`, and the team-specific status/category
IDs (`status_open`, `status_empty`, `chemical_categories`,
`label_date_categories`). The ID settings can also be set from dropdowns at
`/settings_interface`, which reads your team's actual lists from the eLabFTW
Expand Down Expand Up @@ -70,21 +69,15 @@ The Slack bot token is also server-side: set `slack_bot_token` in `secrets.yaml`

## Automation API

- `POST /api/autofill` — PubChem info fill, RDKit image (and, legacy, label upload).
Optional JSON body: `{"id": 123}` for one item, or
`{"start": 0, "end": null, "size": 5, "force": false, "info": true, "label": true, "image": true}`
(defaults shown, autofills the 5 most recently modified Resources with IDs between 0 and `null` (infinity)). Errors are reported to the Slack error channel, like the old `main.py`.
- `POST /api/check_peroxides` — checks the inventory against the class A–D
peroxide-former lists and sends Slack reminders. Returns match counts.

The `/add_resource` UI route also triggers an autofill of the new item in
the background, so info/images appear immediately after creation.

Label printing (`/print`) generates the PDF on the fly from the item's current
data. Attaching a `label.pdf` upload to each resource during autofill is a
legacy feature, disabled by default; set `auto_upload_labels: true` in
`config.yaml` to re-enable it (the `label` flag in the autofill body is
ignored unless it is set).
data.

There is no autofill any more (removed 2026-10): chemical details live on the
compound each resource is linked to, instead of being copied from PubChem into
every resource's fields.

## Running the server

Expand Down Expand Up @@ -141,9 +134,8 @@ The PTH tracker is a separate project (https://github.com/ddomlab/pth_analysis).

The systemd timers (in `client/`) execute the automated actions. They can be automatically installed with the `install.sh` script in `client/`

This installs `eln-autofill.timer` (every 10 minutes — adjust `OnCalendar` to
taste, including excluding backup windows) and `eln-peroxide-check.timer`
(May 1 and Nov 1). The key is stored at `/etc/eln-client/api_key`; the server
This installs `eln-peroxide-check.timer` (May 1 and Nov 1), and removes the old
`eln-autofill.timer` from hosts that still have it. The key is stored at `/etc/eln-client/api_key`; the server
URL is set via `ELN_SERVER_URL` in the `.service` files.
Pass it at install time to skip editing them; with the Docker deploy on the same
host that's the local port:
Expand Down
145 changes: 0 additions & 145 deletions automations/autofill.py

This file was deleted.

4 changes: 2 additions & 2 deletions automations/image_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

def generate_image(smiles: str) -> str:
mol = Chem.MolFromSmiles(smiles)
# the basename becomes the uploaded file's real_name, which autofill checks
# for, so it must stay "RDKitImage.png"
# the basename becomes the uploaded file's real_name; existing resources
# carry their structure image as "RDKitImage.png", so keep the name
filename = str(Path(tempfile.gettempdir()) / "RDKitImage.png")
Draw.MolToFile(mol, filename)
return filename
7 changes: 7 additions & 0 deletions automations/labels/generate_label.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ def add_item(self, id: int):
)
)

def pdf(self) -> bytes:
"""The labels added so far as PDF data, without writing a file (so two people
printing at the same time can't get each other's labels)."""
data = self.label_writer.write_labels(self.records, target=None)
self.records = []
return data

# generates pdf for all labels in records
def write_labels(self, target: str | None = None):
self.label_writer.write_labels(self.records, target=target or self.path)
Expand Down
23 changes: 23 additions & 0 deletions automations/routine_checks.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Routine checks on opened bottles, kept as steps on each bottle's ELN page.
#
# A bottle with one of these tags gets the step when it is marked open in the
# Resource Manager app, due `every_months` later. Scanning it with "Tested" ticks
# the step and adds the next one; "Mark Empty" closes it. The tags come from the
# bottle's compound when the bottle is added (see eln_common/bottle_tags.py).
#
# Intervals: common lab-safety practice for peroxide formers (test after opening).
# Change them here; the app reads this file when it starts.

checks:
- tag: "Peroxide former: A"
step: "Test for peroxides (class A)"
every_months: 3
- tag: "Peroxide former: B"
step: "Test for peroxides (class B)"
every_months: 6
- tag: "Peroxide former: C"
step: "Test for peroxides (class C)"
every_months: 6
- tag: "Peroxide former: D"
step: "Test for peroxides (class D)"
every_months: 12
10 changes: 0 additions & 10 deletions client/eln-autofill.service

This file was deleted.

10 changes: 0 additions & 10 deletions client/eln-autofill.timer

This file was deleted.

3 changes: 0 additions & 3 deletions client/eln_timer_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
to be run by the systemd timers in this directory (replacing the old cron jobs),
but can be run by hand:

eln_timer_client.py autofill
eln_timer_client.py check_peroxides

Uses only the Python standard library, so it needs no environment to run.
Expand All @@ -25,8 +24,6 @@
API_KEY_FILE = os.environ.get("ELN_API_KEY_FILE", "/etc/eln-client/api_key")

TASKS = {
# autofill the 5 most recently created items
"autofill": {"size": 5, "info": True, "label": False, "image": True},
"check_peroxides": {},
}

Expand Down
15 changes: 11 additions & 4 deletions client/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,13 @@ KEY_FILE=/etc/eln-client/api_key

install -d /opt/eln-client
install -m 755 "$SCRIPT_DIR/eln_timer_client.py" /opt/eln-client/
install -m 644 "$SCRIPT_DIR"/eln-autofill.{service,timer} /etc/systemd/system/
install -m 644 "$SCRIPT_DIR"/eln-peroxide-check.{service,timer} /etc/systemd/system/

# optional: point the timers at a server other than the default in the .service files,
# e.g. sudo ELN_SERVER_URL=http://127.0.0.1:5001 ./install.sh (the Docker deploy's local port)
if [[ -n ${ELN_SERVER_URL:-} ]]; then
sed -i "s|^Environment=ELN_SERVER_URL=.*|Environment=ELN_SERVER_URL=$ELN_SERVER_URL|" \
/etc/systemd/system/eln-autofill.service /etc/systemd/system/eln-peroxide-check.service
/etc/systemd/system/eln-peroxide-check.service
echo "Timers will call $ELN_SERVER_URL"
fi

Expand All @@ -42,12 +41,20 @@ if [[ ! -f $KEY_FILE ]]; then
echo "Wrote $KEY_FILE"
fi

# autofill was removed (compounds now hold the chemical details): take its old
# timer off hosts that still have it, so it stops calling a route that is gone
if [[ -f /etc/systemd/system/eln-autofill.timer ]]; then
systemctl disable --now eln-autofill.timer || true
rm -f /etc/systemd/system/eln-autofill.service /etc/systemd/system/eln-autofill.timer
echo "Removed the old eln-autofill timer"
fi

systemctl daemon-reload
systemctl enable --now eln-autofill.timer eln-peroxide-check.timer
systemctl enable --now eln-peroxide-check.timer

echo
echo "Installed. Check status with:"
echo " systemctl list-timers 'eln-*'"
echo "To point at a non-local server, edit ELN_SERVER_URL in"
echo " /etc/systemd/system/eln-autofill.service and eln-peroxide-check.service,"
echo " /etc/systemd/system/eln-peroxide-check.service,"
echo "then: systemctl daemon-reload"
10 changes: 2 additions & 8 deletions config-ex.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,6 @@ eln_web_url: https://eln.ddomlab.org
# Where generated label PDFs are written before printing/upload
printer_path: /tmp/label.pdf

# Legacy feature: /print used to fetch a label.pdf stored on each resource, so
# autofill uploaded one to every new item. /print now generates labels on the
# fly, making the uploads redundant; set this to true to keep attaching
# label.pdf to resources anyway.
auto_upload_labels: false

## Team-specific IDs ##########################################################
# eLabFTW statuses and resource categories are per-team lists with arbitrary
# IDs, so they must be mapped here for your instance. Two ways to find them:
Expand All @@ -33,13 +27,13 @@ auto_upload_labels: false
status_open: 4
# Status a resource gets when marked as empty
status_empty: 5
# Resource categories that get PubChem info + an RDKit structure image on autofill
# Resource categories that hold chemicals
chemical_categories: [2, 3]
# Resource categories whose printed labels show a "Received" date
label_date_categories: [2, 3, 4]

## Slack integration ##########################################################
# Set to true to have the automations report to Slack (autofill errors,
# Set to true to have the automations report to Slack (errors,
# peroxide reminders). Requires slack_bot_token in secrets.yaml. When false
# (the default), messages are written to the server log instead.
slack_enabled: false
Expand Down
Loading
Loading