botmux 2.85.1 → 2.86.0
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/core/command-handler.d.ts.map +1 -1
- package/dist/core/command-handler.js +209 -1
- package/dist/core/command-handler.js.map +1 -1
- package/dist/core/cost-calculator.d.ts.map +1 -1
- package/dist/core/cost-calculator.js +7 -106
- package/dist/core/cost-calculator.js.map +1 -1
- package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
- package/dist/core/dashboard-ipc-server.js +240 -2
- package/dist/core/dashboard-ipc-server.js.map +1 -1
- package/dist/core/passthrough-commands.d.ts.map +1 -1
- package/dist/core/passthrough-commands.js +1 -1
- package/dist/core/passthrough-commands.js.map +1 -1
- package/dist/core/role-resolver.d.ts +1 -0
- package/dist/core/role-resolver.d.ts.map +1 -1
- package/dist/core/role-resolver.js +14 -0
- package/dist/core/role-resolver.js.map +1 -1
- package/dist/dashboard/web/app.d.ts.map +1 -1
- package/dist/dashboard/web/app.js +15 -4
- package/dist/dashboard/web/app.js.map +1 -1
- package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
- package/dist/dashboard/web/bot-defaults.js +116 -0
- package/dist/dashboard/web/bot-defaults.js.map +1 -1
- package/dist/dashboard/web/groups.d.ts +2 -0
- package/dist/dashboard/web/groups.d.ts.map +1 -1
- package/dist/dashboard/web/groups.js +419 -3
- package/dist/dashboard/web/groups.js.map +1 -1
- package/dist/dashboard/web/i18n.d.ts.map +1 -1
- package/dist/dashboard/web/i18n.js +617 -3
- package/dist/dashboard/web/i18n.js.map +1 -1
- package/dist/dashboard/web/insights.d.ts +2 -0
- package/dist/dashboard/web/insights.d.ts.map +1 -0
- package/dist/dashboard/web/insights.js +1523 -0
- package/dist/dashboard/web/insights.js.map +1 -0
- package/dist/dashboard/web/role-profile-match.d.ts +31 -0
- package/dist/dashboard/web/role-profile-match.d.ts.map +1 -0
- package/dist/dashboard/web/role-profile-match.js +58 -0
- package/dist/dashboard/web/role-profile-match.js.map +1 -0
- package/dist/dashboard/web/roles.d.ts +1 -0
- package/dist/dashboard/web/roles.d.ts.map +1 -1
- package/dist/dashboard/web/roles.js +520 -27
- package/dist/dashboard/web/roles.js.map +1 -1
- package/dist/dashboard/web/sessions.d.ts.map +1 -1
- package/dist/dashboard/web/sessions.js +84 -0
- package/dist/dashboard/web/sessions.js.map +1 -1
- package/dist/dashboard-web/app.js +1243 -831
- package/dist/dashboard-web/index.html +2 -1
- package/dist/dashboard-web/style.css +1085 -3
- package/dist/dashboard.js +215 -3
- package/dist/dashboard.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +34 -1
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +34 -1
- package/dist/i18n/zh.js.map +1 -1
- package/dist/services/group-creator.d.ts +6 -0
- package/dist/services/group-creator.d.ts.map +1 -1
- package/dist/services/group-creator.js +54 -5
- package/dist/services/group-creator.js.map +1 -1
- package/dist/services/insight/antigravity-span-reader.d.ts +3 -0
- package/dist/services/insight/antigravity-span-reader.d.ts.map +1 -0
- package/dist/services/insight/antigravity-span-reader.js +249 -0
- package/dist/services/insight/antigravity-span-reader.js.map +1 -0
- package/dist/services/insight/classify.d.ts +7 -0
- package/dist/services/insight/classify.d.ts.map +1 -0
- package/dist/services/insight/classify.js +46 -0
- package/dist/services/insight/classify.js.map +1 -0
- package/dist/services/insight/claude-span-reader.d.ts +3 -0
- package/dist/services/insight/claude-span-reader.d.ts.map +1 -0
- package/dist/services/insight/claude-span-reader.js +257 -0
- package/dist/services/insight/claude-span-reader.js.map +1 -0
- package/dist/services/insight/codex-span-reader.d.ts +3 -0
- package/dist/services/insight/codex-span-reader.d.ts.map +1 -0
- package/dist/services/insight/codex-span-reader.js +290 -0
- package/dist/services/insight/codex-span-reader.js.map +1 -0
- package/dist/services/insight/intent.d.ts +5 -0
- package/dist/services/insight/intent.d.ts.map +1 -0
- package/dist/services/insight/intent.js +145 -0
- package/dist/services/insight/intent.js.map +1 -0
- package/dist/services/insight/jsonl.d.ts +10 -0
- package/dist/services/insight/jsonl.d.ts.map +1 -0
- package/dist/services/insight/jsonl.js +36 -0
- package/dist/services/insight/jsonl.js.map +1 -0
- package/dist/services/insight/prompt.d.ts +3 -0
- package/dist/services/insight/prompt.d.ts.map +1 -0
- package/dist/services/insight/prompt.js +99 -0
- package/dist/services/insight/prompt.js.map +1 -0
- package/dist/services/insight/redact.d.ts +4 -0
- package/dist/services/insight/redact.d.ts.map +1 -0
- package/dist/services/insight/redact.js +67 -0
- package/dist/services/insight/redact.js.map +1 -0
- package/dist/services/insight/report.d.ts +29 -0
- package/dist/services/insight/report.d.ts.map +1 -0
- package/dist/services/insight/report.js +1126 -0
- package/dist/services/insight/report.js.map +1 -0
- package/dist/services/insight/safe-detail.d.ts +5 -0
- package/dist/services/insight/safe-detail.d.ts.map +1 -0
- package/dist/services/insight/safe-detail.js +59 -0
- package/dist/services/insight/safe-detail.js.map +1 -0
- package/dist/services/insight/scrub.d.ts +22 -0
- package/dist/services/insight/scrub.d.ts.map +1 -0
- package/dist/services/insight/scrub.js +70 -0
- package/dist/services/insight/scrub.js.map +1 -0
- package/dist/services/insight/types.d.ts +394 -0
- package/dist/services/insight/types.d.ts.map +1 -0
- package/dist/services/insight/types.js +2 -0
- package/dist/services/insight/types.js.map +1 -0
- package/dist/services/role-profile-store.d.ts +25 -0
- package/dist/services/role-profile-store.d.ts.map +1 -0
- package/dist/services/role-profile-store.js +171 -0
- package/dist/services/role-profile-store.js.map +1 -0
- package/dist/services/transcript-resolver.d.ts +26 -0
- package/dist/services/transcript-resolver.d.ts.map +1 -0
- package/dist/services/transcript-resolver.js +111 -0
- package/dist/services/transcript-resolver.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,23 +1,48 @@
|
|
|
1
|
-
// Roles page:
|
|
2
|
-
// Displays groups as collapsible sections with bots nested inside.
|
|
3
|
-
// Each bot has its own per-group role definition selectable for editing.
|
|
1
|
+
// Roles page: group role editor + reusable role profile management.
|
|
4
2
|
import { botAvatarHtml, escapeHtml, loadNameMaps, loadingHtml, t } from './ui.js';
|
|
3
|
+
import { hasExplicitChatRole, summarizeGroupProfileMatches, } from './role-profile-match.js';
|
|
5
4
|
const MAX_ROLE_BYTES = 4096;
|
|
5
|
+
const PROFILE_ID_RE = /^[A-Za-z0-9._-]{1,64}$/;
|
|
6
6
|
let cache = [];
|
|
7
|
+
let allBots = [];
|
|
8
|
+
let profiles = [];
|
|
9
|
+
let profileEntries = [];
|
|
10
|
+
let groupProfileEntriesById = new Map();
|
|
11
|
+
let groupEffectiveRolesByBot = new Map();
|
|
12
|
+
let groupProfileContextLoaded = false;
|
|
13
|
+
let activeTab = 'groups';
|
|
7
14
|
let selectedGroupId = null;
|
|
8
15
|
let selectedBotId = null;
|
|
9
16
|
let editingContent = '';
|
|
10
17
|
let expandedGroups = new Set();
|
|
11
|
-
|
|
18
|
+
let selectedProfileId = null;
|
|
19
|
+
let selectedProfileBotId = null;
|
|
20
|
+
let profileEditingContent = '';
|
|
21
|
+
let selectedApplyGroupId = null;
|
|
22
|
+
function isValidProfileId(profileId) {
|
|
23
|
+
return PROFILE_ID_RE.test(profileId) && profileId !== '.' && profileId !== '..';
|
|
24
|
+
}
|
|
25
|
+
function hashChatId() {
|
|
26
|
+
const [, query = ''] = location.hash.split('?');
|
|
27
|
+
const chatId = new URLSearchParams(query).get('chatId')?.trim();
|
|
28
|
+
return chatId || null;
|
|
29
|
+
}
|
|
30
|
+
function pageHtml(tab) {
|
|
31
|
+
const isProfiles = tab === 'profiles';
|
|
12
32
|
return `<section class="page roles-page">
|
|
13
|
-
<div class="page-heading">
|
|
33
|
+
<div class="page-heading roles-heading">
|
|
14
34
|
<div>
|
|
15
35
|
<p class="eyebrow">${t('nav.roles')}</p>
|
|
16
36
|
<h1>${t('roles.title')}</h1>
|
|
17
37
|
<p>${t('roles.subtitle')}</p>
|
|
18
38
|
</div>
|
|
19
39
|
</div>
|
|
20
|
-
<
|
|
40
|
+
<nav class="wf-subnav roles-subnav">
|
|
41
|
+
<a href="#/roles" ${isProfiles ? '' : 'class="active"'}>${t('roles.tabGroups')}</a>
|
|
42
|
+
<a href="#/roles/profile" ${isProfiles ? 'class="active"' : ''}>${t('roles.tabProfiles')}</a>
|
|
43
|
+
</nav>
|
|
44
|
+
|
|
45
|
+
<div id="roles-by-group-view" class="roles-layout" ${isProfiles ? 'hidden' : ''}>
|
|
21
46
|
<div class="roles-tree-panel">
|
|
22
47
|
<div class="roles-tree-header">
|
|
23
48
|
<input type="search" id="roles-search" placeholder="${t('roles.search')}" />
|
|
@@ -48,11 +73,34 @@ function pageHtml() {
|
|
|
48
73
|
</div>
|
|
49
74
|
</div>
|
|
50
75
|
</div>
|
|
76
|
+
|
|
77
|
+
<div id="roles-profiles-view" class="roles-layout roles-profiles-layout" ${isProfiles ? '' : 'hidden'}>
|
|
78
|
+
<div class="roles-tree-panel">
|
|
79
|
+
<div class="roles-tree-header roles-profile-create">
|
|
80
|
+
<input type="text" id="roles-profile-id" placeholder="${t('roles.profileIdPlaceholder')}" maxlength="64" />
|
|
81
|
+
<button type="button" id="roles-profile-select">${t('roles.openProfile')}</button>
|
|
82
|
+
</div>
|
|
83
|
+
<div class="roles-tree-header">
|
|
84
|
+
<input type="search" id="roles-profile-search" placeholder="${t('roles.profileSearch')}" />
|
|
85
|
+
<button type="button" id="roles-profile-refresh">${t('roles.refresh')}</button>
|
|
86
|
+
</div>
|
|
87
|
+
<div id="roles-profile-list" class="roles-tree"></div>
|
|
88
|
+
</div>
|
|
89
|
+
<div class="roles-editor-panel">
|
|
90
|
+
<div id="roles-profile-empty" class="roles-editor-empty">${t('roles.profileSelectHint')}</div>
|
|
91
|
+
<div id="roles-profile-detail" class="roles-editor-form roles-profile-detail" style="display:none"></div>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
51
94
|
</section>`;
|
|
52
95
|
}
|
|
53
96
|
async function loadGroups() {
|
|
54
97
|
const r = await fetch('/api/groups');
|
|
55
98
|
const data = await r.json();
|
|
99
|
+
allBots = (data.bots ?? []).map((b) => ({
|
|
100
|
+
larkAppId: b.larkAppId,
|
|
101
|
+
botName: b.botName ?? b.larkAppId,
|
|
102
|
+
botAvatarUrl: b.botAvatarUrl,
|
|
103
|
+
}));
|
|
56
104
|
cache = (data.chats ?? []).map((c) => ({
|
|
57
105
|
chatId: c.chatId,
|
|
58
106
|
name: c.name ?? c.chatId,
|
|
@@ -65,10 +113,72 @@ async function loadGroups() {
|
|
|
65
113
|
})),
|
|
66
114
|
}));
|
|
67
115
|
}
|
|
116
|
+
async function loadProfiles() {
|
|
117
|
+
const r = await fetch('/api/role-profiles');
|
|
118
|
+
const data = await r.json();
|
|
119
|
+
profiles = data.profiles ?? [];
|
|
120
|
+
}
|
|
121
|
+
async function loadProfileEntries(profileId) {
|
|
122
|
+
const r = await fetch(`/api/role-profiles/${encodeURIComponent(profileId)}`);
|
|
123
|
+
const data = await r.json();
|
|
124
|
+
profileEntries = data.entries ?? [];
|
|
125
|
+
}
|
|
68
126
|
async function loadRole(larkAppId, chatId) {
|
|
69
127
|
const r = await fetch(`/api/roles/${encodeURIComponent(larkAppId)}/${encodeURIComponent(chatId)}`);
|
|
70
128
|
return r.json();
|
|
71
129
|
}
|
|
130
|
+
function roleKey(larkAppId, chatId) {
|
|
131
|
+
return `${larkAppId}\u0000${chatId}`;
|
|
132
|
+
}
|
|
133
|
+
async function loadGroupProfileContext() {
|
|
134
|
+
const detailPairs = await Promise.all(profiles.map(async (profile) => {
|
|
135
|
+
try {
|
|
136
|
+
const r = await fetch(`/api/role-profiles/${encodeURIComponent(profile.profileId)}`);
|
|
137
|
+
const body = await r.json().catch(() => ({}));
|
|
138
|
+
return [profile.profileId, Array.isArray(body.entries) ? body.entries : []];
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return [profile.profileId, []];
|
|
142
|
+
}
|
|
143
|
+
}));
|
|
144
|
+
const nextEffectiveRoles = new Map();
|
|
145
|
+
const seen = new Set();
|
|
146
|
+
await Promise.all(cache.flatMap(group => group.memberBots
|
|
147
|
+
.filter(bot => bot.inChat)
|
|
148
|
+
.map(async (bot) => {
|
|
149
|
+
const key = roleKey(bot.larkAppId, group.chatId);
|
|
150
|
+
if (seen.has(key))
|
|
151
|
+
return;
|
|
152
|
+
seen.add(key);
|
|
153
|
+
try {
|
|
154
|
+
const role = await loadRole(bot.larkAppId, group.chatId);
|
|
155
|
+
const hasEffectiveRole = role.hasEffectiveRole ?? role.hasRole;
|
|
156
|
+
const effectiveContent = 'effectiveContent' in role ? role.effectiveContent : role.content;
|
|
157
|
+
nextEffectiveRoles.set(key, {
|
|
158
|
+
content: hasEffectiveRole ? String(effectiveContent ?? '') : null,
|
|
159
|
+
source: role.effectiveSource ?? (role.hasRole ? 'chat' : 'none'),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
nextEffectiveRoles.set(key, null);
|
|
164
|
+
}
|
|
165
|
+
})));
|
|
166
|
+
groupProfileEntriesById = new Map(detailPairs);
|
|
167
|
+
groupEffectiveRolesByBot = nextEffectiveRoles;
|
|
168
|
+
}
|
|
169
|
+
async function refreshGroupProfileContext() {
|
|
170
|
+
try {
|
|
171
|
+
await loadGroupProfileContext();
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
groupProfileEntriesById = new Map();
|
|
175
|
+
groupEffectiveRolesByBot = new Map();
|
|
176
|
+
}
|
|
177
|
+
finally {
|
|
178
|
+
groupProfileContextLoaded = true;
|
|
179
|
+
renderTree(document.getElementById('roles-search')?.value ?? '');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
72
182
|
async function saveRole(larkAppId, chatId, content) {
|
|
73
183
|
const r = await fetch(`/api/roles/${encodeURIComponent(larkAppId)}/${encodeURIComponent(chatId)}`, {
|
|
74
184
|
method: 'PUT',
|
|
@@ -81,12 +191,66 @@ async function deleteRole(larkAppId, chatId) {
|
|
|
81
191
|
const r = await fetch(`/api/roles/${encodeURIComponent(larkAppId)}/${encodeURIComponent(chatId)}`, { method: 'DELETE' });
|
|
82
192
|
return r.ok;
|
|
83
193
|
}
|
|
194
|
+
async function loadProfileEntry(profileId, larkAppId) {
|
|
195
|
+
const r = await fetch(`/api/role-profiles/${encodeURIComponent(profileId)}/${encodeURIComponent(larkAppId)}`);
|
|
196
|
+
return r.json();
|
|
197
|
+
}
|
|
198
|
+
async function saveProfileEntry(profileId, larkAppId, content) {
|
|
199
|
+
const r = await fetch(`/api/role-profiles/${encodeURIComponent(profileId)}/${encodeURIComponent(larkAppId)}`, {
|
|
200
|
+
method: 'PUT',
|
|
201
|
+
headers: { 'content-type': 'application/json' },
|
|
202
|
+
body: JSON.stringify({ content, allowEmpty: true }),
|
|
203
|
+
});
|
|
204
|
+
return r.ok;
|
|
205
|
+
}
|
|
206
|
+
async function deleteProfileEntry(profileId, larkAppId) {
|
|
207
|
+
const r = await fetch(`/api/role-profiles/${encodeURIComponent(profileId)}/${encodeURIComponent(larkAppId)}`, { method: 'DELETE' });
|
|
208
|
+
return r.ok;
|
|
209
|
+
}
|
|
210
|
+
function byteLength(s) {
|
|
211
|
+
return new TextEncoder().encode(s).length;
|
|
212
|
+
}
|
|
84
213
|
function botRoleCount(group) {
|
|
85
214
|
return group.memberBots.filter(b => b.inChat && b.hasRole).length;
|
|
86
215
|
}
|
|
87
216
|
function botInChatCount(group) {
|
|
88
217
|
return group.memberBots.filter(b => b.inChat).length;
|
|
89
218
|
}
|
|
219
|
+
function profileHasEntry(profile, larkAppId) {
|
|
220
|
+
return (profile.botEntries ?? []).some(entry => entry.larkAppId === larkAppId && entry.hasEntry);
|
|
221
|
+
}
|
|
222
|
+
function entryForBot(larkAppId) {
|
|
223
|
+
return profileEntries.find(entry => entry.larkAppId === larkAppId);
|
|
224
|
+
}
|
|
225
|
+
function switchTab(tab) {
|
|
226
|
+
activeTab = tab;
|
|
227
|
+
document.getElementById('roles-by-group-view')?.toggleAttribute('hidden', tab !== 'groups');
|
|
228
|
+
document.getElementById('roles-profiles-view')?.toggleAttribute('hidden', tab !== 'profiles');
|
|
229
|
+
}
|
|
230
|
+
function renderRolesGroupProfileStatus(group) {
|
|
231
|
+
if (!profiles.length || !groupProfileContextLoaded)
|
|
232
|
+
return '';
|
|
233
|
+
const rolesByBot = new Map();
|
|
234
|
+
for (const bot of group.memberBots) {
|
|
235
|
+
if (!bot.inChat)
|
|
236
|
+
continue;
|
|
237
|
+
rolesByBot.set(bot.larkAppId, groupEffectiveRolesByBot.get(roleKey(bot.larkAppId, group.chatId)) ?? null);
|
|
238
|
+
}
|
|
239
|
+
if (!hasExplicitChatRole(rolesByBot))
|
|
240
|
+
return '';
|
|
241
|
+
const best = summarizeGroupProfileMatches(group.memberBots, profiles, groupProfileEntriesById, rolesByBot)[0];
|
|
242
|
+
if (!best)
|
|
243
|
+
return `<div class="roles-profile-match muted">${t('groups.profileStatusUnmatched')}</div>`;
|
|
244
|
+
const key = best.kind === 'full' ? 'groups.profileStatusFullChat' : 'groups.profileStatusPartial';
|
|
245
|
+
return `<div class="roles-profile-match ${best.kind}">
|
|
246
|
+
${escapeHtml(t(key, {
|
|
247
|
+
name: best.profileId,
|
|
248
|
+
matched: best.matched,
|
|
249
|
+
total: best.total,
|
|
250
|
+
chat: best.chatMatched,
|
|
251
|
+
}))}
|
|
252
|
+
</div>`;
|
|
253
|
+
}
|
|
90
254
|
function renderTree(filter = '') {
|
|
91
255
|
const tree = document.getElementById('roles-tree');
|
|
92
256
|
if (!tree)
|
|
@@ -139,28 +303,25 @@ function renderTree(filter = '') {
|
|
|
139
303
|
<div class="roles-group-meta">
|
|
140
304
|
${roleCount}/${totalInChat} ${t('roles.botsWithRoles')}
|
|
141
305
|
</div>
|
|
306
|
+
${renderRolesGroupProfileStatus(g)}
|
|
142
307
|
</div>
|
|
143
308
|
<span class="roles-group-chevron"></span>
|
|
144
309
|
</div>
|
|
145
310
|
<div class="roles-bot-list">${botRows}</div>
|
|
146
311
|
</div>`;
|
|
147
312
|
}).join('');
|
|
148
|
-
// Group row click → toggle expand
|
|
149
313
|
tree.querySelectorAll('.roles-group-row').forEach(row => {
|
|
150
314
|
row.addEventListener('click', () => {
|
|
151
315
|
const gid = row.dataset.groupId;
|
|
152
316
|
if (!gid)
|
|
153
317
|
return;
|
|
154
|
-
if (expandedGroups.has(gid))
|
|
318
|
+
if (expandedGroups.has(gid))
|
|
155
319
|
expandedGroups.delete(gid);
|
|
156
|
-
|
|
157
|
-
else {
|
|
320
|
+
else
|
|
158
321
|
expandedGroups.add(gid);
|
|
159
|
-
}
|
|
160
322
|
renderTree(document.getElementById('roles-search')?.value ?? '');
|
|
161
323
|
});
|
|
162
324
|
});
|
|
163
|
-
// Bot row click → select for editing
|
|
164
325
|
tree.querySelectorAll('.roles-bot-row').forEach(row => {
|
|
165
326
|
row.addEventListener('click', (e) => {
|
|
166
327
|
e.stopPropagation();
|
|
@@ -209,7 +370,7 @@ function updateByteCount() {
|
|
|
209
370
|
const el = document.getElementById('roles-editor-bytecount');
|
|
210
371
|
if (!el)
|
|
211
372
|
return;
|
|
212
|
-
const len =
|
|
373
|
+
const len = byteLength(editingContent);
|
|
213
374
|
el.textContent = `${len} / ${MAX_ROLE_BYTES} bytes`;
|
|
214
375
|
el.className = `roles-bytecount ${len > 3800 ? 'warn' : ''} ${len > MAX_ROLE_BYTES ? 'over' : ''}`;
|
|
215
376
|
updateSaveButton(len);
|
|
@@ -218,7 +379,7 @@ function updateSaveButton(byteLen) {
|
|
|
218
379
|
const btn = document.getElementById('roles-save');
|
|
219
380
|
if (!btn)
|
|
220
381
|
return;
|
|
221
|
-
const len = byteLen ??
|
|
382
|
+
const len = byteLen ?? byteLength(editingContent);
|
|
222
383
|
btn.disabled = len > MAX_ROLE_BYTES || editingContent.trim().length === 0;
|
|
223
384
|
}
|
|
224
385
|
function updatePreview() {
|
|
@@ -249,31 +410,326 @@ function resetEditor() {
|
|
|
249
410
|
if (delBtn)
|
|
250
411
|
delBtn.style.display = 'none';
|
|
251
412
|
}
|
|
252
|
-
|
|
253
|
-
|
|
413
|
+
function renderProfileList(filter = '') {
|
|
414
|
+
const list = document.getElementById('roles-profile-list');
|
|
415
|
+
if (!list)
|
|
416
|
+
return;
|
|
417
|
+
const q = filter.toLowerCase();
|
|
418
|
+
const filtered = profiles.filter(p => !q || p.profileId.toLowerCase().includes(q));
|
|
419
|
+
if (filtered.length === 0) {
|
|
420
|
+
list.innerHTML = `<div class="roles-empty">${t('roles.profileEmpty')}</div>`;
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
list.innerHTML = filtered.map(p => {
|
|
424
|
+
const selected = selectedProfileId === p.profileId;
|
|
425
|
+
const hasAnyLocal = (p.botEntries ?? []).some(entry => entry.hasEntry);
|
|
426
|
+
return `
|
|
427
|
+
<div class="roles-profile-row ${selected ? 'selected' : ''}" data-profile-id="${escapeHtml(p.profileId)}">
|
|
428
|
+
<div class="roles-profile-row-main">
|
|
429
|
+
<div class="roles-profile-name">${escapeHtml(p.profileId)}</div>
|
|
430
|
+
<div class="roles-group-meta">${p.entryCount} ${t('roles.profileEntries')}</div>
|
|
431
|
+
</div>
|
|
432
|
+
<span class="roles-badge ${hasAnyLocal ? 'has-role' : 'no-role'}">${hasAnyLocal ? t('roles.configured') : t('roles.profileMissing')}</span>
|
|
433
|
+
</div>`;
|
|
434
|
+
}).join('');
|
|
435
|
+
list.querySelectorAll('.roles-profile-row').forEach(row => {
|
|
436
|
+
row.addEventListener('click', () => {
|
|
437
|
+
const profileId = row.dataset.profileId;
|
|
438
|
+
if (profileId)
|
|
439
|
+
void selectProfile(profileId);
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
async function selectProfile(profileId) {
|
|
444
|
+
if (!isValidProfileId(profileId.trim()))
|
|
445
|
+
return;
|
|
446
|
+
selectedProfileId = profileId.trim();
|
|
447
|
+
selectedProfileBotId = null;
|
|
448
|
+
profileEditingContent = '';
|
|
449
|
+
selectedApplyGroupId = selectedApplyGroupId ?? cache[0]?.chatId ?? null;
|
|
450
|
+
await loadProfileEntries(selectedProfileId);
|
|
451
|
+
renderProfileList(document.getElementById('roles-profile-search')?.value ?? '');
|
|
452
|
+
renderProfileDetail();
|
|
453
|
+
}
|
|
454
|
+
function renderProfileDetail() {
|
|
455
|
+
const empty = document.getElementById('roles-profile-empty');
|
|
456
|
+
const detail = document.getElementById('roles-profile-detail');
|
|
457
|
+
if (!empty || !detail)
|
|
458
|
+
return;
|
|
459
|
+
if (!selectedProfileId) {
|
|
460
|
+
empty.style.display = '';
|
|
461
|
+
detail.style.display = 'none';
|
|
462
|
+
detail.innerHTML = '';
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
empty.style.display = 'none';
|
|
466
|
+
detail.style.display = '';
|
|
467
|
+
const selectedBot = allBots.find(b => b.larkAppId === selectedProfileBotId);
|
|
468
|
+
const entry = selectedProfileBotId ? entryForBot(selectedProfileBotId) : undefined;
|
|
469
|
+
detail.innerHTML = `
|
|
470
|
+
<div class="roles-profile-title">
|
|
471
|
+
<div>
|
|
472
|
+
<div class="roles-editor-breadcrumb">
|
|
473
|
+
<span>${escapeHtml(selectedProfileId)}</span>
|
|
474
|
+
${selectedBot ? `<span class="roles-breadcrumb-sep">›</span><span>${escapeHtml(selectedBot.botName ?? selectedBot.larkAppId)}</span>` : ''}
|
|
475
|
+
</div>
|
|
476
|
+
<div class="roles-editor-meta-line">${t('roles.profileRuntimeHint')}</div>
|
|
477
|
+
</div>
|
|
478
|
+
</div>
|
|
479
|
+
<div class="roles-profile-grid">
|
|
480
|
+
<div class="roles-profile-bots">
|
|
481
|
+
<div class="roles-profile-section-title">${t('roles.profileBots')}</div>
|
|
482
|
+
<div class="roles-profile-bot-list">
|
|
483
|
+
${allBots.map(bot => {
|
|
484
|
+
const hasEntry = !!entryForBot(bot.larkAppId);
|
|
485
|
+
const selected = selectedProfileBotId === bot.larkAppId;
|
|
486
|
+
return `
|
|
487
|
+
<div class="roles-bot-row roles-profile-bot-row ${selected ? 'selected' : ''}" data-profile-bot-id="${escapeHtml(bot.larkAppId)}">
|
|
488
|
+
${botAvatarHtml({ name: bot.botName, larkAppId: bot.larkAppId, size: 'sm' })}
|
|
489
|
+
<div class="roles-bot-info">
|
|
490
|
+
<div class="roles-bot-name">${escapeHtml(bot.botName ?? bot.larkAppId)}</div>
|
|
491
|
+
<div class="roles-bot-id">${escapeHtml(bot.larkAppId)}</div>
|
|
492
|
+
</div>
|
|
493
|
+
<span class="roles-badge ${hasEntry ? 'has-role' : 'no-role'}">${hasEntry ? t('roles.configured') : t('roles.unconfigured')}</span>
|
|
494
|
+
</div>`;
|
|
495
|
+
}).join('')}
|
|
496
|
+
</div>
|
|
497
|
+
</div>
|
|
498
|
+
<div class="roles-profile-editor">
|
|
499
|
+
${selectedProfileBotId ? `
|
|
500
|
+
<textarea id="roles-profile-textarea" placeholder="${t('roles.profileEditorPlaceholder')}" rows="12">${escapeHtml(profileEditingContent || entry?.content || '')}</textarea>
|
|
501
|
+
<div class="roles-editor-footer">
|
|
502
|
+
<span id="roles-profile-bytecount" class="roles-bytecount"></span>
|
|
503
|
+
<div class="roles-editor-actions">
|
|
504
|
+
<button type="button" id="roles-profile-delete" class="danger" ${entry ? '' : 'style="display:none"'}>${t('roles.delete')}</button>
|
|
505
|
+
<button type="button" id="roles-profile-save" class="primary">${t('roles.saveEntry')}</button>
|
|
506
|
+
</div>
|
|
507
|
+
</div>
|
|
508
|
+
<div id="roles-profile-preview" class="roles-preview"></div>
|
|
509
|
+
` : `<div class="roles-editor-empty roles-profile-inline-empty">${t('roles.profileBotSelectHint')}</div>`}
|
|
510
|
+
</div>
|
|
511
|
+
</div>
|
|
512
|
+
<div class="roles-profile-apply">
|
|
513
|
+
<div class="roles-profile-section-title">${t('roles.applyToGroup')}</div>
|
|
514
|
+
<div class="roles-profile-apply-controls">
|
|
515
|
+
<select id="roles-profile-apply-group">
|
|
516
|
+
${cache.map(g => `<option value="${escapeHtml(g.chatId)}" ${selectedApplyGroupId === g.chatId ? 'selected' : ''}>${escapeHtml(g.name ?? g.chatId)}</option>`).join('')}
|
|
517
|
+
</select>
|
|
518
|
+
<label class="roles-profile-force"><input type="checkbox" id="roles-profile-apply-force"> ${t('roles.applyForce')}</label>
|
|
519
|
+
</div>
|
|
520
|
+
<div id="roles-profile-apply-bots"></div>
|
|
521
|
+
<div class="roles-editor-actions">
|
|
522
|
+
<button type="button" id="roles-profile-preview-apply">${t('roles.previewApply')}</button>
|
|
523
|
+
<button type="button" id="roles-profile-apply" class="primary">${t('roles.applyProfile')}</button>
|
|
524
|
+
</div>
|
|
525
|
+
<div id="roles-profile-apply-status" class="roles-profile-status"></div>
|
|
526
|
+
</div>
|
|
527
|
+
`;
|
|
528
|
+
detail.querySelectorAll('.roles-profile-bot-row').forEach(row => {
|
|
529
|
+
row.addEventListener('click', () => {
|
|
530
|
+
const botId = row.dataset.profileBotId;
|
|
531
|
+
if (botId)
|
|
532
|
+
void selectProfileBot(botId);
|
|
533
|
+
});
|
|
534
|
+
});
|
|
535
|
+
renderProfileApplyBots();
|
|
536
|
+
bindProfileEditor();
|
|
537
|
+
}
|
|
538
|
+
async function selectProfileBot(botId) {
|
|
539
|
+
if (!selectedProfileId)
|
|
540
|
+
return;
|
|
541
|
+
selectedProfileBotId = botId;
|
|
542
|
+
const entry = await loadProfileEntry(selectedProfileId, botId);
|
|
543
|
+
profileEditingContent = entry.content ?? '';
|
|
544
|
+
await loadProfileEntries(selectedProfileId);
|
|
545
|
+
renderProfileDetail();
|
|
546
|
+
}
|
|
547
|
+
function bindProfileEditor() {
|
|
548
|
+
const textarea = document.getElementById('roles-profile-textarea');
|
|
549
|
+
if (textarea) {
|
|
550
|
+
profileEditingContent = textarea.value;
|
|
551
|
+
updateProfileByteCount();
|
|
552
|
+
updateProfilePreview();
|
|
553
|
+
textarea.addEventListener('input', (e) => {
|
|
554
|
+
profileEditingContent = e.target.value;
|
|
555
|
+
updateProfileByteCount();
|
|
556
|
+
updateProfilePreview();
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
document.getElementById('roles-profile-save')?.addEventListener('click', async function () {
|
|
560
|
+
if (!selectedProfileId || !selectedProfileBotId)
|
|
561
|
+
return;
|
|
562
|
+
this.disabled = true;
|
|
563
|
+
this.textContent = '...';
|
|
564
|
+
try {
|
|
565
|
+
const ok = await saveProfileEntry(selectedProfileId, selectedProfileBotId, profileEditingContent);
|
|
566
|
+
await loadProfiles();
|
|
567
|
+
await loadProfileEntries(selectedProfileId);
|
|
568
|
+
renderProfileList(document.getElementById('roles-profile-search')?.value ?? '');
|
|
569
|
+
void refreshGroupProfileContext();
|
|
570
|
+
renderProfileDetail();
|
|
571
|
+
flashProfileStatus(ok ? t('roles.saved') : t('roles.saveFailed'), !ok);
|
|
572
|
+
}
|
|
573
|
+
finally {
|
|
574
|
+
this.disabled = false;
|
|
575
|
+
this.textContent = t('roles.saveEntry');
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
document.getElementById('roles-profile-delete')?.addEventListener('click', async function () {
|
|
579
|
+
if (!selectedProfileId || !selectedProfileBotId)
|
|
580
|
+
return;
|
|
581
|
+
if (!confirm(t('roles.confirmDeleteProfileEntry')))
|
|
582
|
+
return;
|
|
583
|
+
this.disabled = true;
|
|
584
|
+
try {
|
|
585
|
+
await deleteProfileEntry(selectedProfileId, selectedProfileBotId);
|
|
586
|
+
profileEditingContent = '';
|
|
587
|
+
await loadProfiles();
|
|
588
|
+
await loadProfileEntries(selectedProfileId);
|
|
589
|
+
renderProfileList(document.getElementById('roles-profile-search')?.value ?? '');
|
|
590
|
+
void refreshGroupProfileContext();
|
|
591
|
+
renderProfileDetail();
|
|
592
|
+
}
|
|
593
|
+
finally {
|
|
594
|
+
this.disabled = false;
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
document.getElementById('roles-profile-apply-group')?.addEventListener('change', (e) => {
|
|
598
|
+
selectedApplyGroupId = e.target.value;
|
|
599
|
+
renderProfileApplyBots();
|
|
600
|
+
});
|
|
601
|
+
document.getElementById('roles-profile-preview-apply')?.addEventListener('click', () => runProfileApply(true));
|
|
602
|
+
document.getElementById('roles-profile-apply')?.addEventListener('click', () => runProfileApply(false));
|
|
603
|
+
}
|
|
604
|
+
function updateProfileByteCount() {
|
|
605
|
+
const el = document.getElementById('roles-profile-bytecount');
|
|
606
|
+
const btn = document.getElementById('roles-profile-save');
|
|
607
|
+
if (!el)
|
|
608
|
+
return;
|
|
609
|
+
const len = byteLength(profileEditingContent);
|
|
610
|
+
el.textContent = `${len} / ${MAX_ROLE_BYTES} bytes`;
|
|
611
|
+
el.className = `roles-bytecount ${len > 3800 ? 'warn' : ''} ${len > MAX_ROLE_BYTES ? 'over' : ''}`;
|
|
612
|
+
if (btn)
|
|
613
|
+
btn.disabled = len > MAX_ROLE_BYTES || profileEditingContent.trim().length === 0;
|
|
614
|
+
}
|
|
615
|
+
function updateProfilePreview() {
|
|
616
|
+
const preview = document.getElementById('roles-profile-preview');
|
|
617
|
+
if (!preview)
|
|
618
|
+
return;
|
|
619
|
+
preview.innerHTML = profileEditingContent.trim()
|
|
620
|
+
? `<strong>${t('roles.preview')}</strong><pre>${escapeHtml(profileEditingContent)}</pre>`
|
|
621
|
+
: `<small>${t('roles.previewEmpty')}</small>`;
|
|
622
|
+
}
|
|
623
|
+
function renderProfileApplyBots() {
|
|
624
|
+
const wrap = document.getElementById('roles-profile-apply-bots');
|
|
625
|
+
if (!wrap)
|
|
626
|
+
return;
|
|
627
|
+
const groupId = selectedApplyGroupId ?? cache[0]?.chatId ?? '';
|
|
628
|
+
const group = cache.find(g => g.chatId === groupId);
|
|
629
|
+
const bots = group?.memberBots.filter(b => b.inChat) ?? [];
|
|
630
|
+
if (!group || bots.length === 0) {
|
|
631
|
+
wrap.innerHTML = `<div class="roles-empty">${t('roles.noChats')}</div>`;
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
wrap.innerHTML = bots.map(bot => {
|
|
635
|
+
const hasEntry = !!entryForBot(bot.larkAppId);
|
|
636
|
+
return `
|
|
637
|
+
<label class="checkbox-row roles-profile-apply-bot">
|
|
638
|
+
<input type="checkbox" name="profile-apply-bot" value="${escapeHtml(bot.larkAppId)}" ${hasEntry ? 'checked' : ''}>
|
|
639
|
+
<span>${escapeHtml(bot.botName ?? bot.larkAppId)}</span>
|
|
640
|
+
<small>${hasEntry ? t('roles.configured') : t('roles.profileMissing')}</small>
|
|
641
|
+
</label>`;
|
|
642
|
+
}).join('');
|
|
643
|
+
}
|
|
644
|
+
async function runProfileApply(preview) {
|
|
645
|
+
const profileId = selectedProfileId;
|
|
646
|
+
if (!profileId)
|
|
647
|
+
return;
|
|
648
|
+
const groupId = selectedApplyGroupId ?? cache[0]?.chatId;
|
|
649
|
+
if (!groupId)
|
|
650
|
+
return;
|
|
651
|
+
const force = document.getElementById('roles-profile-apply-force')?.checked === true;
|
|
652
|
+
const selected = [...document.querySelectorAll('input[name=profile-apply-bot]:checked')].map(i => i.value);
|
|
653
|
+
const status = document.getElementById('roles-profile-apply-status');
|
|
654
|
+
if (selected.length === 0) {
|
|
655
|
+
if (status)
|
|
656
|
+
status.textContent = t('roles.applyPickBots');
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
if (status)
|
|
660
|
+
status.textContent = '...';
|
|
661
|
+
const results = await Promise.all(selected.map(async (larkAppId) => {
|
|
662
|
+
const r = await fetch(`/api/role-profiles/${encodeURIComponent(profileId)}/apply`, {
|
|
663
|
+
method: 'POST',
|
|
664
|
+
headers: { 'content-type': 'application/json' },
|
|
665
|
+
body: JSON.stringify({ chatId: groupId, larkAppId, force, preview }),
|
|
666
|
+
});
|
|
667
|
+
const body = await r.json().catch(() => ({}));
|
|
668
|
+
return { larkAppId, ok: r.ok && body.ok !== false, status: r.status, error: body.error, wouldRefuse: body.wouldRefuse };
|
|
669
|
+
}));
|
|
670
|
+
if (status) {
|
|
671
|
+
status.innerHTML = results.map(r => {
|
|
672
|
+
const bot = allBots.find(b => b.larkAppId === r.larkAppId);
|
|
673
|
+
const label = escapeHtml(bot?.botName ?? r.larkAppId);
|
|
674
|
+
const outcome = r.ok
|
|
675
|
+
? (preview ? (r.wouldRefuse ? t('roles.applyWouldRefuse') : t('roles.applyPreviewOk')) : t('roles.applyOk'))
|
|
676
|
+
: `${t('roles.applyFailed')}: ${escapeHtml(r.error ?? `HTTP ${r.status}`)}`;
|
|
677
|
+
return `<div>${label}: ${outcome}</div>`;
|
|
678
|
+
}).join('');
|
|
679
|
+
}
|
|
680
|
+
if (!preview) {
|
|
681
|
+
await loadGroups();
|
|
682
|
+
renderTree(document.getElementById('roles-search')?.value ?? '');
|
|
683
|
+
void refreshGroupProfileContext();
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
function flashProfileStatus(text, isError = false) {
|
|
687
|
+
const footer = document.querySelector('#roles-profile-detail .roles-editor-footer');
|
|
688
|
+
if (!footer)
|
|
689
|
+
return;
|
|
690
|
+
const statusEl = document.createElement('span');
|
|
691
|
+
statusEl.className = `roles-saved-flash ${isError ? 'roles-save-error' : ''}`;
|
|
692
|
+
statusEl.textContent = ` ${text}`;
|
|
693
|
+
footer.appendChild(statusEl);
|
|
694
|
+
setTimeout(() => statusEl.remove(), isError ? 3000 : 2000);
|
|
695
|
+
}
|
|
696
|
+
async function renderRolesSurface(root, tab) {
|
|
697
|
+
activeTab = tab;
|
|
698
|
+
root.innerHTML = pageHtml(tab);
|
|
254
699
|
expandedGroups.clear();
|
|
255
700
|
resetEditor();
|
|
256
|
-
|
|
701
|
+
switchTab(activeTab);
|
|
257
702
|
const treeEl = document.getElementById('roles-tree');
|
|
703
|
+
const profileListEl = document.getElementById('roles-profile-list');
|
|
258
704
|
if (treeEl)
|
|
259
705
|
treeEl.innerHTML = loadingHtml();
|
|
706
|
+
if (profileListEl)
|
|
707
|
+
profileListEl.innerHTML = loadingHtml();
|
|
260
708
|
await loadGroups();
|
|
261
|
-
await
|
|
262
|
-
|
|
709
|
+
await loadProfiles();
|
|
710
|
+
await loadNameMaps();
|
|
711
|
+
if (tab === 'profiles') {
|
|
712
|
+
const requestedChatId = hashChatId();
|
|
713
|
+
if (requestedChatId && cache.some(g => g.chatId === requestedChatId)) {
|
|
714
|
+
selectedApplyGroupId = requestedChatId;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
263
717
|
for (const g of cache) {
|
|
264
718
|
if (botRoleCount(g) > 0)
|
|
265
719
|
expandedGroups.add(g.chatId);
|
|
266
720
|
}
|
|
267
721
|
renderTree();
|
|
268
|
-
|
|
722
|
+
renderProfileList();
|
|
723
|
+
if (selectedProfileId)
|
|
724
|
+
await selectProfile(selectedProfileId);
|
|
725
|
+
void refreshGroupProfileContext();
|
|
269
726
|
document.getElementById('roles-search')?.addEventListener('input', (e) => {
|
|
270
727
|
renderTree(e.target.value);
|
|
271
728
|
});
|
|
272
|
-
// Refresh
|
|
273
729
|
document.getElementById('roles-refresh')?.addEventListener('click', async () => {
|
|
274
730
|
await loadGroups();
|
|
275
731
|
renderTree(document.getElementById('roles-search')?.value ?? '');
|
|
276
|
-
|
|
732
|
+
void refreshGroupProfileContext();
|
|
277
733
|
if (selectedGroupId && selectedBotId) {
|
|
278
734
|
const role = await loadRole(selectedBotId, selectedGroupId);
|
|
279
735
|
const textarea = document.getElementById('roles-editor-textarea');
|
|
@@ -287,7 +743,6 @@ export async function renderRolesPage(root) {
|
|
|
287
743
|
delBtn.style.display = role.hasRole ? '' : 'none';
|
|
288
744
|
}
|
|
289
745
|
});
|
|
290
|
-
// Save
|
|
291
746
|
document.getElementById('roles-save')?.addEventListener('click', async function () {
|
|
292
747
|
if (!selectedGroupId || !selectedBotId)
|
|
293
748
|
return;
|
|
@@ -298,10 +753,10 @@ export async function renderRolesPage(root) {
|
|
|
298
753
|
if (ok) {
|
|
299
754
|
await loadGroups();
|
|
300
755
|
renderTree(document.getElementById('roles-search')?.value ?? '');
|
|
756
|
+
void refreshGroupProfileContext();
|
|
301
757
|
const delBtn = document.getElementById('roles-delete');
|
|
302
758
|
if (delBtn)
|
|
303
759
|
delBtn.style.display = '';
|
|
304
|
-
// Brief saved indicator
|
|
305
760
|
const statusEl = document.createElement('span');
|
|
306
761
|
statusEl.className = 'roles-saved-flash';
|
|
307
762
|
statusEl.textContent = ` ${t('roles.saved')}`;
|
|
@@ -310,7 +765,6 @@ export async function renderRolesPage(root) {
|
|
|
310
765
|
setTimeout(() => statusEl.remove(), 2000);
|
|
311
766
|
}
|
|
312
767
|
else {
|
|
313
|
-
// Show error feedback
|
|
314
768
|
const statusEl = document.createElement('span');
|
|
315
769
|
statusEl.className = 'roles-saved-flash roles-save-error';
|
|
316
770
|
statusEl.textContent = editingContent.trim().length === 0
|
|
@@ -326,7 +780,6 @@ export async function renderRolesPage(root) {
|
|
|
326
780
|
this.textContent = t('roles.save');
|
|
327
781
|
}
|
|
328
782
|
});
|
|
329
|
-
// Delete
|
|
330
783
|
document.getElementById('roles-delete')?.addEventListener('click', async function () {
|
|
331
784
|
if (!selectedGroupId || !selectedBotId)
|
|
332
785
|
return;
|
|
@@ -340,6 +793,7 @@ export async function renderRolesPage(root) {
|
|
|
340
793
|
await loadGroups();
|
|
341
794
|
resetEditor();
|
|
342
795
|
renderTree(document.getElementById('roles-search')?.value ?? '');
|
|
796
|
+
void refreshGroupProfileContext();
|
|
343
797
|
}
|
|
344
798
|
}
|
|
345
799
|
finally {
|
|
@@ -347,11 +801,50 @@ export async function renderRolesPage(root) {
|
|
|
347
801
|
this.textContent = t('roles.delete');
|
|
348
802
|
}
|
|
349
803
|
});
|
|
350
|
-
// Live edit
|
|
351
804
|
document.getElementById('roles-editor-textarea')?.addEventListener('input', (e) => {
|
|
352
805
|
editingContent = e.target.value;
|
|
353
806
|
updateByteCount();
|
|
354
807
|
updatePreview();
|
|
355
808
|
});
|
|
809
|
+
document.getElementById('roles-profile-search')?.addEventListener('input', (e) => {
|
|
810
|
+
renderProfileList(e.target.value);
|
|
811
|
+
});
|
|
812
|
+
document.getElementById('roles-profile-refresh')?.addEventListener('click', async () => {
|
|
813
|
+
await loadGroups();
|
|
814
|
+
await loadProfiles();
|
|
815
|
+
void refreshGroupProfileContext();
|
|
816
|
+
if (selectedProfileId)
|
|
817
|
+
await loadProfileEntries(selectedProfileId);
|
|
818
|
+
renderProfileList(document.getElementById('roles-profile-search')?.value ?? '');
|
|
819
|
+
renderProfileDetail();
|
|
820
|
+
});
|
|
821
|
+
document.getElementById('roles-profile-select')?.addEventListener('click', async () => {
|
|
822
|
+
const input = document.getElementById('roles-profile-id');
|
|
823
|
+
const profileId = input?.value.trim();
|
|
824
|
+
if (!profileId)
|
|
825
|
+
return;
|
|
826
|
+
if (!isValidProfileId(profileId)) {
|
|
827
|
+
input?.setCustomValidity(t('roles.profileIdInvalid'));
|
|
828
|
+
input?.reportValidity();
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
input?.setCustomValidity('');
|
|
832
|
+
await selectProfile(profileId);
|
|
833
|
+
if (!location.hash.startsWith('#/roles/profile')) {
|
|
834
|
+
location.hash = '#/roles/profile';
|
|
835
|
+
}
|
|
836
|
+
else {
|
|
837
|
+
switchTab('profiles');
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
document.getElementById('roles-profile-id')?.addEventListener('input', (e) => {
|
|
841
|
+
e.target.setCustomValidity('');
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
export async function renderRolesPage(root) {
|
|
845
|
+
await renderRolesSurface(root, 'groups');
|
|
846
|
+
}
|
|
847
|
+
export async function renderRoleProfilesPage(root) {
|
|
848
|
+
await renderRolesSurface(root, 'profiles');
|
|
356
849
|
}
|
|
357
850
|
//# sourceMappingURL=roles.js.map
|