Show team-based access in project access management
- Add source, team_slug, team_role fields to AccessPermissionResponse schema - Update list_project_permissions endpoint to include team members with source="team" - Display team-based access in AccessManagement component with read-only styling - Add "Source" column to differentiate explicit vs team-based permissions - Team-based access shows "Via team" in actions column (not editable)
This commit is contained in:
@@ -114,3 +114,32 @@
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* Access source styling */
|
||||
.access-source {
|
||||
display: inline-block;
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.access-source--explicit {
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.access-source--team {
|
||||
background: var(--color-info-bg, #e3f2fd);
|
||||
color: var(--color-info, #1976d2);
|
||||
}
|
||||
|
||||
/* Team access row styling */
|
||||
.team-access-row {
|
||||
background: var(--bg-secondary, #fafafa);
|
||||
}
|
||||
|
||||
.team-access-row td.actions .text-muted {
|
||||
font-size: 0.8125rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@@ -208,85 +208,104 @@ export function AccessManagement({ projectName }: AccessManagementProps) {
|
||||
<tr>
|
||||
<th>User</th>
|
||||
<th>Access Level</th>
|
||||
<th>Source</th>
|
||||
<th>Granted</th>
|
||||
<th>Expires</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{permissions.map((p) => (
|
||||
<tr key={p.id}>
|
||||
<td>{p.user_id}</td>
|
||||
<td>
|
||||
{editingUser === p.user_id ? (
|
||||
<select
|
||||
value={editLevel}
|
||||
onChange={(e) => setEditLevel(e.target.value as AccessLevel)}
|
||||
disabled={submitting}
|
||||
>
|
||||
<option value="read">Read</option>
|
||||
<option value="write">Write</option>
|
||||
<option value="admin">Admin</option>
|
||||
</select>
|
||||
) : (
|
||||
<span className={`access-badge access-badge--${p.level}`}>
|
||||
{p.level}
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td>{new Date(p.created_at).toLocaleDateString()}</td>
|
||||
<td>
|
||||
{editingUser === p.user_id ? (
|
||||
<input
|
||||
type="date"
|
||||
value={editExpiresAt}
|
||||
onChange={(e) => setEditExpiresAt(e.target.value)}
|
||||
disabled={submitting}
|
||||
min={new Date().toISOString().split('T')[0]}
|
||||
/>
|
||||
) : (
|
||||
formatExpiration(p.expires_at)
|
||||
)}
|
||||
</td>
|
||||
<td className="actions">
|
||||
{editingUser === p.user_id ? (
|
||||
<>
|
||||
<button
|
||||
className="btn btn-sm btn-primary"
|
||||
onClick={() => handleUpdate(p.user_id)}
|
||||
{permissions.map((p) => {
|
||||
const isTeamBased = p.source === 'team';
|
||||
return (
|
||||
<tr key={p.id} className={isTeamBased ? 'team-access-row' : ''}>
|
||||
<td>{p.user_id}</td>
|
||||
<td>
|
||||
{editingUser === p.user_id && !isTeamBased ? (
|
||||
<select
|
||||
value={editLevel}
|
||||
onChange={(e) => setEditLevel(e.target.value as AccessLevel)}
|
||||
disabled={submitting}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-sm"
|
||||
onClick={cancelEdit}
|
||||
<option value="read">Read</option>
|
||||
<option value="write">Write</option>
|
||||
<option value="admin">Admin</option>
|
||||
</select>
|
||||
) : (
|
||||
<span className={`access-badge access-badge--${p.level}`}>
|
||||
{p.level}
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
{isTeamBased ? (
|
||||
<span className="access-source access-source--team" title={`Team role: ${p.team_role}`}>
|
||||
Team: {p.team_slug}
|
||||
</span>
|
||||
) : (
|
||||
<span className="access-source access-source--explicit">
|
||||
Explicit
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td>{new Date(p.created_at).toLocaleDateString()}</td>
|
||||
<td>
|
||||
{editingUser === p.user_id && !isTeamBased ? (
|
||||
<input
|
||||
type="date"
|
||||
value={editExpiresAt}
|
||||
onChange={(e) => setEditExpiresAt(e.target.value)}
|
||||
disabled={submitting}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<button
|
||||
className="btn btn-sm"
|
||||
onClick={() => startEdit(p)}
|
||||
disabled={submitting}
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-sm btn-danger"
|
||||
onClick={() => handleRevoke(p.user_id)}
|
||||
disabled={submitting}
|
||||
>
|
||||
Revoke
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
min={new Date().toISOString().split('T')[0]}
|
||||
/>
|
||||
) : (
|
||||
formatExpiration(p.expires_at)
|
||||
)}
|
||||
</td>
|
||||
<td className="actions">
|
||||
{isTeamBased ? (
|
||||
<span className="text-muted" title="Manage access via team settings">
|
||||
Via team
|
||||
</span>
|
||||
) : editingUser === p.user_id ? (
|
||||
<>
|
||||
<button
|
||||
className="btn btn-sm btn-primary"
|
||||
onClick={() => handleUpdate(p.user_id)}
|
||||
disabled={submitting}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-sm"
|
||||
onClick={cancelEdit}
|
||||
disabled={submitting}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<button
|
||||
className="btn btn-sm"
|
||||
onClick={() => startEdit(p)}
|
||||
disabled={submitting}
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-sm btn-danger"
|
||||
onClick={() => handleRevoke(p.user_id)}
|
||||
disabled={submitting}
|
||||
>
|
||||
Revoke
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user