tool_user_admin
Self-service account panel: lets the logged-in user view and edit their own basic data (full name, password, email, avatar) from the username link in the header, without granting them access to the user-management section.
What it does / why & when to use it
tool_user_admin opens a small panel — launched by clicking the username in the main menu header — that renders a few components of the current user's own record from the Users section (dd128): the user's id, username and profile (read-only), plus their full name, password, email and avatar image (editable). It exists so any logged-in person can maintain their own credentials and profile picture even when their profile does not grant write (or any) access to the Users management section as a whole.
The tool itself is UI-only: it ships no server module and has no remotely callable actions of its own. Every read and save it performs goes through the ordinary, already-gated component pipeline against the user's own dd128 record. What lets a low-privilege user use it is a core permission rule for the caller's own Users record (see below), not anything in the tool.
Concrete heritage scenario: a documentalist at a museum is given a profile that lets them catalogue objects but explicitly not administer other users. They notice their displayed name is misspelled and they want to set a recognisable avatar and rotate their password after onboarding. They click their name in the top bar; the User admin panel opens showing their id / username / profile as read-only context, and editable fields for full name, password, email and a user image (with the standard upload tool enabled on the image). They fix the name, set a new password, upload a portrait, and close the panel — all without ever touching the Users section or seeing any other account.
Use it when: you want end users to self-manage their own account essentials from the header. Do not expect it to administer other users, to expose admin actions, or to dispatch any server action on this class — it is a front-end editor over the logged user's own Users-section record.
How it works (server + client)
Server. tools/tool_user_admin/ ships no server/ package — confirmed: there is no tools/tool_user_admin/server/ directory, so dd_tools_api.tool_request refuses any action named against it at dispatch gate 5 (tool has no server module). There is no isAvailable/onRegister/onRemove override.
The behaviour that lets a low-privilege user edit their own data regardless of their profile's Users-section grant lives in core, not in the tool: the own-user-record rules (resolveOwnUserRecordPermission, applied through getRecordComponentPermission in src/core/security/permissions.ts). When the target is dd128 and section_id equals the caller's user id, the profile level is overridden before the matrix is consulted:
| Components | Forced level | Why |
|---|---|---|
dd452 name, dd134 email, dd133 password, dd522 image, dd1206 UI preferences |
2 (write) | the self-service edit — most profiles grant 0 on dd128 |
dd1725 profile, dd515 developer, dd132 username, dd330 section id |
1 (read) for non-global-admins | anti self-elevation |
dd244 security administrator |
1 (read), global admins included | anti self-elevation |
Every door onto these components consults the rule: the read action's source gate (gateReadSource, src/core/api/handlers/dd_core_api.ts), the component get_data gate (src/core/section/read_facade.ts), the per-element context stamp (src/core/section/read.ts) and the save write door. Another user's record keeps the profile level. Up to 7.0.0-beta.7 the two read gates skipped the rule, so the panel failed with Insufficient permissions for any user with no dd128 grant. Gate: test/unit/user_self_record_permissions.test.ts.
Client (tools/tool_user_admin/js/). The instance is tool_user_admin.js; render_tool_user_admin.js builds the body; index.js re-exports the module. Notable points:
- Launch path. It is not surfaced by matching an element. The header username link wires a click/Enter handler (
core/menu/js/view_default_edit_menu.js) that callsmenu.open_tool_user_admin_handler()(core/menu/js/menu.js), which finds the tool context inself.context.tools(built server-side bymenu::get_tools()/create_tool_simple_context) and calls the sharedopen_tool({tool_context, caller})fromtool_common.js. - Fixed target = the logged user. Unlike element-driven tools, this one hard-codes its target:
section_tipo = 'dd128'andsection_id = page_globals.user_id.build_user_section()builds a dedicateddd128section instance with its ownrequest_configwhoseshow.ddo_mapis supplied byget_ddo_map()(overriding the section's default show map), withfilterandinspectordisabled. - The ddo_map (the components rendered, all
section_tipo: 'dd128'):
| tipo | model | role |
|---|---|---|
dd330 |
component_section_id |
section id — read-only (permissions: 1) |
dd132 |
component_input_text |
username — read-only |
dd1725 |
component_select |
user profile — read-only |
dd452 |
component_input_text |
full user name — editable |
dd133 |
component_password |
password — editable |
dd134 |
component_email |
email — editable |
dd522 |
component_image |
user image — editable (upload tool enabled via show_interface.tools: true) |
All non-image components set show_interface.tools: false to hide inline tooling; the image keeps tools: true so the upload tool is available. These declarations are applied by the client, not the server (see properties.show_interface on a client-sent ddo). (A dd170 projects/filter ddo is present but commented out.)
- Rendering. render_tool_user_admin.js::edit builds a standard tool wrapper (ui.tool.build_wrapper_edit); get_content_data lays out one component_column per ddo inside a CSS grid components_container, resolving each ddo to a live instance via self.get_component(ddo) (which itself fixes section_id = page_globals.user_id, section_tipo = 'dd128', lang = page_globals.dedalo_data_lang) and rendering them in parallel.
- Active field style switcher. Below the components, render_active_style_switcher draws one radio per entry of the style registry ACTIVE_STYLES (client/dedalo/core/page/js/active_style.js: default, lift, margin). A change calls save_active_style(id), which applies data-active_style on <html> (each non-default style is one stylesheet, client/dedalo/core/page/css/active_styles/<id>.less), then writes { active_style } into the user's own dd1206 User interface preferences (component_json) through the normal component save, keeping any other keys. The page reads it back on every load from page_globals.ui_preferences (resolveUserUiPreferences, src/core/security/user_identity.ts; engineering/wire_contract/WC-2026-11-02-ui-preferences.md); localStorage dedalo_active_style is only the first-paint cache read by theme-init.js. Adding a style: a registry entry, its .less file imported at the end of main.less, and its label — gate test/unit/active_style_registry.test.ts.
- Demo guard. build() throws (and the panel surfaces an error instead of editable fields) when page_globals.dedalo_entity === 'dedalo_demo' and page_globals.username === 'dedalo', so the demo's shared account cannot be mutated; the comment notes the server security controls already enforce this independently.
- Close. on_close_actions('modal') destroys the tool instance without refreshing the caller (the caller is the menu, not a component_json).
Actions & options
This tool declares no API actions of its own:
apiActions |
Value |
|---|---|
tool_user_admin |
(no server module) — UI-only, nothing dispatchable on this tool in either engine |
There is therefore no tool_request envelope, no permission gate spec and no backgroundRunnable for this tool. Saves are issued by the rendered component instances through their normal save paths against the logged user's own dd128 record; each is gated by the ordinary component permission checks, which apply the own-record rules above. The client-side inputs the tool effectively works with are not request options but fixed globals it reads:
| Reads (client global) | Used for |
|---|---|
page_globals.user_id |
section_id of the target Users record (the logged user) |
page_globals.dedalo_data_lang / dedalo_data_nolan |
component / section language and no-language context |
page_globals.dedalo_entity, page_globals.username |
demo-account guard in build() |
fixed 'dd128' |
target section_tipo (Users section) and the ddo_map parent/section tipos |
How it is registered & surfaced
tools/tool_user_admin/register.json is a column-keyed dump (string/relation/misc/… keyed by component tipo — a seeded matrix-row snapshot, not a hand-authored file); importTools() passes it through as-is (see register.json reference). Essentials it carries:
dd1326name =tool_user_admin;dd1327version2.0.2;dd1328minimum Dédalo version6.0.0;dd1644developer = "Dédalo team".dd799label = User admin (localized across project languages — Administración de usuario, Administration des utilisateurs, etc.);dd612description = "Manages logged user basic data directly from the menu link".dd1350affected_tipos =["dd85"].dd999config carries an emptyddo_map(the operative ddo_map is built client-side inget_ddo_map(), not from config).- The affected_models / show_in_inspector / show_in_component / require_translatable / always_active / active flags (
dd1330/dd1331/dd1332/dd1333/dd1601/dd1354) appear as relations to their ontology records in this v6 dump rather than as inline values.
Surfacing: this tool is menu-launched, not element-attached. It is delivered in the menu's tool context list (menu::get_tools()), and the header username link is the trigger. It does not render as an inspector panel or as an inline component button on ordinary records — there is no show_in_* button placement that exposes it next to a section or component the way data tools do. It opens as the User admin panel from the username menu (see core/menu/js/menu.js and core/menu/js/view_default_edit_menu.js).
Examples
There is no server tool_request to show — the tool issues none. The realistic "usage" is the launch path and the fixed target it builds.
Launch (what the username-link click handler does):
// core/menu/js/menu.js
menu.prototype.open_tool_user_admin_handler = function() {
const self = this
// find the tool in the menu's server-provided tool list
const tool_user_admin = self.context.tools.find(el => el.model === 'tool_user_admin')
if (!tool_user_admin) {
console.error('Tool user admin is not available in tools. Check your user profile tools.')
return
}
// shared opener from tool_common.js
open_tool({ tool_context: tool_user_admin, caller: self })
}
Target the tool builds (the logged user's own Users record), from build_user_section():
const section_tipo = 'dd128' // Users section
const section_id = '' + page_globals.user_id // the logged user
const request_config = [{
api_engine : 'dedalo',
type : 'main',
show : { ddo_map : self.get_ddo_map() }, // overrides the section default show map
sqo : { section_tipo: [section_tipo], limit: 1, offset: 0 }
}]
// section instance is built with filter=false, inspector=false
Related
- Creating new tools · Server contract — the tool model,
apiActions(incl. the no-server-module UI-only case), gates and lifecycle this page builds on. - tool_indexation, tool_diffusion — other UI-only tools with no server module (front-end orchestrators over existing component/section pipelines).
- tool_export — contrast: a section tool with a real dispatchable action and a permission-gated declarative
apiActionsmap; see Exporting data. - Core glue (client):
core/menu/js/menu.js(open_tool_user_admin_handler),core/menu/js/view_default_edit_menu.js(username link handler). Server-side permission resolution:src/core/security/permissions.ts::getRecordComponentPermission(the own-record rules above, thengetPermissions). - Source:
tools/tool_user_admin/register.json(noserver/package),tools/tool_user_admin/js/{tool_user_admin,render_tool_user_admin,index}.js,tools/tool_user_admin/css/tool_user_admin.less.