leedab 0.1.8 → 0.2.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/agent/resolveMember.d.ts +21 -0
- package/dist/agent/resolveMember.js +44 -0
- package/dist/dashboard/routes.d.ts +12 -0
- package/dist/dashboard/routes.js +235 -15
- package/dist/dashboard/server.js +6 -1
- package/dist/dashboard/static/admin.html +688 -0
- package/dist/dashboard/static/index.html +4 -26
- package/dist/dashboard/static/sessions.html +20 -24
- package/dist/license.d.ts +3 -0
- package/dist/license.js +12 -3
- package/dist/team/permissions.d.ts +65 -0
- package/dist/team/permissions.js +138 -0
- package/dist/team/syncAllowlists.d.ts +7 -0
- package/dist/team/syncAllowlists.js +62 -0
- package/dist/team.d.ts +10 -7
- package/dist/team.js +62 -68
- package/dist/templates/verticals/supply-chain/WORKFLOWS.md +5 -0
- package/dist/workflows/registry.d.ts +22 -0
- package/dist/workflows/registry.js +46 -0
- package/package.json +1 -1
- package/dist/dashboard/static/app.js +0 -351
- package/dist/dashboard/static/console.html +0 -252
- package/dist/dashboard/static/settings.html +0 -274
- package/dist/dashboard/static/team.html +0 -215
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<link rel="icon" type="image/png" href="/favicon.png">
|
|
7
|
-
<title>LeedAB — Setup</title>
|
|
8
|
-
<link rel="stylesheet" href="/style.css">
|
|
9
|
-
</head>
|
|
10
|
-
<body>
|
|
11
|
-
<style>
|
|
12
|
-
.page-header {
|
|
13
|
-
display: flex;
|
|
14
|
-
align-items: center;
|
|
15
|
-
justify-content: space-between;
|
|
16
|
-
padding: 0 20px;
|
|
17
|
-
height: 52px;
|
|
18
|
-
border-bottom: 1px solid var(--border);
|
|
19
|
-
background: var(--bg);
|
|
20
|
-
}
|
|
21
|
-
.page-header-left {
|
|
22
|
-
display: flex;
|
|
23
|
-
align-items: center;
|
|
24
|
-
gap: 10px;
|
|
25
|
-
}
|
|
26
|
-
.page-header-title {
|
|
27
|
-
font-size: 14px;
|
|
28
|
-
font-weight: 600;
|
|
29
|
-
letter-spacing: -0.01em;
|
|
30
|
-
}
|
|
31
|
-
.page-nav {
|
|
32
|
-
display: flex;
|
|
33
|
-
align-items: center;
|
|
34
|
-
gap: 2px;
|
|
35
|
-
}
|
|
36
|
-
.page-nav a, .page-nav button {
|
|
37
|
-
color: var(--text-dim);
|
|
38
|
-
text-decoration: none;
|
|
39
|
-
display: flex;
|
|
40
|
-
align-items: center;
|
|
41
|
-
gap: 5px;
|
|
42
|
-
padding: 6px 12px;
|
|
43
|
-
border-radius: 8px;
|
|
44
|
-
font-size: 13px;
|
|
45
|
-
font-weight: 450;
|
|
46
|
-
transition: all 0.15s;
|
|
47
|
-
background: none;
|
|
48
|
-
border: none;
|
|
49
|
-
cursor: pointer;
|
|
50
|
-
font-family: inherit;
|
|
51
|
-
}
|
|
52
|
-
.page-nav a:hover, .page-nav button:hover {
|
|
53
|
-
color: var(--text-secondary);
|
|
54
|
-
background: var(--surface-raised);
|
|
55
|
-
}
|
|
56
|
-
.page-nav .theme-btn {
|
|
57
|
-
border: 1px solid var(--border);
|
|
58
|
-
padding: 5px 8px;
|
|
59
|
-
}
|
|
60
|
-
</style>
|
|
61
|
-
|
|
62
|
-
<div class="page-header">
|
|
63
|
-
<div class="page-header-left">
|
|
64
|
-
<span class="page-header-title">Settings</span>
|
|
65
|
-
</div>
|
|
66
|
-
<div class="page-nav">
|
|
67
|
-
<a href="/">
|
|
68
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m22 2-7 20-4-9-9-4 20-7Z"/><path d="M22 2 11 13"/></svg>
|
|
69
|
-
Chat
|
|
70
|
-
</a>
|
|
71
|
-
<a href="/console.html">
|
|
72
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
73
|
-
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/>
|
|
74
|
-
</svg>
|
|
75
|
-
Console
|
|
76
|
-
</a>
|
|
77
|
-
<button class="theme-btn" onclick="toggleTheme()" title="Toggle theme">
|
|
78
|
-
<svg id="theme-icon-sun" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
|
|
79
|
-
<svg id="theme-icon-moon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
|
|
80
|
-
</button>
|
|
81
|
-
</div>
|
|
82
|
-
</div>
|
|
83
|
-
|
|
84
|
-
<div class="container">
|
|
85
|
-
<main>
|
|
86
|
-
<div id="toast" class="toast hidden"></div>
|
|
87
|
-
|
|
88
|
-
<!-- Channels -->
|
|
89
|
-
<section class="section">
|
|
90
|
-
<h2 class="section-title">Channels</h2>
|
|
91
|
-
<p class="section-desc">How your team reaches the agent.</p>
|
|
92
|
-
|
|
93
|
-
<div class="cards">
|
|
94
|
-
<!-- Telegram -->
|
|
95
|
-
<div class="card" id="card-telegram">
|
|
96
|
-
<div class="card-icon">
|
|
97
|
-
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
98
|
-
<path d="m22 2-7 20-4-9-9-4 20-7Z"/><path d="M22 2 11 13"/>
|
|
99
|
-
</svg>
|
|
100
|
-
</div>
|
|
101
|
-
<div class="card-body">
|
|
102
|
-
<h3>Telegram</h3>
|
|
103
|
-
<div class="card-status" id="status-telegram">
|
|
104
|
-
<span class="dot"></span> Not connected
|
|
105
|
-
</div>
|
|
106
|
-
</div>
|
|
107
|
-
<button class="btn" onclick="showTelegramForm()">Connect</button>
|
|
108
|
-
</div>
|
|
109
|
-
|
|
110
|
-
<!-- WhatsApp -->
|
|
111
|
-
<div class="card" id="card-whatsapp">
|
|
112
|
-
<div class="card-icon">
|
|
113
|
-
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
114
|
-
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/>
|
|
115
|
-
</svg>
|
|
116
|
-
</div>
|
|
117
|
-
<div class="card-body">
|
|
118
|
-
<h3>WhatsApp</h3>
|
|
119
|
-
<div class="card-status" id="status-whatsapp">
|
|
120
|
-
<span class="dot"></span> Not connected
|
|
121
|
-
</div>
|
|
122
|
-
</div>
|
|
123
|
-
<button class="btn" onclick="connectWhatsApp()">Connect</button>
|
|
124
|
-
</div>
|
|
125
|
-
|
|
126
|
-
<!-- Teams -->
|
|
127
|
-
<div class="card" id="card-teams">
|
|
128
|
-
<div class="card-icon">
|
|
129
|
-
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
130
|
-
<rect x="2" y="7" width="20" height="14" rx="2" ry="2"/>
|
|
131
|
-
<path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"/>
|
|
132
|
-
</svg>
|
|
133
|
-
</div>
|
|
134
|
-
<div class="card-body">
|
|
135
|
-
<h3>Microsoft Teams</h3>
|
|
136
|
-
<div class="card-status" id="status-teams">
|
|
137
|
-
<span class="dot"></span> Not connected
|
|
138
|
-
</div>
|
|
139
|
-
</div>
|
|
140
|
-
<button class="btn" onclick="showTeamsForm()">Connect</button>
|
|
141
|
-
</div>
|
|
142
|
-
</div>
|
|
143
|
-
</section>
|
|
144
|
-
|
|
145
|
-
<!-- Telegram inline form -->
|
|
146
|
-
<div id="telegram-form" class="form-panel hidden">
|
|
147
|
-
<h3>Connect Telegram</h3>
|
|
148
|
-
<p class="form-help">Get a bot token from <a href="https://t.me/BotFather" target="_blank">@BotFather</a> on Telegram.</p>
|
|
149
|
-
<label>Bot Token
|
|
150
|
-
<input type="password" id="telegram-token" placeholder="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11">
|
|
151
|
-
</label>
|
|
152
|
-
<div class="form-actions">
|
|
153
|
-
<button class="btn" onclick="connectTelegram()">Connect</button>
|
|
154
|
-
<button class="btn btn-ghost" onclick="hideTelegramForm()">Cancel</button>
|
|
155
|
-
</div>
|
|
156
|
-
</div>
|
|
157
|
-
|
|
158
|
-
<!-- Teams inline form -->
|
|
159
|
-
<div id="teams-form" class="form-panel hidden">
|
|
160
|
-
<h3>Connect Microsoft Teams</h3>
|
|
161
|
-
<p class="form-help">Enter your Azure AD app credentials. <a href="https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps" target="_blank">Create one here</a></p>
|
|
162
|
-
<label>App (Client) ID
|
|
163
|
-
<input type="text" id="teams-app-id" placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx">
|
|
164
|
-
</label>
|
|
165
|
-
<label>Tenant ID
|
|
166
|
-
<input type="text" id="teams-tenant-id" placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx">
|
|
167
|
-
</label>
|
|
168
|
-
<div class="form-actions">
|
|
169
|
-
<button class="btn" onclick="connectTeams()">Connect</button>
|
|
170
|
-
<button class="btn btn-ghost" onclick="hideTeamsForm()">Cancel</button>
|
|
171
|
-
</div>
|
|
172
|
-
</div>
|
|
173
|
-
|
|
174
|
-
<!-- WhatsApp QR panel -->
|
|
175
|
-
<div id="whatsapp-panel" class="form-panel hidden">
|
|
176
|
-
<h3>WhatsApp Pairing</h3>
|
|
177
|
-
<p class="form-help">Open WhatsApp on your phone → Settings → Linked Devices → Link a Device</p>
|
|
178
|
-
<div id="whatsapp-qr" class="qr-area">
|
|
179
|
-
<p>Starting pairing...</p>
|
|
180
|
-
</div>
|
|
181
|
-
<p class="form-help" style="margin-top:12px">Once paired, this page will update automatically.</p>
|
|
182
|
-
<button class="btn btn-ghost" onclick="hideWhatsAppPanel()">Cancel</button>
|
|
183
|
-
</div>
|
|
184
|
-
|
|
185
|
-
<!-- Channel Access / Allowlist -->
|
|
186
|
-
<section class="section" style="margin-top:48px">
|
|
187
|
-
<h2 class="section-title">Channel Access</h2>
|
|
188
|
-
<p class="section-desc">Only users on the allowlist can message the agent. Add their user ID to grant access.</p>
|
|
189
|
-
|
|
190
|
-
<div class="form-panel">
|
|
191
|
-
<h3>Allowlist</h3>
|
|
192
|
-
<label>Channel
|
|
193
|
-
<select id="allowlist-channel" onchange="loadAllowlist()" style="display:block;width:100%;margin-top:4px;padding:10px 12px;background:var(--bg);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:0.9rem;font-family:inherit">
|
|
194
|
-
<option value="telegram">Telegram</option>
|
|
195
|
-
<option value="whatsapp">WhatsApp</option>
|
|
196
|
-
<option value="teams">Teams</option>
|
|
197
|
-
</select>
|
|
198
|
-
</label>
|
|
199
|
-
<div id="allowlist-entries" style="margin:16px 0"></div>
|
|
200
|
-
<label>Add user ID
|
|
201
|
-
<input type="text" id="allowlist-userid" placeholder="e.g. 6549960466">
|
|
202
|
-
</label>
|
|
203
|
-
<p style="margin:4px 0 12px;font-size:0.8rem;color:var(--text-faint)">
|
|
204
|
-
Telegram: message <a href="https://t.me/userinfobot" target="_blank" style="color:var(--accent)">@userinfobot</a> to get your numeric user ID.
|
|
205
|
-
WhatsApp: use the phone number. Teams: use the Azure AD object ID.
|
|
206
|
-
</p>
|
|
207
|
-
<div class="form-actions">
|
|
208
|
-
<button class="btn" onclick="addAllowlistEntry()">Add to allowlist</button>
|
|
209
|
-
</div>
|
|
210
|
-
</div>
|
|
211
|
-
</section>
|
|
212
|
-
|
|
213
|
-
<!-- Credential Vault -->
|
|
214
|
-
<section class="section" style="margin-top:48px">
|
|
215
|
-
<h2 class="section-title">Credential Vault</h2>
|
|
216
|
-
<p class="section-desc">Stored logins for services the agent accesses via browser (Gmail, Drive, CRM, etc.)</p>
|
|
217
|
-
|
|
218
|
-
<div id="vault-list"></div>
|
|
219
|
-
|
|
220
|
-
<div class="form-panel" style="margin-top:16px">
|
|
221
|
-
<h3>Add credential</h3>
|
|
222
|
-
<label>Service name
|
|
223
|
-
<input type="text" id="vault-service" placeholder="e.g. gmail, salesforce, shipstation">
|
|
224
|
-
</label>
|
|
225
|
-
<label>Login URL
|
|
226
|
-
<input type="text" id="vault-url" placeholder="https://...">
|
|
227
|
-
</label>
|
|
228
|
-
<label>Username / email
|
|
229
|
-
<input type="text" id="vault-username">
|
|
230
|
-
</label>
|
|
231
|
-
<label>Password
|
|
232
|
-
<input type="password" id="vault-password">
|
|
233
|
-
</label>
|
|
234
|
-
<label>Notes for agent
|
|
235
|
-
<textarea id="vault-notes" placeholder="e.g. Use the shipping dashboard, not the admin panel"></textarea>
|
|
236
|
-
</label>
|
|
237
|
-
<div class="form-actions">
|
|
238
|
-
<button class="btn" onclick="addVaultEntry()">Add to vault</button>
|
|
239
|
-
</div>
|
|
240
|
-
</div>
|
|
241
|
-
</section>
|
|
242
|
-
</main>
|
|
243
|
-
|
|
244
|
-
<footer>
|
|
245
|
-
<p>Your files, credentials, and memory stay on this device.</p>
|
|
246
|
-
</footer>
|
|
247
|
-
</div>
|
|
248
|
-
|
|
249
|
-
<script>
|
|
250
|
-
function initTheme() {
|
|
251
|
-
const saved = localStorage.getItem("leedab-theme") || "dark";
|
|
252
|
-
document.documentElement.setAttribute("data-theme", saved);
|
|
253
|
-
updateThemeIcon(saved);
|
|
254
|
-
}
|
|
255
|
-
function toggleTheme() {
|
|
256
|
-
const current = document.documentElement.getAttribute("data-theme") || "dark";
|
|
257
|
-
const next = current === "dark" ? "light" : "dark";
|
|
258
|
-
document.documentElement.setAttribute("data-theme", next);
|
|
259
|
-
localStorage.setItem("leedab-theme", next);
|
|
260
|
-
updateThemeIcon(next);
|
|
261
|
-
}
|
|
262
|
-
function updateThemeIcon(theme) {
|
|
263
|
-
const sun = document.getElementById("theme-icon-sun");
|
|
264
|
-
const moon = document.getElementById("theme-icon-moon");
|
|
265
|
-
if (sun && moon) {
|
|
266
|
-
sun.style.display = theme === "dark" ? "block" : "none";
|
|
267
|
-
moon.style.display = theme === "light" ? "block" : "none";
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
initTheme();
|
|
271
|
-
</script>
|
|
272
|
-
<script src="/app.js"></script>
|
|
273
|
-
</body>
|
|
274
|
-
</html>
|
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<link rel="icon" type="image/png" href="/favicon.png">
|
|
7
|
-
<title>LeedAB — Team</title>
|
|
8
|
-
<link rel="stylesheet" href="/style.css">
|
|
9
|
-
</head>
|
|
10
|
-
<body>
|
|
11
|
-
<style>
|
|
12
|
-
.page-header { display:flex; align-items:center; justify-content:space-between; padding:0 20px; height:52px; border-bottom:1px solid var(--border); background:var(--bg); }
|
|
13
|
-
.page-header-left { display:flex; align-items:center; gap:10px; }
|
|
14
|
-
.page-header-title { font-size:14px; font-weight:600; letter-spacing:-0.01em; }
|
|
15
|
-
.page-nav { display:flex; align-items:center; gap:2px; }
|
|
16
|
-
.page-nav a, .page-nav button { color:var(--text-dim); text-decoration:none; display:flex; align-items:center; gap:5px; padding:6px 12px; border-radius:8px; font-size:13px; font-weight:450; transition:all 0.15s; background:none; border:none; cursor:pointer; font-family:inherit; }
|
|
17
|
-
.page-nav a:hover, .page-nav button:hover { color:var(--text-secondary); background:var(--surface-raised); }
|
|
18
|
-
.page-nav .theme-btn { border:1px solid var(--border); padding:5px 8px; }
|
|
19
|
-
</style>
|
|
20
|
-
|
|
21
|
-
<div class="page-header">
|
|
22
|
-
<div class="page-header-left">
|
|
23
|
-
<span class="page-header-title">Team</span>
|
|
24
|
-
</div>
|
|
25
|
-
<div class="page-nav">
|
|
26
|
-
<a href="/">
|
|
27
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m22 2-7 20-4-9-9-4 20-7Z"/><path d="M22 2 11 13"/></svg>
|
|
28
|
-
Chat
|
|
29
|
-
</a>
|
|
30
|
-
<a href="/console.html">
|
|
31
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
|
|
32
|
-
Console
|
|
33
|
-
</a>
|
|
34
|
-
<button class="theme-btn" onclick="toggleTheme()" title="Toggle theme">
|
|
35
|
-
<svg id="theme-icon-sun" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
|
|
36
|
-
<svg id="theme-icon-moon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
|
|
37
|
-
</button>
|
|
38
|
-
</div>
|
|
39
|
-
</div>
|
|
40
|
-
|
|
41
|
-
<div class="container">
|
|
42
|
-
|
|
43
|
-
<main>
|
|
44
|
-
<div id="toast" class="toast hidden"></div>
|
|
45
|
-
|
|
46
|
-
<section class="section">
|
|
47
|
-
<h2 class="section-title">Members</h2>
|
|
48
|
-
<div id="team-list"></div>
|
|
49
|
-
</section>
|
|
50
|
-
|
|
51
|
-
<section class="section">
|
|
52
|
-
<div class="form-panel">
|
|
53
|
-
<h3>Add member</h3>
|
|
54
|
-
<label>Name
|
|
55
|
-
<input type="text" id="member-name" placeholder="e.g. Alice">
|
|
56
|
-
</label>
|
|
57
|
-
<label>Email (optional)
|
|
58
|
-
<input type="email" id="member-email" placeholder="alice@company.com">
|
|
59
|
-
</label>
|
|
60
|
-
<label>Role
|
|
61
|
-
<select id="member-role" style="display:block;width:100%;margin-top:4px;padding:10px 12px;background:var(--bg);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:0.9rem;font-family:inherit">
|
|
62
|
-
<option value="operator">Operator — can use agent, view logs</option>
|
|
63
|
-
<option value="admin">Admin — full access</option>
|
|
64
|
-
<option value="viewer">Viewer — read-only</option>
|
|
65
|
-
</select>
|
|
66
|
-
</label>
|
|
67
|
-
<div class="form-actions">
|
|
68
|
-
<button class="btn" onclick="addTeamMember()">Add member</button>
|
|
69
|
-
</div>
|
|
70
|
-
</div>
|
|
71
|
-
</section>
|
|
72
|
-
</main>
|
|
73
|
-
|
|
74
|
-
<footer>
|
|
75
|
-
<p>Your files, credentials, and memory stay on this device.</p>
|
|
76
|
-
</footer>
|
|
77
|
-
</div>
|
|
78
|
-
|
|
79
|
-
<style>
|
|
80
|
-
.team-table {
|
|
81
|
-
width: 100%;
|
|
82
|
-
border-collapse: collapse;
|
|
83
|
-
}
|
|
84
|
-
.team-table th,
|
|
85
|
-
.team-table td {
|
|
86
|
-
text-align: left;
|
|
87
|
-
padding: 10px 12px;
|
|
88
|
-
font-size: 0.875rem;
|
|
89
|
-
border-bottom: 1px solid var(--border);
|
|
90
|
-
}
|
|
91
|
-
.team-table th {
|
|
92
|
-
color: var(--text-faint);
|
|
93
|
-
font-weight: 500;
|
|
94
|
-
font-size: 0.75rem;
|
|
95
|
-
text-transform: uppercase;
|
|
96
|
-
letter-spacing: 0.05em;
|
|
97
|
-
}
|
|
98
|
-
.role-badge {
|
|
99
|
-
display: inline-block;
|
|
100
|
-
padding: 2px 8px;
|
|
101
|
-
border-radius: 6px;
|
|
102
|
-
font-size: 11px;
|
|
103
|
-
font-weight: 500;
|
|
104
|
-
}
|
|
105
|
-
.role-admin { background: var(--accent-soft); color: var(--accent); }
|
|
106
|
-
.role-operator { background: rgba(34, 197, 94, 0.1); color: var(--success); }
|
|
107
|
-
.role-viewer { background: rgba(113, 113, 122, 0.1); color: var(--text-dim); }
|
|
108
|
-
</style>
|
|
109
|
-
|
|
110
|
-
<script>
|
|
111
|
-
function initTheme() {
|
|
112
|
-
const saved = localStorage.getItem("leedab-theme") || "dark";
|
|
113
|
-
document.documentElement.setAttribute("data-theme", saved);
|
|
114
|
-
updateThemeIcon(saved);
|
|
115
|
-
}
|
|
116
|
-
function toggleTheme() {
|
|
117
|
-
const current = document.documentElement.getAttribute("data-theme") || "dark";
|
|
118
|
-
const next = current === "dark" ? "light" : "dark";
|
|
119
|
-
document.documentElement.setAttribute("data-theme", next);
|
|
120
|
-
localStorage.setItem("leedab-theme", next);
|
|
121
|
-
updateThemeIcon(next);
|
|
122
|
-
}
|
|
123
|
-
function updateThemeIcon(theme) {
|
|
124
|
-
const sun = document.getElementById("theme-icon-sun");
|
|
125
|
-
const moon = document.getElementById("theme-icon-moon");
|
|
126
|
-
if (sun && moon) {
|
|
127
|
-
sun.style.display = theme === "dark" ? "block" : "none";
|
|
128
|
-
moon.style.display = theme === "light" ? "block" : "none";
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
initTheme();
|
|
132
|
-
|
|
133
|
-
document.addEventListener("DOMContentLoaded", loadTeam);
|
|
134
|
-
|
|
135
|
-
async function loadTeam() {
|
|
136
|
-
const container = document.getElementById("team-list");
|
|
137
|
-
try {
|
|
138
|
-
const res = await fetch("/api/team");
|
|
139
|
-
const team = await res.json();
|
|
140
|
-
|
|
141
|
-
if (!team.length) {
|
|
142
|
-
container.innerHTML = '<div class="vault-empty">No team members yet.</div>';
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
container.innerHTML = `
|
|
147
|
-
<table class="team-table">
|
|
148
|
-
<thead>
|
|
149
|
-
<tr><th>Name</th><th>Email</th><th>Role</th><th>Added</th><th></th></tr>
|
|
150
|
-
</thead>
|
|
151
|
-
<tbody>
|
|
152
|
-
${team.map(m => `
|
|
153
|
-
<tr>
|
|
154
|
-
<td>${esc(m.name)}</td>
|
|
155
|
-
<td>${m.email ? esc(m.email) : '<span style="color:var(--text-faint)">-</span>'}</td>
|
|
156
|
-
<td><span class="role-badge role-${m.role}">${m.role}</span></td>
|
|
157
|
-
<td style="color:var(--text-dim)">${new Date(m.createdAt).toLocaleDateString()}</td>
|
|
158
|
-
<td><button class="btn btn-danger" onclick="removeMember('${m.id}')">Remove</button></td>
|
|
159
|
-
</tr>
|
|
160
|
-
`).join("")}
|
|
161
|
-
</tbody>
|
|
162
|
-
</table>`;
|
|
163
|
-
} catch {
|
|
164
|
-
container.innerHTML = '<div class="vault-empty">Could not load team.</div>';
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
async function addTeamMember() {
|
|
169
|
-
const name = document.getElementById("member-name").value.trim();
|
|
170
|
-
const email = document.getElementById("member-email").value.trim();
|
|
171
|
-
const role = document.getElementById("member-role").value;
|
|
172
|
-
|
|
173
|
-
if (!name) {
|
|
174
|
-
showToast("Name is required", "error");
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
try {
|
|
179
|
-
await fetch("/api/team", {
|
|
180
|
-
method: "POST",
|
|
181
|
-
headers: { "Content-Type": "application/json" },
|
|
182
|
-
body: JSON.stringify({ name, email, role }),
|
|
183
|
-
});
|
|
184
|
-
document.getElementById("member-name").value = "";
|
|
185
|
-
document.getElementById("member-email").value = "";
|
|
186
|
-
showToast(`Added ${name}`, "success");
|
|
187
|
-
loadTeam();
|
|
188
|
-
} catch {
|
|
189
|
-
showToast("Failed to add member", "error");
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
async function removeMember(id) {
|
|
194
|
-
try {
|
|
195
|
-
await fetch(`/api/team?id=${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
196
|
-
showToast("Member removed", "success");
|
|
197
|
-
loadTeam();
|
|
198
|
-
} catch {
|
|
199
|
-
showToast("Failed to remove member", "error");
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function showToast(message, type) {
|
|
204
|
-
const toast = document.getElementById("toast");
|
|
205
|
-
toast.textContent = message;
|
|
206
|
-
toast.className = `toast ${type}`;
|
|
207
|
-
setTimeout(() => toast.classList.add("hidden"), 4000);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function esc(str) {
|
|
211
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
212
|
-
}
|
|
213
|
-
</script>
|
|
214
|
-
</body>
|
|
215
|
-
</html>
|