avl_console/
teams.rs

1use axum::{
2    extract::State,
3    response::{Html, IntoResponse, Json},
4    routing::{get, post},
5    Router,
6};
7use serde::{Deserialize, Serialize};
8use std::collections::{HashMap, HashSet};
9use std::sync::Arc;
10
11use crate::{error::ConsoleError, state::ConsoleState};
12
13/// Team Management UI HTML
14const TEAM_MANAGEMENT_HTML: &str = r#"<!DOCTYPE html>
15<html lang="pt-BR">
16<head>
17    <meta charset="UTF-8">
18    <meta name="viewport" content="width=device-width, initial-scale=1.0">
19    <title>Team Management & RBAC - AVL Console</title>
20    <style>
21        * { margin: 0; padding: 0; box-sizing: border-box; }
22        body {
23            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
24            background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
25            min-height: 100vh;
26            padding: 20px;
27        }
28        .container {
29            max-width: 1600px;
30            margin: 0 auto;
31            background: white;
32            border-radius: 20px;
33            padding: 40px;
34            box-shadow: 0 20px 60px rgba(0,0,0,0.3);
35        }
36        h1 {
37            color: #f5576c;
38            margin-bottom: 10px;
39            font-size: 36px;
40        }
41        .subtitle {
42            color: #666;
43            margin-bottom: 30px;
44            font-size: 16px;
45        }
46        .tabs {
47            display: flex;
48            gap: 10px;
49            margin-bottom: 30px;
50            border-bottom: 2px solid #e9ecef;
51        }
52        .tab {
53            padding: 15px 30px;
54            background: none;
55            border: none;
56            border-bottom: 3px solid transparent;
57            cursor: pointer;
58            font-weight: 600;
59            color: #6c757d;
60            transition: all 0.2s;
61            font-size: 15px;
62        }
63        .tab.active {
64            color: #f5576c;
65            border-bottom-color: #f5576c;
66        }
67        .tab:hover {
68            color: #f5576c;
69        }
70        .tab-content {
71            display: none;
72        }
73        .tab-content.active {
74            display: block;
75        }
76        .action-bar {
77            display: flex;
78            justify-content: space-between;
79            align-items: center;
80            margin-bottom: 25px;
81        }
82        .search-box {
83            flex: 1;
84            max-width: 400px;
85            position: relative;
86        }
87        .search-box input {
88            width: 100%;
89            padding: 12px 40px 12px 15px;
90            border: 2px solid #dee2e6;
91            border-radius: 10px;
92            font-size: 14px;
93        }
94        .search-box input:focus {
95            outline: none;
96            border-color: #f5576c;
97        }
98        .search-icon {
99            position: absolute;
100            right: 15px;
101            top: 50%;
102            transform: translateY(-50%);
103            color: #adb5bd;
104        }
105        .btn {
106            padding: 12px 24px;
107            border: none;
108            border-radius: 10px;
109            font-weight: 600;
110            cursor: pointer;
111            transition: all 0.2s;
112            font-size: 14px;
113        }
114        .btn-primary {
115            background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
116            color: white;
117        }
118        .btn-primary:hover {
119            transform: translateY(-2px);
120            box-shadow: 0 6px 20px rgba(245, 87, 108, 0.4);
121        }
122        .btn-secondary {
123            background: white;
124            color: #f5576c;
125            border: 2px solid #f5576c;
126        }
127        .btn-secondary:hover {
128            background: #fff5f7;
129        }
130        .team-grid {
131            display: grid;
132            grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
133            gap: 20px;
134        }
135        .team-card {
136            background: white;
137            border: 2px solid #e9ecef;
138            border-radius: 16px;
139            padding: 25px;
140            transition: all 0.2s;
141            cursor: pointer;
142        }
143        .team-card:hover {
144            border-color: #f5576c;
145            box-shadow: 0 8px 24px rgba(245, 87, 108, 0.15);
146            transform: translateY(-4px);
147        }
148        .team-header {
149            display: flex;
150            align-items: center;
151            gap: 15px;
152            margin-bottom: 15px;
153        }
154        .team-icon {
155            width: 50px;
156            height: 50px;
157            border-radius: 12px;
158            background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
159            display: flex;
160            align-items: center;
161            justify-content: center;
162            font-size: 24px;
163        }
164        .team-info {
165            flex: 1;
166        }
167        .team-name {
168            font-weight: 600;
169            font-size: 18px;
170            color: #495057;
171        }
172        .team-members {
173            font-size: 13px;
174            color: #6c757d;
175        }
176        .team-description {
177            color: #6c757d;
178            font-size: 14px;
179            margin-bottom: 15px;
180            line-height: 1.5;
181        }
182        .team-roles {
183            display: flex;
184            flex-wrap: wrap;
185            gap: 6px;
186        }
187        .role-badge {
188            padding: 4px 10px;
189            border-radius: 6px;
190            font-size: 11px;
191            font-weight: 600;
192            background: #f8f9fa;
193            color: #495057;
194        }
195        .users-table {
196            width: 100%;
197            border-collapse: collapse;
198            background: white;
199        }
200        .users-table thead {
201            background: #f8f9fa;
202        }
203        .users-table th {
204            padding: 15px;
205            text-align: left;
206            font-size: 13px;
207            font-weight: 600;
208            color: #495057;
209            text-transform: uppercase;
210            letter-spacing: 0.5px;
211        }
212        .users-table td {
213            padding: 15px;
214            border-bottom: 1px solid #e9ecef;
215            font-size: 14px;
216        }
217        .users-table tbody tr:hover {
218            background: #f8f9fa;
219        }
220        .user-avatar {
221            width: 40px;
222            height: 40px;
223            border-radius: 50%;
224            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
225            display: flex;
226            align-items: center;
227            justify-content: center;
228            color: white;
229            font-weight: 600;
230            font-size: 16px;
231        }
232        .user-info {
233            display: flex;
234            align-items: center;
235            gap: 12px;
236        }
237        .user-details {
238            display: flex;
239            flex-direction: column;
240        }
241        .user-name {
242            font-weight: 600;
243            color: #495057;
244        }
245        .user-email {
246            font-size: 12px;
247            color: #6c757d;
248        }
249        .status-badge {
250            padding: 6px 12px;
251            border-radius: 8px;
252            font-size: 12px;
253            font-weight: 600;
254        }
255        .status-active {
256            background: #d4edda;
257            color: #155724;
258        }
259        .status-inactive {
260            background: #f8d7da;
261            color: #721c24;
262        }
263        .permissions-grid {
264            display: grid;
265            grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
266            gap: 15px;
267        }
268        .permission-card {
269            background: #f8f9fa;
270            border: 2px solid #e9ecef;
271            border-radius: 12px;
272            padding: 20px;
273        }
274        .permission-card.enabled {
275            border-color: #f5576c;
276            background: #fff5f7;
277        }
278        .permission-title {
279            font-weight: 600;
280            color: #495057;
281            margin-bottom: 8px;
282            display: flex;
283            align-items: center;
284            gap: 10px;
285        }
286        .permission-desc {
287            font-size: 13px;
288            color: #6c757d;
289            margin-bottom: 12px;
290        }
291        .toggle-switch {
292            position: relative;
293            width: 50px;
294            height: 26px;
295        }
296        .toggle-switch input {
297            opacity: 0;
298            width: 0;
299            height: 0;
300        }
301        .toggle-slider {
302            position: absolute;
303            cursor: pointer;
304            top: 0;
305            left: 0;
306            right: 0;
307            bottom: 0;
308            background-color: #ccc;
309            transition: 0.4s;
310            border-radius: 26px;
311        }
312        .toggle-slider:before {
313            position: absolute;
314            content: "";
315            height: 18px;
316            width: 18px;
317            left: 4px;
318            bottom: 4px;
319            background-color: white;
320            transition: 0.4s;
321            border-radius: 50%;
322        }
323        .toggle-switch input:checked + .toggle-slider {
324            background-color: #f5576c;
325        }
326        .toggle-switch input:checked + .toggle-slider:before {
327            transform: translateX(24px);
328        }
329        .modal {
330            display: none;
331            position: fixed;
332            top: 0;
333            left: 0;
334            right: 0;
335            bottom: 0;
336            background: rgba(0,0,0,0.6);
337            align-items: center;
338            justify-content: center;
339            z-index: 1000;
340        }
341        .modal.active {
342            display: flex;
343        }
344        .modal-content {
345            background: white;
346            border-radius: 20px;
347            padding: 40px;
348            max-width: 600px;
349            width: 90%;
350            max-height: 90vh;
351            overflow-y: auto;
352        }
353        .modal-header {
354            margin-bottom: 25px;
355        }
356        .modal-title {
357            font-size: 24px;
358            font-weight: 600;
359            color: #495057;
360        }
361        .form-group {
362            margin-bottom: 20px;
363        }
364        .form-group label {
365            display: block;
366            font-weight: 600;
367            color: #495057;
368            margin-bottom: 8px;
369            font-size: 14px;
370        }
371        .form-group input,
372        .form-group select,
373        .form-group textarea {
374            width: 100%;
375            padding: 12px;
376            border: 2px solid #dee2e6;
377            border-radius: 8px;
378            font-size: 14px;
379        }
380        .form-group input:focus,
381        .form-group select:focus,
382        .form-group textarea:focus {
383            outline: none;
384            border-color: #f5576c;
385        }
386        .form-actions {
387            display: flex;
388            gap: 10px;
389            justify-content: flex-end;
390            margin-top: 30px;
391        }
392        .audit-log {
393            background: #f8f9fa;
394            border-radius: 12px;
395            padding: 20px;
396        }
397        .log-entry {
398            padding: 15px;
399            border-bottom: 1px solid #e9ecef;
400            display: flex;
401            align-items: start;
402            gap: 15px;
403        }
404        .log-entry:last-child {
405            border-bottom: none;
406        }
407        .log-icon {
408            font-size: 24px;
409        }
410        .log-content {
411            flex: 1;
412        }
413        .log-action {
414            font-weight: 600;
415            color: #495057;
416            margin-bottom: 4px;
417        }
418        .log-details {
419            font-size: 13px;
420            color: #6c757d;
421        }
422        .log-time {
423            font-size: 12px;
424            color: #adb5bd;
425        }
426    </style>
427</head>
428<body>
429    <div class="container">
430        <h1>👥 Team Management & RBAC</h1>
431        <p class="subtitle">Enterprise-grade access control and team collaboration</p>
432
433        <div class="tabs">
434            <button class="tab active" onclick="switchTab('teams')">Teams</button>
435            <button class="tab" onclick="switchTab('users')">Users</button>
436            <button class="tab" onclick="switchTab('roles')">Roles & Permissions</button>
437            <button class="tab" onclick="switchTab('audit')">Audit Log</button>
438        </div>
439
440        <!-- Teams Tab -->
441        <div id="teams-tab" class="tab-content active">
442            <div class="action-bar">
443                <div class="search-box">
444                    <input type="text" placeholder="Search teams..." onkeyup="searchTeams(this.value)">
445                    <span class="search-icon">🔍</span>
446                </div>
447                <button class="btn btn-primary" onclick="openModal('newTeam')">➕ New Team</button>
448            </div>
449            <div class="team-grid" id="teamsGrid"></div>
450        </div>
451
452        <!-- Users Tab -->
453        <div id="users-tab" class="tab-content">
454            <div class="action-bar">
455                <div class="search-box">
456                    <input type="text" placeholder="Search users..." onkeyup="searchUsers(this.value)">
457                    <span class="search-icon">🔍</span>
458                </div>
459                <button class="btn btn-primary" onclick="openModal('newUser')">➕ Invite User</button>
460            </div>
461            <table class="users-table">
462                <thead>
463                    <tr>
464                        <th>User</th>
465                        <th>Role</th>
466                        <th>Teams</th>
467                        <th>Status</th>
468                        <th>Last Active</th>
469                        <th>Actions</th>
470                    </tr>
471                </thead>
472                <tbody id="usersTable"></tbody>
473            </table>
474        </div>
475
476        <!-- Roles Tab -->
477        <div id="roles-tab" class="tab-content">
478            <div class="action-bar">
479                <h2 style="color: #495057; margin: 0;">Configure Role Permissions</h2>
480                <select id="roleSelect" onchange="loadPermissions(this.value)" style="padding: 10px; border-radius: 8px; border: 2px solid #dee2e6;">
481                    <option value="admin">Admin</option>
482                    <option value="developer">Developer</option>
483                    <option value="viewer">Viewer</option>
484                </select>
485            </div>
486            <div class="permissions-grid" id="permissionsGrid"></div>
487        </div>
488
489        <!-- Audit Log Tab -->
490        <div id="audit-tab" class="tab-content">
491            <div class="audit-log" id="auditLog"></div>
492        </div>
493    </div>
494
495    <!-- Modals -->
496    <div id="newTeamModal" class="modal">
497        <div class="modal-content">
498            <div class="modal-header">
499                <h2 class="modal-title">Create New Team</h2>
500            </div>
501            <form onsubmit="createTeam(event)">
502                <div class="form-group">
503                    <label>Team Name</label>
504                    <input type="text" name="name" required placeholder="Engineering, Design, Marketing...">
505                </div>
506                <div class="form-group">
507                    <label>Description</label>
508                    <textarea name="description" rows="3" placeholder="Brief description of the team's purpose"></textarea>
509                </div>
510                <div class="form-group">
511                    <label>Default Role</label>
512                    <select name="default_role">
513                        <option value="developer">Developer</option>
514                        <option value="viewer">Viewer</option>
515                    </select>
516                </div>
517                <div class="form-actions">
518                    <button type="button" class="btn btn-secondary" onclick="closeModal('newTeam')">Cancel</button>
519                    <button type="submit" class="btn btn-primary">Create Team</button>
520                </div>
521            </form>
522        </div>
523    </div>
524
525    <div id="newUserModal" class="modal">
526        <div class="modal-content">
527            <div class="modal-header">
528                <h2 class="modal-title">Invite User</h2>
529            </div>
530            <form onsubmit="inviteUser(event)">
531                <div class="form-group">
532                    <label>Email</label>
533                    <input type="email" name="email" required placeholder="user@company.com">
534                </div>
535                <div class="form-group">
536                    <label>Role</label>
537                    <select name="role">
538                        <option value="developer">Developer</option>
539                        <option value="viewer">Viewer</option>
540                        <option value="admin">Admin</option>
541                    </select>
542                </div>
543                <div class="form-group">
544                    <label>Teams</label>
545                    <select name="teams" multiple style="height: 100px;">
546                        <option value="team_1">Engineering</option>
547                        <option value="team_2">Design</option>
548                        <option value="team_3">Marketing</option>
549                    </select>
550                </div>
551                <div class="form-actions">
552                    <button type="button" class="btn btn-secondary" onclick="closeModal('newUser')">Cancel</button>
553                    <button type="submit" class="btn btn-primary">Send Invitation</button>
554                </div>
555            </form>
556        </div>
557    </div>
558
559    <script>
560        let currentTab = 'teams';
561
562        async function loadTeams() {
563            try {
564                const response = await fetch('/teams/list');
565                const data = await response.json();
566                renderTeams(data.teams);
567            } catch (error) {
568                console.error('Failed to load teams:', error);
569            }
570        }
571
572        async function loadUsers() {
573            try {
574                const response = await fetch('/teams/users');
575                const data = await response.json();
576                renderUsers(data.users);
577            } catch (error) {
578                console.error('Failed to load users:', error);
579            }
580        }
581
582        async function loadAuditLog() {
583            try {
584                const response = await fetch('/teams/audit');
585                const data = await response.json();
586                renderAuditLog(data.logs);
587            } catch (error) {
588                console.error('Failed to load audit log:', error);
589            }
590        }
591
592        function renderTeams(teams) {
593            const grid = document.getElementById('teamsGrid');
594            grid.innerHTML = teams.map(team => `
595                <div class="team-card" onclick="viewTeam('${team.id}')">
596                    <div class="team-header">
597                        <div class="team-icon">${team.icon}</div>
598                        <div class="team-info">
599                            <div class="team-name">${team.name}</div>
600                            <div class="team-members">${team.member_count} members</div>
601                        </div>
602                    </div>
603                    <div class="team-description">${team.description}</div>
604                    <div class="team-roles">
605                        ${team.roles.map(role => `<span class="role-badge">${role}</span>`).join('')}
606                    </div>
607                </div>
608            `).join('');
609        }
610
611        function renderUsers(users) {
612            const table = document.getElementById('usersTable');
613            table.innerHTML = users.map(user => `
614                <tr>
615                    <td>
616                        <div class="user-info">
617                            <div class="user-avatar">${user.name.charAt(0)}</div>
618                            <div class="user-details">
619                                <div class="user-name">${user.name}</div>
620                                <div class="user-email">${user.email}</div>
621                            </div>
622                        </div>
623                    </td>
624                    <td><span class="role-badge">${user.role}</span></td>
625                    <td>${user.teams.join(', ')}</td>
626                    <td><span class="status-badge status-${user.status}">${user.status}</span></td>
627                    <td>${user.last_active}</td>
628                    <td>
629                        <button class="btn-secondary" style="padding: 6px 12px; font-size: 12px;" onclick="editUser('${user.id}')">Edit</button>
630                    </td>
631                </tr>
632            `).join('');
633        }
634
635        function renderAuditLog(logs) {
636            const logDiv = document.getElementById('auditLog');
637            logDiv.innerHTML = logs.map(log => `
638                <div class="log-entry">
639                    <div class="log-icon">${log.icon}</div>
640                    <div class="log-content">
641                        <div class="log-action">${log.action}</div>
642                        <div class="log-details">${log.details}</div>
643                        <div class="log-time">${log.time}</div>
644                    </div>
645                </div>
646            `).join('');
647        }
648
649        function loadPermissions(role) {
650            const permissions = {
651                admin: [
652                    {name: 'Manage Users', desc: 'Create, edit, and delete users', enabled: true},
653                    {name: 'Manage Teams', desc: 'Create and configure teams', enabled: true},
654                    {name: 'View Billing', desc: 'Access billing and invoices', enabled: true},
655                    {name: 'Manage Database', desc: 'Full database access', enabled: true},
656                    {name: 'Manage Storage', desc: 'Full storage access', enabled: true},
657                    {name: 'View Logs', desc: 'Access system logs', enabled: true},
658                ],
659                developer: [
660                    {name: 'Manage Users', desc: 'Create, edit, and delete users', enabled: false},
661                    {name: 'Manage Teams', desc: 'Create and configure teams', enabled: false},
662                    {name: 'View Billing', desc: 'Access billing and invoices', enabled: false},
663                    {name: 'Manage Database', desc: 'Full database access', enabled: true},
664                    {name: 'Manage Storage', desc: 'Full storage access', enabled: true},
665                    {name: 'View Logs', desc: 'Access system logs', enabled: true},
666                ],
667                viewer: [
668                    {name: 'Manage Users', desc: 'Create, edit, and delete users', enabled: false},
669                    {name: 'Manage Teams', desc: 'Create and configure teams', enabled: false},
670                    {name: 'View Billing', desc: 'Access billing and invoices', enabled: false},
671                    {name: 'Manage Database', desc: 'Full database access', enabled: false},
672                    {name: 'Manage Storage', desc: 'Full storage access', enabled: false},
673                    {name: 'View Logs', desc: 'Access system logs', enabled: true},
674                ]
675            };
676
677            const grid = document.getElementById('permissionsGrid');
678            grid.innerHTML = permissions[role].map((perm, idx) => `
679                <div class="permission-card ${perm.enabled ? 'enabled' : ''}">
680                    <div class="permission-title">
681                        <span>${perm.name}</span>
682                        <label class="toggle-switch" style="margin-left: auto;">
683                            <input type="checkbox" ${perm.enabled ? 'checked' : ''} onchange="togglePermission('${role}', ${idx}, this.checked)">
684                            <span class="toggle-slider"></span>
685                        </label>
686                    </div>
687                    <div class="permission-desc">${perm.desc}</div>
688                </div>
689            `).join('');
690        }
691
692        function switchTab(tab) {
693            currentTab = tab;
694            document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
695            document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
696            event.target.classList.add('active');
697            document.getElementById(`${tab}-tab`).classList.add('active');
698
699            if (tab === 'teams') loadTeams();
700            else if (tab === 'users') loadUsers();
701            else if (tab === 'roles') loadPermissions('admin');
702            else if (tab === 'audit') loadAuditLog();
703        }
704
705        function openModal(modal) {
706            document.getElementById(`${modal}Modal`).classList.add('active');
707        }
708
709        function closeModal(modal) {
710            document.getElementById(`${modal}Modal`).classList.remove('active');
711        }
712
713        async function createTeam(e) {
714            e.preventDefault();
715            const formData = new FormData(e.target);
716            const data = Object.fromEntries(formData);
717
718            try {
719                await fetch('/teams/create', {
720                    method: 'POST',
721                    headers: {'Content-Type': 'application/json'},
722                    body: JSON.stringify(data)
723                });
724                closeModal('newTeam');
725                loadTeams();
726            } catch (error) {
727                console.error('Failed to create team:', error);
728            }
729        }
730
731        async function inviteUser(e) {
732            e.preventDefault();
733            const formData = new FormData(e.target);
734            const data = Object.fromEntries(formData);
735
736            try {
737                await fetch('/teams/invite', {
738                    method: 'POST',
739                    headers: {'Content-Type': 'application/json'},
740                    body: JSON.stringify(data)
741                });
742                closeModal('newUser');
743                loadUsers();
744            } catch (error) {
745                console.error('Failed to invite user:', error);
746            }
747        }
748
749        function searchTeams(query) {
750            // Implement search
751        }
752
753        function searchUsers(query) {
754            // Implement search
755        }
756
757        function viewTeam(id) {
758            // Implement team details view
759        }
760
761        function editUser(id) {
762            // Implement user edit
763        }
764
765        function togglePermission(role, idx, enabled) {
766            // Implement permission toggle
767            console.log(`Toggle ${role} permission ${idx}: ${enabled}`);
768        }
769
770        // Initialize
771        loadTeams();
772    </script>
773</body>
774</html>"#;
775
776/// Role enum
777#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
778#[serde(rename_all = "lowercase")]
779pub enum Role {
780    Admin,
781    Developer,
782    Viewer,
783    Custom(String),
784}
785
786impl Role {
787    pub fn default_permissions(&self) -> HashSet<Permission> {
788        match self {
789            Role::Admin => HashSet::from([
790                Permission::ManageUsers,
791                Permission::ManageTeams,
792                Permission::ViewBilling,
793                Permission::ManageDatabase,
794                Permission::ManageStorage,
795                Permission::ViewLogs,
796                Permission::ManageSettings,
797            ]),
798            Role::Developer => HashSet::from([
799                Permission::ManageDatabase,
800                Permission::ManageStorage,
801                Permission::ViewLogs,
802            ]),
803            Role::Viewer => HashSet::from([Permission::ViewLogs]),
804            Role::Custom(_) => HashSet::new(),
805        }
806    }
807}
808
809/// Permission enum
810#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
811pub enum Permission {
812    ManageUsers,
813    ManageTeams,
814    ViewBilling,
815    ManageDatabase,
816    ManageStorage,
817    ViewLogs,
818    ManageSettings,
819}
820
821/// Team structure
822#[derive(Debug, Serialize, Deserialize, Clone)]
823pub struct Team {
824    pub id: String,
825    pub name: String,
826    pub description: String,
827    pub icon: String,
828    pub member_count: usize,
829    pub roles: Vec<String>,
830    pub created_at: String,
831}
832
833/// User structure
834#[derive(Debug, Serialize, Deserialize, Clone)]
835pub struct User {
836    pub id: String,
837    pub name: String,
838    pub email: String,
839    pub role: Role,
840    pub teams: Vec<String>,
841    pub status: String,
842    pub last_active: String,
843    pub permissions: HashSet<Permission>,
844}
845
846/// Audit log entry
847#[derive(Debug, Serialize, Deserialize, Clone)]
848pub struct AuditLogEntry {
849    pub id: String,
850    pub action: String,
851    pub details: String,
852    pub icon: String,
853    pub user_id: String,
854    pub time: String,
855}
856
857/// Teams list response
858#[derive(Debug, Serialize)]
859struct TeamsResponse {
860    teams: Vec<Team>,
861}
862
863/// Users list response
864#[derive(Debug, Serialize)]
865struct UsersResponse {
866    users: Vec<User>,
867}
868
869/// Audit log response
870#[derive(Debug, Serialize)]
871struct AuditLogResponse {
872    logs: Vec<AuditLogEntry>,
873}
874
875/// Team management UI
876async fn team_management_ui() -> impl IntoResponse {
877    Html(TEAM_MANAGEMENT_HTML)
878}
879
880/// List all teams
881async fn list_teams(
882    State(state): State<Arc<ConsoleState>>,
883) -> Result<Json<TeamsResponse>, ConsoleError> {
884    // Mock data - in production, load from database
885    let teams = vec![
886        Team {
887            id: "team_1".to_string(),
888            name: "Engineering".to_string(),
889            description: "Core development team building AVL Platform features".to_string(),
890            icon: "⚙️".to_string(),
891            member_count: 12,
892            roles: vec!["Admin".to_string(), "Developer".to_string()],
893            created_at: "2024-01-15".to_string(),
894        },
895        Team {
896            id: "team_2".to_string(),
897            name: "Design".to_string(),
898            description: "UI/UX designers crafting beautiful experiences".to_string(),
899            icon: "🎨".to_string(),
900            member_count: 5,
901            roles: vec!["Developer".to_string(), "Viewer".to_string()],
902            created_at: "2024-02-01".to_string(),
903        },
904        Team {
905            id: "team_3".to_string(),
906            name: "Marketing".to_string(),
907            description: "Growth and customer success team".to_string(),
908            icon: "📊".to_string(),
909            member_count: 8,
910            roles: vec!["Viewer".to_string()],
911            created_at: "2024-02-15".to_string(),
912        },
913    ];
914
915    Ok(Json(TeamsResponse { teams }))
916}
917
918/// List all users
919async fn list_users(
920    State(state): State<Arc<ConsoleState>>,
921) -> Result<Json<UsersResponse>, ConsoleError> {
922    // Mock data - in production, load from database
923    let users = vec![
924        User {
925            id: "user_1".to_string(),
926            name: "Alice Johnson".to_string(),
927            email: "alice@company.com".to_string(),
928            role: Role::Admin,
929            teams: vec!["Engineering".to_string()],
930            status: "active".to_string(),
931            last_active: "2 minutes ago".to_string(),
932            permissions: Role::Admin.default_permissions(),
933        },
934        User {
935            id: "user_2".to_string(),
936            name: "Bob Smith".to_string(),
937            email: "bob@company.com".to_string(),
938            role: Role::Developer,
939            teams: vec!["Engineering".to_string(), "Design".to_string()],
940            status: "active".to_string(),
941            last_active: "1 hour ago".to_string(),
942            permissions: Role::Developer.default_permissions(),
943        },
944        User {
945            id: "user_3".to_string(),
946            name: "Carol White".to_string(),
947            email: "carol@company.com".to_string(),
948            role: Role::Viewer,
949            teams: vec!["Marketing".to_string()],
950            status: "active".to_string(),
951            last_active: "3 hours ago".to_string(),
952            permissions: Role::Viewer.default_permissions(),
953        },
954    ];
955
956    Ok(Json(UsersResponse { users }))
957}
958
959/// Get audit log
960async fn get_audit_log(
961    State(state): State<Arc<ConsoleState>>,
962) -> Result<Json<AuditLogResponse>, ConsoleError> {
963    // Mock data - in production, load from database
964    let logs = vec![
965        AuditLogEntry {
966            id: "log_1".to_string(),
967            action: "User Invited".to_string(),
968            details: "alice@company.com invited bob@company.com to Engineering team".to_string(),
969            icon: "✉️".to_string(),
970            user_id: "user_1".to_string(),
971            time: "2 hours ago".to_string(),
972        },
973        AuditLogEntry {
974            id: "log_2".to_string(),
975            action: "Permission Changed".to_string(),
976            details: "bob@company.com role changed from Viewer to Developer".to_string(),
977            icon: "🔐".to_string(),
978            user_id: "user_1".to_string(),
979            time: "5 hours ago".to_string(),
980        },
981        AuditLogEntry {
982            id: "log_3".to_string(),
983            action: "Team Created".to_string(),
984            details: "New team 'Design' created with 5 members".to_string(),
985            icon: "🎨".to_string(),
986            user_id: "user_1".to_string(),
987            time: "1 day ago".to_string(),
988        },
989    ];
990
991    Ok(Json(AuditLogResponse { logs }))
992}
993
994/// Create a new team
995async fn create_team(
996    State(state): State<Arc<ConsoleState>>,
997    Json(payload): Json<Team>,
998) -> Result<Json<Team>, ConsoleError> {
999    // In production, save to database
1000    Ok(Json(payload))
1001}
1002
1003/// Invite a user
1004async fn invite_user(
1005    State(state): State<Arc<ConsoleState>>,
1006    Json(payload): Json<User>,
1007) -> Result<Json<User>, ConsoleError> {
1008    // In production, send invitation email and save to database
1009    Ok(Json(payload))
1010}
1011
1012/// Check if user has permission
1013pub fn has_permission(user: &User, permission: &Permission) -> bool {
1014    user.permissions.contains(permission)
1015}
1016
1017/// Create router for team management
1018pub fn router(state: Arc<ConsoleState>) -> Router {
1019    Router::new()
1020        .route("/", get(team_management_ui))
1021        .route("/list", get(list_teams))
1022        .route("/users", get(list_users))
1023        .route("/audit", get(get_audit_log))
1024        .route("/create", post(create_team))
1025        .route("/invite", post(invite_user))
1026        .with_state(state)
1027}
1028
1029#[cfg(test)]
1030mod tests {
1031    use super::*;
1032
1033    #[test]
1034    fn test_admin_permissions() {
1035        let perms = Role::Admin.default_permissions();
1036        assert!(perms.contains(&Permission::ManageUsers));
1037        assert!(perms.contains(&Permission::ManageDatabase));
1038    }
1039
1040    #[test]
1041    fn test_developer_permissions() {
1042        let perms = Role::Developer.default_permissions();
1043        assert!(!perms.contains(&Permission::ManageUsers));
1044        assert!(perms.contains(&Permission::ManageDatabase));
1045    }
1046
1047    #[test]
1048    fn test_viewer_permissions() {
1049        let perms = Role::Viewer.default_permissions();
1050        assert!(!perms.contains(&Permission::ManageUsers));
1051        assert!(perms.contains(&Permission::ViewLogs));
1052    }
1053}