Retire the Superset reader setup and the ATOMA dashboard (PD-13)

ATOMA's users use the Petrolyst app; Superset keeps admins, editors and the
app's embedded dashboards. Removed: the "ATOMA Reader" role and its rules
(petrolyst_reader.py, ReaderAccessMixin, reader.css, the head template, the
SQL-hiding patch), the atoma-reader mapping (it now maps to nothing in
Superset), the iframe allowance for the Map tab, and the "atoma" dashboard
with its Fleet/Map tab, D0 and dashboard.css. The bundle holds atoma-overview
and atoma-site only (byte-identical to before); the guest role keeps the
reader's 12 permissions in roles/atoma_guest.py. Brand files now name
petrolyst-web. SPEC marks the retired parts as history.

Checks (local): bundle checks pass; image rebuilt and healthy; import with
column drift 0 and unchanged embedding ids; the production config loads with
OAuth, embedding off, no reader hook; admin sees atoma-site with its tabs and
filter column; the app's browser tests pass 10/10. shellcheck not run (not
installed). Not deployed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Nasim SadatandClaude Opus 5.5 committed 2026-10-29 21:10:19 +01:00
1 parent 1b8e57adb4
commit da3f803947
22 files changed
+164 -702

No files matched your search

+65 -8
View File
@@ -1,23 +1,80 @@
"""The role "ATOMA Guest": what the Petrolyst app's embedded dashboards may do (PD-12 trial).
"""The role "ATOMA Guest": what the Petrolyst app's embedded dashboards may do (PD-12).
Superset gives every embedded viewer (a guest pass signed for one dashboard) the role named by
GUEST_ROLE_NAME. That role gets the same permissions as "ATOMA Reader": reading a dashboard
and its charts' data, nothing else. Which dashboard a guest sees comes from the pass, not from
the role. Superset leaves the SQL out of chart-data responses for guests.
GUEST_ROLE_NAME. Which dashboard a guest sees comes from the pass, not from the role. Superset
leaves the SQL out of chart-data responses for guests.
Embedding is switched on in the local stack only (.local/fmdb-scratch, superset_config_local.py),
so this script is not part of init.sh; run it by hand inside the Superset container:
so this script is not part of init.sh; run it by hand inside the Superset container, once, and
again whenever PERMISSIONS change:
docker exec <superset> /app/.venv/bin/python /app/petrolyst-roles/atoma_guest.py
Creates the role if needed and gives it exactly PERMISSIONS: missing ones are added, any other
is removed (also one added by hand in the UI). A second run changes nothing.
The list was established on the local stack by reading the ATOMA dashboard as a reader (the
former "ATOMA Reader" role, retired with PD-13): everything a dashboard asks the server for, and
nothing more. No menu access, nothing to write, no Explore, CSV, sharing, drill or query view.
"""
from __future__ import annotations
import sys
from atoma_reader import PERMISSIONS, sync
ROLE = "ATOMA Guest"
PERMISSIONS = [
("can_read", "Dashboard"), # the dashboard and its charts' definitions
("can_dashboard", "Superset"), # the dashboard page
("can_dashboard_permalink", "Superset"), # a dashboard permalink
("can_read", "Chart"), # chart data
("can_time_range", "Api"), # the Viewing period filter
("can_read", "DashboardFilterStateRestApi"), # native filter state
("can_write", "DashboardFilterStateRestApi"),
("can_read", "DashboardPermalinkRestApi"),
("can_read", "CurrentUserRestApi"), # who is signed in
("can_read", "SecurityRestApi"), # the CSRF token
("can_get", "MenuApi"),
("can_log", "Superset"), # Superset's event log
]
def sync(name: str, permissions: list[tuple[str, str]]) -> int:
"""Give the role `name` exactly `permissions`, creating it if needed."""
from superset.app import create_app
app = create_app()
with app.app_context():
from superset import db, security_manager as sm
wanted = []
for permission, view in permissions:
pvm = sm.find_permission_view_menu(permission, view)
if pvm is None:
print(f"no permission {permission} on {view}: run `superset init` first?",
file=sys.stderr)
return 1
wanted.append(pvm)
role = sm.find_role(name) or sm.add_role(name)
added = [pvm for pvm in wanted if pvm not in role.permissions]
removed = [pvm for pvm in role.permissions if pvm not in wanted]
for pvm in added:
sm.add_permission_role(role, pvm)
for pvm in removed:
sm.del_permission_role(role, pvm)
db.session.commit()
print(f'role "{name}": {len(wanted)} permissions, {len(added)} added, '
f"{len(removed)} removed")
for pvm in removed:
print(f" removed {pvm.permission.name} on {pvm.view_menu.name}")
return 0
def main() -> int:
return sync(ROLE, PERMISSIONS)
if __name__ == "__main__":
sys.exit(sync(ROLE, PERMISSIONS))
sys.exit(main())
-76
View File
@@ -1,76 +0,0 @@
"""The role "ATOMA Reader": read the dashboards granted to it, nothing else.
Runs inside the Superset container, after `superset init` (stacks/fuel-monitoring/superset/init.sh):
docker exec <superset> /app/.venv/bin/python /app/petrolyst-roles/atoma_reader.py
Creates the role if needed and gives it exactly PERMISSIONS: missing ones are added, any other
is removed (also one added by hand in the UI). A second run changes nothing. Readers reach a
dashboard only when it lists this role (DASHBOARD_RBAC; dashboards/atoma/import_bundle.py
attaches it), and petrolyst_reader.py adds the rules a role cannot express.
The list was established on the local stack by reading the ATOMA dashboard as a reader:
everything the dashboard asks the server for, and nothing more. No menu access, nothing to
write, no Explore, CSV, sharing, drill or query view.
"""
from __future__ import annotations
import sys
ROLE = "ATOMA Reader"
PERMISSIONS = [
("can_read", "Dashboard"), # the dashboard and its charts' definitions
("can_dashboard", "Superset"), # the dashboard page
("can_dashboard_permalink", "Superset"), # a dashboard permalink
("can_read", "Chart"), # chart data
("can_time_range", "Api"), # the Viewing period filter
("can_read", "DashboardFilterStateRestApi"), # native filter state
("can_write", "DashboardFilterStateRestApi"),
("can_read", "DashboardPermalinkRestApi"),
("can_read", "CurrentUserRestApi"), # who is signed in
("can_read", "SecurityRestApi"), # the CSRF token
("can_get", "MenuApi"),
("can_log", "Superset"), # Superset's event log
]
def sync(name: str, permissions: list[tuple[str, str]]) -> int:
"""Give the role `name` exactly `permissions`, creating it if needed (atoma_guest.py too)."""
from superset.app import create_app
app = create_app()
with app.app_context():
from superset import db, security_manager as sm
wanted = []
for permission, view in permissions:
pvm = sm.find_permission_view_menu(permission, view)
if pvm is None:
print(f"no permission {permission} on {view}: run `superset init` first?",
file=sys.stderr)
return 1
wanted.append(pvm)
role = sm.find_role(name) or sm.add_role(name)
added = [pvm for pvm in wanted if pvm not in role.permissions]
removed = [pvm for pvm in role.permissions if pvm not in wanted]
for pvm in added:
sm.add_permission_role(role, pvm)
for pvm in removed:
sm.del_permission_role(role, pvm)
db.session.commit()
print(f'role "{name}": {len(wanted)} permissions, {len(added)} added, '
f"{len(removed)} removed")
for pvm in removed:
print(f" removed {pvm.permission.name} on {pvm.view_menu.name}")
return 0
def main() -> int:
return sync(ROLE, PERMISSIONS)
if __name__ == "__main__":
sys.exit(main())