Add profile management API and CLI (list/get/create/edit/remove/rename)#38
Open
Add profile management API and CLI (list/get/create/edit/remove/rename)#38
Conversation
Introduces a new Profile::Manage module (mirroring User::Manage) that
exposes full CRUD plus rename operations for profile files. Key points:
- New manageProfiles permission gates all operations
- Profile ID is the filename stem; profile JSON 'name' field is the
human-readable display name (decoupled from the ID)
- All operations work on every file in profiles/*.json, including
inactive (active: false) ones
- Active profiles are validated via Profile->validate() before write;
inactive profiles are accepted as-is (drafts)
- rename() uses OS rename(2) for atomicity; cache is explicitly
invalidated so Data::load picks up renames and deletes where mtime
of remaining files would otherwise be unchanged
- Data::invalidate_profile_cache() exported for this purpose
Backend changes:
app/server/lib/Profile/Manage.pm (new)
app/server/lib/Data.pm invalidate_profile_cache export
app/server/lib/User.pm manageProfiles permission + import
app/server/lib/App.pm 6 new /profiles/* route handlers
CLI changes:
cli/dockside_cli.py api_profile_*, cmd_profile_*,
_add_profile_fields, profile subcommand
https://claude.ai/code/session_01H8i2RbXH3JTLacSF2Y8dja
Adds a full admin UI at /admin (users, roles, profiles) and a self-service account editor at /account, accessible from the top navbar. Frontend: - AdminSidebar: sectioned vertical nav (USERS / ROLES / PROFILES) mirroring the devtainer sidebar pattern; collapses per section; active/inactive dot for profiles - AdminMain: renders UserDetail / RoleDetail / ProfileDetail based on route - UserDetail: name, email, role select, password, permissions (tri-state ValueTag), resources (allow/deny tag rows), gh_token reveal, SshEditor; selfEdit=true mode restricted to personal fields only - RoleDetail: name, permissions (on/off ValueTag), resources; delete disabled if role assigned to any user - ProfileDetail: structured fields (id, name, description, active toggle, version) + json-editor-vue for the full profile body; inline rename (only when no unsaved edits) - PermissionsEditor: data-driven from schemas/admin.js; groups permissions; shows inherited-from-role hint for users; tri-state (inherit/grant/deny) for users, bi-state (on/off) for roles - ResourcesEditor: per-resource-type tag rows with allow/deny ValueTag; add-new inline input; serialises to array or object form - SshEditor: publicKeys textarea + keypairs mini-UI (add modal, delete); private key never shown after entry - ValueTag: new tri-state chip (grey=inherited, green=granted, red=denied) - JsonEditor: thin wrapper around json-editor-vue (same package for Vue 2+3; Vue 3 migration = 2-line change in this file only) - ConfirmModal: thin b-modal wrapper for delete confirmations - Vuex admin module (namespaced) with users/roles/profiles state and full CRUD actions - schemas/admin.js: data-only permission/resource definitions; adding a new permission = one line here, no template changes needed - services/admin.js: axios API service; POST for user/profile create+update - Routes: /admin, /admin/:type, /admin/:type/:id, /account - Header: Admin link (manageUsers|manageProfiles only) + username link - webpack.common.js: mainFields + babel transpile for json-editor-vue deps Backend (App.pm, User/Manage.pm, User.pm): - POST body reading support via has_request_body() callback dispatch; get_args() merges JSON body with query string (query string wins) - /users/me and /users/me/update routes for self-service (no manageUsers permission required; whitelist: name, email, gh_token, ssh only) - SPA HTML now served for /admin/* and /account routes - updateSelf() in User::Manage whitelists personal fields only https://claude.ai/code/session_011NEhVStYQAP6JzmTKWUCV7
…ltips - Use allowInherit=true on resource ValueTags so clicking cycles green (allowed) → red (denied) → removed, instead of skipping denied - Remove @blur="cancelAdd" so typing a new resource value is not lost when the user clicks elsewhere; Enter commits, Escape cancels - Add nullLabel prop to ValueTag for context-sensitive absent tooltips; use "remove" for resources and improve all three state tooltip texts - Update legend text to accurately describe the three-state cycling https://claude.ai/code/session_011NEhVStYQAP6JzmTKWUCV7
…on; profile JSON view - New ResourceTagsInput.vue: vue-tags-input-based component replacing the ValueTag+plain-input approach. Features: - Autocomplete from server-supplied runtimes, networks, IDEs, profiles, authModes - 'value:disabled' convention in autocomplete to deny a specific resource value - Green tags = allowed, red tags = denied, grey = plain (images) - Enter key intercepted at wrapper level to prevent accidental form submission - Value prop drives display (controlled); local tags updated immediately on change - ResourcesEditor.vue rewritten to use ResourceTagsInput per resource row. Reads hostResources and profiles from Vuex store for autosuggestion lists. - schemas/admin.js: added allowDeny flag to RESOURCES (false for images). - store/admin.js: added hostResources state, setHostResources mutation, and fetchResources action (non-fatal); included in fetchAll. - services/admin.js: added getResources() calling GET /resources. - App.pm: added GET /resources endpoint returning runtimes, networks, IDEs, and static authModes from HOSTINFO and Containers; added $HOSTINFO/$HOSTNAME to Data imports and added use Containers. - JsonEditor.vue: added readonly prop; passes readOnly to json-editor-vue and hides the mode switcher when readonly. - ProfileDetail.vue: show profile JSON as a read-only tree when in view mode (was previously just a hint saying "switch to Edit mode"). https://claude.ai/code/session_011NEhVStYQAP6JzmTKWUCV7
ResourceTagsInput.vue: - Autocomplete opens on focus (autocomplete-min-length=0) - Click a tag to toggle allowed↔denied (green✓↔red✗); event delegation on the wrapper captures tag-text clicks, excluding the × close button - ✓/✗ indicators on allowed/denied tags via CSS ::after on tag text div - Updated placeholder: 'Type to add · value:disabled to deny · * to allow all' ResourcesEditor.vue: - * prepended to every resource type's suggestion list so "allow all" is always the first autocomplete option UserDetail.vue: - gh_token is now write-once: when the server returns '<redacted>' the field shows a locked "Token set" badge and is not editable - In save payload, gh_token is omitted when blank (not being changed), preserving the existing server-side token without sending '<redacted>' ProfileDetail.vue: - New profiles are pre-populated with PROFILE_TEMPLATE_BODY (version 4), listing every top-level property with empty defaults so users can see what the schema supports https://claude.ai/code/session_011NEhVStYQAP6JzmTKWUCV7
- admin.js: switch createRole/updateRole from GET+params to POST+JSON to avoid bracket-notation serialisation of nested permission objects - App.pm: use get_args() for role create/update handlers so POST JSON body is read; add /resources endpoint returning runtimes, networks, IDEs, authModes from host introspection - User/Manage.pm: guard _decode_value against Perl refs (prevents 'length called on a reference' warnings when POST JSON decodes nested data structures as native hashrefs/arrayrefs) - UserDetail.vue: default new-user resources to ["*"] for all resource types; block save when role field is empty with a clear error message https://claude.ai/code/session_011NEhVStYQAP6JzmTKWUCV7
- store/index.js + App.pm: add reactive profiles state to main store (init from window.dockside.profiles); add fetchProfiles action calling new /profiles/mine endpoint which returns the calling user's accessible profiles in the same dict format as the page bootstrap - Container.vue: use store profiles (reactive) instead of static window.dockside.profiles; dispatch fetchProfiles on prelaunch so navigating to /container/new always shows current profile list - store/admin.js: after createProfile/updateProfile/removeProfile/ renameProfile dispatch root fetchProfiles to keep the launch page in sync immediately after admin changes - Header.vue: show Launch and Docs navbar items in /admin routes (removed !isAdminRoute guard) - ResourceTagsInput.vue: fix double ✓/✗ indicators (> div::after hit both .ti-content and .ti-actions divs); switch to .ti-tag-center > span::before for single left-side indicator; add subtle border-left + tinted background on .ti-actions to visually separate the dismiss × from the tag text; update handleTagAreaClick to read text from .ti-tag-center > span - UserDetail.vue: gh_token is now editable in edit/new mode even when a token is already set; placeholder changes to "Enter new token to replace existing, or leave blank to keep"; locked badge only shown in view mode when token is set https://claude.ai/code/session_011NEhVStYQAP6JzmTKWUCV7
…v link - App.pm: include name and email in window.dockside.user bootstrap via $User->details() so they're available client-side without an extra request - Header.vue: add displayName computed — returns first word of name (first name for multi-word, or the only name as surname fallback), then obfuscated email (first 1-3 chars + … + @Domain), then username as final fallback; tooltip still shows full username https://claude.ai/code/session_011NEhVStYQAP6JzmTKWUCV7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Introduces a new Profile::Manage module (mirroring User::Manage) that
exposes full CRUD plus rename operations for profile files. Key points:
human-readable display name (decoupled from the ID)
inactive (active: false) ones
inactive profiles are accepted as-is (drafts)
invalidated so Data::load picks up renames and deletes where mtime
of remaining files would otherwise be unchanged
Backend changes:
app/server/lib/Profile/Manage.pm (new)
app/server/lib/Data.pm invalidate_profile_cache export
app/server/lib/User.pm manageProfiles permission + import
app/server/lib/App.pm 6 new /profiles/* route handlers
CLI changes:
cli/dockside_cli.py api_profile_, cmd_profile_,
_add_profile_fields, profile subcommand
https://claude.ai/code/session_01H8i2RbXH3JTLacSF2Y8dja