From cfd2c717cbfc4bddce3df2678bcde80954d5560f Mon Sep 17 00:00:00 2001
From: geoffreyweal
Date: Fri, 31 Jul 2026 08:19:47 +1200
Subject: [PATCH] Add OOD embedding, custom templates, a script browser and a
Nodes page
Recorded under [Unreleased] in CHANGELOG.md. No version number is assigned
and VERSION stays at 2.0.2, since the release number is the maintainer's
call.
Adds Open OnDemand embedding, a custom template system, a script browser, a
Nodes page, module-aware form widgets and a substantially extended history
page.
Open OnDemand integration
- OOD-integrated (embedded) mode: Open Composer renders inside OOD's own
chrome (live OOD navbar and footer) via a reverse-proxy dashboard
initializer under ood_integration/, with no iframe.
Home page, templates and the script browser
- All Templates page (/all_templates): every template as one flat list,
including applications hidden from the index page.
- Custom templates: save the current form values as a reusable template,
with inline rename/re-describe, delete, and drag-and-drop ordering.
- Load or Create a Script from Disk: a full-page file browser that opens
.sl/.sh/.bash/.sbatch/.batch/.slurm files in the generic scheduler form
with script location and name pre-filled.
- New Script and New Custom Template pickers.
- home_format: small (compact letter-grouped listing), per-category badge
colors, and automatic GPU badges via the tags manifest key.
Application forms
- module_load widget: a dropdown of every available version of an
environment module, rewriting the existing module load line in place so
surrounding hand-written script lines survive.
- dependent_module_select widget: a module list that follows the value of
another (driver) widget.
- remember_last on select widgets.
- Script-to-form parsing: editing the script pane updates the widgets, and
opening a job from the history page reconstructs the whole form.
- Empty-script submission warning.
History page
- sacct is now the source of truth, so jobs submitted outside Open Composer
appear too, with their script fetched live.
- Job Efficiency panel: wall time, CPU, memory, and GPU utilization and
memory for GPU jobs.
- Live job details modal, with disclosure of the exact command run.
- Output/Error columns with an in-page file viewer.
- Cancel All Jobs, Delete All History and Refresh, plus per-job
cancellation with a progress bar.
- Per-column sorting and Cancelled/Unknown status filters.
Nodes page
- A new page listing the cluster's compute nodes with state, CPU, memory
and generic resources, built from sinfo, with one dynamically discovered
column per GRES type.
Job submission
- Automatic SSH key provisioning on Submit (opt-in, off by default).
- Configurable modules_list_url for the site module catalog.
Configuration and theming
- The accent palette, GPU memory capacities and navbar/footer branding are
now configuration rather than hard-coded values, so a site can be
restyled without editing a view.
- conf.yml.erb.sample reorganized into ten numbered sections, matching the
installation documentation.
Also extends the form.yml smoke tests under misc/tests/ to cover the new
widgets, falling back to macOS's bundled jsc when node is unavailable.
Co-Authored-By: Claude Opus 5 (1M context)
---
.gitignore | 12 +-
CHANGELOG.md | 38 +
README.md | 123 +-
conf.yml.erb.sample | 286 +++-
docs/application.html | 431 +++++-
docs/application_ja.html | 312 +++-
docs/img/all_templates.png | Bin 0 -> 363671 bytes
docs/img/application_orca.png | Bin 0 -> 554971 bytes
docs/img/application_slurm.png | Bin 0 -> 444779 bytes
docs/img/bundle.png | Bin 129797 -> 148213 bytes
docs/img/clone.png | Bin 85856 -> 128317 bytes
docs/img/dependent_module_select.png | Bin 0 -> 27692 bytes
docs/img/hide-script-header.png | Bin 249931 -> 166736 bytes
docs/img/history.png | Bin 0 -> 490769 bytes
docs/img/history_page.png | Bin 179817 -> 558005 bytes
docs/img/history_page2.png | Bin 152182 -> 371290 bytes
docs/img/history_page3.png | Bin 169828 -> 455316 bytes
docs/img/history_page5.png | Bin 108498 -> 104600 bytes
docs/img/history_table3.png | Bin 59991 -> 92415 bytes
docs/img/home.png | Bin 0 -> 357473 bytes
docs/img/load_script.png | Bin 0 -> 169263 bytes
docs/img/navbar.png | Bin 35485 -> 106125 bytes
docs/img/new_repo.png | Bin 167165 -> 237569 bytes
docs/img/newapp.png | Bin 21215 -> 28915 bytes
docs/img/no_entry_warning_message.png | Bin 0 -> 168443 bytes
docs/img/nodes.png | Bin 0 -> 132563 bytes
docs/img/overwrite_warning.png | Bin 50949 -> 0 bytes
docs/img/path2.png | Bin 74012 -> 79851 bytes
docs/img/submit2.png | Bin 192620 -> 100693 bytes
docs/install.html | 561 +++++--
docs/install_ja.html | 550 ++++++-
docs/manual.html | 343 +++-
docs/manual_ja.html | 335 +++-
generic_apps/Fujitsu_TCS/form.yml | 12 +
generic_apps/Fujitsu_TCS/logo.png | Bin 0 -> 3512 bytes
generic_apps/Fujitsu_TCS/manifest.yml | 6 +
generic_apps/Grid_Engine/form.yml | 12 +
generic_apps/Grid_Engine/logo.png | Bin 0 -> 34931 bytes
generic_apps/Grid_Engine/manifest.yml | 6 +
generic_apps/PBS/form.yml | 12 +
generic_apps/PBS/icon.png | Bin 0 -> 4131 bytes
generic_apps/PBS/manifest.yml | 6 +
generic_apps/Slurm/README_icon.md | 1 +
generic_apps/Slurm/Slurm_logo.svg | 6 +
generic_apps/Slurm/form.yml | 12 +
generic_apps/Slurm/manifest.yml | 6 +
lib/form.rb | 302 +++-
lib/history.rb | 1374 ++++++++---------
lib/index.rb | 171 +-
lib/scheduler.rb | 61 +
lib/schedulers/fujitsu_tcs.rb | 85 +
lib/schedulers/miyabi.rb | 54 +-
lib/schedulers/pbspro.rb | 124 ++
lib/schedulers/sge.rb | 154 ++
lib/schedulers/slurm.rb | 601 ++++++-
misc/check_erb.rb | 81 +
misc/tests/README.md | 7 +-
misc/tests/extract_samples.rb | 67 +-
misc/tests/run_tests.rb | 28 +-
ood_integration/ADMIN_CHECKLIST.md | 114 ++
ood_integration/dashboard.env.example | 45 +
.../initializers/opencomposer_embed.rb | 370 +++++
.../views/apps/opencomposer_embed.html.erb | 8 +
public/OpenOnDemand_powered_by_invert.svg | 37 +
public/RIKEN_powered_by.svg | 59 +
public/Riken_Logo.gif | Bin 0 -> 9636 bytes
public/app_default.svg | 29 +
public/favicon.ico | Bin 0 -> 32038 bytes
public/form.js | 505 +++++-
public/history.js | 613 +++++++-
run.rb | 1310 ++++++++++++++--
views/all_templates.erb | 10 +
views/form.erb | 360 +++--
views/history.erb | 185 ++-
views/index.erb | 254 ++-
views/layout.erb | 323 +++-
views/load_script.erb | 367 +++++
views/new_script.erb | 24 +
views/new_template.erb | 107 ++
views/nodes.erb | 454 ++++++
80 files changed, 9732 insertions(+), 1621 deletions(-)
create mode 100644 docs/img/all_templates.png
create mode 100644 docs/img/application_orca.png
create mode 100644 docs/img/application_slurm.png
create mode 100644 docs/img/dependent_module_select.png
create mode 100644 docs/img/history.png
create mode 100644 docs/img/home.png
create mode 100644 docs/img/load_script.png
create mode 100644 docs/img/no_entry_warning_message.png
create mode 100644 docs/img/nodes.png
delete mode 100644 docs/img/overwrite_warning.png
create mode 100644 generic_apps/Fujitsu_TCS/form.yml
create mode 100644 generic_apps/Fujitsu_TCS/logo.png
create mode 100644 generic_apps/Fujitsu_TCS/manifest.yml
create mode 100644 generic_apps/Grid_Engine/form.yml
create mode 100644 generic_apps/Grid_Engine/logo.png
create mode 100644 generic_apps/Grid_Engine/manifest.yml
create mode 100644 generic_apps/PBS/form.yml
create mode 100644 generic_apps/PBS/icon.png
create mode 100644 generic_apps/PBS/manifest.yml
create mode 100644 generic_apps/Slurm/README_icon.md
create mode 100644 generic_apps/Slurm/Slurm_logo.svg
create mode 100644 generic_apps/Slurm/form.yml
create mode 100644 generic_apps/Slurm/manifest.yml
create mode 100644 misc/check_erb.rb
create mode 100644 ood_integration/ADMIN_CHECKLIST.md
create mode 100644 ood_integration/dashboard.env.example
create mode 100644 ood_integration/initializers/opencomposer_embed.rb
create mode 100644 ood_integration/views/apps/opencomposer_embed.html.erb
create mode 100644 public/OpenOnDemand_powered_by_invert.svg
create mode 100644 public/RIKEN_powered_by.svg
create mode 100644 public/Riken_Logo.gif
create mode 100644 public/app_default.svg
create mode 100644 public/favicon.ico
create mode 100644 views/all_templates.erb
create mode 100644 views/load_script.erb
create mode 100644 views/new_script.erb
create mode 100644 views/new_template.erb
create mode 100644 views/nodes.erb
diff --git a/.gitignore b/.gitignore
index cc53b3f6..55a81587 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,12 +2,18 @@
.DS_Store
# bundle
-Gemfile
-Gemfile.lock
+# Anchored to the repo root so the misc/Gemfile template stays tracked — the
+# install docs tell administrators to copy it here.
+/Gemfile
+/Gemfile.lock
vendor/
.bundle
-TODO_ja.md
+
+# Local site configuration, copied from conf.yml.erb.sample
conf.yml.erb
+# Passenger restart trigger
+tmp/
+
# Regenerated from docs by misc/tests/run_tests.rb
misc/tests/samples/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 94c0e197..2f6af533 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,43 @@
# Changelog
+## [Unreleased]
+### Add
+- Add an OOD-integrated (embedded) mode that renders Open Composer inside Open OnDemand's own navbar and footer, via a reverse-proxy dashboard initializer under `ood_integration/` and without an iframe.
+- Add an All Templates page (`/all_templates`) listing every template as one flat list, including applications hidden from the index page.
+- Add custom templates: save the current form values as a reusable template, then rename, re-describe, delete or reorder them from the index page.
+- Add a "Load or Create a Script from Disk" file browser that opens an existing batch script in the generic scheduler form with its location and name pre-filled.
+- Add New Script and New Custom Template pickers for choosing which application to start from.
+- Add a Nodes page listing the cluster's compute nodes with their state, CPU, memory and generic resources, with one dynamically discovered column per GRES type.
+- Add a `module_load` widget: a dropdown of every available version of an environment module, which rewrites the existing `module load` line in place so surrounding script lines are preserved.
+- Add a `dependent_module_select` widget whose module list follows the value of another widget.
+- Add `remember_last` to `select` widgets, restoring the user's previous choice from browser storage.
+- Add script-to-form parsing, so editing the script pane, or opening a job from the history page, reconstructs the form from the script's scheduler directives.
+- Add an empty-script submission warning (`empty_script_warning`).
+- Add a Job Efficiency panel to the history page (`history_efficiency`), reporting wall time, CPU, memory and, for GPU jobs, GPU utilization and GPU memory.
+- Add output and error columns to the history page, with an in-page file viewer.
+- Add "Cancel All Jobs", "Delete All History" and "Refresh" actions to the history page, plus per-job cancellation with progress reporting.
+- Add optional SSH key provisioning on submit (`ensure_ssh_key`), disabled by default.
+- Add a `modules_list_url` setting for the module catalog behind the module widgets.
+- Add `home_format`, `category_badge_colors`, `accent_color`, `body_text_color`, `gpu_memory` and `favicon` settings.
+- Add `hidden`, `documentation` and `tags` manifest keys, and allow `category` to be given as a list.
+
+### Changed
+- Use `sacct` as the source of truth on the history page, so jobs submitted outside Open Composer also appear, with their script fetched live.
+- Move the generic per-scheduler script templates to `generic_apps_dir`, reachable at `/_generic/` and used as the fallback when an application's `form.yml` is missing.
+- Replace the hard-coded accent palette and GPU memory capacities with settings, so a site can be restyled without editing a view.
+- Reorganize `conf.yml.erb.sample` into ten numbered sections, matching section 2 of `docs/install.html`.
+- Pass `scheduler_env` to every scheduler command, not only to submission, cancellation and status queries.
+- Extend the `form.yml` smoke tests to cover the new widgets, and fall back to macOS's bundled `jsc` when `node` is unavailable.
+
+### Fixed
+- Anchor the `enable--
name: Gaussian
-category: Quantum Chemistry
+category: Chemistry
+tags:
+ - gpu
icon: icon.png
-description: |
- [Gaussian](https://gaussian.com) is a general purpose computational chemistry software package.
+description: Gaussian is a general purpose computational chemistry software package.
+hidden: true
+homepage: https://gaussian.com/
+documentation: https://docs.example.org/applications/gaussian/
related_apps:
OVITO:
icon: ovito.png
@@ -71,16 +82,24 @@
1.1. manifest.yml
ImageJ:
name: Application name (If this key is omitted, the directory name is used instead)
-
category:Category name
-
icon: Path to image file for icon. URL, Bootstrap icon, or Font Awesome icon is also possible. For Bootstrap icons, write icon: bi-airplane-fill. For Font Awesome icons, write icon: fa-solid fa-gear.
+
category: Category name. Applications are grouped under this name on the home page and in the categorized list on the New Custom Template page. An application with no category is grouped under the heading Application, and a category of Others is sorted after all the others.
+ A list of categories is also accepted, but it only has an effect on the listings that treat the entries individually: the All Templates and New Script lists draw one badge per entry (colored from category_badge_colors in conf.yml.erb, gray when the category is not listed there), the navbar search box matches and badges each entry, and the application counts as featured if any entry matches new_script_featured_category. Sorting uses the first entry as the primary category.
+ A list places the application under each of its categories on the home page and on the New Custom Template page, so it appears once per category. Category headings follow the same order as for single-valued categories.
+
tags: List of free-form tags. The only tag currently acted on is gpu (matched case-insensitively): it adds a black GPU badge to the application's row in the All Templates and New Script lists and in the navbar search results. Other tags are read but have no effect. Applications whose name matches a GPU-domain module in the site module list are badged automatically and need no tag.
+
icon: Icon for the application. The value is resolved in this order: an http:// or https:// URL is used as it is; otherwise the value is treated as a path relative to the application's own directory and used if that file exists; otherwise, if it begins with bi- or fa-, it is rendered as an icon font class — the whole value becomes the class attribute, so icon: bi-airplane-fill (Bootstrap icon) and icon: fa-solid fa-gear (Font Awesome icon) both work; otherwise the built-in app_default.svg placeholder is used silently, with no error message. bi- and fa- are the only prefixes recognized, so the fas fa-gear and fas://name spellings used in Open OnDemand's own manifests fall through to the placeholder here. Keep the file in the application directory itself rather than a sub-directory, and remember that a placeholder tile usually means a mistyped file name. If icon is omitted, app_default.svg is used.
description: Description of the application
+
hidden: If set to true, the application is left out of the home page listing and out of the categorized list on the New Custom Template page. It is still listed on the All Templates and New Script pages, still appears in the New Custom Template page's search results, is still offered by the navbar search box, and is still reachable by a direct link or by inheritance. Use it to keep the home page focused while a large catalog stays searchable — not to restrict access. The default is false.
+
homepage: URL for the software's homepage. Displayed as a link on the application page.
+
documentation: URL for the software's documentation. Displayed as a link on the application page.
related_apps: Specify an application registered in Open OnDemand. The specified application is displayed on the history page. As with icon:, you can specify icon images, etc. If no image is specified, the image registered in Open OnDemand is used.
1.2. form.yml
The form.yml is composed of five main sections: form, script, header, check and submit.
- The form and script sections are required fields, but header, check and submit sections can be omitted.
+ Only form is required: if the parsed document has no top-level form: key, the page fails with In ./<file>, "form:" must be defined.
+ The body of form may be empty, however — a bare form: with no widgets below it is legal, and renders just the header fields and the script area (the generic applications under generic_apps_dir are written exactly this way).
+ The script, header, check and submit sections can all be omitted. With no script section nothing is generated for the script area, which simply starts empty for the user to type into or for a loaded script to fill.
The following figure shows the scope of form, script, and header sections.
@@ -106,6 +125,8 @@
2. Description of widget
radio widget: Displays radio buttons.
checkbox widget: Displays checkboxes.
path widget: Displays an input field for path of a file or directory on the Open OnDemand server.
+
module_load widget: Displays a dropdown listing the installed versions of one named environment module.
+
dependent_module_select widget: Displays a dropdown whose options are loaded dynamically based on the value of a driver widget.
2.1. number widget
@@ -115,7 +136,7 @@
2.1. number widget
The label is the displayed name,
value is the default value,
min and max set the range, and step determines the increment.
- The required key specifies whether the input is mandatory, and help provides a tooltip below the input field.
+ The required key specifies whether the input is mandatory, and help is static help text rendered in small print underneath the input field. It is always visible; it is not a tooltip.
The script section specifies how the input value will appear in the job script.
The #{nodes} in the script section will be replaced with the input value.
@@ -263,6 +284,28 @@
2.4. select widget
module load #{package_1}
mpiexec #{package_2}
+
+ Setting remember_last: true on a select widget causes the browser to remember the last value the user selected and restore it automatically the next time they visit the form.
+ This is useful for fields like a project or partition selector that users rarely change between job submissions.
+
+
form:
+ partition:
+ widget: select
+ label: Partition
+ remember_last: true
+ options:
+ - [ Small Queue, small ]
+ - [ Large Queue, large ]
+
+script: |
+ #SBATCH --partition=#{partition}
+
+ The remembered value is kept in the browser's local storage under a key made from the widget's name, and the string stored is the option's displayed text (the first element of the option array).
+ Because the key is the widget name alone, two applications that use the same widget name share a single remembered value — which is what lets a common account or partition selector follow the user across applications, but is surprising if it was not intended.
+ If an option's displayed text is later changed, the stored string no longer matches any option and nothing is restored.
+ Restoring is skipped entirely when a form is opened from a saved template or from the History page, so the values those pages pre-fill always win.
+ Only plain select widgets honor remember_last.
+
2.5. multi_select widget
@@ -427,7 +470,123 @@
2.8. path widget
-
2.9. Functions
+
2.9. module_load widget
+
+ Displays a dropdown listing the installed versions of one named environment module.
+ The entries are not written in form.yml: the widget renders a single dropdown showing Loading…, and the browser fills it in after the page has loaded by asking the application's /_module_avail route for the versions of the module.
+
+
+ The module key is required and holds the module name to look up.
+ The name is matched case-insensitively against the module list, and the entries offered are the full Name/version strings built from the spelling used in that list.
+ The entries are sorted in descending string order — a plain text comparison, not a version-aware one, so Python/3.9.5 is listed above Python/3.11.6 — and the module's default version, if the list gives one and that version appears among the module's own versions, is then moved to the top.
+
+
+ Besides module, the keys honored are label, value, help and indent.
+ value pre-selects the entry that matches it exactly, written in full Name/version form; if it matches nothing, the first entry is selected.
+ An options key is ignored — the entries always come from the module list.
+ required is accepted, but its only effect is to append an asterisk to the label; it is not enforced when the form is submitted.
+
+
form:
+ gromacs_module:
+ widget: module_load
+ label: GROMACS module
+ module: GROMACS
+ help: Only versions installed on the cluster are listed.
+
+script: |
+ module load #{gromacs_module}
+ gmx mdrun -s topol.tpr
+
+ If the module name is not in the list — or if the module key is missing — the dropdown holds a single empty entry labeled No modules found; if the request itself fails, it shows Error loading modules.
+ In both cases the widget's value is empty, so a script line that refers to it is left out of the job script, unless the reference is written #{:gromacs_module} (see section 10).
+
+
+ When the widget is referenced in the script and/or submit section, changing the selection rewrites the existing module load line in place instead of regenerating the script.
+ Every line that consists of module load on its own, or of module load followed by this widget's own module name with or without a version, is replaced by module load plus the new selection, keeping the line's original indentation; nothing else in the text area is touched, including lines the user has typed.
+ If no such line is present yet, the widget falls back to the normal script update, which inserts the line from the template.
+
+
+ The module list itself is a JSON file cached under data_dir as .module-list-cache.json and refreshed at most once every 24 hours; the same list is used for every cluster. See Network and external dependencies in the installation document for where it is downloaded from.
+ Because the widget has no options of its own it cannot drive the actions described in section 3, but it can be their target (for example hide-gromacs_module or disable-gromacs_module).
+
+
+
2.10. dependent_module_select widget
+
+ Displays a dropdown whose options are loaded dynamically according to the currently-selected value of a driver widget.
+ Use it when one select widget (e.g. an application version) determines which set of modules should be offered in a second select.
+ As with the module_load widget (section 2.9), the entries come from /_module_avail after the page has loaded, and the dropdown shows Loading… until they arrive.
+
+
+ The driver key names the field whose value is watched. It must name a widget that is rendered on the same page; if it does not, the dropdown stays on Loading….
+ The string tested is the driver option's value — the second element of its options array — not the text displayed in the dropdown. (As elsewhere, an option with no second element falls back to its first element.)
+
+
+ The options key is a list of matcher entries. Each entry names a module and, optionally, one of two matchers:
+
+
+
prefix: matches when the driver's value starts with the given string.
+
contains: matches when the driver's value contains the given string anywhere.
+
+
+ The entries are tried in the order they are written and the first match wins, so list more specific matchers before more general ones. Within a single entry, prefix: is tested before contains:.
+ If no entry matches, the module of the last entry in the list is used as a fallback, which makes it natural to put the general case last (an entry with a module and no matcher can never match on its own, so as the last entry it acts purely as a default).
+ Whenever the resolved module name changes the version list is re-fetched and the first entry of the new list is selected; if the resolved name does not change, the dropdown is left as it is.
+
+ In this example, selecting AlphaFold/2.3.2 in the driver loads the available versions of AlphaFold2DB, while selecting AlphaFold/3.0.0, AlphaFold/3.0.1, or AlphaFold/3.0.2 loads the versions of AlphaFold3DB.
+ Note that the prefixes are tested against the option values (AlphaFold/2.3.2 and so on), not against the labels shown in the dropdown.
+
+
+
+ The example below uses contains: instead, because the distinguishing part of the driver's value is not at the start of the string.
+ Any CUDA version whose value contains -gpu selects cuDNN-GPU; anything else falls through to the last entry, which has no matcher and therefore acts as the default.
+
The zeropadding(key, digit) function pads a number with leading zeros.
If the number of digits is less than the value specified in the second argument, digit, the missing digits are filled with '0'.
@@ -489,8 +648,16 @@
3. Dynamic Form Widget
3.1. Widget setting
- Sets the min, max, step, label, value, required, and help.
- Specifies set-(min|max|step|label|value|required|help)-(KEY)[_(num|1st element in options)]:(VALUE) from the third element and onward of each options array.
+ Sets the min, max, step, label, value, required, or help of another widget.
+ Each action is written as a YAML mapping entry from the third element onward of an options array, in one of the following shapes:
+
+
+
set-(min|max|step|label|value|required|help)-(KEY): (VALUE) — targets the whole widget KEY.
+
set-(min|max|step|label|value|required|help)-(KEY)_(num): (VALUE) — an underscore followed by a 1-based field number targets one input field of a multi-field widget, e.g. set-max-time_1: 24. This shape is recognized only for number, text and email widgets.
+
set-required-(KEY)-(1st element in options): (true|false) — a hyphen followed by an option's first element targets one option of a checkbox widget. This shape is recognized only for set-required- on checkbox widgets; no other set- attribute accepts an option name.
+
+
+ The value is type-checked: min, max and step must be numbers, and required must be true or false.
In the following example, if you select Medium for node_type, the label and maximum value for cores will be Number of Cores (1-8) and 8.
@@ -546,8 +713,9 @@
3.1. Widget setting
#SBATCH --time=#{time_1}:#{time_2}:00
- For select, radio, and checkbox widgets,
- use 1st element in options to specify the target option.
+ For select, radio, and checkbox widgets the option is not named in the action itself:
+ use set-value-(KEY) and give the option's first element as the value.
+ The option whose displayed text is exactly equal to that value is then selected or ticked; if no option matches, the widget is left unchanged.
In the following example, when you select GPU for node_type, Enable GPU for enable_gpu is checked.
form:
@@ -566,16 +734,32 @@
3.1. Widget setting
script: |
#SBATCH --partition=#{node_type}
+
+ set-min-, set-max-, set-step-, set-label-, set-help- and set-required- are re-evaluated whenever any driving widget changes, so when the option that set them is no longer selected the target returns to the value declared in its own form entry.
+ set-value- behaves differently: it is applied when its option is selected and is never undone, so the target keeps the last value written to it.
+ In the first example above, choosing Medium and then Small again restores max to 4, but a value written by set-value- would stay.
+ Give every option its own set-value- action when you need the value to change back.
+ On a checkbox or radio target, set-value- can only tick the option whose text matches; it never unticks one.
+
3.2. Widget disabling
Disables or enables the widget.
- Specifies [disable|enable]-(KEY)[-(1st element in options)][_num] for the third element and onward of each options array.
+ Each action is written as a plain string (not a mapping entry) from the third element onward of an options array, in one of the following shapes:
+
+
+
[disable|enable]-(KEY) — the whole widget KEY, whatever its widget type.
+
[disable|enable]-(KEY)-(num) — a hyphen followed by a 1-based field number targets one input field of a multi-field number, text or email widget. Note that the separator here is a hyphen, unlike the underscore used by the set- actions in section 3.1.
+
[disable|enable]-(KEY)-(1st element in options) — a hyphen followed by an option's first element targets one option of a select, multi_select, radio or checkbox widget.
+
+
+ Unrecognized action names are ignored silently, with no warning.
+ The widget name and the option text are placed directly into a regular expression when the action is matched, so avoid regular-expression metacharacters (for example ., *, +, ?, |, (, ), [) in the first element of any option you intend to target this way.
In the following example, when Fugaku is selected for cluster,
the GPU option for node_type and the cuda_ver widget will be disabled.
- If a key is disabled, its line in the script section will also be deleted.
+ If a key is disabled, its line in the script section will also be deleted, unless the reference is written with a leading colon (for example #{:cuda_ver}), in which case the line is still output with the value replaced by an empty string (see section 10).
form:
cluster:
@@ -665,34 +849,45 @@
4. Available combinations
Widget
label, value, help, required, indent
options
size
separator
direction
min, max, step
-
show_files, favorites
+
show_files, favorites
remember_last
-
number
OK
OK
OK
+
number
OK
OK
OK
-
text, email
OK
OK
+
text, email
OK
OK
-
select
OK
OK
+
select
OK
OK
OK
-
multi_select
OK
OK
OK
+
multi_select
OK
OK
OK
radio
OK
OK
-
OK
+
OK
+
+
+
checkbox
OK
OK
OK
OK
-
checkbox
OK
OK
OK
OK
+
path
OK
OK
-
path
OK
OK
+
module_load
label, value, help, indent
module (see 2.9)
+
+
+
dependent_module_select
label, help, indent
driver, prefix/contains/module (see 2.10)
+
+ The header section renders fewer widget types than the form section: only number, text, email, select, multi_select, radio, checkbox and path are drawn there.
+ A module_load or dependent_module_select widget placed in the header section is silently skipped — no dropdown is drawn and no module list is fetched.
+ Define those two widgets in the form section only.
+
5. Script section
5.1. Change label
@@ -771,28 +966,49 @@
5.4. Special variables
#{OC_APP_NAME}: Application name defined in name of manifest.yml
#{OC_DIR_NAME}: The name of the directory where manifest.yml and related files are stored (the last path element of each application’s URL).
#{OC_SCRIPT_LOCATION}: Script Location defined in the header section
-
#{OC_CLUSTER_NAME}: Cluster name defined in the header section (This is only available when cluster is defined in ./conf.yml.erb)
+
#{OC_CLUSTER_NAME}: Cluster name defined in the header section (This is only available when more than one cluster is defined under clusters: in ./conf.yml.erb, because lib/header.yml.erb only draws the _cluster_name widget in that case)
#{OC_SCRIPT_NAME}: Script Name defined in the header section
#{OC_JOB_NAME}: Job Name defined in the header section
-
5.5. Disable warning
+
5.5. Loading a script back into the form
- If you try to modify a widget that affects the script or submit section after manually editing those sections, a warning like the one below will appear.
- If you click "Discard and continue", the changes you made to the section will be discarded and a new script will be generated.
+ The script area works in both directions. As well as the widgets writing the script, Open Composer reads the script back and updates the widgets from it: when a saved script is loaded (from the History page, or from disk through the file browser) the form is reconstructed from the script's contents, and while the user edits the script by hand the widgets follow along, about half a second after the last keystroke.
+ This is done by matching each line of the script against a pattern derived from the corresponding line of the script template, so whether a field can be recovered from a script depends on how its template line is written.
-
- To disable this warning, set overwrite_warning: false in the configuration of the section where you want to disable the warning.
+ The rules to follow when writing template lines are:
+
+
+
Give every line a literal prefix. A pattern is registered for a line only if the text before its first #{...} is not empty, so a line that begins with an interpolation (e.g. #{command} -n 4) is never matched back to its widget.
+
Keep those prefixes distinct. Each pattern is applied to the first line of the script that it matches, so if two template lines start with the same text (e.g. two different fields both writing #SBATCH --ntasks=) both fields will read the same script line.
+
Do not use the template functions on a line you want parsed back. A line whose interpolations use calc(), zeropadding(), dirname() or basename() cannot be reversed: its pattern is registered for placement purposes only, with no fields attached to it. Plain #{key} interpolations are reversed, and a line may contain any number of them — each becomes a capture group whose text is written back to that widget.
+
There is exactly one exception to the previous rule. A line that begins with #SBATCH --time= and does use those functions is handled by a dedicated Slurm-time parser. It accepts every Slurm --time format (D-HH:MM:SS, D-HH:MM, D-HH, HH:MM:SS, MM:SS and MM) and fills the line's fields, in the order they appear in the template, with days, hours, minutes and seconds. Any field beyond the fourth is set to 0, so the time fields must be written in exactly that order.
+
+
+ The captured text is applied according to the target widget:
+
+
+
number, text, email: written straight into the input field.
+
select, module_load, dependent_module_select: the option whose value (the second element of its options array) equals the captured text is selected. Nothing happens if no option matches, or while the widget is disabled.
+
radio: the button whose value equals the captured text is selected.
+
checkbox: the captured text is split on the widget's separator (a comma if none is set), and every option whose value appears in the result is ticked.
+
multi_select and path: not applied. The reverse parser has no case for these two widget types, so text captured for them is discarded and the field keeps whatever it already held — the cd #{working_dir} line of section 2.8, for instance, is not read back. A path field is still pre-filled on the server in the History case described below, when its template line starts with #SBATCH.
+
+
+ When a script is loaded, a matched line may also open a hidden section: if the field it belongs to is disabled, the enable- actions that control that field are triggered so the section appears with the loaded value already in it.
+ For the number, text, email and Slurm-time cases this happens only when the line is unique to that field — if the same line also matches a pattern belonging to another widget, the value is still applied but no section is opened.
+ For example, if two fields both emit #SBATCH --ntasks=, an advanced section is opened only by a line that belongs to it alone, such as its own #SBATCH --cpus-per-task= line.
+ While the user is typing in the script area, no section is ever opened and disabled fields are left untouched.
+
+
+ When a job's script is not held in the history database — because history_store_script is false, or because Open Composer did not submit the job — the script is fetched from the scheduler with sacct -B and the widgets' value keys are additionally pre-filled on the server, before the page is rendered. That path is narrower than the one described above: a template line is considered only if it starts with #SBATCH, and it is matched only against script lines that start with #SBATCH. On an ordinary History load the stored script is used as it is and all widget reconstruction happens in the browser.
+ Every other line of the script is ignored, which means that on a non-Slurm scheduler this server-side step does nothing.
+ zeropadding(field, N) is understood there; the other functions are not, and their captures are discarded.
+ The script template is also read as plain text, so an application that writes its script with label: and content: keys (see section 5.1) gets no server-side pre-fill.
+ In all of these cases the parsing done in the browser still runs, so the form still ends up matching the loaded script.
-
script:
- overwrite_warning: false
- content: |
-
-
submit:
- overwrite_warning: false
- content: |
6. Check section
Variables defined in the form section can be validated in the check section using the Ruby language and the function oc_assert(condition, message).
@@ -825,7 +1041,7 @@
6. Check section
@OC_APP_NAME: Application name defined in name of manifest.yml
@OC_DIR_NAME: The name of the directory where manifest.yml and related files are stored (the last path element of each application’s URL).
@OC_SCRIPT_LOCATION: Script Location defined in the header section
-
@OC_CLUSTER_NAME: Cluster name defined in the header section (This is only available when cluster is defined in ./conf.yml.erb)
+
@OC_CLUSTER_NAME: Cluster name defined in the header section (This is only available when more than one cluster is defined under clusters: in ./conf.yml.erb)
@OC_SCRIPT_NAME: Script Name defined in the header section
@OC_JOB_NAME: Job Name defined in the header section
@@ -879,11 +1095,31 @@
7. Submit section
- If you also set action: save in the script section, its contents will be saved before executing the commands in the submit section.
+ The job script is always written to Script Location/Script Name before the commands in the submit section run — this is what makes the mv #{OC_SCRIPT_NAME} … in the example above work. Setting action: save in the script section only suppresses the job submission that would otherwise follow the submit section.
The button at the bottom of the "Confirm" screen will change from "Submit" to "Save," as shown below.
+
7.1. Empty script warning
+
+ When the user submits the form — with the Submit button, or with the Submit button inside the confirmation dialog of an action: confirm application, since clicking Confirm itself only opens that dialog — Open Composer automatically checks whether the batch script contains any real commands.
+ A line is considered boilerplate and ignored if it is blank, starts with # (comments, #SBATCH directives, or the shebang line), or starts with module.
+ If no real commands are found, a browser confirmation dialog is shown before the job is submitted:
+
+
No commands have been added to the batch script.
+
+Are you sure you want to submit?
+
+ Clicking Cancel returns the user to the form without submitting.
+ Clicking OK proceeds with submission as normal.
+ This check is automatic and enabled by default. It does not apply to an application whose script section sets action: save, because that writes a file instead of submitting a job.
+
+
+ To disable the warning, set empty_script_warning: false in conf.yml.erb:
+
+
empty_script_warning: false
+
+
8. Header section
You can define the header section.
@@ -891,7 +1127,7 @@
8. Header section
The example below defines a new widget, script_content, that hides the job script.
- The _script_location and _script are widgets defined in lib/header.yml.erb (if a cluster is defined in ./conf.yml.erb, you must also define _cluster_name).
+ The _script_location and _script are widgets defined in lib/header.yml.erb (if more than one cluster is defined under clusters: in ./conf.yml.erb, you must also define _cluster_name).
header:
@@ -936,15 +1172,124 @@
8. Header section
- [large]
<% end %>
-
9. Supplement
+
9. ERB and Dynamic Configuration
+
+ When a form file is named form.yml.erb, Open Composer processes it as Embedded Ruby (ERB) before parsing the YAML.
+ This lets you generate form content dynamically at page load time — for example, running a shell command to build the list of options for a dropdown, or reading values from the OpenComposer configuration.
+
+
+
9.1. Accessing @conf
+
+ The variable @conf is available inside form.yml.erb and contains the full OpenComposer configuration hash loaded from conf.yml.erb.
+ It is set only while a page is being rendered. The same form.yml.erb is re-evaluated when the form is submitted, and on that request @conf is nil — so always use safe navigation (@conf&.fetch('login_node', nil)) or wrap the lookup in begin/rescue, otherwise submitting the form fails with a NoMethodError page instead of submitting the job.
+ Use it to read site-specific settings such as the login node hostname.
+
+ You can run a shell command inside ERB to populate a select widget's options list at page load time.
+ If the OOD web server does not have direct access to the job scheduler daemon, prefix the command with
+ ssh to the login node. Use @login_node, which Open Composer has already resolved for the cluster the user selected (@cluster_name is available too); @conf['login_node'] is the raw setting and is a Hash keyed by cluster name on a multi-cluster site.
+
+
+ The example below populates a "Project" dropdown from the user's Slurm associations by running sacctmgr via SSH.
+ It always renders a select widget — if no accounts are found the options list is empty rather than crashing.
+
Wrap the shell command in a begin/rescue block so a failed command produces an empty list rather than a form error.
+
Use options: [] (not omitting the key) when the list is empty — a missing options key parses as nil in YAML and causes a runtime error.
+
BatchMode=yes prevents SSH from hanging waiting for a password; ConnectTimeout=10 prevents the page from stalling if the login node is unreachable.
+
The Passenger web process runs as the logged-in OOD user, so their SSH key to the login node must already be set up.
+
+
+
9.3. Inheriting a base form with read_yaml
+
+ Use the read_yaml(path) helper to load another form file (including .erb files) and then modify its content before emitting YAML.
+ This is how the applications in the apps/ directory inherit their common Slurm fields from one of two base forms: SlurmBasic for CPU jobs, or SlurmGPU — which adds the partition-aware GPU type and GPU-count selectors and the corresponding --gpus-per-node script lines — for GPU jobs. Point base_path at whichever one matches your application.
+
+
+ Inside a form.yml.erb, the local variable yml_path holds the path of the form file being processed, always written without the .erb suffix (for example ./apps/MyApp/form.yml), so File.dirname(yml_path) is the application's own directory.
+
+
<%
+base_path = File.join(File.dirname(yml_path), '../SlurmBasic/form.yml')
+base = read_yaml(base_path)
+
+# Insert an application-specific field before the time widget
+new_form = {}
+base['form'].each do |k, v|
+ if k == 'time_days_hours_minutes'
+ new_form['myapp_module'] = {
+ 'widget' => 'module_load',
+ 'module' => 'myapp',
+ 'label' => 'MyApp Module'
+ }
+ end
+ new_form[k] = v
+end
+
+base['form'] = new_form
+base['script'] = base['script'].rstrip + "\n\n" + <<~'APP_SCRIPT'
+ module load #{myapp_module}
+APP_SCRIPT
+%>
+<%= base.to_yaml -%>
+
+ Note the single-quoted heredoc (<<~'APP_SCRIPT'). Any script text containing #{...} must be written in a single-quoted heredoc or a single-quoted Ruby string: #{...} is Open Composer's form interpolation syntax, but inside a <% %> block a double-quoted string would make Ruby try to interpolate the widget name first, and the page would fail with NameError: undefined local variable or method 'myapp_module'.
+
+
+ read_yaml automatically finds and processes the .erb version of the file if it exists, so SlurmBasic's ERB header (which populates the Project dropdown) is executed correctly when inherited.
+
+
+
10. Supplement
All the samples on this page are written in the Bash language, but you can use other shells as well. However, the submit section can only be written in the Bash language.
Widget names can only contain alphanumeric characters and underscores. Numbers and underscores cannot start the name. Please also avoid using widget names that end with an underscore followed by a number (e.g., nodes_1). The same rule applies to the directory name in which the application is saved. However, when defining the header section in form.yml, the widget names beginning with underscores (e.g. _script_location) used in lib/header.yml.erb can be used.
If there is no second element in options, the first element is used instead.
+
The label and help texts are inserted into the page as HTML rather than as plain text, so simple markup such as <br>, <b>, <code> or an <a href="…" target="_blank"> link can be used in them. The same applies to text supplied by a set-label- or set-help- action. Because of this, a literal < must be written as < and a literal & as &; a double quote in a label also breaks the attribute the label is copied into, so write it as ". Note that markup in a label is flattened to plain text if a set-required- action later rewrites that label. The first and second elements of an options array are escaped and always appear literally.
+
module_load and dependent_module_select values reach the check section as the selected Name/version string, so @my_module can be validated like any text field.
In the script section, if a variable used in a line does not have a value, the line is not displayed. However, if you add a colon to the beginning of the variable (e.g. #{:nodes} or #{basename(:input_file)}), the line will be output even if the variable does not have a value.
The order of processing that Open Composer performs before submitting a job script is as follows.
The "Submit" button is clicked in the application page
+
If empty_script_warning is enabled, check whether the batch script contains any real commands and show a confirmation dialog if not (see section 7.1)
Execute the script written in the check section in form.yml (if the check section exists)
Execute the script written in the submit section in form.yml (if the submit section exists)