aimeat 3.0.0 → 3.1.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/build-stamp.json +4 -4
- package/dist/locales/en.json +9103 -9101
- package/dist/locales/es.json +9095 -0
- package/dist/locales/fi.json +9103 -9101
- package/dist/public/cortex-bundled/aimeat-input.js +611 -0
- package/dist/public/cortex-bundled/aimeat-input.yaml +149 -0
- package/dist/public/css/views/agents-detail.css +11 -0
- package/dist/public/js/utils.js +12 -7
- package/dist/public/spa.html +5 -2
- package/dist/public/views/admin/email-tab.js +459 -458
- package/dist/public/views/profile/agents/agent-card.js +25 -0
- package/dist/public/views/profile/agents/scope-model.js +296 -288
- package/dist/public/views/profile/agents-tab.js +26 -3
- package/dist/scripts/check-ai-disclosure.d.ts.map +1 -1
- package/dist/scripts/check-ai-disclosure.js +33 -18
- package/dist/scripts/check-ai-disclosure.js.map +1 -1
- package/dist/scripts/check-locales.d.ts +2 -0
- package/dist/scripts/check-locales.d.ts.map +1 -0
- package/dist/scripts/check-locales.js +46 -0
- package/dist/scripts/check-locales.js.map +1 -0
- package/dist/scripts/lib/locale-files.d.ts +40 -0
- package/dist/scripts/lib/locale-files.d.ts.map +1 -0
- package/dist/scripts/lib/locale-files.js +181 -0
- package/dist/scripts/lib/locale-files.js.map +1 -0
- package/dist/scripts/locale-extract.d.ts +2 -0
- package/dist/scripts/locale-extract.d.ts.map +1 -0
- package/dist/scripts/locale-extract.js +99 -0
- package/dist/scripts/locale-extract.js.map +1 -0
- package/dist/scripts/locale-merge.d.ts +2 -0
- package/dist/scripts/locale-merge.d.ts.map +1 -0
- package/dist/scripts/locale-merge.js +114 -0
- package/dist/scripts/locale-merge.js.map +1 -0
- package/dist/src/auth/middleware.d.ts +6 -0
- package/dist/src/auth/middleware.d.ts.map +1 -1
- package/dist/src/auth/middleware.js +24 -4
- package/dist/src/auth/middleware.js.map +1 -1
- package/dist/src/cli/connect/mcp/tools/agent-management.d.ts +1 -0
- package/dist/src/cli/connect/mcp/tools/agent-management.d.ts.map +1 -1
- package/dist/src/cli/connect/mcp/tools/agent-management.js +9 -0
- package/dist/src/cli/connect/mcp/tools/agent-management.js.map +1 -1
- package/dist/src/cli/connect/mcp/tools/schedules.d.ts +3 -0
- package/dist/src/cli/connect/mcp/tools/schedules.d.ts.map +1 -1
- package/dist/src/cli/connect/mcp/tools/schedules.js +5 -0
- package/dist/src/cli/connect/mcp/tools/schedules.js.map +1 -1
- package/dist/src/cli/connect/tool-call-defs-agent.d.ts +2 -0
- package/dist/src/cli/connect/tool-call-defs-agent.d.ts.map +1 -1
- package/dist/src/cli/connect/tool-call-defs-agent.js +16 -0
- package/dist/src/cli/connect/tool-call-defs-agent.js.map +1 -1
- package/dist/src/cli/connect/tool-call-defs-organism.d.ts.map +1 -1
- package/dist/src/cli/connect/tool-call-defs-organism.js +6 -0
- package/dist/src/cli/connect/tool-call-defs-organism.js.map +1 -1
- package/dist/src/config-operator.d.ts +4 -1
- package/dist/src/config-operator.d.ts.map +1 -1
- package/dist/src/config-operator.js +4 -4
- package/dist/src/config-operator.js.map +1 -1
- package/dist/src/data/library-packs/cortex.d.ts +3 -0
- package/dist/src/data/library-packs/cortex.d.ts.map +1 -1
- package/dist/src/data/library-packs/cortex.js +58 -0
- package/dist/src/data/library-packs/cortex.js.map +1 -1
- package/dist/src/generated/api-types.d.ts +90 -2
- package/dist/src/generated/api-types.d.ts.map +1 -1
- package/dist/src/i18n.d.ts +1 -1
- package/dist/src/i18n.d.ts.map +1 -1
- package/dist/src/i18n.js +13 -6
- package/dist/src/i18n.js.map +1 -1
- package/dist/src/mcp/agent-management.d.ts +6 -2
- package/dist/src/mcp/agent-management.d.ts.map +1 -1
- package/dist/src/mcp/agent-management.js +34 -3
- package/dist/src/mcp/agent-management.js.map +1 -1
- package/dist/src/mcp/agent-schedules.d.ts +6 -2
- package/dist/src/mcp/agent-schedules.d.ts.map +1 -1
- package/dist/src/mcp/agent-schedules.js +22 -1
- package/dist/src/mcp/agent-schedules.js.map +1 -1
- package/dist/src/mcp/annotations.d.ts +1 -0
- package/dist/src/mcp/annotations.d.ts.map +1 -1
- package/dist/src/mcp/annotations.js +5 -0
- package/dist/src/mcp/annotations.js.map +1 -1
- package/dist/src/mcp/catalog/definitions/agent-messaging.d.ts +2 -0
- package/dist/src/mcp/catalog/definitions/agent-messaging.d.ts.map +1 -1
- package/dist/src/mcp/catalog/definitions/agent-messaging.js +12 -0
- package/dist/src/mcp/catalog/definitions/agent-messaging.js.map +1 -1
- package/dist/src/mcp/catalog/definitions/schedules-tasks-memory.d.ts.map +1 -1
- package/dist/src/mcp/catalog/definitions/schedules-tasks-memory.js +7 -0
- package/dist/src/mcp/catalog/definitions/schedules-tasks-memory.js.map +1 -1
- package/dist/src/mcp/catalog/scopes.d.ts.map +1 -1
- package/dist/src/mcp/catalog/scopes.js +5 -0
- package/dist/src/mcp/catalog/scopes.js.map +1 -1
- package/dist/src/mcp/catalog/surfaces.d.ts.map +1 -1
- package/dist/src/mcp/catalog/surfaces.js +4 -2
- package/dist/src/mcp/catalog/surfaces.js.map +1 -1
- package/dist/src/routes/admin-features.d.ts.map +1 -1
- package/dist/src/routes/admin-features.js +5 -4
- package/dist/src/routes/admin-features.js.map +1 -1
- package/dist/src/routes/agents/constants.d.ts +18 -2
- package/dist/src/routes/agents/constants.d.ts.map +1 -1
- package/dist/src/routes/agents/constants.js +18 -2
- package/dist/src/routes/agents/constants.js.map +1 -1
- package/dist/src/routes/agents/device-auth.d.ts +2 -0
- package/dist/src/routes/agents/device-auth.d.ts.map +1 -1
- package/dist/src/routes/agents/device-auth.js +22 -2
- package/dist/src/routes/agents/device-auth.js.map +1 -1
- package/dist/src/routes/agents/management.d.ts +7 -0
- package/dist/src/routes/agents/management.d.ts.map +1 -1
- package/dist/src/routes/agents/management.js +59 -4
- package/dist/src/routes/agents/management.js.map +1 -1
- package/dist/src/routes/agents/profile-metadata.d.ts +4 -0
- package/dist/src/routes/agents/profile-metadata.d.ts.map +1 -1
- package/dist/src/routes/agents/profile-metadata.js +31 -2
- package/dist/src/routes/agents/profile-metadata.js.map +1 -1
- package/dist/src/routes/agents/registration.d.ts +2 -0
- package/dist/src/routes/agents/registration.d.ts.map +1 -1
- package/dist/src/routes/agents/registration.js +3 -0
- package/dist/src/routes/agents/registration.js.map +1 -1
- package/dist/src/routes/ai-transparency.d.ts.map +1 -1
- package/dist/src/routes/ai-transparency.js +5 -1
- package/dist/src/routes/ai-transparency.js.map +1 -1
- package/dist/src/routes/auth.d.ts +3 -0
- package/dist/src/routes/auth.d.ts.map +1 -1
- package/dist/src/routes/auth.js +22 -5
- package/dist/src/routes/auth.js.map +1 -1
- package/dist/src/routes/portal.d.ts +3 -0
- package/dist/src/routes/portal.d.ts.map +1 -1
- package/dist/src/routes/portal.js +39 -17
- package/dist/src/routes/portal.js.map +1 -1
- package/dist/src/routes/schedules.d.ts +3 -0
- package/dist/src/routes/schedules.d.ts.map +1 -1
- package/dist/src/routes/schedules.js +16 -29
- package/dist/src/routes/schedules.js.map +1 -1
- package/dist/src/services/agent-profile-write.d.ts +13 -1
- package/dist/src/services/agent-profile-write.d.ts.map +1 -1
- package/dist/src/services/agent-profile-write.js +55 -0
- package/dist/src/services/agent-profile-write.js.map +1 -1
- package/dist/src/services/build-app-prompt.d.ts.map +1 -1
- package/dist/src/services/build-app-prompt.js +22 -0
- package/dist/src/services/build-app-prompt.js.map +1 -1
- package/dist/src/services/email-templates.d.ts.map +1 -1
- package/dist/src/services/email-templates.js +79 -3
- package/dist/src/services/email-templates.js.map +1 -1
- package/dist/src/services/notification-templates.d.ts +5 -3
- package/dist/src/services/notification-templates.d.ts.map +1 -1
- package/dist/src/services/notification-templates.js +23 -3
- package/dist/src/services/notification-templates.js.map +1 -1
- package/dist/src/services/schedule-write.d.ts +25 -2
- package/dist/src/services/schedule-write.d.ts.map +1 -1
- package/dist/src/services/schedule-write.js +44 -0
- package/dist/src/services/schedule-write.js.map +1 -1
- package/dist/src/storage/providers/postgres-kysely/db-types.d.ts +2 -0
- package/dist/src/storage/providers/postgres-kysely/db-types.d.ts.map +1 -1
- package/dist/src/storage/providers/postgres-kysely/methods/identity.d.ts.map +1 -1
- package/dist/src/storage/providers/postgres-kysely/methods/identity.js +5 -1
- package/dist/src/storage/providers/postgres-kysely/methods/identity.js.map +1 -1
- package/dist/src/storage/providers/postgres-kysely/methods/sessions.d.ts +1 -0
- package/dist/src/storage/providers/postgres-kysely/methods/sessions.d.ts.map +1 -1
- package/dist/src/storage/providers/postgres-kysely/methods/sessions.js +4 -0
- package/dist/src/storage/providers/postgres-kysely/methods/sessions.js.map +1 -1
- package/dist/src/storage/providers/postgres-kysely/migrations/0034_agent_console_url.sql +16 -0
- package/dist/src/storage/providers/postgres-kysely/migrations/0035_agent_registered_by.sql +19 -0
- package/dist/src/storage/providers/sqlite/methods/extensions-notify.d.ts +2 -0
- package/dist/src/storage/providers/sqlite/methods/extensions-notify.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/methods/extensions-notify.js +4 -0
- package/dist/src/storage/providers/sqlite/methods/extensions-notify.js.map +1 -1
- package/dist/src/storage/providers/sqlite/methods/owner.d.ts +5 -0
- package/dist/src/storage/providers/sqlite/methods/owner.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/methods/owner.js +13 -4
- package/dist/src/storage/providers/sqlite/methods/owner.js.map +1 -1
- package/dist/src/storage/providers/sqlite/repos/auth.d.ts +2 -0
- package/dist/src/storage/providers/sqlite/repos/auth.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/repos/auth.js +4 -0
- package/dist/src/storage/providers/sqlite/repos/auth.js.map +1 -1
- package/dist/src/storage/providers/sqlite/schema-tables-1.d.ts +3 -0
- package/dist/src/storage/providers/sqlite/schema-tables-1.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/schema-tables-1.js +4 -2
- package/dist/src/storage/providers/sqlite/schema-tables-1.js.map +1 -1
- package/dist/src/storage/providers/sqlite/schema-tables-2.d.ts +3 -0
- package/dist/src/storage/providers/sqlite/schema-tables-2.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/schema-tables-2.js +4 -3
- package/dist/src/storage/providers/sqlite/schema-tables-2.js.map +1 -1
- package/dist/src/storage/providers/sqlite/schema-tables-3.d.ts +3 -0
- package/dist/src/storage/providers/sqlite/schema-tables-3.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/schema-tables-3.js +6 -9
- package/dist/src/storage/providers/sqlite/schema-tables-3.js.map +1 -1
- package/dist/src/storage/providers/sqlite/schema.d.ts +6 -0
- package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
- package/dist/src/storage/providers/sqlite/schema.js +9 -0
- package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
- package/dist/src/storage/repositories/session.repository.d.ts +14 -0
- package/dist/src/storage/repositories/session.repository.d.ts.map +1 -1
- package/dist/src/storage/repositories/session.repository.js +2 -0
- package/dist/src/storage/repositories/session.repository.js.map +1 -1
- package/dist/src/storage/storage-factory.d.ts +6 -0
- package/dist/src/storage/storage-factory.d.ts.map +1 -1
- package/dist/src/storage/storage-factory.js +38 -19
- package/dist/src/storage/storage-factory.js.map +1 -1
- package/dist/src/storage/types/identity.d.ts +31 -0
- package/dist/src/storage/types/identity.d.ts.map +1 -1
- package/dist/src/utils/gaii.d.ts +11 -0
- package/dist/src/utils/gaii.d.ts.map +1 -1
- package/dist/src/utils/gaii.js +16 -0
- package/dist/src/utils/gaii.js.map +1 -1
- package/dist/static/sdk-libs/ai/strings.js +5 -2
- package/dist/static/sdk-libs/auth/i18n.js +9 -4
- package/dist/static/sdk-libs/auth/modal.js +11 -3
- package/dist/static/sdk-libs/auth/pill-strings.js +52 -0
- package/dist/static/sdk-libs/auth/pill.js +16 -2
- package/dist/static/sdk-libs/dist/aimeat-ai.js +30 -1
- package/dist/static/sdk-libs/dist/aimeat-auth.js +61 -5
- package/dist/static/sdk-libs/dist/aimeat-game.js +32 -2
- package/dist/static/sdk-libs/dist/aimeat-header.js +9 -4
- package/dist/static/sdk-libs/game/i18n.js +38 -7
- package/dist/static/sdk-libs/header/index.js +11 -5
- package/package.json +4 -1
|
@@ -1,458 +1,459 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file email-tab.js
|
|
3
|
-
* @description Admin dashboard Email tab — SMTP config view, test-send, group
|
|
4
|
-
* broadcast, and per-locale email template editor with AI-prompt generation.
|
|
5
|
-
* @structure EmailTab (default), TemplateEditor, buildAiPrompt
|
|
6
|
-
* @usage Mounted by the admin dashboard tab router.
|
|
7
|
-
* @version-history
|
|
8
|
-
* v1.1.0 — 2026-06-02 — Admin design unification: raw textareas → adm-textarea
|
|
9
|
-
* adm-input-full; inline button color styles → adm-btn-success/adm-btn-danger.
|
|
10
|
-
*/
|
|
11
|
-
import { h } from 'preact';
|
|
12
|
-
import { useState, useEffect } from 'preact/hooks';
|
|
13
|
-
import htm from 'htm';
|
|
14
|
-
const html = htm.bind(h);
|
|
15
|
-
import { t } from '/js/i18n.js';
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
-
|
|
56
|
-
-
|
|
57
|
-
-
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
61
|
-
-
|
|
62
|
-
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const [
|
|
82
|
-
const [
|
|
83
|
-
const [
|
|
84
|
-
const [
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
<div style="font-size:.72rem;
|
|
139
|
-
<div
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
<button style=${tabStyle(view === '
|
|
152
|
-
<button style=${tabStyle(view === '
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
const [
|
|
204
|
-
const [
|
|
205
|
-
const [
|
|
206
|
-
const [
|
|
207
|
-
const [
|
|
208
|
-
const [
|
|
209
|
-
const [
|
|
210
|
-
const [
|
|
211
|
-
const [
|
|
212
|
-
const [
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const [
|
|
218
|
-
const [
|
|
219
|
-
const [
|
|
220
|
-
const [
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
await
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
await
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
<${EconRow} label=${t('dashboard.
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
<span
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
<span
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
<${EconRow} label=${t('dashboard.
|
|
349
|
-
<${EconRow} label=${t('dashboard.
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
<
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
<
|
|
377
|
-
<
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
<option value="
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
<
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
<
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @file email-tab.js
|
|
3
|
+
* @description Admin dashboard Email tab — SMTP config view, test-send, group
|
|
4
|
+
* broadcast, and per-locale email template editor with AI-prompt generation.
|
|
5
|
+
* @structure EmailTab (default), TemplateEditor, buildAiPrompt
|
|
6
|
+
* @usage Mounted by the admin dashboard tab router.
|
|
7
|
+
* @version-history
|
|
8
|
+
* v1.1.0 — 2026-06-02 — Admin design unification: raw textareas → adm-textarea
|
|
9
|
+
* adm-input-full; inline button color styles → adm-btn-success/adm-btn-danger.
|
|
10
|
+
*/
|
|
11
|
+
import { h } from 'preact';
|
|
12
|
+
import { useState, useEffect } from 'preact/hooks';
|
|
13
|
+
import htm from 'htm';
|
|
14
|
+
const html = htm.bind(h);
|
|
15
|
+
import { t } from '/js/i18n.js';
|
|
16
|
+
import { LOCALES } from '/js/utils.js';
|
|
17
|
+
import { EconRow, Empty, ExpandableHelp } from './shared.js';
|
|
18
|
+
import { useConfirm } from '/components/Modal.js';
|
|
19
|
+
import { CopyButton } from '/components/CopyButton.js';
|
|
20
|
+
import { sendTestEmail, getEmailTemplates, sendGroupEmail, saveEmailTemplate, resetEmailTemplate, seedEmailTemplates, resetAllEmailTemplates, saveConfig } from '/js/services/admin.js';
|
|
21
|
+
import { swallowed } from '/js/swallowed.js';
|
|
22
|
+
|
|
23
|
+
const TEMPLATE_IDS = ['notification', 'verification', 'magic_link', 'match_suggestion'];
|
|
24
|
+
const TEMPLATE_LABELS = {
|
|
25
|
+
verification: 'dashboard.emailTplVerification',
|
|
26
|
+
magic_link: 'dashboard.emailTplMagicLink',
|
|
27
|
+
notification: 'dashboard.emailTplNotification',
|
|
28
|
+
match_suggestion: 'dashboard.emailTplMatchSuggestion',
|
|
29
|
+
};
|
|
30
|
+
const USED_IN_LABELS = {
|
|
31
|
+
ghii: 'dashboard.emailUsedInGhii',
|
|
32
|
+
system: 'dashboard.emailUsedInSystem',
|
|
33
|
+
matching: 'dashboard.emailUsedInMatching',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/* ── AI Prompt builder ── */
|
|
37
|
+
function buildAiPrompt(tpl, locale) {
|
|
38
|
+
const langName = locale === 'fi' ? 'Finnish (suomi)' : 'English';
|
|
39
|
+
const paramList = (tpl.params || []).map(p =>
|
|
40
|
+
` ${p} — ${(tpl.paramDescriptions || {})[p] || 'system parameter'}`
|
|
41
|
+
).join('\n');
|
|
42
|
+
|
|
43
|
+
return `I need you to create a beautiful, professional HTML email template for the AIMEAT Protocol.
|
|
44
|
+
|
|
45
|
+
Template type: ${t(TEMPLATE_LABELS[tpl.id] || tpl.id)}
|
|
46
|
+
Purpose: ${t(USED_IN_LABELS[tpl.usedIn] || tpl.usedIn)}
|
|
47
|
+
Language: ${langName} — ALL user-facing text in the template must be written in ${langName}.
|
|
48
|
+
|
|
49
|
+
IMPORTANT — Parameter tags that MUST be preserved exactly as-is in the output:
|
|
50
|
+
${paramList}
|
|
51
|
+
|
|
52
|
+
These tags are replaced by the system with real data when the email is sent. You must include them in the appropriate places in both HTML and plain text versions. Do NOT translate or modify these tags.
|
|
53
|
+
|
|
54
|
+
Requirements:
|
|
55
|
+
- Modern, clean design with good typography
|
|
56
|
+
- Mobile-responsive (max-width container, fluid layout)
|
|
57
|
+
- Inline CSS only (email clients don't support external stylesheets)
|
|
58
|
+
- Professional color scheme (consider using indigo/purple #4f46e5 as accent)
|
|
59
|
+
- AIMEAT branding in header
|
|
60
|
+
- Clear visual hierarchy
|
|
61
|
+
- Footer with "Sent by AIMEAT Protocol" and unsubscribe note (in ${langName})
|
|
62
|
+
- Both HTML version and plain text fallback version
|
|
63
|
+
- All text content must be in ${langName}
|
|
64
|
+
|
|
65
|
+
Here is the current default template for reference:
|
|
66
|
+
---HTML---
|
|
67
|
+
${tpl.defaultHtml || tpl.preview || ''}
|
|
68
|
+
---TEXT---
|
|
69
|
+
${tpl.defaultText || tpl.text || ''}
|
|
70
|
+
---END---
|
|
71
|
+
|
|
72
|
+
Please provide:
|
|
73
|
+
1. The complete HTML email template (in ${langName})
|
|
74
|
+
2. The plain text version (in ${langName})
|
|
75
|
+
|
|
76
|
+
Make it visually stunning while keeping parameter tags intact.`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* ── Single Template Editor ── */
|
|
80
|
+
function TemplateEditor({ tpl, locale, onSave, onReset }) {
|
|
81
|
+
const [view, setView] = useState('preview'); // preview | html | text
|
|
82
|
+
const [editHtml, setEditHtml] = useState(tpl.preview || '');
|
|
83
|
+
const [editText, setEditText] = useState(tpl.text || '');
|
|
84
|
+
const [saving, setSaving] = useState(false);
|
|
85
|
+
const [msg, setMsg] = useState(null);
|
|
86
|
+
const { confirm, ConfirmUI } = useConfirm();
|
|
87
|
+
|
|
88
|
+
// Sync when tpl data or locale changes
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
setEditHtml(tpl.preview || '');
|
|
91
|
+
setEditText(tpl.text || '');
|
|
92
|
+
setMsg(null);
|
|
93
|
+
}, [tpl.id, tpl.preview, tpl.text, locale]);
|
|
94
|
+
|
|
95
|
+
async function doSave() {
|
|
96
|
+
setSaving(true);
|
|
97
|
+
setMsg(null);
|
|
98
|
+
try {
|
|
99
|
+
await onSave(tpl.id, locale, editHtml, editText);
|
|
100
|
+
setMsg({ ok: true, text: t('dashboard.emailTplSaved') });
|
|
101
|
+
} catch (e) {
|
|
102
|
+
setMsg({ ok: false, text: e.message });
|
|
103
|
+
}
|
|
104
|
+
setSaving(false);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function doReset() {
|
|
108
|
+
confirm(t('dashboard.emailTplResetConfirm'), async () => {
|
|
109
|
+
setSaving(true);
|
|
110
|
+
setMsg(null);
|
|
111
|
+
try {
|
|
112
|
+
await onReset(tpl.id, locale);
|
|
113
|
+
setEditHtml(tpl.defaultHtml || '');
|
|
114
|
+
setEditText(tpl.defaultText || '');
|
|
115
|
+
setMsg({ ok: true, text: t('dashboard.emailTplResetDone') });
|
|
116
|
+
} catch (e) {
|
|
117
|
+
setMsg({ ok: false, text: e.message });
|
|
118
|
+
}
|
|
119
|
+
setSaving(false);
|
|
120
|
+
}, { danger: true });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const hasChanges = editHtml !== (tpl.preview || '') || editText !== (tpl.text || '');
|
|
124
|
+
|
|
125
|
+
const tabStyle = (active) => `
|
|
126
|
+
padding:6px 14px;font-size:.78rem;border:none;border-radius:4px 4px 0 0;cursor:pointer;
|
|
127
|
+
background:${active ? 'rgba(79,70,229,0.15)' : 'transparent'};
|
|
128
|
+
color:${active ? '#818cf8' : 'var(--text-dim)'};
|
|
129
|
+
font-weight:${active ? '600' : '400'};
|
|
130
|
+
border-bottom:2px solid ${active ? '#818cf8' : 'transparent'};
|
|
131
|
+
`;
|
|
132
|
+
|
|
133
|
+
return html`
|
|
134
|
+
<div style="border-top:1px solid var(--glass-border);padding:12px 0 0">
|
|
135
|
+
<!-- Parameters reference -->
|
|
136
|
+
${tpl.params && tpl.params.length > 0 && html`
|
|
137
|
+
<div style="margin-bottom:10px;padding:8px 10px;border-radius:6px;background:rgba(245,158,11,0.06);border:1px solid rgba(245,158,11,0.15)">
|
|
138
|
+
<div style="font-size:.72rem;font-weight:600;color:#f59e0b;margin-bottom:4px">${t('dashboard.emailTplParams')}</div>
|
|
139
|
+
<div style="font-size:.72rem;color:var(--text-dim);margin-bottom:6px">${t('dashboard.emailTplParamsExplain')}</div>
|
|
140
|
+
<div class="adm-flex-wrap" style="gap:6px">
|
|
141
|
+
${tpl.params.map(p => html`
|
|
142
|
+
<span style="font-size:.72rem;background:rgba(0,0,0,0.2);color:#f59e0b;padding:2px 8px;border-radius:4px;font-family:monospace"
|
|
143
|
+
title=${(tpl.paramDescriptions || {})[p] || ''}>${p}</span>
|
|
144
|
+
`)}
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
`}
|
|
148
|
+
|
|
149
|
+
<!-- Sub-tabs -->
|
|
150
|
+
<div style="display:flex;gap:2px;border-bottom:1px solid var(--glass-border)">
|
|
151
|
+
<button style=${tabStyle(view === 'preview')} onClick=${() => setView('preview')}>${t('dashboard.emailTplPreview')}</button>
|
|
152
|
+
<button style=${tabStyle(view === 'html')} onClick=${() => setView('html')}>${t('dashboard.emailTplEditHtml')}</button>
|
|
153
|
+
<button style=${tabStyle(view === 'text')} onClick=${() => setView('text')}>${t('dashboard.emailTplEditText')}</button>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<!-- Content area -->
|
|
157
|
+
${view === 'preview' && html`
|
|
158
|
+
<iframe
|
|
159
|
+
srcdoc=${editHtml}
|
|
160
|
+
style="width:100%;height:420px;border:1px solid var(--glass-border);border-top:none;border-radius:0 0 6px 6px;background:#f4f4f7"
|
|
161
|
+
sandbox="allow-same-origin allow-scripts"
|
|
162
|
+
/>
|
|
163
|
+
`}
|
|
164
|
+
${view === 'html' && html`
|
|
165
|
+
<textarea
|
|
166
|
+
class="adm-textarea adm-input-full"
|
|
167
|
+
value=${editHtml}
|
|
168
|
+
onInput=${e => setEditHtml(e.target.value)}
|
|
169
|
+
style="height:420px;font-size:12px;border-top:none;border-radius:0 0 6px 6px"
|
|
170
|
+
spellcheck="false"
|
|
171
|
+
/>
|
|
172
|
+
`}
|
|
173
|
+
${view === 'text' && html`
|
|
174
|
+
<textarea
|
|
175
|
+
class="adm-textarea adm-input-full"
|
|
176
|
+
value=${editText}
|
|
177
|
+
onInput=${e => setEditText(e.target.value)}
|
|
178
|
+
style="height:300px;font-size:13px;border-top:none;border-radius:0 0 6px 6px"
|
|
179
|
+
spellcheck="false"
|
|
180
|
+
/>
|
|
181
|
+
`}
|
|
182
|
+
|
|
183
|
+
<!-- Action buttons -->
|
|
184
|
+
<div class="adm-flex-center" style="flex-wrap:wrap;margin-top:10px">
|
|
185
|
+
<button class="adm-btn-action adm-text-sm" onClick=${doSave} disabled=${saving || !hasChanges}>
|
|
186
|
+
${saving ? '...' : t('dashboard.emailTplSave')}</button>
|
|
187
|
+
<${CopyButton} text=${buildAiPrompt(tpl, locale)} className="adm-btn-action adm-text-sm"
|
|
188
|
+
label=${t('dashboard.emailTplAiPrompt')} copiedLabel=${t('dashboard.emailTplAiPrompt')}
|
|
189
|
+
onCopied=${() => setMsg({ ok: true, text: t('dashboard.emailTplAiPromptCopied') })} />
|
|
190
|
+
${tpl.isCustom && html`
|
|
191
|
+
<button class="adm-btn-action adm-btn-danger adm-text-sm" onClick=${doReset} disabled=${saving}>${t('dashboard.emailTplReset')}</button>
|
|
192
|
+
`}
|
|
193
|
+
${msg && html`<span class="adm-text-sm" style="color:${msg.ok ? '#22c55e' : '#ef4444'}">${msg.text}</span>`}
|
|
194
|
+
</div>
|
|
195
|
+
<${ConfirmUI} />
|
|
196
|
+
</div>
|
|
197
|
+
`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* ── Main Tab ── */
|
|
201
|
+
export default function EmailTab({ data, reload, locale }) {
|
|
202
|
+
const email = data.email;
|
|
203
|
+
const [to, setTo] = useState('');
|
|
204
|
+
const [testTpl, setTestTpl] = useState('notification');
|
|
205
|
+
const [sending, setSending] = useState(false);
|
|
206
|
+
const [result, setResult] = useState(null);
|
|
207
|
+
const [tplLocale, setTplLocale] = useState(locale || 'en');
|
|
208
|
+
const [templates, setTemplates] = useState(null);
|
|
209
|
+
const [tplLoading, setTplLoading] = useState(false);
|
|
210
|
+
const [expanded, setExpanded] = useState({});
|
|
211
|
+
const [seeded, setSeeded] = useState(false);
|
|
212
|
+
const [seedMsg, setSeedMsg] = useState(null);
|
|
213
|
+
const [cfgSaving, setCfgSaving] = useState(false);
|
|
214
|
+
const { confirm, ConfirmUI } = useConfirm();
|
|
215
|
+
|
|
216
|
+
// Group send state
|
|
217
|
+
const [grpGroup, setGrpGroup] = useState('operators');
|
|
218
|
+
const [grpSubject, setGrpSubject] = useState('');
|
|
219
|
+
const [grpBody, setGrpBody] = useState('');
|
|
220
|
+
const [grpSending, setGrpSending] = useState(false);
|
|
221
|
+
const [grpResult, setGrpResult] = useState(null);
|
|
222
|
+
|
|
223
|
+
useEffect(() => {
|
|
224
|
+
if (email && email.enabled) loadTemplates(tplLocale);
|
|
225
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- Reload templates when the locale or enabled-state changes; email is the whole config object (new identity per parent render) so email?.enabled is the stable signal; keying on `email` would refetch every render.
|
|
226
|
+
}, [tplLocale, email?.enabled]);
|
|
227
|
+
|
|
228
|
+
async function loadTemplates(loc) {
|
|
229
|
+
setTplLoading(true);
|
|
230
|
+
try {
|
|
231
|
+
const r = await getEmailTemplates(loc);
|
|
232
|
+
setTemplates(r.data.templates || []);
|
|
233
|
+
setSeeded(!!r.data.seeded);
|
|
234
|
+
} catch (err) {
|
|
235
|
+
swallowed('email-tab: loadTemplates', err);
|
|
236
|
+
setTemplates(null);
|
|
237
|
+
}
|
|
238
|
+
setTplLoading(false);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function toggleSmtpConfig(dotPath, newValue) {
|
|
242
|
+
setCfgSaving(true);
|
|
243
|
+
try {
|
|
244
|
+
await saveConfig([{ path: dotPath, value: newValue }]);
|
|
245
|
+
reload();
|
|
246
|
+
} catch (err) { swallowed('email-tab: toggleSmtpConfig', err); }
|
|
247
|
+
setCfgSaving(false);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (!email) return html`<${Empty} text=${t('dashboard.emailNotAvailable')} />`;
|
|
251
|
+
|
|
252
|
+
if (!email.enabled) {
|
|
253
|
+
return html`
|
|
254
|
+
<p class="adm-text-dim adm-text-base adm-mb-md">${t('dashboard.emailDisabledExplain')}</p>
|
|
255
|
+
<${ExpandableHelp} title=${t('dashboard.emailSmtpHelp')}>
|
|
256
|
+
<p>${t('dashboard.emailSmtpHelpDetail')}</p>
|
|
257
|
+
</${ExpandableHelp}>
|
|
258
|
+
`;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async function doSend() {
|
|
262
|
+
if (!to) return;
|
|
263
|
+
setSending(true);
|
|
264
|
+
setResult(null);
|
|
265
|
+
try {
|
|
266
|
+
const r = await sendTestEmail(to, testTpl, tplLocale);
|
|
267
|
+
if (r.data?.sent) {
|
|
268
|
+
setResult({ ok: true, msg: t('dashboard.emailSent') });
|
|
269
|
+
} else {
|
|
270
|
+
setResult({ ok: false, msg: t('dashboard.emailSendFailed') });
|
|
271
|
+
}
|
|
272
|
+
} catch (e) {
|
|
273
|
+
setResult({ ok: false, msg: e.message });
|
|
274
|
+
}
|
|
275
|
+
setSending(false);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async function doGroupSend() {
|
|
279
|
+
if (!grpSubject || !grpBody) return;
|
|
280
|
+
setGrpSending(true);
|
|
281
|
+
setGrpResult(null);
|
|
282
|
+
try {
|
|
283
|
+
const r = await sendGroupEmail(grpGroup, grpSubject, grpBody);
|
|
284
|
+
setGrpResult({ ok: true, msg: t('dashboard.emailGroupSent').replace('{sent}', r.data.sent).replace('{total}', r.data.total) });
|
|
285
|
+
} catch (e) {
|
|
286
|
+
setGrpResult({ ok: false, msg: e.message });
|
|
287
|
+
}
|
|
288
|
+
setGrpSending(false);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function toggle(id) {
|
|
292
|
+
setExpanded(prev => ({ ...prev, [id]: !prev[id] }));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async function handleSaveTemplate(id, loc, htmlContent, textContent) {
|
|
296
|
+
await saveEmailTemplate(id, loc, htmlContent, textContent);
|
|
297
|
+
await loadTemplates(loc);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async function handleResetTemplate(id, loc) {
|
|
301
|
+
await resetEmailTemplate(id, loc);
|
|
302
|
+
await loadTemplates(loc);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async function doSeedDefaults() {
|
|
306
|
+
setSeedMsg(null);
|
|
307
|
+
try {
|
|
308
|
+
const r = await seedEmailTemplates();
|
|
309
|
+
setSeedMsg({ ok: true, text: t('dashboard.emailTplSeeded').replace('{count}', r.data.count) });
|
|
310
|
+
await loadTemplates(tplLocale);
|
|
311
|
+
} catch (e) {
|
|
312
|
+
setSeedMsg({ ok: false, text: e.message });
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function doResetAll() {
|
|
317
|
+
confirm(t('dashboard.emailTplResetAllConfirm'), async () => {
|
|
318
|
+
setSeedMsg(null);
|
|
319
|
+
try {
|
|
320
|
+
const r = await resetAllEmailTemplates();
|
|
321
|
+
setSeedMsg({ ok: true, text: t('dashboard.emailTplResetAllDone').replace('{count}', r.data.count) });
|
|
322
|
+
await loadTemplates(tplLocale);
|
|
323
|
+
} catch (e) {
|
|
324
|
+
setSeedMsg({ ok: false, text: e.message });
|
|
325
|
+
}
|
|
326
|
+
}, { danger: true });
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return html`
|
|
330
|
+
<p class="adm-text-dim adm-text-base adm-mb-md">${t('dashboard.emailExplain')}</p>
|
|
331
|
+
|
|
332
|
+
<!-- SMTP Configuration -->
|
|
333
|
+
<div class="adm-card">
|
|
334
|
+
<h4 class="adm-mb-md" style="margin:0">${t('dashboard.smtpConfig')}</h4>
|
|
335
|
+
<${EconRow} label=${t('dashboard.host')} value=${email.smtp_host || '\u2014'} />
|
|
336
|
+
<${EconRow} label=${t('dashboard.port')} value=${email.smtp_port || '\u2014'} />
|
|
337
|
+
<div class="adm-hrow">
|
|
338
|
+
<span class="adm-hmetric">${t('dashboard.secure')}</span>
|
|
339
|
+
<span><label style="cursor:pointer"><input type="checkbox" checked=${email.smtp_secure} disabled=${cfgSaving}
|
|
340
|
+
onChange=${ev => toggleSmtpConfig('email.smtp_secure', ev.target.checked)} /> ${email.smtp_secure ? '\u2705' : '\u274C'}</label></span>
|
|
341
|
+
</div>
|
|
342
|
+
<div class="adm-hrow">
|
|
343
|
+
<span class="adm-hmetric">${t('dashboard.rejectUnauthorized')}</span>
|
|
344
|
+
<span><label style="cursor:pointer"><input type="checkbox" checked=${email.smtp_reject_unauthorized} disabled=${cfgSaving}
|
|
345
|
+
onChange=${ev => toggleSmtpConfig('email.smtp_reject_unauthorized', ev.target.checked)} /> ${email.smtp_reject_unauthorized ? '\u2705' : '\u274C'}
|
|
346
|
+
<span style="font-size:.72rem;color:var(--text-dim);margin-left:6px">${t('dashboard.rejectUnauthorizedHelp')}</span></label></span>
|
|
347
|
+
</div>
|
|
348
|
+
<${EconRow} label=${t('dashboard.from')} value=${email.smtp_from || '\u2014'} />
|
|
349
|
+
<${EconRow} label=${t('dashboard.emailUserConfigured')} value=${email.smtp_user_configured ? '\u2705' : '\u274C'} />
|
|
350
|
+
<${EconRow} label=${t('dashboard.emailPassConfigured')} value=${email.smtp_pass_configured ? '\u2705' : '\u274C'} />
|
|
351
|
+
</div>
|
|
352
|
+
|
|
353
|
+
<!-- Test Email -->
|
|
354
|
+
<div class="adm-card adm-mt-md">
|
|
355
|
+
<h4 class="adm-mb-md" style="margin:0">${t('dashboard.testEmail')}</h4>
|
|
356
|
+
<div class="adm-flex-center" style="flex-wrap:wrap">
|
|
357
|
+
<input type="email" class="adm-input" value=${to}
|
|
358
|
+
onInput=${e => setTo(e.target.value)}
|
|
359
|
+
placeholder=${t('dashboard.emailPlaceholder')}
|
|
360
|
+
style="flex:1;min-width:200px" />
|
|
361
|
+
<select class="adm-input" value=${testTpl} onChange=${e => setTestTpl(e.target.value)}
|
|
362
|
+
style="width:auto;min-width:140px">
|
|
363
|
+
${TEMPLATE_IDS.map(id => html`
|
|
364
|
+
<option value=${id}>${t(TEMPLATE_LABELS[id])}</option>
|
|
365
|
+
`)}
|
|
366
|
+
</select>
|
|
367
|
+
<button class="adm-btn" onClick=${doSend} disabled=${sending || !to}>
|
|
368
|
+
${sending ? '...' : t('dashboard.send')}
|
|
369
|
+
</button>
|
|
370
|
+
</div>
|
|
371
|
+
${result && html`<div class="adm-mt-sm adm-text-base" style="color:${result.ok ? '#22c55e' : '#ef4444'}">${result.msg}</div>`}
|
|
372
|
+
</div>
|
|
373
|
+
|
|
374
|
+
<!-- Send to Group -->
|
|
375
|
+
<div class="adm-card adm-mt-md">
|
|
376
|
+
<h4 class="adm-mb-md" style="margin:0">${t('dashboard.emailGroupTitle')}</h4>
|
|
377
|
+
<p class="adm-text-dim adm-text-base adm-mb-md" style="margin:0">${t('dashboard.emailGroupExplain')}</p>
|
|
378
|
+
<div class="adm-flex-wrap adm-mb-sm">
|
|
379
|
+
<select class="adm-input" value=${grpGroup} onChange=${e => setGrpGroup(e.target.value)}
|
|
380
|
+
style="width:auto;min-width:140px">
|
|
381
|
+
<option value="operators">${t('dashboard.emailGroupOperators')}</option>
|
|
382
|
+
<option value="all">${t('dashboard.emailGroupAll')}</option>
|
|
383
|
+
</select>
|
|
384
|
+
</div>
|
|
385
|
+
<input type="text" class="adm-input adm-input-full adm-mb-sm" value=${grpSubject}
|
|
386
|
+
onInput=${e => setGrpSubject(e.target.value)}
|
|
387
|
+
placeholder=${t('dashboard.emailGroupSubject')} />
|
|
388
|
+
<textarea class="adm-textarea adm-input-full" value=${grpBody}
|
|
389
|
+
onInput=${e => setGrpBody(e.target.value)}
|
|
390
|
+
placeholder=${t('dashboard.emailGroupBody')}
|
|
391
|
+
rows="4" />
|
|
392
|
+
<div class="adm-mt-sm">
|
|
393
|
+
<button class="adm-btn" onClick=${doGroupSend} disabled=${grpSending || !grpSubject || !grpBody}>
|
|
394
|
+
${grpSending ? '...' : t('dashboard.emailGroupSend')}
|
|
395
|
+
</button>
|
|
396
|
+
</div>
|
|
397
|
+
${grpResult && html`<div class="adm-mt-sm adm-text-base" style="color:${grpResult.ok ? '#22c55e' : '#ef4444'}">${grpResult.msg}</div>`}
|
|
398
|
+
</div>
|
|
399
|
+
|
|
400
|
+
<!-- Email Templates -->
|
|
401
|
+
<div class="adm-card adm-mt-md">
|
|
402
|
+
<div class="adm-flex-between adm-mb-md">
|
|
403
|
+
<h4 style="margin:0">${t('dashboard.emailTemplatesTitle')}</h4>
|
|
404
|
+
<div style="display:flex;gap:4px">
|
|
405
|
+
${LOCALES.map(l => html`
|
|
406
|
+
<button class=${tplLocale === l ? 'adm-btn' : 'adm-btn-action'} style="padding:4px 10px;font-size:.75rem"
|
|
407
|
+
onClick=${() => setTplLocale(l)}>${l.toUpperCase()}</button>
|
|
408
|
+
`)}
|
|
409
|
+
</div>
|
|
410
|
+
</div>
|
|
411
|
+
<p class="adm-text-dim adm-text-base adm-mb-md" style="margin:0">${t('dashboard.emailTemplatesExplain')}</p>
|
|
412
|
+
|
|
413
|
+
<!-- Seed / Reset All buttons -->
|
|
414
|
+
<div class="adm-flex-center" style="flex-wrap:wrap;margin-bottom:14px;padding:10px 12px;border-radius:6px;background:rgba(255,255,255,0.02);border:1px solid var(--glass-border)">
|
|
415
|
+
${!seeded && html`
|
|
416
|
+
<button class="adm-btn-action adm-btn-success adm-text-sm"
|
|
417
|
+
onClick=${doSeedDefaults}>${t('dashboard.emailTplSeedDefaults')}</button>
|
|
418
|
+
<span style="font-size:.78rem" class="adm-text-dim">${t('dashboard.emailTplSeedExplain')}</span>
|
|
419
|
+
`}
|
|
420
|
+
${seeded && html`
|
|
421
|
+
<button class="adm-btn-action adm-btn-danger adm-text-sm"
|
|
422
|
+
onClick=${doResetAll}>${t('dashboard.emailTplResetAll')}</button>
|
|
423
|
+
<button class="adm-btn-action adm-text-sm"
|
|
424
|
+
onClick=${doSeedDefaults}>${t('dashboard.emailTplReseed')}</button>
|
|
425
|
+
<span style="font-size:.78rem" class="adm-text-dim">${t('dashboard.emailTplSeededStatus')}</span>
|
|
426
|
+
`}
|
|
427
|
+
${seedMsg && html`<span class="adm-text-sm" style="color:${seedMsg.ok ? '#22c55e' : '#ef4444'}">${seedMsg.text}</span>`}
|
|
428
|
+
</div>
|
|
429
|
+
|
|
430
|
+
${tplLoading ? html`<p class="adm-text-dim adm-text-base">...</p>` : null}
|
|
431
|
+
|
|
432
|
+
${templates && templates.map(tpl => html`
|
|
433
|
+
<div style="border:1px solid ${expanded[tpl.id] ? '#818cf8' : 'var(--glass-border)'};border-radius:8px;margin-bottom:10px;overflow:hidden;transition:border-color .2s ease">
|
|
434
|
+
<div class="adm-flex-between" style="padding:10px 14px;cursor:pointer;background:${expanded[tpl.id] ? 'rgba(79,70,229,0.04)' : 'rgba(255,255,255,.03)'}"
|
|
435
|
+
onClick=${() => toggle(tpl.id)}>
|
|
436
|
+
<span class="adm-flex-center">
|
|
437
|
+
<strong>${t(TEMPLATE_LABELS[tpl.id] || tpl.id)}</strong>
|
|
438
|
+
<span class="adm-text-dim" style="font-size:.72rem">${t(USED_IN_LABELS[tpl.usedIn] || tpl.usedIn)}</span>
|
|
439
|
+
${tpl.isCustom && html`<span style="font-size:.65rem;background:rgba(79,70,229,0.15);color:#818cf8;padding:1px 6px;border-radius:3px;font-weight:600">${t('dashboard.emailTplCustomBadge')}</span>`}
|
|
440
|
+
</span>
|
|
441
|
+
<span class="adm-text-dim" style="font-size:.75rem">${expanded[tpl.id] ? '\u25B2' : '\u25BC'}</span>
|
|
442
|
+
</div>
|
|
443
|
+
${expanded[tpl.id] && html`
|
|
444
|
+
<div style="padding:0 14px 14px">
|
|
445
|
+
<${TemplateEditor}
|
|
446
|
+
key=${`${tpl.id}-${tplLocale}`}
|
|
447
|
+
tpl=${tpl}
|
|
448
|
+
locale=${tplLocale}
|
|
449
|
+
onSave=${handleSaveTemplate}
|
|
450
|
+
onReset=${handleResetTemplate}
|
|
451
|
+
/>
|
|
452
|
+
</div>
|
|
453
|
+
`}
|
|
454
|
+
</div>
|
|
455
|
+
`)}
|
|
456
|
+
</div>
|
|
457
|
+
<${ConfirmUI} />
|
|
458
|
+
`;
|
|
459
|
+
}
|