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:
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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)$")
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user