lobstakit-cloud 1.0.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/bin/lobstakit.js +2 -0
- package/lib/config.js +176 -0
- package/lib/gateway.js +104 -0
- package/lib/proxy.js +33 -0
- package/package.json +16 -0
- package/public/css/styles.css +579 -0
- package/public/index.html +507 -0
- package/public/js/app.js +198 -0
- package/public/js/login.js +93 -0
- package/public/js/manage.js +1274 -0
- package/public/js/setup.js +755 -0
- package/public/login.html +73 -0
- package/public/manage.html +734 -0
- package/server.js +1357 -0
|
@@ -0,0 +1,734 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" class="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Dashboard - LobstaKit</title>
|
|
7
|
+
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🦞</text></svg>">
|
|
8
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
9
|
+
<script>
|
|
10
|
+
tailwind.config = {
|
|
11
|
+
darkMode: 'class',
|
|
12
|
+
theme: {
|
|
13
|
+
extend: {
|
|
14
|
+
colors: {
|
|
15
|
+
lobsta: {
|
|
16
|
+
bg: '#0A0A0A',
|
|
17
|
+
card: '#171717',
|
|
18
|
+
border: '#262626',
|
|
19
|
+
accent: '#DC2626',
|
|
20
|
+
'accent-dark': '#991B1B',
|
|
21
|
+
'accent-light': '#EF4444',
|
|
22
|
+
light: '#FF6B6B',
|
|
23
|
+
success: '#22c55e',
|
|
24
|
+
warning: '#f59e0b',
|
|
25
|
+
error: '#ef4444',
|
|
26
|
+
muted: '#737373',
|
|
27
|
+
gray: '#404040',
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
</script>
|
|
34
|
+
<link rel="stylesheet" href="/css/styles.css">
|
|
35
|
+
</head>
|
|
36
|
+
<body class="bg-lobsta-bg text-gray-100 min-h-screen">
|
|
37
|
+
<!-- Navigation -->
|
|
38
|
+
<nav class="bg-lobsta-card border-b border-lobsta-border sticky top-0 z-50">
|
|
39
|
+
<div class="max-w-6xl mx-auto px-4">
|
|
40
|
+
<div class="flex items-center justify-between h-16">
|
|
41
|
+
<a href="/" class="flex items-center space-x-3">
|
|
42
|
+
<span class="text-2xl">🛠️</span>
|
|
43
|
+
<span class="font-bold text-xl text-white">LobstaKit</span>
|
|
44
|
+
</a>
|
|
45
|
+
<div class="flex items-center space-x-4">
|
|
46
|
+
<a href="/manage" class="px-3 py-2 rounded-lg hover:bg-lobsta-border transition bg-lobsta-border">
|
|
47
|
+
Dashboard
|
|
48
|
+
</a>
|
|
49
|
+
<a href="/setup" class="px-3 py-2 rounded-lg hover:bg-lobsta-border transition">
|
|
50
|
+
Setup
|
|
51
|
+
</a>
|
|
52
|
+
<button onclick="logout()" class="text-sm text-lobsta-muted hover:text-white transition px-3 py-2">
|
|
53
|
+
Logout
|
|
54
|
+
</button>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</nav>
|
|
59
|
+
|
|
60
|
+
<!-- Main Content -->
|
|
61
|
+
<main class="max-w-6xl mx-auto px-4 py-8">
|
|
62
|
+
<div class="space-y-6">
|
|
63
|
+
<!-- Header -->
|
|
64
|
+
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
|
65
|
+
<div>
|
|
66
|
+
<h1 class="text-3xl font-bold text-white">Dashboard</h1>
|
|
67
|
+
<p class="text-lobsta-muted mt-1">Manage your Lobsta gateway</p>
|
|
68
|
+
</div>
|
|
69
|
+
<div class="flex flex-wrap gap-2">
|
|
70
|
+
<button onclick="refreshAll()" class="btn btn-secondary">
|
|
71
|
+
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
72
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
|
73
|
+
</svg>
|
|
74
|
+
Refresh
|
|
75
|
+
</button>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<!-- Quick Stats Cards -->
|
|
80
|
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-4">
|
|
81
|
+
<div class="card" id="card-gateway">
|
|
82
|
+
<div class="flex items-center space-x-3">
|
|
83
|
+
<div class="w-12 h-12 rounded-lg bg-blue-500/20 flex items-center justify-center">
|
|
84
|
+
<span class="text-2xl">🤖</span>
|
|
85
|
+
</div>
|
|
86
|
+
<div>
|
|
87
|
+
<p class="text-lobsta-muted text-sm">Gateway</p>
|
|
88
|
+
<p class="font-semibold text-lg" id="gateway-status">Checking...</p>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
<div class="card" id="card-config">
|
|
93
|
+
<div class="flex items-center space-x-3">
|
|
94
|
+
<div class="w-12 h-12 rounded-lg bg-purple-500/20 flex items-center justify-center">
|
|
95
|
+
<span class="text-2xl">⚙️</span>
|
|
96
|
+
</div>
|
|
97
|
+
<div>
|
|
98
|
+
<p class="text-lobsta-muted text-sm">Configuration</p>
|
|
99
|
+
<p class="font-semibold text-lg" id="config-status">Checking...</p>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
<div class="card" id="card-domain">
|
|
104
|
+
<div class="flex items-center space-x-3">
|
|
105
|
+
<div class="w-12 h-12 rounded-lg bg-red-500/20 flex items-center justify-center">
|
|
106
|
+
<span class="text-2xl">🌐</span>
|
|
107
|
+
</div>
|
|
108
|
+
<div>
|
|
109
|
+
<p class="text-lobsta-muted text-sm">Domain</p>
|
|
110
|
+
<p class="font-semibold text-lg truncate" id="domain-status">Checking...</p>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
<div class="card" id="card-health">
|
|
115
|
+
<div class="flex items-center space-x-3">
|
|
116
|
+
<div class="w-12 h-12 rounded-lg bg-emerald-500/20 flex items-center justify-center">
|
|
117
|
+
<span class="text-2xl">💚</span>
|
|
118
|
+
</div>
|
|
119
|
+
<div>
|
|
120
|
+
<p class="text-lobsta-muted text-sm">Health</p>
|
|
121
|
+
<p class="font-semibold text-lg" id="health-status">Checking...</p>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
<div class="card" id="card-security">
|
|
126
|
+
<div class="flex items-center space-x-3">
|
|
127
|
+
<div class="w-12 h-12 rounded-lg bg-indigo-500/20 flex items-center justify-center">
|
|
128
|
+
<span class="text-2xl">🛡️</span>
|
|
129
|
+
</div>
|
|
130
|
+
<div>
|
|
131
|
+
<p class="text-lobsta-muted text-sm">Security</p>
|
|
132
|
+
<p class="font-semibold text-lg" id="security-quick-status">Checking...</p>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<!-- Main Content Grid -->
|
|
139
|
+
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
140
|
+
<!-- Left Column -->
|
|
141
|
+
<div class="lg:col-span-2 space-y-6">
|
|
142
|
+
<!-- Quick Actions -->
|
|
143
|
+
<div class="card">
|
|
144
|
+
<h2 class="text-xl font-semibold mb-4">Quick Actions</h2>
|
|
145
|
+
<div class="grid grid-cols-2 gap-3">
|
|
146
|
+
<button onclick="restartGateway()" id="btn-restart" class="btn btn-secondary">
|
|
147
|
+
🔄 Restart Gateway
|
|
148
|
+
</button>
|
|
149
|
+
<a href="/setup" class="btn btn-primary text-center">
|
|
150
|
+
⚙️ Reconfigure
|
|
151
|
+
</a>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<!-- Channels Management -->
|
|
156
|
+
<div class="card" id="channels-card">
|
|
157
|
+
<div class="flex items-center justify-between mb-4">
|
|
158
|
+
<h2 class="text-xl font-semibold">📡 Channels</h2>
|
|
159
|
+
<button onclick="refreshChannels()" class="btn btn-sm btn-secondary">
|
|
160
|
+
Refresh
|
|
161
|
+
</button>
|
|
162
|
+
</div>
|
|
163
|
+
<div id="channels-list" class="space-y-3">
|
|
164
|
+
<p class="text-sm text-lobsta-muted">Loading channels...</p>
|
|
165
|
+
</div>
|
|
166
|
+
<!-- Inline config forms (shown when Add is clicked) -->
|
|
167
|
+
<div id="channel-form-telegram" class="channel-inline-form hidden">
|
|
168
|
+
<h3 class="text-sm font-semibold mb-3">📱 Configure Telegram</h3>
|
|
169
|
+
<div class="space-y-3">
|
|
170
|
+
<div>
|
|
171
|
+
<label class="block text-xs font-medium mb-1 text-lobsta-muted">Bot Token</label>
|
|
172
|
+
<div class="relative">
|
|
173
|
+
<input type="password" id="manage-tg-bot-token"
|
|
174
|
+
class="input w-full text-sm pr-16"
|
|
175
|
+
placeholder="123456789:ABCdefGhIjKlMnOpQrStUvWxYz"
|
|
176
|
+
autocomplete="off">
|
|
177
|
+
<button onclick="toggleVisibility('manage-tg-bot-token', this)"
|
|
178
|
+
class="absolute right-3 top-1/2 -translate-y-1/2 text-xs text-lobsta-muted hover:text-white transition" type="button">
|
|
179
|
+
Show
|
|
180
|
+
</button>
|
|
181
|
+
</div>
|
|
182
|
+
<p class="text-xs text-lobsta-muted mt-1">
|
|
183
|
+
Create with <a href="https://t.me/BotFather" target="_blank" class="text-lobsta-accent hover:underline">@BotFather</a> → <code class="bg-lobsta-bg px-1 rounded">/newbot</code>
|
|
184
|
+
</p>
|
|
185
|
+
<p class="text-xs text-lobsta-error mt-1 hidden" id="manage-tg-token-error"></p>
|
|
186
|
+
</div>
|
|
187
|
+
<div>
|
|
188
|
+
<label class="block text-xs font-medium mb-1 text-lobsta-muted">Your Telegram User ID</label>
|
|
189
|
+
<input type="text" id="manage-tg-user-id"
|
|
190
|
+
class="input w-full text-sm"
|
|
191
|
+
placeholder="123456789"
|
|
192
|
+
inputmode="numeric">
|
|
193
|
+
<p class="text-xs text-lobsta-muted mt-1">
|
|
194
|
+
Get from <a href="https://t.me/userinfobot" target="_blank" class="text-lobsta-accent hover:underline">@userinfobot</a>
|
|
195
|
+
</p>
|
|
196
|
+
<p class="text-xs text-lobsta-error mt-1 hidden" id="manage-tg-userid-error"></p>
|
|
197
|
+
</div>
|
|
198
|
+
<div class="flex gap-2">
|
|
199
|
+
<button onclick="saveChannel('telegram')" class="btn btn-primary text-sm" id="btn-save-telegram">
|
|
200
|
+
Save & Connect
|
|
201
|
+
</button>
|
|
202
|
+
<button onclick="cancelChannelForm('telegram')" class="btn btn-secondary text-sm">
|
|
203
|
+
Cancel
|
|
204
|
+
</button>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
<div id="channel-form-discord" class="channel-inline-form hidden">
|
|
209
|
+
<h3 class="text-sm font-semibold mb-3">🎮 Configure Discord</h3>
|
|
210
|
+
<div class="space-y-3">
|
|
211
|
+
<div>
|
|
212
|
+
<label class="block text-xs font-medium mb-1 text-lobsta-muted">Bot Token</label>
|
|
213
|
+
<div class="relative">
|
|
214
|
+
<input type="password" id="manage-dc-bot-token"
|
|
215
|
+
class="input w-full text-sm pr-16"
|
|
216
|
+
placeholder="MTIz..."
|
|
217
|
+
autocomplete="off">
|
|
218
|
+
<button onclick="toggleVisibility('manage-dc-bot-token', this)"
|
|
219
|
+
class="absolute right-3 top-1/2 -translate-y-1/2 text-xs text-lobsta-muted hover:text-white transition" type="button">
|
|
220
|
+
Show
|
|
221
|
+
</button>
|
|
222
|
+
</div>
|
|
223
|
+
<p class="text-xs text-lobsta-muted mt-1">
|
|
224
|
+
Create at <a href="https://discord.com/developers/applications" target="_blank" class="text-lobsta-accent hover:underline">discord.com/developers</a> → Bot → Copy token
|
|
225
|
+
</p>
|
|
226
|
+
<p class="text-xs text-lobsta-error mt-1 hidden" id="manage-dc-token-error"></p>
|
|
227
|
+
</div>
|
|
228
|
+
<div>
|
|
229
|
+
<label class="block text-xs font-medium mb-1 text-lobsta-muted">Server ID</label>
|
|
230
|
+
<input type="text" id="manage-dc-server-id"
|
|
231
|
+
class="input w-full text-sm"
|
|
232
|
+
placeholder="123456789012345678"
|
|
233
|
+
inputmode="numeric">
|
|
234
|
+
<p class="text-xs text-lobsta-muted mt-1">
|
|
235
|
+
Right-click server → Copy Server ID (Developer Mode required)
|
|
236
|
+
</p>
|
|
237
|
+
<p class="text-xs text-lobsta-error mt-1 hidden" id="manage-dc-serverid-error"></p>
|
|
238
|
+
</div>
|
|
239
|
+
<div class="flex gap-2">
|
|
240
|
+
<button onclick="saveChannel('discord')" class="btn btn-primary text-sm" id="btn-save-discord">
|
|
241
|
+
Save & Connect
|
|
242
|
+
</button>
|
|
243
|
+
<button onclick="cancelChannelForm('discord')" class="btn btn-secondary text-sm">
|
|
244
|
+
Cancel
|
|
245
|
+
</button>
|
|
246
|
+
</div>
|
|
247
|
+
</div>
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
|
|
251
|
+
<!-- Private Memory -->
|
|
252
|
+
<div class="card" id="memory-card">
|
|
253
|
+
<div class="flex items-center justify-between mb-4 gap-3">
|
|
254
|
+
<div class="flex items-center space-x-3 min-w-0 flex-1 overflow-hidden">
|
|
255
|
+
<div class="w-10 h-10 rounded-lg bg-purple-500/20 flex items-center justify-center flex-shrink-0">
|
|
256
|
+
<span class="text-xl">🧠</span>
|
|
257
|
+
</div>
|
|
258
|
+
<div class="min-w-0 overflow-hidden">
|
|
259
|
+
<h2 class="text-xl font-semibold truncate">Memory</h2>
|
|
260
|
+
<p class="text-lobsta-muted text-sm truncate">Embedding provider for memory search</p>
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
<span class="card-header-badge inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-500/20 text-gray-400" id="memory-badge">
|
|
264
|
+
Checking...
|
|
265
|
+
</span>
|
|
266
|
+
</div>
|
|
267
|
+
|
|
268
|
+
<!-- Toggle Row -->
|
|
269
|
+
<div class="bg-lobsta-bg rounded-lg p-4 mb-3">
|
|
270
|
+
<div class="flex items-center justify-between">
|
|
271
|
+
<div class="flex-1 mr-4">
|
|
272
|
+
<p class="text-sm font-medium text-white">Private Memory</p>
|
|
273
|
+
<p class="text-xs text-lobsta-muted mt-0.5">Run memory embeddings locally — no data leaves your server</p>
|
|
274
|
+
</div>
|
|
275
|
+
<label class="toggle-switch">
|
|
276
|
+
<input type="checkbox" id="memory-toggle" onchange="handleMemoryToggle(this.checked)">
|
|
277
|
+
<span class="toggle-slider"></span>
|
|
278
|
+
</label>
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
|
|
282
|
+
<!-- Status Info -->
|
|
283
|
+
<div id="memory-status-info" class="space-y-2">
|
|
284
|
+
<div class="flex items-center justify-between text-sm" id="memory-provider-row">
|
|
285
|
+
<span class="text-lobsta-muted">Provider</span>
|
|
286
|
+
<span id="memory-provider-text" class="text-white">—</span>
|
|
287
|
+
</div>
|
|
288
|
+
<div class="flex items-center justify-between text-sm" id="memory-model-row">
|
|
289
|
+
<span class="text-lobsta-muted">Model</span>
|
|
290
|
+
<span id="memory-model-text" class="text-white">—</span>
|
|
291
|
+
</div>
|
|
292
|
+
<div class="flex items-center justify-between text-sm" id="memory-download-row">
|
|
293
|
+
<span class="text-lobsta-muted">Local Model</span>
|
|
294
|
+
<span id="memory-download-text" class="text-white">—</span>
|
|
295
|
+
</div>
|
|
296
|
+
</div>
|
|
297
|
+
|
|
298
|
+
<!-- Download Action Area -->
|
|
299
|
+
<div id="memory-download-action" class="hidden mt-3">
|
|
300
|
+
<div class="bg-lobsta-bg rounded-lg p-3">
|
|
301
|
+
<div id="memory-download-idle">
|
|
302
|
+
<div class="flex items-center justify-between gap-3">
|
|
303
|
+
<div class="min-w-0">
|
|
304
|
+
<p class="text-sm text-yellow-400">📥 Model not yet downloaded</p>
|
|
305
|
+
<p class="text-xs text-lobsta-muted mt-0.5">~313MB • Will download on first use, or download now</p>
|
|
306
|
+
</div>
|
|
307
|
+
<button onclick="downloadModel()" class="btn-download-model" id="btn-download-model">
|
|
308
|
+
Download
|
|
309
|
+
</button>
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
<div id="memory-download-progress" class="hidden">
|
|
313
|
+
<div class="flex items-center space-x-3 mb-2">
|
|
314
|
+
<div class="spinner text-lobsta-accent"></div>
|
|
315
|
+
<div>
|
|
316
|
+
<p class="text-sm font-medium text-white">Downloading embeddinggemma-300M...</p>
|
|
317
|
+
<p class="text-xs text-lobsta-muted" id="memory-download-progress-text">Starting download...</p>
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
<div class="download-progress-bar">
|
|
321
|
+
<div class="download-progress-fill" id="memory-download-progress-fill" style="width: 0%"></div>
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
<div id="memory-download-complete" class="hidden">
|
|
325
|
+
<p class="text-sm text-green-400">✅ Model downloaded successfully</p>
|
|
326
|
+
<p class="text-xs text-lobsta-muted mt-0.5" id="memory-download-size-text"></p>
|
|
327
|
+
</div>
|
|
328
|
+
<div id="memory-download-error" class="hidden">
|
|
329
|
+
<p class="text-sm text-red-400" id="memory-download-error-text">❌ Download failed</p>
|
|
330
|
+
<button onclick="downloadModel()" class="btn-download-model mt-2">
|
|
331
|
+
Retry Download
|
|
332
|
+
</button>
|
|
333
|
+
</div>
|
|
334
|
+
</div>
|
|
335
|
+
</div>
|
|
336
|
+
|
|
337
|
+
<!-- Note -->
|
|
338
|
+
<div class="mt-3 p-2.5 rounded-lg bg-lobsta-bg border border-lobsta-border" id="memory-note">
|
|
339
|
+
<p class="text-xs text-lobsta-muted">
|
|
340
|
+
💡 First enable downloads ~313MB model. Requires ~512MB RAM.
|
|
341
|
+
</p>
|
|
342
|
+
</div>
|
|
343
|
+
</div>
|
|
344
|
+
|
|
345
|
+
<!-- Security & Remote Access -->
|
|
346
|
+
<div class="card" id="security-card">
|
|
347
|
+
<div class="flex items-center justify-between mb-4 gap-3">
|
|
348
|
+
<div class="flex items-center space-x-3 min-w-0 flex-1 overflow-hidden">
|
|
349
|
+
<div class="w-10 h-10 rounded-lg bg-indigo-500/20 flex items-center justify-center flex-shrink-0">
|
|
350
|
+
<span class="text-xl">🛡️</span>
|
|
351
|
+
</div>
|
|
352
|
+
<div class="min-w-0 overflow-hidden">
|
|
353
|
+
<h2 class="text-xl font-semibold truncate">Security & Remote Access</h2>
|
|
354
|
+
<p class="text-lobsta-muted text-sm truncate">Firewall, intrusion prevention, VPN & Web Chat</p>
|
|
355
|
+
</div>
|
|
356
|
+
</div>
|
|
357
|
+
<span class="card-header-badge inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-500/20 text-gray-400" id="security-badge">
|
|
358
|
+
Checking...
|
|
359
|
+
</span>
|
|
360
|
+
</div>
|
|
361
|
+
|
|
362
|
+
<!-- Why Tailscale? Explainer -->
|
|
363
|
+
<div class="mb-4 rounded-lg border-l-4 border-indigo-500" style="background: #1a1a2e;">
|
|
364
|
+
<button onclick="toggleWhyTailscale()" class="w-full text-left p-4 flex items-center justify-between hover:opacity-90 transition">
|
|
365
|
+
<span class="text-sm font-semibold text-indigo-300">🛡️ Why Tailscale?</span>
|
|
366
|
+
<svg id="why-ts-chevron" class="w-4 h-4 text-indigo-400 transform transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
367
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
|
368
|
+
</svg>
|
|
369
|
+
</button>
|
|
370
|
+
<div id="why-tailscale-content" class="hidden px-4 pb-4">
|
|
371
|
+
<p class="text-xs text-gray-300 mb-3">
|
|
372
|
+
Your Clawdbot server is powerful — it has access to your AI keys, chat history, and memory.
|
|
373
|
+
Tailscale creates a private encrypted network (WireGuard) so you can securely access your server from anywhere without exposing it to the internet.
|
|
374
|
+
</p>
|
|
375
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
|
376
|
+
<div class="rounded-lg p-3" style="background: rgba(239,68,68,0.08); border: 1px solid rgba(239,68,68,0.15);">
|
|
377
|
+
<p class="text-xs font-semibold text-red-400 mb-2">Without Tailscale</p>
|
|
378
|
+
<ul class="text-xs text-gray-400 space-y-1.5">
|
|
379
|
+
<li>❌ Web Chat only works on localhost</li>
|
|
380
|
+
<li>❌ No remote SSH without opening ports</li>
|
|
381
|
+
<li>❌ Dashboard only on server's IP</li>
|
|
382
|
+
</ul>
|
|
383
|
+
</div>
|
|
384
|
+
<div class="rounded-lg p-3" style="background: rgba(34,197,94,0.08); border: 1px solid rgba(34,197,94,0.15);">
|
|
385
|
+
<p class="text-xs font-semibold text-green-400 mb-2">With Tailscale</p>
|
|
386
|
+
<ul class="text-xs text-gray-400 space-y-1.5">
|
|
387
|
+
<li>✅ Web Chat from any device</li>
|
|
388
|
+
<li>✅ SSH without exposing port 22</li>
|
|
389
|
+
<li>✅ Zero-trust, no open ports</li>
|
|
390
|
+
<li>✅ Works behind firewalls & NATs</li>
|
|
391
|
+
<li>✅ End-to-end WireGuard encryption</li>
|
|
392
|
+
</ul>
|
|
393
|
+
</div>
|
|
394
|
+
</div>
|
|
395
|
+
</div>
|
|
396
|
+
</div>
|
|
397
|
+
|
|
398
|
+
<!-- Security Checklist -->
|
|
399
|
+
<div class="bg-lobsta-bg rounded-lg divide-y divide-lobsta-border mb-4" id="security-checklist">
|
|
400
|
+
<div class="flex items-center justify-between p-3">
|
|
401
|
+
<div class="flex items-center space-x-3">
|
|
402
|
+
<span class="text-lg" id="sec-firewall-icon">○</span>
|
|
403
|
+
<div>
|
|
404
|
+
<p class="text-sm font-medium">Firewall (UFW)</p>
|
|
405
|
+
<p class="text-xs text-lobsta-muted">Block unauthorized access</p>
|
|
406
|
+
</div>
|
|
407
|
+
</div>
|
|
408
|
+
<span class="text-xs font-medium px-2 py-0.5 rounded bg-gray-500/20 text-gray-400" id="sec-firewall-badge">—</span>
|
|
409
|
+
</div>
|
|
410
|
+
<div class="flex items-center justify-between p-3">
|
|
411
|
+
<div class="flex items-center space-x-3">
|
|
412
|
+
<span class="text-lg" id="sec-fail2ban-icon">○</span>
|
|
413
|
+
<div>
|
|
414
|
+
<p class="text-sm font-medium">Fail2ban</p>
|
|
415
|
+
<p class="text-xs text-lobsta-muted">Auto-ban brute force attacks</p>
|
|
416
|
+
</div>
|
|
417
|
+
</div>
|
|
418
|
+
<span class="text-xs font-medium px-2 py-0.5 rounded bg-gray-500/20 text-gray-400" id="sec-fail2ban-badge">—</span>
|
|
419
|
+
</div>
|
|
420
|
+
<div class="flex items-center justify-between p-3">
|
|
421
|
+
<div class="flex items-center space-x-3">
|
|
422
|
+
<span class="text-lg" id="sec-ssh-icon">○</span>
|
|
423
|
+
<div>
|
|
424
|
+
<p class="text-sm font-medium">SSH Hardening</p>
|
|
425
|
+
<p class="text-xs text-lobsta-muted">Key-only auth, limited retries</p>
|
|
426
|
+
</div>
|
|
427
|
+
</div>
|
|
428
|
+
<span class="text-xs font-medium px-2 py-0.5 rounded bg-gray-500/20 text-gray-400" id="sec-ssh-badge">—</span>
|
|
429
|
+
</div>
|
|
430
|
+
<div class="flex items-center justify-between p-3">
|
|
431
|
+
<div class="flex items-center space-x-3">
|
|
432
|
+
<span class="text-lg" id="sec-kernel-icon">○</span>
|
|
433
|
+
<div>
|
|
434
|
+
<p class="text-sm font-medium">Kernel Hardening</p>
|
|
435
|
+
<p class="text-xs text-lobsta-muted">Network stack protection</p>
|
|
436
|
+
</div>
|
|
437
|
+
</div>
|
|
438
|
+
<span class="text-xs font-medium px-2 py-0.5 rounded bg-gray-500/20 text-gray-400" id="sec-kernel-badge">—</span>
|
|
439
|
+
</div>
|
|
440
|
+
<div class="flex items-center justify-between p-3">
|
|
441
|
+
<div class="flex items-center space-x-3">
|
|
442
|
+
<span class="text-lg" id="sec-updates-icon">○</span>
|
|
443
|
+
<div>
|
|
444
|
+
<p class="text-sm font-medium">Auto Updates</p>
|
|
445
|
+
<p class="text-xs text-lobsta-muted">Automatic security patches</p>
|
|
446
|
+
</div>
|
|
447
|
+
</div>
|
|
448
|
+
<span class="text-xs font-medium px-2 py-0.5 rounded bg-gray-500/20 text-gray-400" id="sec-updates-badge">—</span>
|
|
449
|
+
</div>
|
|
450
|
+
<div class="flex items-center justify-between p-3">
|
|
451
|
+
<div class="flex items-center space-x-3">
|
|
452
|
+
<span class="text-lg" id="sec-tailscale-icon">○</span>
|
|
453
|
+
<div>
|
|
454
|
+
<p class="text-sm font-medium">Tailscale VPN</p>
|
|
455
|
+
<p class="text-xs text-lobsta-muted">Secure private network access</p>
|
|
456
|
+
</div>
|
|
457
|
+
</div>
|
|
458
|
+
<span class="text-xs font-medium px-2 py-0.5 rounded bg-gray-500/20 text-gray-400" id="sec-tailscale-badge">—</span>
|
|
459
|
+
</div>
|
|
460
|
+
</div>
|
|
461
|
+
|
|
462
|
+
<!-- Harden Button (hidden when fully hardened) -->
|
|
463
|
+
<div id="security-harden-section" class="mb-4 hidden">
|
|
464
|
+
<button onclick="hardenServer()" class="btn btn-warning w-full" id="btn-harden">
|
|
465
|
+
🛡️ Harden Server — Apply All Security Settings
|
|
466
|
+
</button>
|
|
467
|
+
<p class="text-xs text-lobsta-muted text-center mt-2">
|
|
468
|
+
Installs and configures firewall, fail2ban, SSH hardening, kernel protection, auto-updates, and Tailscale.
|
|
469
|
+
</p>
|
|
470
|
+
</div>
|
|
471
|
+
|
|
472
|
+
<!-- Hardening Progress -->
|
|
473
|
+
<div id="security-harden-progress" class="hidden mb-4">
|
|
474
|
+
<div class="bg-lobsta-bg rounded-lg p-4">
|
|
475
|
+
<div class="flex items-center space-x-3">
|
|
476
|
+
<div class="spinner text-lobsta-accent"></div>
|
|
477
|
+
<div>
|
|
478
|
+
<p class="text-sm font-medium">Hardening server...</p>
|
|
479
|
+
<p class="text-xs text-lobsta-muted" id="harden-progress-text">Installing packages — this may take a minute</p>
|
|
480
|
+
</div>
|
|
481
|
+
</div>
|
|
482
|
+
</div>
|
|
483
|
+
</div>
|
|
484
|
+
|
|
485
|
+
<!-- Hardening Result -->
|
|
486
|
+
<div id="security-harden-result" class="hidden mb-4">
|
|
487
|
+
<div class="rounded-lg p-4" id="harden-result-box">
|
|
488
|
+
<p class="text-sm font-medium" id="harden-result-title"></p>
|
|
489
|
+
<p class="text-xs mt-1" id="harden-result-text"></p>
|
|
490
|
+
</div>
|
|
491
|
+
</div>
|
|
492
|
+
|
|
493
|
+
<!-- Divider -->
|
|
494
|
+
<div class="border-t border-lobsta-border my-4"></div>
|
|
495
|
+
|
|
496
|
+
<!-- Tailscale VPN Section -->
|
|
497
|
+
<div>
|
|
498
|
+
<div class="flex items-center justify-between mb-3">
|
|
499
|
+
<h3 class="text-sm font-semibold text-lobsta-muted uppercase tracking-wider">Tailscale VPN</h3>
|
|
500
|
+
<button onclick="refreshTailscaleStatus()" class="text-lobsta-muted hover:text-white transition" title="Refresh">
|
|
501
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
502
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
|
503
|
+
</svg>
|
|
504
|
+
</button>
|
|
505
|
+
</div>
|
|
506
|
+
|
|
507
|
+
<!-- Tailscale Status Line -->
|
|
508
|
+
<div id="tailscale-status-section" class="bg-lobsta-bg rounded-lg p-3 mb-3">
|
|
509
|
+
<p class="text-sm font-medium" id="tailscale-status-text">Checking Tailscale status...</p>
|
|
510
|
+
<p class="text-xs text-lobsta-muted mt-1" id="tailscale-status-detail"></p>
|
|
511
|
+
</div>
|
|
512
|
+
|
|
513
|
+
<!-- Connected State -->
|
|
514
|
+
<div id="tailscale-connected" class="hidden">
|
|
515
|
+
<div class="bg-green-500/10 border border-green-500/20 rounded-lg p-3 mb-3">
|
|
516
|
+
<div class="flex items-start space-x-3">
|
|
517
|
+
<span class="text-green-400">✓</span>
|
|
518
|
+
<div>
|
|
519
|
+
<p class="text-sm font-medium text-green-400">Connected</p>
|
|
520
|
+
<p class="text-xs text-lobsta-muted mt-1">Hostname: <span class="text-white" id="tailscale-hostname">—</span></p>
|
|
521
|
+
<p class="text-xs text-lobsta-muted">IP: <span class="text-white font-mono" id="tailscale-ip">—</span></p>
|
|
522
|
+
</div>
|
|
523
|
+
</div>
|
|
524
|
+
</div>
|
|
525
|
+
<button onclick="disconnectTailscale()" class="btn btn-secondary w-full text-sm" id="btn-ts-disconnect">
|
|
526
|
+
Disconnect Tailscale
|
|
527
|
+
</button>
|
|
528
|
+
</div>
|
|
529
|
+
|
|
530
|
+
<!-- Not Installed State -->
|
|
531
|
+
<div id="tailscale-not-installed" class="hidden">
|
|
532
|
+
<div class="bg-lobsta-bg rounded-lg p-3">
|
|
533
|
+
<p class="text-xs text-lobsta-muted mb-3">Tailscale is not installed. Install it to enable secure VPN access.</p>
|
|
534
|
+
<button onclick="installTailscale()" class="btn btn-secondary w-full text-sm" id="btn-ts-install">
|
|
535
|
+
📦 Install Tailscale
|
|
536
|
+
</button>
|
|
537
|
+
</div>
|
|
538
|
+
</div>
|
|
539
|
+
|
|
540
|
+
<!-- Not Connected State: Connect form -->
|
|
541
|
+
<div id="tailscale-connect-form" class="hidden">
|
|
542
|
+
<div class="bg-lobsta-bg rounded-lg p-3">
|
|
543
|
+
<p class="text-xs text-lobsta-muted mb-3">
|
|
544
|
+
Get an auth key from
|
|
545
|
+
<a href="https://login.tailscale.com/admin/settings/keys" target="_blank" class="text-lobsta-accent hover:underline">Tailscale Admin → Keys</a>,
|
|
546
|
+
then paste it below.
|
|
547
|
+
</p>
|
|
548
|
+
<div class="space-y-2">
|
|
549
|
+
<div class="relative">
|
|
550
|
+
<input type="password" id="tailscale-auth-key"
|
|
551
|
+
class="input w-full text-sm pr-16"
|
|
552
|
+
placeholder="tskey-auth-..."
|
|
553
|
+
autocomplete="off">
|
|
554
|
+
<button onclick="toggleVisibility('tailscale-auth-key', this)"
|
|
555
|
+
class="absolute right-3 top-1/2 -translate-y-1/2 text-xs text-lobsta-muted hover:text-white transition" type="button">
|
|
556
|
+
Show
|
|
557
|
+
</button>
|
|
558
|
+
</div>
|
|
559
|
+
<p class="text-xs text-lobsta-error hidden" id="tailscale-key-error"></p>
|
|
560
|
+
<button onclick="connectTailscale()" class="btn btn-primary w-full text-sm" id="btn-ts-connect">
|
|
561
|
+
🔗 Connect to Tailscale
|
|
562
|
+
</button>
|
|
563
|
+
</div>
|
|
564
|
+
</div>
|
|
565
|
+
</div>
|
|
566
|
+
</div>
|
|
567
|
+
|
|
568
|
+
<!-- Divider -->
|
|
569
|
+
<div class="border-t border-lobsta-border my-4"></div>
|
|
570
|
+
|
|
571
|
+
<!-- Web Chat Access Section -->
|
|
572
|
+
<div>
|
|
573
|
+
<div class="flex items-center justify-between mb-3">
|
|
574
|
+
<h3 class="text-sm font-semibold text-lobsta-muted uppercase tracking-wider">💬 Web Chat Access</h3>
|
|
575
|
+
</div>
|
|
576
|
+
|
|
577
|
+
<!-- Web Chat: Ready (Tailscale + Serve active) -->
|
|
578
|
+
<div id="webchat-ready" class="hidden">
|
|
579
|
+
<div class="bg-lobsta-bg rounded-lg p-4">
|
|
580
|
+
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px;">
|
|
581
|
+
<span style="font-size: 1.25rem;">💬</span>
|
|
582
|
+
<h4 style="font-weight: 600;">Web Chat</h4>
|
|
583
|
+
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-green-500/20 text-green-400">Ready</span>
|
|
584
|
+
</div>
|
|
585
|
+
<p class="text-lobsta-muted text-sm mb-3">Chat with your AI securely from any device on your Tailscale network.</p>
|
|
586
|
+
<a id="webchat-open-link" href="#" target="_blank" class="btn btn-accent w-full text-center block mb-3" style="background: #DC2626; color: white; padding: 10px 16px; border-radius: 8px; font-weight: 600; display: block; text-decoration: none;">
|
|
587
|
+
🔗 Open Web Chat
|
|
588
|
+
</a>
|
|
589
|
+
<div class="text-xs text-lobsta-muted mt-2" style="word-break: break-all;">
|
|
590
|
+
<span id="webchat-url">—</span>
|
|
591
|
+
<button onclick="copyWebchatUrl()" class="text-lobsta-accent ml-2 hover:underline">Copy</button>
|
|
592
|
+
</div>
|
|
593
|
+
</div>
|
|
594
|
+
|
|
595
|
+
<!-- Gateway Token -->
|
|
596
|
+
<div id="webchat-token-section" class="bg-lobsta-bg rounded-lg p-3 mt-3 hidden">
|
|
597
|
+
<p class="text-sm font-medium text-white mb-2">🔑 Gateway Token</p>
|
|
598
|
+
<div class="flex items-center gap-2">
|
|
599
|
+
<code class="flex-1 bg-lobsta-card px-2 py-1.5 rounded text-xs font-mono text-gray-300 truncate" id="webchat-token-display">••••••••</code>
|
|
600
|
+
<button onclick="toggleGatewayToken()" class="btn btn-sm btn-secondary text-xs" id="btn-toggle-token" title="Show/Hide token">
|
|
601
|
+
👁️
|
|
602
|
+
</button>
|
|
603
|
+
<button onclick="copyGatewayToken()" class="btn btn-sm btn-secondary text-xs" id="btn-copy-token" title="Copy token">
|
|
604
|
+
📋
|
|
605
|
+
</button>
|
|
606
|
+
</div>
|
|
607
|
+
<p class="text-xs text-lobsta-muted mt-2">
|
|
608
|
+
💡 First time? Paste this token in the Web Chat settings to authenticate.
|
|
609
|
+
</p>
|
|
610
|
+
</div>
|
|
611
|
+
</div>
|
|
612
|
+
|
|
613
|
+
<!-- Web Chat: Setup Needed (Tailscale connected but Serve not enabled) -->
|
|
614
|
+
<div id="webchat-setup-needed" class="hidden">
|
|
615
|
+
<div class="bg-lobsta-bg rounded-lg p-4">
|
|
616
|
+
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px;">
|
|
617
|
+
<span style="font-size: 1.25rem;">💬</span>
|
|
618
|
+
<h4 style="font-weight: 600;">Web Chat</h4>
|
|
619
|
+
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-yellow-500/20 text-yellow-400">Setup Needed</span>
|
|
620
|
+
</div>
|
|
621
|
+
<p class="text-lobsta-muted text-sm mb-3">Tailscale Serve needs to be enabled on your tailnet for HTTPS Web Chat access.</p>
|
|
622
|
+
<a id="webchat-serve-enable-link" href="https://login.tailscale.com/admin/settings" target="_blank" class="btn btn-secondary w-full text-center block mb-2" style="display: block; text-decoration: none; padding: 8px 16px; border-radius: 8px;">
|
|
623
|
+
Enable Tailscale Serve →
|
|
624
|
+
</a>
|
|
625
|
+
<button onclick="retryServeSetup()" class="btn btn-accent w-full" id="btn-retry-serve" style="background: #DC2626; color: white; padding: 10px 16px; border-radius: 8px; font-weight: 600; width: 100%; border: none; cursor: pointer;">
|
|
626
|
+
↻ Retry Setup
|
|
627
|
+
</button>
|
|
628
|
+
</div>
|
|
629
|
+
</div>
|
|
630
|
+
|
|
631
|
+
<!-- Web Chat: Tailscale Required -->
|
|
632
|
+
<div id="webchat-needs-tailscale" class="hidden">
|
|
633
|
+
<div class="bg-lobsta-bg rounded-lg p-4">
|
|
634
|
+
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 12px;">
|
|
635
|
+
<span style="font-size: 1.25rem;">💬</span>
|
|
636
|
+
<h4 style="font-weight: 600;">Web Chat</h4>
|
|
637
|
+
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-gray-500/20 text-gray-400">Tailscale Required</span>
|
|
638
|
+
</div>
|
|
639
|
+
<p class="text-lobsta-muted text-sm">Connect Tailscale above to enable secure Web Chat access from any device.</p>
|
|
640
|
+
</div>
|
|
641
|
+
</div>
|
|
642
|
+
|
|
643
|
+
<!-- Web Chat: Loading -->
|
|
644
|
+
<div id="webchat-loading">
|
|
645
|
+
<div class="bg-lobsta-bg rounded-lg p-3">
|
|
646
|
+
<p class="text-sm text-lobsta-muted">Checking Web Chat access...</p>
|
|
647
|
+
</div>
|
|
648
|
+
</div>
|
|
649
|
+
</div>
|
|
650
|
+
</div>
|
|
651
|
+
|
|
652
|
+
<!-- Logs Section -->
|
|
653
|
+
<div class="card">
|
|
654
|
+
<div class="flex items-center justify-between mb-4">
|
|
655
|
+
<h2 class="text-xl font-semibold">Logs</h2>
|
|
656
|
+
<button onclick="refreshLogs()" class="btn btn-sm btn-secondary">
|
|
657
|
+
Refresh
|
|
658
|
+
</button>
|
|
659
|
+
</div>
|
|
660
|
+
<div id="log-viewer" class="bg-lobsta-bg border border-lobsta-border rounded-lg p-4 font-mono text-xs leading-relaxed text-lobsta-muted max-h-96 overflow-y-auto whitespace-pre-wrap break-all">
|
|
661
|
+
Loading logs...
|
|
662
|
+
</div>
|
|
663
|
+
</div>
|
|
664
|
+
</div>
|
|
665
|
+
|
|
666
|
+
<!-- Sidebar -->
|
|
667
|
+
<div class="space-y-6">
|
|
668
|
+
<!-- Configuration Info -->
|
|
669
|
+
<div class="card">
|
|
670
|
+
<h2 class="text-xl font-semibold mb-4">Configuration</h2>
|
|
671
|
+
<dl class="space-y-3 text-sm" id="config-details">
|
|
672
|
+
<div class="flex justify-between">
|
|
673
|
+
<dt class="text-lobsta-muted">Model</dt>
|
|
674
|
+
<dd id="info-model">—</dd>
|
|
675
|
+
</div>
|
|
676
|
+
<div class="flex justify-between">
|
|
677
|
+
<dt class="text-lobsta-muted">Channel</dt>
|
|
678
|
+
<dd id="info-channel">—</dd>
|
|
679
|
+
</div>
|
|
680
|
+
<div class="flex justify-between">
|
|
681
|
+
<dt class="text-lobsta-muted">Domain</dt>
|
|
682
|
+
<dd id="info-domain" class="truncate">—</dd>
|
|
683
|
+
</div>
|
|
684
|
+
</dl>
|
|
685
|
+
<a href="/setup" class="btn btn-primary w-full mt-4 text-center block">
|
|
686
|
+
Edit Configuration
|
|
687
|
+
</a>
|
|
688
|
+
</div>
|
|
689
|
+
|
|
690
|
+
<!-- Security / Account -->
|
|
691
|
+
<div class="card">
|
|
692
|
+
<h2 class="text-xl font-semibold mb-4">🔒 Account</h2>
|
|
693
|
+
<div class="space-y-3" id="password-change-form">
|
|
694
|
+
<div>
|
|
695
|
+
<label class="block text-xs font-medium mb-1 text-lobsta-muted">Login Email</label>
|
|
696
|
+
<input type="email" id="account-email" class="input w-full text-sm" placeholder="you@email.com" autocomplete="email" disabled>
|
|
697
|
+
<p class="text-xs text-lobsta-muted mt-1">Change email below with your current password</p>
|
|
698
|
+
</div>
|
|
699
|
+
<hr class="border-lobsta-border">
|
|
700
|
+
<div>
|
|
701
|
+
<label class="block text-xs font-medium mb-1 text-lobsta-muted">Current Password</label>
|
|
702
|
+
<input type="password" id="current-password" class="input w-full text-sm" placeholder="Current password" autocomplete="current-password">
|
|
703
|
+
</div>
|
|
704
|
+
<div>
|
|
705
|
+
<label class="block text-xs font-medium mb-1 text-lobsta-muted">New Email <span class="text-lobsta-muted">(optional)</span></label>
|
|
706
|
+
<input type="email" id="new-email" class="input w-full text-sm" placeholder="Leave blank to keep current" autocomplete="email">
|
|
707
|
+
</div>
|
|
708
|
+
<div>
|
|
709
|
+
<label class="block text-xs font-medium mb-1 text-lobsta-muted">New Password <span class="text-lobsta-muted">(optional)</span></label>
|
|
710
|
+
<input type="password" id="new-password" class="input w-full text-sm" placeholder="Leave blank to keep current" autocomplete="new-password">
|
|
711
|
+
</div>
|
|
712
|
+
<div>
|
|
713
|
+
<label class="block text-xs font-medium mb-1 text-lobsta-muted">Confirm New Password</label>
|
|
714
|
+
<input type="password" id="confirm-new-password" class="input w-full text-sm" placeholder="Confirm password" autocomplete="new-password">
|
|
715
|
+
</div>
|
|
716
|
+
<p class="text-xs text-lobsta-error hidden" id="password-change-error"></p>
|
|
717
|
+
<p class="text-xs text-green-400 hidden" id="password-change-success"></p>
|
|
718
|
+
<button onclick="changePassword()" class="btn btn-secondary w-full text-sm" id="btn-change-password">
|
|
719
|
+
Update Account
|
|
720
|
+
</button>
|
|
721
|
+
</div>
|
|
722
|
+
</div>
|
|
723
|
+
</div>
|
|
724
|
+
</div>
|
|
725
|
+
</div>
|
|
726
|
+
</main>
|
|
727
|
+
|
|
728
|
+
<!-- Toast Container -->
|
|
729
|
+
<div id="toast-container" class="fixed bottom-4 right-4 space-y-2 z-50"></div>
|
|
730
|
+
|
|
731
|
+
<script src="/js/app.js"></script>
|
|
732
|
+
<script src="/js/manage.js"></script>
|
|
733
|
+
</body>
|
|
734
|
+
</html>
|