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)
|
name: str = Field(..., min_length=1, max_length=255)
|
||||||
account_mode: Literal["existing", "personal"]
|
account_mode: Literal["existing", "personal"]
|
||||||
account_display_code: Optional[str] = Field(None, description="Required when account_mode='existing'")
|
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
|
send_email: bool = True
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -68,4 +68,4 @@ class RoleUpdate(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class AccountRoleUpdate(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({
|
const [createForm, setCreateForm] = useState({
|
||||||
email: '',
|
email: '',
|
||||||
name: '',
|
name: '',
|
||||||
account_role: 'engineer' as 'engineer' | 'viewer',
|
account_role: 'engineer' as 'owner' | 'admin' | 'engineer' | 'viewer',
|
||||||
send_email: true,
|
send_email: true,
|
||||||
})
|
})
|
||||||
const [createLoading, setCreateLoading] = useState(false)
|
const [createLoading, setCreateLoading] = useState(false)
|
||||||
@@ -117,7 +117,7 @@ export function AccountDetailPage() {
|
|||||||
send_email: createForm.send_email,
|
send_email: createForm.send_email,
|
||||||
})
|
})
|
||||||
setShowCreateUserModal(false)
|
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)
|
setTempPassword(result.temporary_password)
|
||||||
setCopiedPassword(false)
|
setCopiedPassword(false)
|
||||||
toast.success(result.email_sent ? 'User created and welcome email sent' : 'User created')
|
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'
|
'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="engineer">Engineer</option>
|
||||||
<option value="viewer">Viewer</option>
|
<option value="viewer">Viewer</option>
|
||||||
</select>
|
</select>
|
||||||
@@ -543,12 +545,14 @@ export function AccountDetailPage() {
|
|||||||
<label className="mb-1 block text-sm font-medium text-foreground">Account Role</label>
|
<label className="mb-1 block text-sm font-medium text-foreground">Account Role</label>
|
||||||
<select
|
<select
|
||||||
value={createForm.account_role}
|
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(
|
className={cn(
|
||||||
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
'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'
|
'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="engineer">Engineer</option>
|
||||||
<option value="viewer">Viewer</option>
|
<option value="viewer">Viewer</option>
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export function UsersPage() {
|
|||||||
name: '',
|
name: '',
|
||||||
account_mode: 'personal' as 'existing' | 'personal',
|
account_mode: 'personal' as 'existing' | 'personal',
|
||||||
account_display_code: '',
|
account_display_code: '',
|
||||||
account_role: 'engineer' as 'engineer' | 'viewer',
|
account_role: 'engineer' as 'owner' | 'admin' | 'engineer' | 'viewer',
|
||||||
send_email: true,
|
send_email: true,
|
||||||
})
|
})
|
||||||
const [createLoading, setCreateLoading] = useState(false)
|
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>
|
<label className="mb-1 block text-sm font-medium text-foreground">Account Role</label>
|
||||||
<select
|
<select
|
||||||
value={createForm.account_role}
|
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(
|
className={cn(
|
||||||
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
|
'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'
|
'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="engineer">Engineer</option>
|
||||||
<option value="viewer">Viewer</option>
|
<option value="viewer">Viewer</option>
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -266,7 +266,7 @@ export interface AdminUserCreate {
|
|||||||
name: string
|
name: string
|
||||||
account_mode: 'existing' | 'personal'
|
account_mode: 'existing' | 'personal'
|
||||||
account_display_code?: string
|
account_display_code?: string
|
||||||
account_role?: 'engineer' | 'viewer'
|
account_role?: 'owner' | 'admin' | 'engineer' | 'viewer'
|
||||||
send_email: boolean
|
send_email: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user