556 lines
24 KiB
HTML
556 lines
24 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Teams — Nexus One AI</title>
|
|
<link rel="stylesheet" href="style.css?v=4">
|
|
<style>
|
|
.tm-layout { display: grid; grid-template-columns: 1fr 380px; min-height: calc(100vh - 64px); }
|
|
@media(max-width:960px){ .tm-layout { grid-template-columns: 1fr; } .tm-detail { display:none; } }
|
|
|
|
/* Left — team list */
|
|
.tm-list-panel { padding: 28px 28px; border-right: 1px solid var(--bdr); overflow-y: auto; }
|
|
.tm-header { display: flex; align-items: center; gap: 14px; margin-bottom: 22px; }
|
|
.tm-title { font-size: 22px; font-weight: 800; color: var(--ink); flex: 1; }
|
|
.tm-add-btn {
|
|
padding: 9px 18px; border-radius: 9px; background: var(--purple);
|
|
color: white; border: none; font-family: inherit; font-size: 13px;
|
|
font-weight: 700; cursor: pointer; transition: .15s;
|
|
}
|
|
.tm-add-btn:hover { background: #6d28d9; }
|
|
|
|
.tm-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 14px; }
|
|
.tm-card {
|
|
background: var(--navy2); border: 1.5px solid var(--bdr);
|
|
border-radius: 14px; padding: 18px 18px 14px; cursor: pointer;
|
|
transition: .15s;
|
|
}
|
|
.tm-card:hover, .tm-card.active { border-color: var(--purple); }
|
|
.tm-card.active { background: rgba(124,58,237,.04); }
|
|
.tm-card-header { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; }
|
|
.tm-avatar {
|
|
width: 42px; height: 42px; border-radius: 12px;
|
|
display: flex; align-items: center; justify-content: center;
|
|
font-size: 20px; font-weight: 800; color: white; flex-shrink: 0;
|
|
}
|
|
.tm-card-name { font-size: 15px; font-weight: 800; color: var(--ink); }
|
|
.tm-card-desc { font-size: 12px; color: var(--lt); margin-top: 2px; }
|
|
.tm-card-stats { display: flex; gap: 14px; margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--bdr); }
|
|
.tm-card-stat { font-size: 12px; color: var(--med); }
|
|
.tm-card-stat strong { color: var(--purple); }
|
|
.tm-card-actions { display: flex; gap: 6px; margin-top: 12px; }
|
|
.tm-sm-btn {
|
|
padding: 5px 12px; border-radius: 7px; font-family: inherit;
|
|
font-size: 12px; font-weight: 600; cursor: pointer; transition: .15s;
|
|
border: 1.5px solid var(--bdr); background: none; color: var(--med);
|
|
}
|
|
.tm-sm-btn:hover { border-color: var(--purple); color: var(--purple); }
|
|
.tm-sm-btn.danger:hover { border-color: #ef4444; color: #ef4444; }
|
|
|
|
/* Right — detail panel */
|
|
.tm-detail {
|
|
background: var(--navy2); border-left: 1px solid var(--bdr);
|
|
display: flex; flex-direction: column; overflow: hidden;
|
|
}
|
|
.tm-detail-header {
|
|
padding: 18px 20px; border-bottom: 1px solid var(--bdr);
|
|
display: flex; align-items: center; gap: 10px;
|
|
}
|
|
.tm-detail-title { font-size: 16px; font-weight: 800; color: var(--ink); flex: 1; }
|
|
.tm-detail-body { flex: 1; overflow-y: auto; padding: 16px 20px; display: flex; flex-direction: column; gap: 18px; }
|
|
.tm-detail-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: var(--lt); gap: 10px; }
|
|
|
|
.tm-section-title { font-size: 11px; font-weight: 700; color: var(--lt); text-transform: uppercase; letter-spacing: .4px; margin-bottom: 10px; }
|
|
|
|
/* Members */
|
|
.tm-member-row {
|
|
display: flex; align-items: center; gap: 10px;
|
|
padding: 8px 10px; border-radius: 9px;
|
|
border: 1px solid var(--bdr); margin-bottom: 6px; background: var(--bg);
|
|
}
|
|
.tm-member-ava {
|
|
width: 32px; height: 32px; border-radius: 8px; background: rgba(124,58,237,.15);
|
|
display: flex; align-items: center; justify-content: center;
|
|
font-size: 13px; font-weight: 800; color: var(--purple); flex-shrink: 0;
|
|
}
|
|
.tm-member-name { font-size: 13px; font-weight: 700; color: var(--ink); flex: 1; }
|
|
.tm-member-email { font-size: 11px; color: var(--lt); }
|
|
.tm-role-badge {
|
|
font-size: 10px; font-weight: 700; padding: 2px 8px; border-radius: 20px;
|
|
background: rgba(124,58,237,.1); color: var(--purple);
|
|
}
|
|
.tm-role-badge.lead { background: rgba(16,185,129,.1); color: #059669; }
|
|
.tm-remove-btn {
|
|
background: none; border: none; cursor: pointer; color: var(--lt);
|
|
font-size: 13px; padding: 3px 6px; border-radius: 5px; transition: .12s;
|
|
}
|
|
.tm-remove-btn:hover { color: #ef4444; }
|
|
|
|
/* Add member row */
|
|
.tm-add-member { display: flex; gap: 8px; margin-top: 8px; }
|
|
.tm-add-member select {
|
|
flex: 1; padding: 7px 10px; border-radius: 8px;
|
|
border: 1.5px solid var(--bdr); background: var(--bg);
|
|
color: var(--ink); font-family: inherit; font-size: 13px;
|
|
}
|
|
.tm-add-member select:focus { outline: none; border-color: var(--purple); }
|
|
.tm-add-member button {
|
|
padding: 7px 14px; border-radius: 8px; background: var(--purple);
|
|
color: white; border: none; font-family: inherit; font-size: 13px;
|
|
font-weight: 600; cursor: pointer;
|
|
}
|
|
|
|
/* Settings */
|
|
.tm-setting-row { display: flex; flex-direction: column; gap: 6px; margin-bottom: 12px; }
|
|
.tm-setting-row label { font-size: 12px; font-weight: 700; color: var(--med); }
|
|
.tm-setting-row select, .tm-setting-row input {
|
|
width: 100%; box-sizing: border-box; padding: 8px 10px; border-radius: 8px;
|
|
border: 1.5px solid var(--bdr); background: var(--bg);
|
|
color: var(--ink); font-family: inherit; font-size: 13px;
|
|
}
|
|
.tm-setting-row select:focus, .tm-setting-row input:focus { outline: none; border-color: var(--purple); }
|
|
.tm-save-settings-btn {
|
|
width: 100%; padding: 9px; border-radius: 9px;
|
|
background: var(--purple); color: white; border: none;
|
|
font-family: inherit; font-size: 13px; font-weight: 700; cursor: pointer;
|
|
}
|
|
.tm-save-settings-btn:hover { background: #6d28d9; }
|
|
|
|
/* Modal */
|
|
.tm-modal-bg {
|
|
display: none; position: fixed; inset: 0; background: rgba(22,13,53,.5);
|
|
z-index: 1000; align-items: center; justify-content: center;
|
|
}
|
|
.tm-modal-bg.open { display: flex; }
|
|
.tm-modal {
|
|
background: var(--navy2); border-radius: 16px; padding: 28px;
|
|
width: 100%; max-width: 440px; border: 1.5px solid var(--bdr);
|
|
}
|
|
.tm-modal h2 { font-size: 18px; font-weight: 800; color: var(--ink); margin-bottom: 18px; }
|
|
.tm-form-field { margin-bottom: 14px; }
|
|
.tm-form-field label { display: block; font-size: 11px; font-weight: 700; color: var(--lt); text-transform: uppercase; letter-spacing: .4px; margin-bottom: 6px; }
|
|
.tm-form-field input, .tm-form-field textarea {
|
|
width: 100%; box-sizing: border-box; padding: 9px 12px; border-radius: 9px;
|
|
border: 1.5px solid var(--bdr); background: var(--bg);
|
|
color: var(--ink); font-family: inherit; font-size: 13px;
|
|
}
|
|
.tm-form-field input:focus, .tm-form-field textarea:focus { outline: none; border-color: var(--purple); }
|
|
.tm-modal-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 20px; }
|
|
.tm-modal-btn {
|
|
padding: 9px 20px; border-radius: 9px; font-family: inherit;
|
|
font-size: 13px; font-weight: 700; cursor: pointer; transition: .15s;
|
|
}
|
|
.tm-modal-btn.cancel { background: none; border: 1.5px solid var(--bdr); color: var(--med); }
|
|
.tm-modal-btn.create { background: var(--purple); border: none; color: white; }
|
|
|
|
/* Toast */
|
|
#tm-toast { position:fixed;bottom:24px;right:24px;z-index:9999;background:var(--ink);color:white;padding:10px 18px;border-radius:10px;font-size:13px;font-weight:600;opacity:0;transform:translateY(8px);transition:.25s;pointer-events:none; }
|
|
#tm-toast.show { opacity:1;transform:translateY(0); }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<header class="topnav">
|
|
<a href="index.html" class="brand">Nexus One <span>AI</span></a>
|
|
<nav>
|
|
<a href="index.html">Home</a>
|
|
<a href="quickstart.html">Quick Start</a>
|
|
<a href="prompts.html">Prompt Library</a>
|
|
<a href="usecases.html">Use Cases</a>
|
|
<span class="nav-sep"></span>
|
|
<div class="nav-dropdown">
|
|
<button class="nav-drop-btn">Help ▾</button>
|
|
<div class="nav-drop-menu">
|
|
<span class="nav-drop-cat">LEARN /</span>
|
|
<a href="quickstart.html">Quick Start</a>
|
|
<a href="models.html">Models</a>
|
|
<span class="nav-drop-cat">SUPPORT /</span>
|
|
<a href="troubleshooting.html">Troubleshoot</a>
|
|
<a href="faq.html">FAQ</a>
|
|
<span class="nav-drop-cat">MORE /</span>
|
|
<a href="glossary.html">Glossary</a>
|
|
<a href="whats-new.html">What's New</a>
|
|
</div>
|
|
</div>
|
|
<div class="nav-dropdown">
|
|
<button class="nav-drop-btn active">Admin ▾</button>
|
|
<div class="nav-drop-menu nav-drop-menu-wide">
|
|
<span class="nav-drop-cat">DOCS /</span>
|
|
<a href="security.html">Security & Privacy</a>
|
|
<a href="admin.html">Admin Guide</a>
|
|
<span class="nav-drop-cat">MONITOR /</span>
|
|
<a href="dashboard.html">Dashboard</a>
|
|
<a href="analytics.html">Usage Analytics</a>
|
|
<a href="audit.html">Audit Log</a>
|
|
<a href="feedback.html">Feedback & Ratings</a>
|
|
<span class="nav-drop-cat">MANAGE /</span>
|
|
<a href="users.html">Users</a>
|
|
<a href="teams.html" class="active">Teams</a>
|
|
<a href="models-admin.html">Model Manager</a>
|
|
<a href="training.html">Training</a>
|
|
<a href="knowledge.html">Knowledge Base</a>
|
|
<span class="nav-drop-cat">TOOLS /</span>
|
|
<a href="apikeys.html">API Keys</a>
|
|
<a href="benchmark.html">Benchmarking</a>
|
|
<a href="model-compare.html">Model Compare</a>
|
|
<a href="api-playground.html">API Playground</a>
|
|
<a href="guardrails.html">Guardrails</a>
|
|
<a href="rag-quality.html">RAG Quality</a>
|
|
<a href="router.html">Model Router</a>
|
|
<a href="connectors.html">Connectors</a>
|
|
<span class="nav-drop-cat">SYSTEM /</span>
|
|
<a href="console.html">Console</a>
|
|
<a href="settings.html">Settings</a>
|
|
</div>
|
|
</div>
|
|
<div class="nav-dropdown">
|
|
<button class="nav-drop-btn">AI Tools ▾</button>
|
|
<div class="nav-drop-menu">
|
|
<span class="nav-drop-cat">INTELLIGENCE /</span>
|
|
<a href="documents.html">Document Intelligence</a>
|
|
<a href="chat-multi.html">Multimodal Chat</a>
|
|
<a href="prompt-studio.html">Prompt Studio</a>
|
|
<a href="meeting.html">Meeting Assistant</a>
|
|
<span class="nav-drop-cat">AUTOMATION /</span>
|
|
<a href="agents.html">Agent Builder</a>
|
|
<a href="schedules.html">Scheduled Jobs</a>
|
|
<a href="workflows.html">Workflow Automation</a>
|
|
<span class="nav-drop-cat">QUALITY /</span>
|
|
<a href="evals.html">AI Eval Suite</a>
|
|
<a href="chatrooms.html">Chat Rooms</a>
|
|
</div>
|
|
</div>
|
|
<a href="notifications.html" style="position:relative">🔔</a>
|
|
</nav>
|
|
<span class="badge" data-brand="tier">Basic Tier</span>
|
|
<div id="nav-org-logo" class="nav-org-logo"></div>
|
|
</header>
|
|
|
|
<div class="tm-layout">
|
|
<!-- Left: Team list -->
|
|
<div class="tm-list-panel">
|
|
<div class="tm-header">
|
|
<div class="tm-title">👥 Teams & Departments</div>
|
|
<button class="tm-add-btn" onclick="openCreateModal()">+ New Team</button>
|
|
</div>
|
|
<div class="tm-grid" id="tm-grid">
|
|
<p style="color:var(--lt)">Loading teams…</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Right: Detail -->
|
|
<div class="tm-detail" id="tm-detail">
|
|
<div class="tm-detail-empty">
|
|
<div style="font-size:38px;opacity:.3">👥</div>
|
|
<p style="font-size:14px;color:var(--lt)">Select a team to manage it</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Create Modal -->
|
|
<div class="tm-modal-bg" id="tm-modal">
|
|
<div class="tm-modal">
|
|
<h2>Create Team</h2>
|
|
<div class="tm-form-field">
|
|
<label>Team Name</label>
|
|
<input id="new-team-name" type="text" placeholder="e.g. Finance, Legal, Engineering">
|
|
</div>
|
|
<div class="tm-form-field">
|
|
<label>Description</label>
|
|
<textarea id="new-team-desc" rows="2" placeholder="What does this team use the AI for?"></textarea>
|
|
</div>
|
|
<div class="tm-modal-actions">
|
|
<button class="tm-modal-btn cancel" onclick="closeModal()">Cancel</button>
|
|
<button class="tm-modal-btn create" onclick="createTeam()">Create Team</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="tm-toast"></div>
|
|
|
|
<script>
|
|
const TEAM_COLORS = ['#7C3AED','#0EA5E9','#10B981','#F59E0B','#EC4899','#6366F1','#14B8A6','#EF4444'];
|
|
const MOCK_TEAMS = [
|
|
{ id:1, name:'Finance & Accounts', description:'Budget analysis, expense classification, vendor invoice review', created_by:'admin', created_at:'2026-05-10T09:00:00Z',
|
|
members:[{id:2,username:'ravi.kumar',email:'ravi.k@iimb.ac.in',user_role:'user',team_role:'lead'},{id:3,username:'priya.nair',email:'priya.n@iimb.ac.in',user_role:'user',team_role:'member'}],
|
|
member_count:2, settings:{allowed_models:'["llama3.1:70b","mistral:7b"]',kb_collections:'["1"]',monthly_token_quota:500000} },
|
|
{ id:2, name:'Legal & Procurement', description:'Contract review, tender analysis, compliance checks', created_by:'admin', created_at:'2026-05-12T10:00:00Z',
|
|
members:[{id:4,username:'anand.m',email:'anand.m@iimb.ac.in',user_role:'user',team_role:'lead'}],
|
|
member_count:1, settings:{allowed_models:'["llama3.1:70b","codellama:34b"]',kb_collections:'["2"]',monthly_token_quota:750000} },
|
|
{ id:3, name:'Research & Faculty', description:'Academic research assistance, literature review, grant writing', created_by:'admin', created_at:'2026-05-15T11:00:00Z',
|
|
members:[],
|
|
member_count:0, settings:{allowed_models:'[]',kb_collections:'[]',monthly_token_quota:0} },
|
|
];
|
|
const MOCK_USERS = [
|
|
{id:2,username:'ravi.kumar',email:'ravi.k@iimb.ac.in'},{id:3,username:'priya.nair',email:'priya.n@iimb.ac.in'},
|
|
{id:4,username:'anand.m',email:'anand.m@iimb.ac.in'},{id:5,username:'deepa.v',email:'deepa.v@iimb.ac.in'},
|
|
{id:6,username:'kiran.rao',email:'kiran.rao@iimb.ac.in'},
|
|
];
|
|
|
|
let teams = [];
|
|
let allUsers = [];
|
|
let selectedTeamId = null;
|
|
|
|
async function loadTeams() {
|
|
try {
|
|
const r = await fetch('/api/teams');
|
|
if (!r.ok) throw new Error();
|
|
teams = await r.json();
|
|
} catch { teams = MOCK_TEAMS; }
|
|
renderGrid();
|
|
}
|
|
|
|
async function loadUsers() {
|
|
try {
|
|
const r = await fetch('/api/users');
|
|
if (!r.ok) throw new Error();
|
|
const d = await r.json();
|
|
allUsers = d.users || d;
|
|
} catch { allUsers = MOCK_USERS; }
|
|
}
|
|
|
|
function teamColor(id) { return TEAM_COLORS[(id - 1) % TEAM_COLORS.length]; }
|
|
function teamInitial(name) { return name.split(/\s+/).map(w=>w[0]).join('').slice(0,2).toUpperCase(); }
|
|
|
|
function renderGrid() {
|
|
const grid = document.getElementById('tm-grid');
|
|
if (!teams.length) {
|
|
grid.innerHTML = `<p style="color:var(--lt);grid-column:1/-1">No teams yet. Click <strong>+ New Team</strong> to create one.</p>`;
|
|
return;
|
|
}
|
|
grid.innerHTML = teams.map(t => {
|
|
const models = JSON.parse(t.settings?.allowed_models || '[]');
|
|
const kbs = JSON.parse(t.settings?.kb_collections || '[]');
|
|
const quota = t.settings?.monthly_token_quota || 0;
|
|
return `
|
|
<div class="tm-card ${t.id === selectedTeamId ? 'active' : ''}" onclick="selectTeam(${t.id})">
|
|
<div class="tm-card-header">
|
|
<div class="tm-avatar" style="background:${teamColor(t.id)}">${teamInitial(t.name)}</div>
|
|
<div>
|
|
<div class="tm-card-name">${t.name}</div>
|
|
<div class="tm-card-desc">${t.description || 'No description'}</div>
|
|
</div>
|
|
</div>
|
|
<div class="tm-card-stats">
|
|
<div class="tm-card-stat">👤 <strong>${t.member_count}</strong> members</div>
|
|
<div class="tm-card-stat">🤖 <strong>${models.length || 'All'}</strong> models</div>
|
|
<div class="tm-card-stat">📚 <strong>${kbs.length || 'All'}</strong> KBs</div>
|
|
${quota ? `<div class="tm-card-stat">🔤 <strong>${(quota/1000).toFixed(0)}k</strong> tok/mo</div>` : ''}
|
|
</div>
|
|
<div class="tm-card-actions">
|
|
<button class="tm-sm-btn" onclick="event.stopPropagation();selectTeam(${t.id})">Manage</button>
|
|
<button class="tm-sm-btn danger" onclick="event.stopPropagation();deleteTeam(${t.id})">Delete</button>
|
|
</div>
|
|
</div>`;
|
|
}).join('');
|
|
}
|
|
|
|
function selectTeam(id) {
|
|
selectedTeamId = id;
|
|
renderGrid();
|
|
const t = teams.find(x => x.id === id);
|
|
if (!t) return;
|
|
renderDetail(t);
|
|
}
|
|
|
|
function renderDetail(t) {
|
|
const models = JSON.parse(t.settings?.allowed_models || '[]');
|
|
const kbs = JSON.parse(t.settings?.kb_collections || '[]');
|
|
const quota = t.settings?.monthly_token_quota || 0;
|
|
const detailEl = document.getElementById('tm-detail');
|
|
|
|
const notInTeam = allUsers.filter(u => !t.members.find(m => m.id === u.id));
|
|
|
|
detailEl.innerHTML = `
|
|
<div class="tm-detail-header">
|
|
<div class="tm-avatar" style="background:${teamColor(t.id)};width:36px;height:36px;border-radius:10px;font-size:16px;display:flex;align-items:center;justify-content:center;color:white;font-weight:800;flex-shrink:0">${teamInitial(t.name)}</div>
|
|
<div class="tm-detail-title">${t.name}</div>
|
|
</div>
|
|
<div class="tm-detail-body">
|
|
|
|
<!-- Members -->
|
|
<div>
|
|
<div class="tm-section-title">Members (${t.members.length})</div>
|
|
${t.members.length ? t.members.map(m => `
|
|
<div class="tm-member-row">
|
|
<div class="tm-member-ava">${m.username[0].toUpperCase()}</div>
|
|
<div style="flex:1;min-width:0">
|
|
<div class="tm-member-name">${m.username}</div>
|
|
<div class="tm-member-email">${m.email}</div>
|
|
</div>
|
|
<span class="tm-role-badge ${m.team_role}">${m.team_role}</span>
|
|
<button class="tm-remove-btn" onclick="removeMember(${t.id},${m.id})" title="Remove">✕</button>
|
|
</div>
|
|
`).join('') : '<p style="font-size:13px;color:var(--lt)">No members yet.</p>'}
|
|
<div class="tm-add-member">
|
|
<select id="add-user-sel">
|
|
<option value="">+ Add member…</option>
|
|
${notInTeam.map(u => `<option value="${u.id}">${u.username} — ${u.email}</option>`).join('')}
|
|
</select>
|
|
<select id="add-role-sel" style="width:100px">
|
|
<option value="member">Member</option>
|
|
<option value="lead">Lead</option>
|
|
</select>
|
|
<button onclick="addMember(${t.id})">Add</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Model Access -->
|
|
<div>
|
|
<div class="tm-section-title">Model Access</div>
|
|
<div class="tm-setting-row">
|
|
<label>Allowed Models <span style="font-weight:400;text-transform:none;letter-spacing:0">(leave blank for all)</span></label>
|
|
<select id="tm-models" multiple style="height:110px">
|
|
${['llama3.1:8b','llama3.1:70b','mistral:7b','codellama:34b','llava:13b','gemma2:9b','llama3.2:3b'].map(m =>
|
|
`<option value="${m}" ${models.includes(m)?'selected':''}>${m}</option>`
|
|
).join('')}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- KB Access -->
|
|
<div>
|
|
<div class="tm-section-title">Knowledge Base Collections</div>
|
|
<div class="tm-setting-row">
|
|
<label>Allowed Collections <span style="font-weight:400;text-transform:none;letter-spacing:0">(leave blank for all)</span></label>
|
|
<select id="tm-kbs" multiple style="height:80px">
|
|
<option value="1" ${kbs.includes('1')?'selected':''}>Company Policies</option>
|
|
<option value="2" ${kbs.includes('2')?'selected':''}>Legal Documents</option>
|
|
<option value="3" ${kbs.includes('3')?'selected':''}>HR Handbook</option>
|
|
<option value="4" ${kbs.includes('4')?'selected':''}>Technical Docs</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Usage Quota -->
|
|
<div>
|
|
<div class="tm-section-title">Usage Quota</div>
|
|
<div class="tm-setting-row">
|
|
<label>Monthly Token Limit (0 = unlimited)</label>
|
|
<input id="tm-quota" type="number" min="0" step="50000" value="${quota}" placeholder="e.g. 1000000">
|
|
</div>
|
|
<button class="tm-save-settings-btn" onclick="saveSettings(${t.id})">💾 Save Settings</button>
|
|
</div>
|
|
|
|
</div>`;
|
|
}
|
|
|
|
async function addMember(teamId) {
|
|
const sel = document.getElementById('add-user-sel');
|
|
const role = document.getElementById('add-role-sel').value;
|
|
const userId = parseInt(sel.value);
|
|
if (!userId) { showToast('Select a user'); return; }
|
|
try {
|
|
await fetch(`/api/teams/${teamId}/members`, {
|
|
method: 'POST',
|
|
headers: {'Content-Type':'application/json'},
|
|
body: JSON.stringify({user_id: userId, role})
|
|
});
|
|
} catch {}
|
|
const user = allUsers.find(u => u.id === userId);
|
|
const team = teams.find(t => t.id === teamId);
|
|
if (team && user) {
|
|
team.members.push({...user, team_role: role});
|
|
team.member_count++;
|
|
}
|
|
renderGrid();
|
|
renderDetail(team);
|
|
showToast('Member added');
|
|
}
|
|
|
|
async function removeMember(teamId, userId) {
|
|
try {
|
|
await fetch(`/api/teams/${teamId}/members/${userId}`, { method:'DELETE' });
|
|
} catch {}
|
|
const team = teams.find(t => t.id === teamId);
|
|
if (team) {
|
|
team.members = team.members.filter(m => m.id !== userId);
|
|
team.member_count--;
|
|
}
|
|
renderGrid();
|
|
renderDetail(team);
|
|
showToast('Member removed');
|
|
}
|
|
|
|
async function saveSettings(teamId) {
|
|
const modelsSel = document.getElementById('tm-models');
|
|
const kbsSel = document.getElementById('tm-kbs');
|
|
const quota = parseInt(document.getElementById('tm-quota').value) || 0;
|
|
const models = [...modelsSel.selectedOptions].map(o => o.value);
|
|
const kbs = [...kbsSel.selectedOptions].map(o => o.value);
|
|
|
|
try {
|
|
await fetch(`/api/teams/${teamId}`, {
|
|
method:'PUT',
|
|
headers:{'Content-Type':'application/json'},
|
|
body: JSON.stringify({
|
|
allowed_models: JSON.stringify(models),
|
|
kb_collections: JSON.stringify(kbs),
|
|
monthly_token_quota: quota
|
|
})
|
|
});
|
|
} catch {}
|
|
const team = teams.find(t => t.id === teamId);
|
|
if (team && team.settings) {
|
|
team.settings.allowed_models = JSON.stringify(models);
|
|
team.settings.kb_collections = JSON.stringify(kbs);
|
|
team.settings.monthly_token_quota = quota;
|
|
}
|
|
renderGrid();
|
|
showToast('Settings saved');
|
|
}
|
|
|
|
async function deleteTeam(id) {
|
|
const team = teams.find(t => t.id === id);
|
|
if (!confirm(`Delete team "${team?.name}"? This cannot be undone.`)) return;
|
|
try { await fetch(`/api/teams/${id}`, { method:'DELETE' }); } catch {}
|
|
teams = teams.filter(t => t.id !== id);
|
|
if (selectedTeamId === id) {
|
|
selectedTeamId = null;
|
|
document.getElementById('tm-detail').innerHTML = `<div class="tm-detail-empty"><div style="font-size:38px;opacity:.3">👥</div><p style="font-size:14px;color:var(--lt)">Select a team to manage it</p></div>`;
|
|
}
|
|
renderGrid();
|
|
showToast('Team deleted');
|
|
}
|
|
|
|
function openCreateModal() { document.getElementById('tm-modal').classList.add('open'); }
|
|
function closeModal() { document.getElementById('tm-modal').classList.remove('open'); }
|
|
|
|
async function createTeam() {
|
|
const name = document.getElementById('new-team-name').value.trim();
|
|
const desc = document.getElementById('new-team-desc').value.trim();
|
|
if (!name) { showToast('Enter a team name'); return; }
|
|
try {
|
|
const r = await fetch('/api/teams', {
|
|
method:'POST', headers:{'Content-Type':'application/json'},
|
|
body: JSON.stringify({name, description: desc})
|
|
});
|
|
const d = await r.json();
|
|
teams.push({id: d.id, name, description: desc, created_by:'admin', created_at: new Date().toISOString(), members:[], member_count:0, settings:{allowed_models:'[]',kb_collections:'[]',monthly_token_quota:0}});
|
|
} catch {
|
|
const id = Date.now() % 10000;
|
|
teams.push({id, name, description: desc, created_by:'admin', created_at: new Date().toISOString(), members:[], member_count:0, settings:{allowed_models:'[]',kb_collections:'[]',monthly_token_quota:0}});
|
|
}
|
|
closeModal();
|
|
document.getElementById('new-team-name').value = '';
|
|
document.getElementById('new-team-desc').value = '';
|
|
renderGrid();
|
|
showToast('Team created');
|
|
}
|
|
|
|
function showToast(msg) {
|
|
const t = document.getElementById('tm-toast');
|
|
t.textContent = msg; t.classList.add('show');
|
|
setTimeout(() => t.classList.remove('show'), 2200);
|
|
}
|
|
|
|
loadTeams();
|
|
loadUsers();
|
|
</script>
|
|
|
|
<script src="auth.js"></script>
|
|
<script src="branding.js"></script>
|
|
</body>
|
|
</html>
|