fix(admin): allow owner and admin account roles in user creation and role management

Four places were hardcoded to engineer|viewer only:
- AccountRoleUpdate schema (user.py) — blocked PUT /admin/users/{id}/account-role at the API level
- AdminUserCreate schema (admin.py) — blocked creating users with owner/admin role
- AccountDetailPage role dropdowns (create form + inline member role changer)
- AccountsPage create user role dropdown

Now all four accept the full set: owner, admin, engineer, viewer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-04-14 13:24:17 +00:00
parent eba50e1f95
commit c5b8229ef6
5 changed files with 14 additions and 8 deletions

View File

@@ -319,7 +319,7 @@ class AdminUserCreate(BaseModel):
name: str = Field(..., min_length=1, max_length=255)
account_mode: Literal["existing", "personal"]
account_display_code: Optional[str] = Field(None, description="Required when account_mode='existing'")
account_role: Optional[Literal["engineer", "viewer"]] = Field(None, description="Required when account_mode='existing'")
account_role: Optional[Literal["owner", "admin", "engineer", "viewer"]] = Field(None, description="Required when account_mode='existing'")
send_email: bool = True

View File

@@ -68,4 +68,4 @@ class RoleUpdate(BaseModel):
class AccountRoleUpdate(BaseModel):
account_role: str = Field(..., pattern="^(engineer|viewer)$")
account_role: str = Field(..., pattern="^(owner|admin|engineer|viewer)$")

View File

@@ -44,7 +44,7 @@ export function AccountDetailPage() {
const [createForm, setCreateForm] = useState({
email: '',
name: '',
account_role: 'engineer' as 'engineer' | 'viewer',
account_role: 'engineer' as 'owner' | 'admin' | 'engineer' | 'viewer',
send_email: true,
})
const [createLoading, setCreateLoading] = useState(false)
@@ -117,7 +117,7 @@ export function AccountDetailPage() {
send_email: createForm.send_email,
})
setShowCreateUserModal(false)
setCreateForm({ email: '', name: '', account_role: 'engineer', send_email: true })
setCreateForm({ email: '', name: '', account_role: 'engineer' as 'owner' | 'admin' | 'engineer' | 'viewer', send_email: true })
setTempPassword(result.temporary_password)
setCopiedPassword(false)
toast.success(result.email_sent ? 'User created and welcome email sent' : 'User created')
@@ -365,6 +365,8 @@ export function AccountDetailPage() {
'focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
)}
>
<option value="owner">Owner</option>
<option value="admin">Admin</option>
<option value="engineer">Engineer</option>
<option value="viewer">Viewer</option>
</select>
@@ -543,12 +545,14 @@ export function AccountDetailPage() {
<label className="mb-1 block text-sm font-medium text-foreground">Account Role</label>
<select
value={createForm.account_role}
onChange={(e) => setCreateForm((f) => ({ ...f, account_role: e.target.value as 'engineer' | 'viewer' }))}
onChange={(e) => setCreateForm((f) => ({ ...f, account_role: e.target.value as 'owner' | 'admin' | 'engineer' | 'viewer' }))}
className={cn(
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
'focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
)}
>
<option value="owner">Owner</option>
<option value="admin">Admin</option>
<option value="engineer">Engineer</option>
<option value="viewer">Viewer</option>
</select>

View File

@@ -74,7 +74,7 @@ export function UsersPage() {
name: '',
account_mode: 'personal' as 'existing' | 'personal',
account_display_code: '',
account_role: 'engineer' as 'engineer' | 'viewer',
account_role: 'engineer' as 'owner' | 'admin' | 'engineer' | 'viewer',
send_email: true,
})
const [createLoading, setCreateLoading] = useState(false)
@@ -700,12 +700,14 @@ export function UsersPage() {
<label className="mb-1 block text-sm font-medium text-foreground">Account Role</label>
<select
value={createForm.account_role}
onChange={(e) => setCreateForm((form) => ({ ...form, account_role: e.target.value as 'engineer' | 'viewer' }))}
onChange={(e) => setCreateForm((form) => ({ ...form, account_role: e.target.value as 'owner' | 'admin' | 'engineer' | 'viewer' }))}
className={cn(
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
'focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
)}
>
<option value="owner">Owner</option>
<option value="admin">Admin</option>
<option value="engineer">Engineer</option>
<option value="viewer">Viewer</option>
</select>

View File

@@ -266,7 +266,7 @@ export interface AdminUserCreate {
name: string
account_mode: 'existing' | 'personal'
account_display_code?: string
account_role?: 'engineer' | 'viewer'
account_role?: 'owner' | 'admin' | 'engineer' | 'viewer'
send_email: boolean
}