378 lines
13 KiB
Markdown
378 lines
13 KiB
Markdown
# Token Management UI Enhancement
|
|
|
|
**Priority**: P3 (Enhancement)
|
|
**Source Reference**: From needs.md line 137
|
|
**Status**: Ready for Implementation
|
|
|
|
## Problem Statement
|
|
|
|
Administrators can create and view registration tokens but cannot delete used or expired tokens from the web interface. Token cleanup requires manual database operations or calling cleanup endpoints, creating operational friction and making token housekeeping difficult.
|
|
|
|
## Feature Description
|
|
|
|
Enhance the Token Management UI to include deletion functionality for registration tokens, allowing administrators to clean up used, expired, or revoked tokens directly from the web interface with proper confirmation dialogs and bulk operations.
|
|
|
|
## Acceptance Criteria
|
|
|
|
1. Delete button for individual tokens in Token Management page
|
|
2. Confirmation dialog before token deletion
|
|
3. Bulk deletion capability with checkbox selection
|
|
4. Visual indication of token status (active, used, expired, revoked)
|
|
5. Filter tokens by status for easier cleanup
|
|
6. Audit trail for token deletions
|
|
7. Safe deletion prevention for tokens with active agent dependencies
|
|
8. Success/error feedback for deletion operations
|
|
|
|
## Technical Approach
|
|
|
|
### 1. Backend API Enhancement
|
|
|
|
**Token Deletion Endpoint** (`aggregator-server/internal/api/handlers/token_management.go`):
|
|
```go
|
|
// DELETE /api/v1/tokens/:id
|
|
func (h *TokenHandler) DeleteToken(c *gin.Context) {
|
|
tokenID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid token ID"})
|
|
return
|
|
}
|
|
|
|
// Check if token has active agents
|
|
activeAgents, err := h.tokenQueries.GetActiveAgentCount(tokenID)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
if activeAgents > 0 {
|
|
c.JSON(http.StatusConflict, gin.H{
|
|
"error": "Cannot delete token with active agents",
|
|
"active_agents": activeAgents,
|
|
})
|
|
return
|
|
}
|
|
|
|
// Delete token and related usage records
|
|
err = h.tokenQueries.DeleteToken(tokenID)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Log deletion for audit trail
|
|
log.Printf("Token %s deleted by user %s", tokenID, getUserID(c))
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "Token deleted successfully"})
|
|
}
|
|
```
|
|
|
|
**Database Operations** (`aggregator-server/internal/database/queries/registration_tokens.go`):
|
|
```sql
|
|
-- Check for active agents using token
|
|
SELECT COUNT(*) FROM agents
|
|
WHERE registration_token_id = $1
|
|
AND last_seen > NOW() - INTERVAL '24 hours';
|
|
|
|
-- Delete token and usage records
|
|
DELETE FROM registration_token_usage WHERE token_id = $1;
|
|
DELETE FROM registration_tokens WHERE id = $1;
|
|
```
|
|
|
|
### 2. Frontend Token Management Enhancement
|
|
|
|
**Enhanced Token Table** (`aggregator-web/src/pages/TokenManagement.tsx`):
|
|
```typescript
|
|
interface Token {
|
|
id: string;
|
|
token: string;
|
|
max_seats: number;
|
|
seats_used: number;
|
|
status: 'active' | 'used' | 'expired' | 'revoked';
|
|
created_at: string;
|
|
expires_at: string;
|
|
active_agents: number;
|
|
}
|
|
|
|
const TokenManagement: React.FC = () => {
|
|
const [tokens, setTokens] = useState<Token[]>([]);
|
|
const [selectedTokens, setSelectedTokens] = useState<string[]>([]);
|
|
const [filter, setFilter] = useState<string>('all');
|
|
|
|
const handleDeleteToken = async (tokenId: string) => {
|
|
if (!window.confirm('Are you sure you want to delete this token? This action cannot be undone.')) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await api.delete(`/tokens/${tokenId}`);
|
|
setTokens(tokens.filter(token => token.id !== tokenId));
|
|
showToast('Token deleted successfully', 'success');
|
|
} catch (error) {
|
|
showToast(error.message, 'error');
|
|
}
|
|
};
|
|
|
|
const handleBulkDelete = async () => {
|
|
if (selectedTokens.length === 0) {
|
|
showToast('No tokens selected', 'warning');
|
|
return;
|
|
}
|
|
|
|
if (!window.confirm(`Are you sure you want to delete ${selectedTokens.length} token(s)?`)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await Promise.all(selectedTokens.map(tokenId => api.delete(`/tokens/${tokenId}`)));
|
|
setTokens(tokens.filter(token => !selectedTokens.includes(token.id)));
|
|
setSelectedTokens([]);
|
|
showToast(`${selectedTokens.length} token(s) deleted successfully`, 'success');
|
|
} catch (error) {
|
|
showToast('Some tokens could not be deleted', 'error');
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="token-management">
|
|
<div className="token-header">
|
|
<h2>Registration Tokens</h2>
|
|
<div className="token-actions">
|
|
<TokenFilter value={filter} onChange={setFilter} />
|
|
<BulkDeleteButton
|
|
selectedCount={selectedTokens.length}
|
|
onDelete={handleBulkDelete}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="token-table">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>
|
|
<input
|
|
type="checkbox"
|
|
onChange={(e) => {
|
|
if (e.target.checked) {
|
|
setSelectedTokens(tokens.map(t => t.id));
|
|
} else {
|
|
setSelectedTokens([]);
|
|
}
|
|
}}
|
|
/>
|
|
</th>
|
|
<th>Token</th>
|
|
<th>Status</th>
|
|
<th>Seats</th>
|
|
<th>Active Agents</th>
|
|
<th>Created</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{tokens.map(token => (
|
|
<tr key={token.id}>
|
|
<td>
|
|
<input
|
|
type="checkbox"
|
|
checked={selectedTokens.includes(token.id)}
|
|
onChange={(e) => {
|
|
if (e.target.checked) {
|
|
setSelectedTokens([...selectedTokens, token.id]);
|
|
} else {
|
|
setSelectedTokens(selectedTokens.filter(id => id !== token.id));
|
|
}
|
|
}}
|
|
/>
|
|
</td>
|
|
<td>
|
|
<code>{token.token.substring(0, 20)}...</code>
|
|
</td>
|
|
<td>
|
|
<TokenStatusBadge status={token.status} />
|
|
</td>
|
|
<td>{token.seats_used}/{token.max_seats}</td>
|
|
<td>{token.active_agents}</td>
|
|
<td>{formatDate(token.created_at)}</td>
|
|
<td>
|
|
<div className="token-actions">
|
|
<CopyTokenButton token={token.token} />
|
|
{token.status !== 'active' && token.active_agents === 0 && (
|
|
<DeleteButton
|
|
onDelete={() => handleDeleteToken(token.id)}
|
|
disabled={token.active_agents > 0}
|
|
/>
|
|
)}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
```
|
|
|
|
### 3. Token Status Management
|
|
|
|
**Token Status Calculation**:
|
|
```go
|
|
func (s *TokenService) GetTokenStatus(token RegistrationToken) string {
|
|
now := time.Now()
|
|
|
|
if token.ExpiresAt.Before(now) {
|
|
return "expired"
|
|
}
|
|
|
|
if token.Status == "revoked" {
|
|
return "revoked"
|
|
}
|
|
|
|
if token.SeatsUsed >= token.MaxSeats {
|
|
return "used"
|
|
}
|
|
|
|
return "active"
|
|
}
|
|
```
|
|
|
|
**Status Badge Component**:
|
|
```typescript
|
|
const TokenStatusBadge: React.FC<{ status: string }> = ({ status }) => {
|
|
const getStatusColor = (status: string) => {
|
|
switch (status) {
|
|
case 'active': return 'green';
|
|
case 'used': return 'blue';
|
|
case 'expired': return 'red';
|
|
case 'revoked': return 'orange';
|
|
default: return 'gray';
|
|
}
|
|
};
|
|
|
|
return (
|
|
<span className={`token-status-badge ${getStatusColor(status)}`}>
|
|
{status.charAt(0).toUpperCase() + status.slice(1)}
|
|
</span>
|
|
);
|
|
};
|
|
```
|
|
|
|
### 4. Safety Measures
|
|
|
|
**Active Agent Check**:
|
|
- Prevent deletion of tokens with agents that checked in within last 24 hours
|
|
- Show warning with number of active agents
|
|
- Require explicit confirmation for tokens with active agents
|
|
|
|
**Audit Logging**:
|
|
```go
|
|
type TokenAuditLog struct {
|
|
TokenID uuid.UUID `json:"token_id"`
|
|
Action string `json:"action"`
|
|
UserID string `json:"user_id"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
IPAddress string `json:"ip_address"`
|
|
}
|
|
```
|
|
|
|
## Definition of Done
|
|
|
|
- ✅ Token deletion API endpoint implemented with safety checks
|
|
- ✅ Individual token deletion in UI with confirmation dialog
|
|
- ✅ Bulk deletion functionality with checkbox selection
|
|
- ✅ Token status filtering and visual indicators
|
|
- ✅ Active agent dependency checking
|
|
- ✅ Audit trail for token operations
|
|
- ✅ Error handling and user feedback
|
|
- ✅ Responsive design for mobile viewing
|
|
|
|
## Test Plan
|
|
|
|
1. **Unit Tests**
|
|
- Token deletion safety checks
|
|
- Active agent count queries
|
|
- Status calculation logic
|
|
|
|
2. **Integration Tests**
|
|
- End-to-end token deletion flow
|
|
- Bulk deletion operations
|
|
- Error handling scenarios
|
|
|
|
3. **Safety Tests**
|
|
- Attempt to delete token with active agents
|
|
- Token status calculations for edge cases
|
|
- Audit trail verification
|
|
|
|
4. **User Acceptance Tests**
|
|
- Administrators can easily identify and delete unused tokens
|
|
- Safety mechanisms prevent accidental deletion of active tokens
|
|
- Clear feedback and confirmation for all operations
|
|
|
|
## Files to Modify
|
|
|
|
- `aggregator-server/internal/api/handlers/token_management.go` - Add deletion endpoint
|
|
- `aggregator-server/internal/database/queries/registration_tokens.go` - Add deletion queries
|
|
- `aggregator-web/src/pages/TokenManagement.tsx` - Add deletion UI
|
|
- `aggregator-web/src/components/TokenStatusBadge.tsx` - Status indicator component
|
|
- `aggregator-web/src/lib/api.ts` - API integration
|
|
|
|
## Token Deletion Rules
|
|
|
|
### Safe to Delete
|
|
- ✅ Tokens with status 'expired'
|
|
- ✅ Tokens with status 'used' and 0 active agents
|
|
- ✅ Tokens with status 'revoked' and 0 active agents
|
|
|
|
### Not Safe to Delete
|
|
- ❌ Tokens with status 'active'
|
|
- ❌ Tokens with any active agents (checked in within 24 hours)
|
|
- ❌ Tokens with pending agent registrations
|
|
|
|
### User Experience
|
|
1. **Warning Messages**: Clear indication of why token cannot be deleted
|
|
2. **Active Agent Count**: Show number of agents using the token
|
|
3. **Confirmation Dialog**: Explicit confirmation before deletion
|
|
4. **Success Feedback**: Clear confirmation of successful deletion
|
|
|
|
## Estimated Effort
|
|
|
|
- **Development**: 10-12 hours
|
|
- **Testing**: 6-8 hours
|
|
- **Review**: 3-4 hours
|
|
|
|
## Dependencies
|
|
|
|
- Existing token management system
|
|
- Agent registration and tracking
|
|
- User authentication and authorization
|
|
|
|
## Risk Assessment
|
|
|
|
**Low Risk** - Enhancement that adds new functionality without affecting existing systems. Safety checks prevent accidental deletion of critical tokens.
|
|
|
|
## Implementation Phases
|
|
|
|
### Phase 1: Backend API
|
|
1. Implement token deletion safety checks
|
|
2. Create deletion API endpoint
|
|
3. Add audit logging
|
|
|
|
### Phase 2: Frontend UI
|
|
1. Add delete buttons and confirmation dialogs
|
|
2. Implement bulk selection and deletion
|
|
3. Add status filtering
|
|
|
|
### Phase 3: Polish
|
|
1. Error handling and user feedback
|
|
2. Mobile responsiveness
|
|
3. Accessibility improvements
|
|
|
|
## Future Enhancements
|
|
|
|
1. **Token Expiration Automation**: Automatic cleanup of expired tokens
|
|
2. **Token Templates**: Pre-configured token settings for common use cases
|
|
3. **Token Usage Analytics**: Detailed analytics on token usage patterns
|
|
4. **Token Import/Export**: Bulk token management capabilities
|
|
5. **Token Permissions**: Role-based access to token management features |