@agenticmail/enterprise 0.5.301 → 0.5.303
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-4VGAZULN.js +1519 -0
- package/dist/chunk-CRXYUYVJ.js +4395 -0
- package/dist/chunk-QCGSFWKU.js +48 -0
- package/dist/cli-agent-YSQVDBOZ.js +1778 -0
- package/dist/cli-recover-2LWWVD4Q.js +487 -0
- package/dist/cli-serve-JZEXPYXY.js +143 -0
- package/dist/cli-verify-TZMX3GWV.js +149 -0
- package/dist/cli.js +5 -5
- package/dist/dashboard/app.js +57 -1
- package/dist/dashboard/components/org-switcher.js +118 -0
- package/dist/dashboard/pages/agents.js +8 -1
- package/dist/dashboard/pages/approvals.js +5 -1
- package/dist/dashboard/pages/dashboard.js +6 -2
- package/dist/dashboard/pages/guardrails.js +20 -16
- package/dist/dashboard/pages/journal.js +6 -2
- package/dist/dashboard/pages/knowledge-contributions.js +18 -10
- package/dist/dashboard/pages/knowledge.js +32 -9
- package/dist/dashboard/pages/messages.js +8 -4
- package/dist/dashboard/pages/org-chart.js +5 -1
- package/dist/dashboard/pages/organizations.js +39 -3
- package/dist/dashboard/pages/skills.js +15 -11
- package/dist/dashboard/pages/task-pipeline.js +6 -2
- package/dist/dashboard/pages/users.js +34 -4
- package/dist/dashboard/pages/workforce.js +5 -1
- package/dist/factory-QYGGXVYW.js +9 -0
- package/dist/index.js +3 -3
- package/dist/postgres-ALNOGUUM.js +819 -0
- package/dist/server-3R5KZPLA.js +15 -0
- package/dist/setup-ZZAOBEY4.js +20 -0
- package/dist/sqlite-2QPVZQ27.js +566 -0
- package/package.json +1 -1
- package/src/admin/routes.ts +32 -1
- package/src/auth/routes.ts +36 -2
- package/src/dashboard/app.js +57 -1
- package/src/dashboard/components/org-switcher.js +118 -0
- package/src/dashboard/pages/agents.js +8 -1
- package/src/dashboard/pages/approvals.js +5 -1
- package/src/dashboard/pages/dashboard.js +6 -2
- package/src/dashboard/pages/guardrails.js +20 -16
- package/src/dashboard/pages/journal.js +6 -2
- package/src/dashboard/pages/knowledge-contributions.js +18 -10
- package/src/dashboard/pages/knowledge.js +32 -9
- package/src/dashboard/pages/messages.js +8 -4
- package/src/dashboard/pages/org-chart.js +5 -1
- package/src/dashboard/pages/organizations.js +39 -3
- package/src/dashboard/pages/skills.js +15 -11
- package/src/dashboard/pages/task-pipeline.js +6 -2
- package/src/dashboard/pages/users.js +34 -4
- package/src/dashboard/pages/workforce.js +5 -1
- package/src/db/adapter.ts +1 -0
- package/src/db/postgres.ts +3 -0
- package/src/db/sqlite.ts +7 -0
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { h, useState, useEffect, Fragment, useApp, engineCall, buildAgentEmailMap, buildAgentDataMap, resolveAgentEmail, renderAgentBadge, getOrgId } from '../components/utils.js';
|
|
2
2
|
import { I } from '../components/icons.js';
|
|
3
3
|
import { HelpButton } from '../components/help-button.js';
|
|
4
|
+
import { useOrgContext } from '../components/org-switcher.js';
|
|
4
5
|
|
|
5
6
|
export function JournalPage() {
|
|
7
|
+
var orgCtx = useOrgContext();
|
|
8
|
+
var effectiveOrgId = orgCtx.selectedOrgId || getOrgId();
|
|
6
9
|
const { toast } = useApp();
|
|
7
10
|
const [entries, setEntries] = useState([]);
|
|
8
11
|
const [total, setTotal] = useState(0);
|
|
@@ -11,9 +14,9 @@ export function JournalPage() {
|
|
|
11
14
|
const [agents, setAgents] = useState([]);
|
|
12
15
|
|
|
13
16
|
const load = () => {
|
|
14
|
-
engineCall('/journal?orgId=' +
|
|
17
|
+
engineCall('/journal?orgId=' + effectiveOrgId + '&limit=50').then(d => { setEntries(d.entries || []); setTotal(d.total || 0); }).catch(() => {});
|
|
15
18
|
engineCall('/journal/stats/default').then(d => setStats(d)).catch(() => {});
|
|
16
|
-
engineCall('/agents?orgId=' +
|
|
19
|
+
engineCall('/agents?orgId=' + effectiveOrgId).then(d => setAgents(d.agents || [])).catch(() => {});
|
|
17
20
|
};
|
|
18
21
|
useEffect(load, []);
|
|
19
22
|
|
|
@@ -29,6 +32,7 @@ export function JournalPage() {
|
|
|
29
32
|
var _tip = { marginTop: 12, padding: 12, background: 'var(--bg-secondary, #1e293b)', borderRadius: 'var(--radius, 8px)', fontSize: 13 };
|
|
30
33
|
|
|
31
34
|
return h('div', { className: 'page-inner' },
|
|
35
|
+
h(orgCtx.Switcher),
|
|
32
36
|
h('div', { className: 'page-header' }, h('h1', { style: { display: 'flex', alignItems: 'center' } }, 'Action Journal', h(HelpButton, { label: 'Action Journal' },
|
|
33
37
|
h('p', null, 'A tamper-proof log of every action agents have taken. Think of it as an audit trail — every tool call, every side effect, recorded with full context.'),
|
|
34
38
|
h('h4', { style: _h4 }, 'Why it matters'),
|
|
@@ -2,9 +2,11 @@ import { h, useState, useEffect, useCallback, Fragment, useApp, engineCall, buil
|
|
|
2
2
|
import { I } from '../components/icons.js';
|
|
3
3
|
import { Modal } from '../components/modal.js';
|
|
4
4
|
import { HelpButton } from '../components/help-button.js';
|
|
5
|
+
import { useOrgContext } from '../components/org-switcher.js';
|
|
5
6
|
|
|
6
7
|
export function KnowledgeContributionsPage() {
|
|
7
8
|
var { toast } = useApp();
|
|
9
|
+
var orgCtx = useOrgContext();
|
|
8
10
|
var [tab, setTab] = useState('bases');
|
|
9
11
|
var [bases, setBases] = useState([]);
|
|
10
12
|
var [roles, setRoles] = useState([]);
|
|
@@ -39,14 +41,17 @@ export function KnowledgeContributionsPage() {
|
|
|
39
41
|
var [searchDays, setSearchDays] = useState(7);
|
|
40
42
|
var [searchAgentFilter, setSearchAgentFilter] = useState('');
|
|
41
43
|
|
|
44
|
+
// Effective org ID: uses client org if selected, else default
|
|
45
|
+
var effectiveOrgId = orgCtx.selectedOrgId || effectiveOrgId;
|
|
46
|
+
|
|
42
47
|
var loadBases = useCallback(function() {
|
|
43
48
|
Promise.all([
|
|
44
|
-
engineCall('/knowledge-contribution/bases?orgId=' +
|
|
49
|
+
engineCall('/knowledge-contribution/bases?orgId=' + effectiveOrgId).catch(function() { return { bases: [] }; }),
|
|
45
50
|
engineCall('/knowledge-bases').catch(function() { return { knowledgeBases: [] }; })
|
|
46
51
|
]).then(function(results) {
|
|
47
52
|
var contribBases = results[0].bases || [];
|
|
48
53
|
var mainBases = (results[1].knowledgeBases || []).map(function(kb) {
|
|
49
|
-
return { id: kb.id, orgId:
|
|
54
|
+
return { id: kb.id, orgId: effectiveOrgId, name: kb.name, description: kb.description, role: 'general', categories: [], contributorCount: 0, entryCount: kb.stats ? kb.stats.documentCount || 0 : 0, createdAt: kb.createdAt, updatedAt: kb.updatedAt, _source: 'main' };
|
|
50
55
|
});
|
|
51
56
|
// Merge: contribution bases first, then main bases not already in contribution
|
|
52
57
|
var ids = {};
|
|
@@ -63,19 +68,19 @@ export function KnowledgeContributionsPage() {
|
|
|
63
68
|
}, []);
|
|
64
69
|
|
|
65
70
|
var loadStats = useCallback(function() {
|
|
66
|
-
engineCall('/knowledge-contribution/stats?orgId=' +
|
|
71
|
+
engineCall('/knowledge-contribution/stats?orgId=' + effectiveOrgId)
|
|
67
72
|
.then(function(d) { setStats(d || {}); })
|
|
68
73
|
.catch(function() {});
|
|
69
74
|
}, []);
|
|
70
75
|
|
|
71
76
|
var loadContributions = useCallback(function() {
|
|
72
|
-
engineCall('/knowledge-contribution/contributions?orgId=' +
|
|
77
|
+
engineCall('/knowledge-contribution/contributions?orgId=' + effectiveOrgId)
|
|
73
78
|
.then(function(d) { setContributions(d.contributions || d.cycles || []); })
|
|
74
79
|
.catch(function() {});
|
|
75
80
|
}, []);
|
|
76
81
|
|
|
77
82
|
var loadSchedules = useCallback(function() {
|
|
78
|
-
engineCall('/knowledge-contribution/schedules?orgId=' +
|
|
83
|
+
engineCall('/knowledge-contribution/schedules?orgId=' + effectiveOrgId)
|
|
79
84
|
.then(function(d) { setSchedules(d.schedules || []); })
|
|
80
85
|
.catch(function() {});
|
|
81
86
|
}, []);
|
|
@@ -86,10 +91,10 @@ export function KnowledgeContributionsPage() {
|
|
|
86
91
|
loadStats();
|
|
87
92
|
loadContributions();
|
|
88
93
|
loadSchedules();
|
|
89
|
-
engineCall('/agents?orgId=' +
|
|
94
|
+
engineCall('/agents?orgId=' + effectiveOrgId).then(function(d) { setAgents(d.agents || []); }).catch(function() {});
|
|
90
95
|
}, [loadBases, loadRoles, loadStats, loadContributions, loadSchedules]);
|
|
91
96
|
|
|
92
|
-
useEffect(function() { load(); }, [load]);
|
|
97
|
+
useEffect(function() { load(); }, [load, effectiveOrgId]);
|
|
93
98
|
|
|
94
99
|
var loadBaseEntries = useCallback(function(baseId) {
|
|
95
100
|
var params = new URLSearchParams();
|
|
@@ -110,7 +115,7 @@ export function KnowledgeContributionsPage() {
|
|
|
110
115
|
try {
|
|
111
116
|
await engineCall('/knowledge-contribution/bases', {
|
|
112
117
|
method: 'POST',
|
|
113
|
-
body: JSON.stringify({ name: baseForm.name, description: baseForm.description, role: baseForm.role, orgId:
|
|
118
|
+
body: JSON.stringify({ name: baseForm.name, description: baseForm.description, role: baseForm.role, orgId: effectiveOrgId })
|
|
114
119
|
});
|
|
115
120
|
toast('Knowledge base created', 'success');
|
|
116
121
|
setShowCreateBase(false);
|
|
@@ -161,7 +166,7 @@ export function KnowledgeContributionsPage() {
|
|
|
161
166
|
try {
|
|
162
167
|
await engineCall('/knowledge-contribution/contribute/' + triggerAgent, {
|
|
163
168
|
method: 'POST',
|
|
164
|
-
body: JSON.stringify({ targetBaseId: triggerBase || undefined, orgId:
|
|
169
|
+
body: JSON.stringify({ targetBaseId: triggerBase || undefined, orgId: effectiveOrgId })
|
|
165
170
|
});
|
|
166
171
|
toast('Contribution triggered', 'success');
|
|
167
172
|
setShowTrigger(false);
|
|
@@ -182,7 +187,7 @@ export function KnowledgeContributionsPage() {
|
|
|
182
187
|
frequency: scheduleForm.frequency,
|
|
183
188
|
dayOfWeek: scheduleForm.dayOfWeek,
|
|
184
189
|
minConfidence: parseFloat(scheduleForm.minConfidence) || 0.7,
|
|
185
|
-
orgId:
|
|
190
|
+
orgId: effectiveOrgId
|
|
186
191
|
})
|
|
187
192
|
});
|
|
188
193
|
toast('Schedule created', 'success');
|
|
@@ -1375,6 +1380,9 @@ export function KnowledgeContributionsPage() {
|
|
|
1375
1380
|
};
|
|
1376
1381
|
|
|
1377
1382
|
return h(Fragment, null,
|
|
1383
|
+
// Org context switcher
|
|
1384
|
+
h(orgCtx.Switcher),
|
|
1385
|
+
|
|
1378
1386
|
// Header
|
|
1379
1387
|
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20 } },
|
|
1380
1388
|
h('div', null,
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import { h, useState, useEffect, useCallback, Fragment, useApp, engineCall, getOrgId } from '../components/utils.js';
|
|
1
|
+
import { h, useState, useEffect, useCallback, Fragment, useApp, engineCall, apiCall, getOrgId } from '../components/utils.js';
|
|
2
2
|
import { I } from '../components/icons.js';
|
|
3
3
|
import { Modal } from '../components/modal.js';
|
|
4
4
|
import { KnowledgeImportWizard, ImportJobsList } from './knowledge-import.js';
|
|
5
5
|
import { HelpButton } from '../components/help-button.js';
|
|
6
|
+
import { useOrgContext } from '../components/org-switcher.js';
|
|
6
7
|
|
|
7
8
|
export function KnowledgeBasePage() {
|
|
8
9
|
const { toast } = useApp();
|
|
9
10
|
const [kbs, setKbs] = useState([]);
|
|
10
11
|
const [creating, setCreating] = useState(false);
|
|
11
|
-
const [form, setForm] = useState({ name: '', description: '' });
|
|
12
|
+
const [form, setForm] = useState({ name: '', description: '', orgId: '' });
|
|
13
|
+
const [clientOrgs, setClientOrgs] = useState([]);
|
|
14
|
+
const orgCtx = useOrgContext();
|
|
12
15
|
const [selected, setSelected] = useState(null); // full KB detail
|
|
13
16
|
const [docs, setDocs] = useState([]);
|
|
14
17
|
const [chunks, setChunks] = useState([]);
|
|
@@ -25,14 +28,20 @@ export function KnowledgeBasePage() {
|
|
|
25
28
|
|
|
26
29
|
const load = useCallback(() => {
|
|
27
30
|
engineCall('/knowledge-bases').then(d => setKbs(d.knowledgeBases || [])).catch(() => {});
|
|
31
|
+
apiCall('/organizations').then(d => setClientOrgs(d.organizations || [])).catch(() => {});
|
|
28
32
|
}, []);
|
|
29
33
|
useEffect(() => { load(); }, [load]);
|
|
30
34
|
|
|
35
|
+
// Filter KBs by selected org context
|
|
36
|
+
const filteredKbs = orgCtx.selectedOrgId
|
|
37
|
+
? kbs.filter(kb => kb.orgId === orgCtx.selectedOrgId || kb.clientOrgId === orgCtx.selectedOrgId)
|
|
38
|
+
: kbs;
|
|
39
|
+
|
|
31
40
|
const create = async () => {
|
|
32
41
|
try {
|
|
33
|
-
await engineCall('/knowledge-bases', { method: 'POST', body: JSON.stringify({ name: form.name, description: form.description, orgId: getOrgId() }) });
|
|
42
|
+
await engineCall('/knowledge-bases', { method: 'POST', body: JSON.stringify({ name: form.name, description: form.description, orgId: getOrgId(), clientOrgId: form.orgId || null }) });
|
|
34
43
|
toast('Knowledge base created', 'success');
|
|
35
|
-
setCreating(false); setForm({ name: '', description: '' }); load();
|
|
44
|
+
setCreating(false); setForm({ name: '', description: '', orgId: '' }); load();
|
|
36
45
|
} catch (e) { toast(e.message, 'error'); }
|
|
37
46
|
};
|
|
38
47
|
|
|
@@ -315,16 +324,29 @@ export function KnowledgeBasePage() {
|
|
|
315
324
|
h('button', { className: 'btn btn-primary', onClick: () => setCreating(true) }, I.plus(), ' New Knowledge Base')
|
|
316
325
|
),
|
|
317
326
|
|
|
327
|
+
// Org context switcher
|
|
328
|
+
h(orgCtx.Switcher),
|
|
329
|
+
|
|
318
330
|
creating && h(Modal, { title: 'Create Knowledge Base', onClose: () => setCreating(false), footer: h(Fragment, null, h('button', { className: 'btn btn-secondary', onClick: () => setCreating(false) }, 'Cancel'), h('button', { className: 'btn btn-primary', onClick: create, disabled: !form.name }, 'Create')) },
|
|
319
331
|
h('div', { className: 'form-group' }, h('label', { className: 'form-label' }, 'Name'), h('input', { className: 'input', value: form.name, onChange: e => setForm(f => ({ ...f, name: e.target.value })) })),
|
|
320
|
-
h('div', { className: 'form-group' }, h('label', { className: 'form-label' }, 'Description'), h('textarea', { className: 'input', value: form.description, onChange: e => setForm(f => ({ ...f, description: e.target.value })) }))
|
|
332
|
+
h('div', { className: 'form-group' }, h('label', { className: 'form-label' }, 'Description'), h('textarea', { className: 'input', value: form.description, onChange: e => setForm(f => ({ ...f, description: e.target.value })) })),
|
|
333
|
+
clientOrgs.length > 0 && h('div', { className: 'form-group' },
|
|
334
|
+
h('label', { className: 'form-label' }, 'Organization'),
|
|
335
|
+
h('select', { className: 'input', value: form.orgId, onChange: e => setForm(f => ({ ...f, orgId: e.target.value })) },
|
|
336
|
+
h('option', { value: '' }, 'My Organization (internal)'),
|
|
337
|
+
clientOrgs.filter(o => o.is_active !== false).map(o =>
|
|
338
|
+
h('option', { key: o.id, value: o.id }, o.name)
|
|
339
|
+
)
|
|
340
|
+
),
|
|
341
|
+
h('div', { style: { fontSize: 11, color: 'var(--text-muted)', marginTop: 4 } }, 'Assign this knowledge base to a client organization for data isolation')
|
|
342
|
+
)
|
|
321
343
|
),
|
|
322
344
|
|
|
323
345
|
loading && h('div', { style: { textAlign: 'center', padding: 40 } }, 'Loading...'),
|
|
324
346
|
|
|
325
|
-
!loading &&
|
|
326
|
-
? h('div', { className: 'card' }, h('div', { className: 'card-body' }, h('div', { className: 'empty-state' }, I.knowledge(), h('h3', null, 'No knowledge bases'), h('p', null, 'Create a knowledge base to give agents access to your documents, policies, and data.'))))
|
|
327
|
-
: !loading && h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))', gap: 16 } },
|
|
347
|
+
!loading && filteredKbs.length === 0
|
|
348
|
+
? h('div', { className: 'card' }, h('div', { className: 'card-body' }, h('div', { className: 'empty-state' }, I.knowledge(), h('h3', null, orgCtx.selectedOrgId ? 'No knowledge bases for this organization' : 'No knowledge bases'), h('p', null, orgCtx.selectedOrgId ? 'Create a knowledge base assigned to this organization to get started.' : 'Create a knowledge base to give agents access to your documents, policies, and data.'))))
|
|
349
|
+
: !loading && h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))', gap: 16 } }, filteredKbs.map(kb =>
|
|
328
350
|
h('div', { key: kb.id, className: 'card', style: { cursor: 'pointer', transition: 'border-color 0.15s' }, onClick: () => selectKb(kb) },
|
|
329
351
|
h('div', { className: 'card-body' },
|
|
330
352
|
h('h3', { style: { fontSize: 15, fontWeight: 600, marginBottom: 4 } }, kb.name),
|
|
@@ -332,7 +354,8 @@ export function KnowledgeBasePage() {
|
|
|
332
354
|
h('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap' } },
|
|
333
355
|
h('span', { className: 'badge badge-info' }, (kb.stats?.documentCount || kb.stats?.documents || kb.stats?.totalDocuments || kb.documents?.length || 0) + ' docs'),
|
|
334
356
|
h('span', { className: 'badge badge-neutral' }, (kb.stats?.chunkCount || kb.stats?.chunks || kb.stats?.totalChunks || 0) + ' chunks'),
|
|
335
|
-
kb.agentIds && kb.agentIds.length > 0 && h('span', { className: 'badge badge-success' }, kb.agentIds.length + ' agent(s)')
|
|
357
|
+
kb.agentIds && kb.agentIds.length > 0 && h('span', { className: 'badge badge-success' }, kb.agentIds.length + ' agent(s)'),
|
|
358
|
+
(function() { var org = kb.clientOrgId && clientOrgs.find(function(o) { return o.id === kb.clientOrgId; }); return org ? h('span', { className: 'badge', style: { background: 'var(--bg-secondary)', fontSize: 10 } }, I.building(), ' ', org.name) : null; })()
|
|
336
359
|
),
|
|
337
360
|
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 10 } },
|
|
338
361
|
h('div', { style: { fontSize: 11, color: 'var(--text-muted)' } }, 'Click to view details \u2192'),
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { h, useState, useEffect, useRef, Fragment, useApp, engineCall, buildAgentEmailMap, resolveAgentEmail, buildAgentDataMap, renderAgentBadge, getOrgId } from '../components/utils.js';
|
|
2
2
|
import { I } from '../components/icons.js';
|
|
3
3
|
import { HelpButton } from '../components/help-button.js';
|
|
4
|
+
import { useOrgContext } from '../components/org-switcher.js';
|
|
4
5
|
|
|
5
6
|
export function MessagesPage() {
|
|
7
|
+
var orgCtx = useOrgContext();
|
|
8
|
+
var effectiveOrgId = orgCtx.selectedOrgId || getOrgId();
|
|
6
9
|
const { toast } = useApp();
|
|
7
10
|
const [messages, setMessages] = useState([]);
|
|
8
11
|
const [agents, setAgents] = useState([]);
|
|
@@ -10,19 +13,19 @@ export function MessagesPage() {
|
|
|
10
13
|
const [mainTab, setMainTab] = useState('messages');
|
|
11
14
|
const [subTab, setSubTab] = useState('all');
|
|
12
15
|
const [showModal, setShowModal] = useState(false);
|
|
13
|
-
const [form, setForm] = useState({ orgId:
|
|
16
|
+
const [form, setForm] = useState({ orgId: effectiveOrgId, fromAgentId: '', toAgentId: '', subject: '', content: '', priority: 'normal' });
|
|
14
17
|
const [selectedNode, setSelectedNode] = useState(null);
|
|
15
18
|
const [nodePositions, setNodePositions] = useState([]);
|
|
16
19
|
const svgRef = useRef(null);
|
|
17
20
|
|
|
18
21
|
const loadMessages = () => {
|
|
19
|
-
engineCall('/messages?orgId=' +
|
|
22
|
+
engineCall('/messages?orgId=' + effectiveOrgId + '&limit=100').then(d => setMessages(d.messages || [])).catch(() => {});
|
|
20
23
|
};
|
|
21
24
|
const loadAgents = () => {
|
|
22
|
-
engineCall('/agents?orgId=' +
|
|
25
|
+
engineCall('/agents?orgId=' + effectiveOrgId).then(d => setAgents(d.agents || [])).catch(() => {});
|
|
23
26
|
};
|
|
24
27
|
const loadTopology = () => {
|
|
25
|
-
engineCall('/messages/topology?orgId=' +
|
|
28
|
+
engineCall('/messages/topology?orgId=' + effectiveOrgId).then(d => setTopology(d.topology || null)).catch(() => {});
|
|
26
29
|
};
|
|
27
30
|
useEffect(() => { loadMessages(); loadAgents(); loadTopology(); }, []);
|
|
28
31
|
|
|
@@ -200,6 +203,7 @@ export function MessagesPage() {
|
|
|
200
203
|
var _tip = { marginTop: 12, padding: 12, background: 'var(--bg-secondary, #1e293b)', borderRadius: 'var(--radius, 8px)', fontSize: 13 };
|
|
201
204
|
|
|
202
205
|
return h('div', { className: 'page-inner' },
|
|
206
|
+
h(orgCtx.Switcher),
|
|
203
207
|
// Page header
|
|
204
208
|
h('div', { className: 'page-header' }, h('h1', { style: { display: 'flex', alignItems: 'center' } }, 'Agent Messages', h(HelpButton, { label: 'Agent Messages' },
|
|
205
209
|
h('p', null, 'All inter-agent and external communications in one place. See how your agents talk to each other and to the outside world.'),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { h, useState, useEffect, useCallback, useRef, Fragment, useApp, engineCall, getOrgId } from '../components/utils.js';
|
|
2
2
|
import { I } from '../components/icons.js';
|
|
3
3
|
import { HelpButton } from '../components/help-button.js';
|
|
4
|
+
import { useOrgContext } from '../components/org-switcher.js';
|
|
4
5
|
|
|
5
6
|
// ─── Inject theme CSS once ───────────────────────────────
|
|
6
7
|
var _injected = false;
|
|
@@ -133,6 +134,8 @@ function OrgSummary(props) {
|
|
|
133
134
|
|
|
134
135
|
// ─── Main Component ─────────────────────────────────────
|
|
135
136
|
export function OrgChartPage() {
|
|
137
|
+
var orgCtx = useOrgContext();
|
|
138
|
+
var effectiveOrgId = orgCtx.selectedOrgId || getOrgId();
|
|
136
139
|
injectCSS();
|
|
137
140
|
var app = useApp();
|
|
138
141
|
var toast = app.toast;
|
|
@@ -151,7 +154,7 @@ export function OrgChartPage() {
|
|
|
151
154
|
setLoading(true); setError(null);
|
|
152
155
|
Promise.all([
|
|
153
156
|
engineCall('/hierarchy/org-chart').catch(function() { return null; }),
|
|
154
|
-
engineCall('/agents?orgId=' +
|
|
157
|
+
engineCall('/agents?orgId=' + effectiveOrgId).catch(function() { return { agents: [] }; }),
|
|
155
158
|
]).then(function(res) {
|
|
156
159
|
var hierRes = res[0]; var agentRes = res[1];
|
|
157
160
|
var avatarMap = {};
|
|
@@ -211,6 +214,7 @@ export function OrgChartPage() {
|
|
|
211
214
|
);
|
|
212
215
|
|
|
213
216
|
return h('div', { style: { height: '100%', display: 'flex', flexDirection: 'column', background: 'var(--oc-bg)', borderRadius: 'var(--radius-lg)', overflow: 'hidden' } },
|
|
217
|
+
h(orgCtx.Switcher, { style: { margin: '8px 12px 0', borderRadius: 6 } }),
|
|
214
218
|
// Toolbar
|
|
215
219
|
h('div', { style: { display: 'flex', alignItems: 'center', gap: 10, padding: '10px 16px', borderBottom: '1px solid var(--oc-border)', background: 'var(--oc-toolbar)', flexShrink: 0, flexWrap: 'wrap' } },
|
|
216
220
|
h('div', { style: { fontWeight: 700, fontSize: 14, color: 'var(--oc-text)', display: 'flex', alignItems: 'center', gap: 6 } },
|
|
@@ -342,9 +342,15 @@ export function OrganizationsPage() {
|
|
|
342
342
|
)
|
|
343
343
|
),
|
|
344
344
|
// Billing rate in header
|
|
345
|
-
detailOrg.billing_rate_per_agent > 0 && h('div', { style: { display: 'flex', alignItems: 'center', gap: 16, padding: '10px 14px', background: 'var(--success-soft, rgba(21,128,61,0.06))', borderRadius: 8, marginBottom: 16, fontSize: 13 } },
|
|
346
|
-
h('div', null, h('strong', null, 'Rate: '), (detailOrg.currency || 'USD') + ' ' + parseFloat(detailOrg.billing_rate_per_agent).toFixed(2) + '/agent/month'),
|
|
347
|
-
h('div', null, h('strong', null, 'Monthly Revenue: '), (
|
|
345
|
+
(detailOrg.billing_rate_per_agent > 0 || detailAgents.some(function(a) { return a.billing_rate > 0; })) && h('div', { style: { display: 'flex', alignItems: 'center', gap: 16, padding: '10px 14px', background: 'var(--success-soft, rgba(21,128,61,0.06))', borderRadius: 8, marginBottom: 16, fontSize: 13, flexWrap: 'wrap' } },
|
|
346
|
+
h('div', null, h('strong', null, 'Default Rate: '), (detailOrg.currency || 'USD') + ' ' + parseFloat(detailOrg.billing_rate_per_agent || 0).toFixed(2) + '/agent/month'),
|
|
347
|
+
h('div', null, h('strong', null, 'Monthly Revenue: '), (function() {
|
|
348
|
+
var total = detailAgents.reduce(function(sum, a) {
|
|
349
|
+
var rate = a.billing_rate > 0 ? parseFloat(a.billing_rate) : parseFloat(detailOrg.billing_rate_per_agent || 0);
|
|
350
|
+
return sum + rate;
|
|
351
|
+
}, 0);
|
|
352
|
+
return (detailOrg.currency || 'USD') + ' ' + total.toFixed(2);
|
|
353
|
+
})()),
|
|
348
354
|
h('div', null, h('strong', null, 'Agents: '), detailAgents.length)
|
|
349
355
|
),
|
|
350
356
|
|
|
@@ -438,6 +444,36 @@ export function OrganizationsPage() {
|
|
|
438
444
|
'No billing data yet. Billing records are created as agents process tasks and accumulate token costs.'
|
|
439
445
|
),
|
|
440
446
|
|
|
447
|
+
// Per-agent billing rates
|
|
448
|
+
detailAgents.length > 0 && h('div', { style: { marginBottom: 20 } },
|
|
449
|
+
h('div', { style: { fontSize: 14, fontWeight: 700, marginBottom: 8 } }, 'Per-Agent Billing Rates'),
|
|
450
|
+
h('div', { style: { fontSize: 11, color: 'var(--text-muted)', marginBottom: 10 } }, 'Set custom billing rates per agent. Leave blank to use the default org rate (' + (detailOrg.currency || 'USD') + ' ' + parseFloat(detailOrg.billing_rate_per_agent || 0).toFixed(2) + '/agent/month).'),
|
|
451
|
+
h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(220px, 1fr))', gap: 10 } },
|
|
452
|
+
detailAgents.map(function(a) {
|
|
453
|
+
var agentRate = a.billing_rate > 0 ? parseFloat(a.billing_rate) : 0;
|
|
454
|
+
var effectiveRate = agentRate > 0 ? agentRate : parseFloat(detailOrg.billing_rate_per_agent || 0);
|
|
455
|
+
return h('div', { key: a.id, style: { padding: 10, background: 'var(--bg-tertiary)', borderRadius: 8 } },
|
|
456
|
+
h('div', { style: { fontWeight: 600, fontSize: 13, marginBottom: 6 } }, a.name || a.id),
|
|
457
|
+
h('div', { style: { display: 'flex', gap: 6, alignItems: 'center' } },
|
|
458
|
+
h('span', { style: { fontSize: 12, color: 'var(--text-muted)' } }, detailOrg.currency || 'USD'),
|
|
459
|
+
h('input', { className: 'input', type: 'number', step: '0.01', min: '0', value: agentRate > 0 ? agentRate : '',
|
|
460
|
+
placeholder: effectiveRate.toFixed(2),
|
|
461
|
+
onChange: function(e) {
|
|
462
|
+
var val = parseFloat(e.target.value) || 0;
|
|
463
|
+
apiCall('/agents/' + a.id, { method: 'PATCH', body: JSON.stringify({ billingRate: val }) })
|
|
464
|
+
.then(function() { toast('Rate updated for ' + (a.name || a.id), 'success'); })
|
|
465
|
+
.catch(function(err) { toast(err.message, 'error'); });
|
|
466
|
+
},
|
|
467
|
+
style: { width: 90, fontSize: 12, padding: '4px 6px' }
|
|
468
|
+
}),
|
|
469
|
+
h('span', { style: { fontSize: 10, color: 'var(--text-muted)' } }, '/mo')
|
|
470
|
+
),
|
|
471
|
+
agentRate > 0 && h('div', { style: { fontSize: 10, color: 'var(--success, #15803d)', marginTop: 4 } }, 'Custom rate')
|
|
472
|
+
);
|
|
473
|
+
})
|
|
474
|
+
)
|
|
475
|
+
),
|
|
476
|
+
|
|
441
477
|
// Stats summary
|
|
442
478
|
(function() {
|
|
443
479
|
var totRev = billingSummary.reduce(function(a, m) { return a + (parseFloat(m.total_revenue) || 0); }, 0);
|
|
@@ -2,8 +2,11 @@ import { h, useState, useEffect, useCallback, Fragment, useApp, engineCall, getO
|
|
|
2
2
|
import { I } from '../components/icons.js';
|
|
3
3
|
import { Modal } from '../components/modal.js';
|
|
4
4
|
import { HelpButton } from '../components/help-button.js';
|
|
5
|
+
import { useOrgContext } from '../components/org-switcher.js';
|
|
5
6
|
|
|
6
7
|
export function SkillsPage() {
|
|
8
|
+
var orgCtx = useOrgContext();
|
|
9
|
+
var effectiveOrgId = orgCtx.selectedOrgId || getOrgId();
|
|
7
10
|
var app = useApp();
|
|
8
11
|
var toast = app.toast;
|
|
9
12
|
var setPage = app.setPage;
|
|
@@ -66,7 +69,7 @@ export function SkillsPage() {
|
|
|
66
69
|
// Load installed skills + statuses
|
|
67
70
|
var loadInstalled = useCallback(function() {
|
|
68
71
|
setInstalledLoading(true);
|
|
69
|
-
engineCall('/community/installed?orgId=' +
|
|
72
|
+
engineCall('/community/installed?orgId=' + effectiveOrgId)
|
|
70
73
|
.then(function(d) {
|
|
71
74
|
var items = d.installed || [];
|
|
72
75
|
setInstalled(items);
|
|
@@ -162,7 +165,7 @@ export function SkillsPage() {
|
|
|
162
165
|
var payload = isMultiField
|
|
163
166
|
? { credentials: tokenModal.fields.reduce(function(o, f) { o[f] = credFields[f].trim(); return o; }, {}) }
|
|
164
167
|
: { token: tokenValue };
|
|
165
|
-
await engineCall('/oauth/authorize/' + tokenModal.skillId + '?orgId=' +
|
|
168
|
+
await engineCall('/oauth/authorize/' + tokenModal.skillId + '?orgId=' + effectiveOrgId, {
|
|
166
169
|
method: 'POST',
|
|
167
170
|
body: JSON.stringify(payload)
|
|
168
171
|
});
|
|
@@ -183,7 +186,7 @@ export function SkillsPage() {
|
|
|
183
186
|
try {
|
|
184
187
|
await engineCall('/community/skills/' + skillId + '/' + (enable ? 'enable' : 'disable'), {
|
|
185
188
|
method: 'PUT',
|
|
186
|
-
body: JSON.stringify({ orgId:
|
|
189
|
+
body: JSON.stringify({ orgId: effectiveOrgId })
|
|
187
190
|
});
|
|
188
191
|
toast('Skill ' + (enable ? 'enabled' : 'disabled'), 'success');
|
|
189
192
|
loadInstalled();
|
|
@@ -201,7 +204,7 @@ export function SkillsPage() {
|
|
|
201
204
|
try {
|
|
202
205
|
await engineCall('/community/skills/' + skillId + '/uninstall', {
|
|
203
206
|
method: 'DELETE',
|
|
204
|
-
body: JSON.stringify({ orgId:
|
|
207
|
+
body: JSON.stringify({ orgId: effectiveOrgId })
|
|
205
208
|
});
|
|
206
209
|
toast('Skill uninstalled', 'success');
|
|
207
210
|
loadInstalled();
|
|
@@ -240,7 +243,7 @@ export function SkillsPage() {
|
|
|
240
243
|
try {
|
|
241
244
|
await engineCall('/community/skills/' + skillId + '/install', {
|
|
242
245
|
method: 'POST',
|
|
243
|
-
body: JSON.stringify({ orgId:
|
|
246
|
+
body: JSON.stringify({ orgId: effectiveOrgId })
|
|
244
247
|
});
|
|
245
248
|
toast('Skill installed', 'success');
|
|
246
249
|
loadInstalled();
|
|
@@ -269,7 +272,7 @@ export function SkillsPage() {
|
|
|
269
272
|
|
|
270
273
|
var loadIntegrations = useCallback(function() {
|
|
271
274
|
setIntLoading(true);
|
|
272
|
-
engineCall('/integrations/catalog?orgId=' +
|
|
275
|
+
engineCall('/integrations/catalog?orgId=' + effectiveOrgId)
|
|
273
276
|
.then(function(d) {
|
|
274
277
|
setIntegrations(d.catalog || []);
|
|
275
278
|
setIntCategories(d.categories || []);
|
|
@@ -302,7 +305,7 @@ export function SkillsPage() {
|
|
|
302
305
|
setOauthSaving(false);
|
|
303
306
|
// For OAuth2, check if app is already configured
|
|
304
307
|
if (int.authType === 'oauth2' && int.oauthProvider) {
|
|
305
|
-
engineCall('/oauth/app-config/' + int.oauthProvider + '?orgId=' +
|
|
308
|
+
engineCall('/oauth/app-config/' + int.oauthProvider + '?orgId=' + effectiveOrgId)
|
|
306
309
|
.then(function(d) { if (d.configured) setOauthAppConfigured(true); })
|
|
307
310
|
.catch(function() {});
|
|
308
311
|
}
|
|
@@ -312,14 +315,14 @@ export function SkillsPage() {
|
|
|
312
315
|
if (!tokenModal || !tokenModal.oauthProvider) return;
|
|
313
316
|
if (!oauthClientId.trim() || !oauthClientSecret.trim()) return;
|
|
314
317
|
setOauthSaving(true);
|
|
315
|
-
engineCall('/oauth/app-config/' + tokenModal.oauthProvider + '?orgId=' +
|
|
318
|
+
engineCall('/oauth/app-config/' + tokenModal.oauthProvider + '?orgId=' + effectiveOrgId, {
|
|
316
319
|
method: 'POST',
|
|
317
320
|
body: JSON.stringify({ clientId: oauthClientId.trim(), clientSecret: oauthClientSecret.trim() })
|
|
318
321
|
})
|
|
319
322
|
.then(function() {
|
|
320
323
|
setOauthAppConfigured(true);
|
|
321
324
|
toast('OAuth app configured for ' + tokenModal.skill.name, 'success');
|
|
322
|
-
return engineCall('/oauth/authorize/' + tokenModal.skillId + '?orgId=' +
|
|
325
|
+
return engineCall('/oauth/authorize/' + tokenModal.skillId + '?orgId=' + effectiveOrgId);
|
|
323
326
|
})
|
|
324
327
|
.then(function(d) {
|
|
325
328
|
if (d && (d.authUrl || d.authorizationUrl)) {
|
|
@@ -336,7 +339,7 @@ export function SkillsPage() {
|
|
|
336
339
|
|
|
337
340
|
var launchOauthFlow = function() {
|
|
338
341
|
if (!tokenModal) return;
|
|
339
|
-
engineCall('/oauth/authorize/' + tokenModal.skillId + '?orgId=' +
|
|
342
|
+
engineCall('/oauth/authorize/' + tokenModal.skillId + '?orgId=' + effectiveOrgId)
|
|
340
343
|
.then(function(d) {
|
|
341
344
|
if (d && (d.authUrl || d.authorizationUrl)) {
|
|
342
345
|
var popup = window.open(d.authUrl || d.authorizationUrl, 'oauth_connect', 'width=600,height=700,popup=yes');
|
|
@@ -353,7 +356,7 @@ export function SkillsPage() {
|
|
|
353
356
|
|
|
354
357
|
var disconnectIntegration = function(int) {
|
|
355
358
|
if (!confirm('Disconnect ' + int.name + '? Agents will lose access to its tools.')) return;
|
|
356
|
-
engineCall('/oauth/disconnect/' + int.skillId + '?orgId=' +
|
|
359
|
+
engineCall('/oauth/disconnect/' + int.skillId + '?orgId=' + effectiveOrgId, { method: 'DELETE' })
|
|
357
360
|
.then(function() { toast(int.name + ' disconnected', 'success'); loadIntegrations(); })
|
|
358
361
|
.catch(function(e) { toast('Failed: ' + e.message, 'error'); });
|
|
359
362
|
};
|
|
@@ -380,6 +383,7 @@ export function SkillsPage() {
|
|
|
380
383
|
// ── Builtin Tab ──
|
|
381
384
|
var renderBuiltin = function() {
|
|
382
385
|
return h(Fragment, null,
|
|
386
|
+
h(orgCtx.Switcher),
|
|
383
387
|
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 } },
|
|
384
388
|
h('div', { style: { position: 'relative', flex: 1, maxWidth: 320 } },
|
|
385
389
|
h('input', {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { h, useState, useEffect, useCallback, useRef, Fragment, useApp, engineCall, getOrgId } from '../components/utils.js';
|
|
2
2
|
import { I } from '../components/icons.js';
|
|
3
3
|
import { HelpButton } from '../components/help-button.js';
|
|
4
|
+
import { useOrgContext } from '../components/org-switcher.js';
|
|
4
5
|
|
|
5
6
|
// ─── Constants ───────────────────────────────────────────
|
|
6
7
|
var NODE_W = 200;
|
|
@@ -438,6 +439,8 @@ export function TaskPipelinePage() {
|
|
|
438
439
|
injectCSS();
|
|
439
440
|
var app = useApp();
|
|
440
441
|
var toast = app.toast;
|
|
442
|
+
var orgCtx = useOrgContext();
|
|
443
|
+
var effectiveOrgId = orgCtx.selectedOrgId || effectiveOrgId;
|
|
441
444
|
var _tasks = useState([]);
|
|
442
445
|
var tasks = _tasks[0]; var setTasks = _tasks[1];
|
|
443
446
|
var _stats = useState({ created: 0, assigned: 0, inProgress: 0, completed: 0, failed: 0, cancelled: 0, total: 0, todayCompleted: 0, todayFailed: 0, todayCreated: 0, avgDurationMs: 0, totalCost: 0, totalTokens: 0, topAgents: [] });
|
|
@@ -474,7 +477,7 @@ export function TaskPipelinePage() {
|
|
|
474
477
|
Promise.all([
|
|
475
478
|
engineCall('/task-pipeline?limit=200'),
|
|
476
479
|
engineCall('/task-pipeline/stats'),
|
|
477
|
-
engineCall('/agents?orgId=' +
|
|
480
|
+
engineCall('/agents?orgId=' + effectiveOrgId).catch(function() { return { agents: [] }; }),
|
|
478
481
|
]).then(function(res) {
|
|
479
482
|
setTasks(res[0]?.tasks || []);
|
|
480
483
|
setStats(res[1] || stats);
|
|
@@ -486,7 +489,7 @@ export function TaskPipelinePage() {
|
|
|
486
489
|
setAgentMap(map);
|
|
487
490
|
}).catch(function(err) { console.error('[TaskPipeline]', err); })
|
|
488
491
|
.finally(function() { setLoading(false); });
|
|
489
|
-
}, []);
|
|
492
|
+
}, [effectiveOrgId]);
|
|
490
493
|
|
|
491
494
|
// SSE
|
|
492
495
|
useEffect(function() {
|
|
@@ -1074,6 +1077,7 @@ export function AgentTaskPipeline(props) {
|
|
|
1074
1077
|
}
|
|
1075
1078
|
|
|
1076
1079
|
return h(Fragment, null,
|
|
1080
|
+
h(orgCtx.Switcher),
|
|
1077
1081
|
active.length > 0 && h('div', { style: { marginBottom: 12 } },
|
|
1078
1082
|
h('div', { style: { fontSize: 11, fontWeight: 600, color: STATUS_COLORS.in_progress, marginBottom: 6, display: 'flex', alignItems: 'center', gap: 4 } },
|
|
1079
1083
|
h('div', { style: { width: 6, height: 6, borderRadius: '50%', background: STATUS_COLORS.in_progress, animation: 'flowPulse 2s infinite' } }),
|
|
@@ -5,7 +5,7 @@ import { HelpButton } from '../components/help-button.js';
|
|
|
5
5
|
|
|
6
6
|
// ─── Permission Editor Component ───────────────────
|
|
7
7
|
|
|
8
|
-
function PermissionEditor({ userId, userName, currentPerms, pageRegistry, onSave, onClose }) {
|
|
8
|
+
function PermissionEditor({ userId, userName, currentPerms, pageRegistry, onSave, onClose, userObj }) {
|
|
9
9
|
// Deep clone perms (skip _allowedAgents from page grants)
|
|
10
10
|
var [grants, setGrants] = useState(function() {
|
|
11
11
|
if (currentPerms === '*') {
|
|
@@ -34,8 +34,13 @@ function PermissionEditor({ userId, userName, currentPerms, pageRegistry, onSave
|
|
|
34
34
|
return (currentPerms || {})._allowedAgents === '*' || !(currentPerms || {})._allowedAgents;
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
+
// Client org assignment
|
|
38
|
+
var [permOrgs, setPermOrgs] = useState([]);
|
|
39
|
+
var [userOrgId, setUserOrgId] = useState((userObj && userObj.clientOrgId) || '');
|
|
40
|
+
|
|
37
41
|
useEffect(function() {
|
|
38
42
|
apiCall('/agents').then(function(d) { setAgents(d.agents || d || []); }).catch(function() {});
|
|
43
|
+
apiCall('/organizations').then(function(d) { setPermOrgs(d.organizations || []); }).catch(function() {});
|
|
39
44
|
}, []);
|
|
40
45
|
var [saving, setSaving] = useState(false);
|
|
41
46
|
var [expandedPage, setExpandedPage] = useState(null);
|
|
@@ -415,8 +420,9 @@ export function UsersPage() {
|
|
|
415
420
|
var toast = app.toast;
|
|
416
421
|
var [users, setUsers] = useState([]);
|
|
417
422
|
var [creating, setCreating] = useState(false);
|
|
418
|
-
var [form, setForm] = useState({ email: '', password: '', name: '', role: 'viewer', permissions: '*' });
|
|
423
|
+
var [form, setForm] = useState({ email: '', password: '', name: '', role: 'viewer', permissions: '*', clientOrgId: '' });
|
|
419
424
|
var [resetTarget, setResetTarget] = useState(null);
|
|
425
|
+
var [clientOrgs, setClientOrgs] = useState([]);
|
|
420
426
|
var [newPassword, setNewPassword] = useState('');
|
|
421
427
|
var [resetting, setResetting] = useState(false);
|
|
422
428
|
var [permTarget, setPermTarget] = useState(null); // user object for permission editing
|
|
@@ -427,6 +433,7 @@ export function UsersPage() {
|
|
|
427
433
|
useEffect(function() {
|
|
428
434
|
load();
|
|
429
435
|
apiCall('/page-registry').then(function(d) { setPageRegistry(d); }).catch(function() {});
|
|
436
|
+
apiCall('/organizations').then(function(d) { setClientOrgs(d.organizations || []); }).catch(function() {});
|
|
430
437
|
}, []);
|
|
431
438
|
|
|
432
439
|
var generateCreatePassword = function() {
|
|
@@ -442,9 +449,10 @@ export function UsersPage() {
|
|
|
442
449
|
try {
|
|
443
450
|
var body = { email: form.email, password: form.password, name: form.name, role: form.role };
|
|
444
451
|
if (form.permissions !== '*') body.permissions = form.permissions;
|
|
452
|
+
if (form.clientOrgId) body.clientOrgId = form.clientOrgId;
|
|
445
453
|
await apiCall('/users', { method: 'POST', body: JSON.stringify(body) });
|
|
446
454
|
toast('User created. They will be prompted to set a new password on first login.', 'success');
|
|
447
|
-
setCreating(false); setForm({ email: '', password: '', name: '', role: 'viewer', permissions: '*' }); setShowCreatePerms(false); load();
|
|
455
|
+
setCreating(false); setForm({ email: '', password: '', name: '', role: 'viewer', permissions: '*', clientOrgId: '' }); setShowCreatePerms(false); load();
|
|
448
456
|
} catch (e) { toast(e.message, 'error'); }
|
|
449
457
|
};
|
|
450
458
|
|
|
@@ -566,6 +574,17 @@ export function UsersPage() {
|
|
|
566
574
|
)
|
|
567
575
|
),
|
|
568
576
|
h('div', { className: 'form-group' }, h('label', { className: 'form-label' }, 'Role'), h('select', { className: 'input', value: form.role, onChange: function(e) { setForm(function(f) { return Object.assign({}, f, { role: e.target.value }); }); } }, h('option', { value: 'viewer' }, 'Viewer'), h('option', { value: 'member' }, 'Member'), h('option', { value: 'admin' }, 'Admin'), h('option', { value: 'owner' }, 'Owner'))),
|
|
577
|
+
// Client organization assignment
|
|
578
|
+
clientOrgs.length > 0 && h('div', { className: 'form-group' },
|
|
579
|
+
h('label', { className: 'form-label' }, 'Client Organization'),
|
|
580
|
+
h('select', { className: 'input', value: form.clientOrgId, onChange: function(e) { setForm(function(f) { return Object.assign({}, f, { clientOrgId: e.target.value }); }); } },
|
|
581
|
+
h('option', { value: '' }, 'None (internal user)'),
|
|
582
|
+
clientOrgs.filter(function(o) { return o.is_active !== false; }).map(function(o) {
|
|
583
|
+
return h('option', { key: o.id, value: o.id }, o.name);
|
|
584
|
+
})
|
|
585
|
+
),
|
|
586
|
+
h('div', { style: { fontSize: 11, color: 'var(--text-muted)', marginTop: 4 } }, 'Assigning to a client org restricts this user to only see that organization\'s agents and data.')
|
|
587
|
+
),
|
|
569
588
|
// Inline permissions for member/viewer
|
|
570
589
|
(form.role === 'member' || form.role === 'viewer') && h('div', { style: { marginTop: 4 } },
|
|
571
590
|
h('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between' } },
|
|
@@ -700,7 +719,7 @@ export function UsersPage() {
|
|
|
700
719
|
h('div', { className: 'card-body-flush' },
|
|
701
720
|
users.length === 0 ? h('div', { style: { padding: 24, textAlign: 'center', color: 'var(--text-muted)' } }, 'No users')
|
|
702
721
|
: h('table', null,
|
|
703
|
-
h('thead', null, h('tr', null, h('th', null, 'Name'), h('th', null, 'Email'), h('th', null, 'Role'), h('th', null, 'Status'), h('th', null, 'Access'), h('th', null, '2FA'), h('th', null, 'Created'), h('th', { style: { width:
|
|
722
|
+
h('thead', null, h('tr', null, h('th', null, 'Name'), h('th', null, 'Email'), h('th', null, 'Role'), h('th', null, 'Organization'), h('th', null, 'Status'), h('th', null, 'Access'), h('th', null, '2FA'), h('th', null, 'Created'), h('th', { style: { width: 240 } }, 'Actions'))),
|
|
704
723
|
h('tbody', null, users.map(function(u) {
|
|
705
724
|
var isRestricted = u.role === 'member' || u.role === 'viewer';
|
|
706
725
|
var isDeactivated = u.isActive === false;
|
|
@@ -709,6 +728,10 @@ export function UsersPage() {
|
|
|
709
728
|
h('td', null, h('strong', null, u.name || '-')),
|
|
710
729
|
h('td', null, h('span', { style: { fontFamily: 'var(--font-mono)', fontSize: 12 } }, u.email)),
|
|
711
730
|
h('td', null, h('span', { className: 'badge badge-' + (u.role === 'owner' ? 'warning' : u.role === 'admin' ? 'primary' : 'neutral') }, u.role)),
|
|
731
|
+
h('td', null, (function() {
|
|
732
|
+
var org = u.clientOrgId && clientOrgs.find(function(o) { return o.id === u.clientOrgId; });
|
|
733
|
+
return org ? h('span', { className: 'badge badge-info', style: { fontSize: 10 } }, org.name) : h('span', { style: { color: 'var(--text-muted)', fontSize: 11 } }, 'Internal');
|
|
734
|
+
})()),
|
|
712
735
|
h('td', null, isDeactivated
|
|
713
736
|
? h('span', { className: 'badge badge-danger', style: { fontSize: 10 } }, 'Deactivated')
|
|
714
737
|
: h('span', { className: 'badge badge-success', style: { fontSize: 10 } }, 'Active')
|
|
@@ -725,6 +748,13 @@ export function UsersPage() {
|
|
|
725
748
|
style: !isRestricted ? { opacity: 0.4 } : {}
|
|
726
749
|
}, I.shield()),
|
|
727
750
|
h('button', { className: 'btn btn-ghost btn-sm', title: 'Reset Password', onClick: function() { setResetTarget(u); setNewPassword(''); } }, I.lock()),
|
|
751
|
+
// Impersonate (owner-only, not self)
|
|
752
|
+
!isSelf && app.user && app.user.role === 'owner' && !isDeactivated && h('button', {
|
|
753
|
+
className: 'btn btn-ghost btn-sm',
|
|
754
|
+
title: 'View as ' + (u.name || u.email),
|
|
755
|
+
onClick: function() { if (app.startImpersonation) app.startImpersonation(u.id); },
|
|
756
|
+
style: { color: 'var(--primary)' }
|
|
757
|
+
}, I.agents()),
|
|
728
758
|
// Deactivate / Reactivate
|
|
729
759
|
!isSelf && h('button', {
|
|
730
760
|
className: 'btn btn-ghost btn-sm',
|