seamm_webui package#
Subpackages#
- seamm_webui.routers package
- Submodules
- seamm_webui.routers.admin module
- seamm_webui.routers.auth module
- seamm_webui.routers.jobs module
- seamm_webui.routers.projects module
- seamm_webui.routers.queues module
- Module contents
Submodules#
seamm_webui.auth module#
Authentication for seamm_webui.
Two modes (see main.py’s –auth flag and dashboard-rewrite-plan.md, Phase 3):
“none” – no login at all; every request acts as the fixed built-in identity. Default when bound to loopback; main.py refuses to start this mode on any other host.
“local” – real per-user login via a signed session cookie, checked against seamm_datastore’s existing User/password_hash (proper salted hashing, already there – see models.py’s User.verify_password).
Both modes funnel through require_permission(), the FastAPI dependency every route already depends on – adding a third mode later (e.g. an OIDC front door) is a change to this file, not to route code.
Deliberately no separate CSRF-cookie mechanism (unlike the old seamm_dashboard’s flask_jwt_extended double-submit setup): this is a JSON-only API behind CORS locked to known origins, so an httpOnly, SameSite=Lax session cookie closes the same gap a CSRF token would, without a second cookie that can collide with another seamm-webui instance’s (see init_auth’s cookie-name note).
- seamm_webui.auth.get_current_user(request: Request) str | None[source]#
Return the username this request’s session cookie asserts, or None if there isn’t one, or it’s missing/tampered/expired. Doesn’t raise – callers (require_permission, GET /api/auth/me) decide what “no identity” means for them.
- seamm_webui.auth.init_auth(mode: Literal['none', 'local'], secret_key: str, cookie_name: str, secure: bool = False)[source]#
Configure auth for this process. Called once from main.py’s create_app(), before any request is served.
cookie_nameshould be unique per running instance (main.py derives it from the port) – browser cookies are scoped by domain only, not port, so two seamm-webui instances both reachable as localhost (a local one plus an SSH-tunneled cluster one, say) would otherwise silently overwrite each other’s session cookie.In “none” mode, also sets an ambient default identity for the whole process (not just per-request, which require_permission already does) – there’s no security boundary in this mode to protect, so code that talks to the datastore directly rather than through an HTTP request (test fixtures, scripts using seamm_webui.db.init_datastore() directly) should just work the way it always did in Phase 1/2, without needing its own request to go through require_permission first. “local” mode deliberately does not do this – db.py’s contextvar default stays None there, so anything that reaches a permission check without going through require_permission fails closed, not open.
- seamm_webui.auth.require_admin()[source]#
FastAPI dependency factory for the admin-only user-management routes (routers/admin.py). Stricter than require_permission(): not just “is anyone logged in,” but “is this identity’s User row tagged with the admin role.” Every account created so far has that role (Phase 3’s “prove who you are, not partition who sees what” – see dashboard-rewrite-plan.md), so this is a no-op today, but the check still belongs here from the start rather than being retrofitted once non-admin accounts exist.
Works in “none” mode too, not just “local” – the fixed identity there (NONE_MODE_USERNAME) genuinely has the admin role in the datastore (seamm_datastore’s _build_initial()), so there’s nothing to special-case; the frontend simply never shows the Admin nav entry in “none” mode (see GET /api/auth/me), since there’s no login flow to manage there anyway.
- seamm_webui.auth.require_permission(action: str)[source]#
FastAPI dependency factory: establish who’s making this request (for seamm_datastore’s permission checks – db.py’s set_current_user) and, in “local” mode, reject if nobody’s logged in.
action(e.g. “read”, “update”) is accepted so call sites already read the way they will if this ever needs to check it, but it isn’t checked here – fine-grained authorization (who can see/touch what) is seamm_datastore’s existing owner/group permission model, which starts applying for real the moment a real per-request identity is set here; this dependency only answers “is anyone logged in at all.”
- seamm_webui.auth.user_is_admin(username: str | None) bool[source]#
True if
usernameexists and has the “admin” role. Shared by require_admin() and GET /api/auth/me (which uses it to decide whether the frontend shows the Admin nav entry at all – see its own docstring for why that’s naturally False, not an error, in “none” mode).
seamm_webui.db module#
Datastore connection management for seamm_webui.
IMPORTANT ordering constraint: init_datastore() must run before anything
else in this process imports seamm_datastore.database.models (directly or
transitively, e.g. via a router). seamm_datastore’s internal
flask_authorize_patch decides – the first time it is imported in a
process – whether to bind to a real Flask current_app or to the
standalone fake_app shim that SEAMMDatastore.__init__ sets up. Since
we have no Flask app, importing the models before init_datastore() has
run would leave that patch trying to use Flask’s real (app-context-less)
current_app proxy and crash. main.py calls init_datastore() first
and only then imports the routers, which is what keeps this safe.
- seamm_webui.db.init_datastore(datastore_dir: str, default_project: str = 'default')[source]#
Connect to the SEAMM datastore at
datastore_dir.NOTE: this must be the datastore directory (what the rest of SEAMM calls
--datastore, default${root}/Jobs), not the general--rootSEAMM config directory (default~/SEAMM, holding the per-code.inifiles) – those are two different, separately configurable directories. Conflating them was a real bug here during scaffolding: pointing this at~/SEAMMdirectly found noseamm.db, silently created a fresh empty one there, and connected to that instead of the real datastore at~/SEAMM/Jobs/seamm.db. Seemain.py’s--root/--datastorehandling, which mirrorsseamm_util.argument_parser’s${root}/Jobsdefault.Only initializes (creates tables/default project/roles) if there is no existing
seamm.dbat that location yet – an existing datastore (e.g. the one the oldseamm_dashboardalready uses) is connected to as-is, never dropped/recreated.Identity is deliberately left unset here (no
login()call) –_current_username’s owndefault=Nonemeans any request that somehow reaches a permission check without going throughseamm_webui.auth.require_permissionfirst fails closed (looks logged-out), not open (looks like the admin account). Establishing who’s making a given request is entirelyrequire_permission’s job now (Phase 3) – seeseamm_webui/auth.py.
- seamm_webui.db.set_current_user(username: str | None) None[source]#
Set the identity seamm_datastore’s permission checks will see for the rest of the current request (or, outside of a request, the rest of the current context).
seamm_webui.auth’srequire_permissioncalls this once it’s established who’s making the request – the fixed built-in identity in “none” mode, or the verified session cookie’s username in “local” mode. Deliberately does not go throughSEAMMDatastore.login()(which re-verifies a password) since a valid session cookie already proved identity.
seamm_webui.main module#
FastAPI application factory and CLI entry point for seamm_webui.
- seamm_webui.main.create_app(datastore_dir: str, port: int = 8010, auth_mode: str = 'none', root: str | None = None, jobserver_name: str | None = None, name: str | None = None) FastAPI[source]#
Build the FastAPI app.
root/jobserver_nameare only used for the read-onlyGET /api/queuesendpoint (queue_config.py/routers/queues.py) –root=None(the default, and what every pre-existing caller/test still gets) means that endpoint reports no queues at all, exactly as if the multi-queue routing feature didn’t exist.jobserver_namedefaults to this host’s hostname, matchingseamm_jobserver’s own--namedefault, whenrootis given but no name is.nameis this dashboard instance’s own display name (frontend header + browser tab title,GET /api/health) – deliberately a separate concept fromjobserver_name, which specifically names the paired JobServer’s.inifile to read for queue routing. Defaults tojobserver_name(itself defaulting to the hostname) when not given, so the common case needs no extra configuration, but a site can pick a friendly display name (e.g. “MolSSI10”) independently of that.
seamm_webui.manage module#
CLI for managing seamm_webui local-mode user accounts.
There’s no admin UI yet (that’s Phase 6) – this is the only way to create a “local”-auth-mode account for now. New accounts default to the admin role (full visibility, same as everyone gets today) rather than seamm_datastore’s real owner/group filtering: Phase 3 is “prove who you are,” not per-user data partitioning – see dashboard-rewrite-plan.md.
- seamm_webui.manage.cmd_set_password(args: Namespace) None[source]#
Reset an existing user’s password.
Deliberately no –password flag (unlike create, which allows one for scripting) – this always prompts via getpass, so the new password never has to be typed anywhere it could be logged or captured (a shell’s command history, a chat transcript run through a shared terminal session, etc.), only into a genuinely hidden terminal prompt.
seamm_webui.queue_config module#
Queue/cluster-target config for seamm_webui.
The queues a submitted job can be routed to (local, TinkerCliffs,
Owl, …) live in <root>/<jobserver-name>.ini – a system/machine
config file the JobServer paired with this Dashboard already reads (see
seamm_scheduler.config), not something seamm_webui itself owns. This module
just remembers where to find it, the same way db.py remembers the
datastore location: set once at startup by main.py’s create_app(),
read by routers/queues.py.
root is the general SEAMM config root (~/SEAMM by default) – NOT
the datastore directory (db.py’s get_datastore_dir()), same
--root vs --datastore distinction documented there. jobserver_name
defaults to this host’s hostname, matching seamm_jobserver’s own
--name default, so the common one-JobServer-per-host case needs no
extra configuration; a host running more than one independent JobServer
instance needs --jobserver-name set explicitly to disambiguate which
one this Dashboard is paired with.
See seamm_jobserver’s docs/developer_guide/campaigns/2026-08-10/
(multi-queue routing) for the full design.
- seamm_webui.queue_config.configure(root: str | None, jobserver_name: str) None[source]#
Called once by
create_app()at startup.root=Nonemeans “no queue config available” –routers/queues.pythen reports no queues at all, the same “feature doesn’t exist” conventionseamm_scheduler.config.load_slurm_config()uses for a missing ini file.
seamm_webui.tls module#
Self-signed TLS certificate generation for non-loopback deployments.
seamm_webui has to be self-contained – no separately-installed/managed httpd (Apache/nginx/Caddy) in front of it, since installs are expected on machines run by non-computer-savvy users. So when it’s asked to bind a non-loopback host with no certificate supplied, it generates and reuses its own self-signed certificate, the same way an SSH server generates a host key on first boot: real, browser-trusted HTTPS (Let’s Encrypt-style) needs the host to be publicly reachable on a real DNS name, which doesn’t hold for cluster-internal deployments.
- seamm_webui.tls.get_or_create_self_signed_cert(root_dir: str) tuple[str, str][source]#
Return (certfile, keyfile) paths under root_dir, generating a self-signed cert/key pair on first use and reusing them on subsequent calls so a restart doesn’t invalidate every browser’s trust decision. Lock-guarded (like get_or_create_secret_key) so two processes starting against the same fresh –root at once can’t race.
seamm_webui.util module#
Job submission utilities.
get_job_id() and the flowchart/job_data.json writing logic are ported from seamm_dashboard/util.py and seamm_dashboard/routes/api/jobs.py’s add_job – deliberately NOT imported from seamm_dashboard (that package must never be imported into this process, see the Base/sys.modules note in db.py). Both dashboards point at the same job.id file (see dashboard-rewrite-plan.md), so this must stay wire-compatible with the original format.
- seamm_webui.util.get_job_id(filename: str) int[source]#
Get the next job id from the given file, incrementing it atomically.
Uses an inter-process file lock so concurrent submissions – from this dashboard, the old one, or a job array – get unique, monotonically increasing ids.
- seamm_webui.util.get_or_create_secret_key(datastore_dir: str) str[source]#
Return the key used to sign session cookies (Phase 3 auth), creating one on first use and persisting it in the datastore directory – same “lives next to seamm.db, not the general –root config dir” convention as get_job_id’s job.id counter file. Without persisting this, every server restart would invalidate every session cookie and log everyone out. Lock-guarded (like get_job_id) so two processes starting against the same fresh datastore at once can’t race and end up disagreeing on the key.
Module contents#
seamm_webui: SPA-based web dashboard for SEAMM.