noho-platform 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/.noho_session.json +6 -0
- package/login.js +49 -0
- package/noho-cli.js +123 -0
- package/noho-dashboard.html +891 -0
- package/noho-lib.js +431 -0
- package/noho-server.js +441 -0
- package/noho_data/pages/3f550d44-2383-45cd-bd12-96d5c16f7fb7_1429be93890e6120.json +21 -0
- package/noho_data/users/3444499b-de5a-4931-b802-e02a054ef0c1.json +20 -0
- package/noho_data/users/3f550d44-2383-45cd-bd12-96d5c16f7fb7.json +22 -0
- package/noho_data/users/d4555ad8-9324-4ad3-a485-c234f17c2708.json +20 -0
- package/package.json +22 -0
- package/test-lib.js +56 -0
- package/test_data/pages/a019aa48-0caf-478f-927e-266c812eae10_10022dfa5d34936d.json +24 -0
- package/test_data/users/a019aa48-0caf-478f-927e-266c812eae10.json +22 -0
|
@@ -0,0 +1,891 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="ar" dir="rtl">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>NOHO - Dashboard</title>
|
|
7
|
+
<style>
|
|
8
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
9
|
+
|
|
10
|
+
:root {
|
|
11
|
+
--primary: #6366f1;
|
|
12
|
+
--primary-dark: #4f46e5;
|
|
13
|
+
--bg: #0f172a;
|
|
14
|
+
--surface: #1e293b;
|
|
15
|
+
--text: #f8fafc;
|
|
16
|
+
--text-secondary: #94a3b8;
|
|
17
|
+
--border: #334155;
|
|
18
|
+
--success: #10b981;
|
|
19
|
+
--error: #ef4444;
|
|
20
|
+
--warning: #f59e0b;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
body {
|
|
24
|
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
25
|
+
background: var(--bg);
|
|
26
|
+
color: var(--text);
|
|
27
|
+
line-height: 1.6;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.container {
|
|
31
|
+
max-width: 1200px;
|
|
32
|
+
margin: 0 auto;
|
|
33
|
+
padding: 20px;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
header {
|
|
37
|
+
background: var(--surface);
|
|
38
|
+
border-bottom: 1px solid var(--border);
|
|
39
|
+
padding: 20px 0;
|
|
40
|
+
margin-bottom: 30px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.header-content {
|
|
44
|
+
display: flex;
|
|
45
|
+
justify-content: space-between;
|
|
46
|
+
align-items: center;
|
|
47
|
+
max-width: 1200px;
|
|
48
|
+
margin: 0 auto;
|
|
49
|
+
padding: 0 20px;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.logo {
|
|
53
|
+
font-size: 28px;
|
|
54
|
+
font-weight: bold;
|
|
55
|
+
background: linear-gradient(135deg, var(--primary), #a855f7);
|
|
56
|
+
-webkit-background-clip: text;
|
|
57
|
+
-webkit-text-fill-color: transparent;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.auth-section {
|
|
61
|
+
display: flex;
|
|
62
|
+
gap: 10px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
button {
|
|
66
|
+
background: var(--primary);
|
|
67
|
+
color: white;
|
|
68
|
+
border: none;
|
|
69
|
+
padding: 10px 20px;
|
|
70
|
+
border-radius: 8px;
|
|
71
|
+
cursor: pointer;
|
|
72
|
+
font-size: 14px;
|
|
73
|
+
transition: all 0.3s;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
button:hover {
|
|
77
|
+
background: var(--primary-dark);
|
|
78
|
+
transform: translateY(-2px);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
button.secondary {
|
|
82
|
+
background: transparent;
|
|
83
|
+
border: 1px solid var(--border);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
button.danger {
|
|
87
|
+
background: var(--error);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.grid {
|
|
91
|
+
display: grid;
|
|
92
|
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
93
|
+
gap: 20px;
|
|
94
|
+
margin-bottom: 30px;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.card {
|
|
98
|
+
background: var(--surface);
|
|
99
|
+
border: 1px solid var(--border);
|
|
100
|
+
border-radius: 12px;
|
|
101
|
+
padding: 24px;
|
|
102
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.card h2 {
|
|
106
|
+
margin-bottom: 16px;
|
|
107
|
+
color: var(--text);
|
|
108
|
+
font-size: 20px;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.form-group {
|
|
112
|
+
margin-bottom: 16px;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
label {
|
|
116
|
+
display: block;
|
|
117
|
+
margin-bottom: 8px;
|
|
118
|
+
color: var(--text-secondary);
|
|
119
|
+
font-size: 14px;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
input, textarea, select {
|
|
123
|
+
width: 100%;
|
|
124
|
+
padding: 12px;
|
|
125
|
+
background: var(--bg);
|
|
126
|
+
border: 1px solid var(--border);
|
|
127
|
+
border-radius: 8px;
|
|
128
|
+
color: var(--text);
|
|
129
|
+
font-size: 14px;
|
|
130
|
+
font-family: inherit;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
textarea {
|
|
134
|
+
min-height: 200px;
|
|
135
|
+
font-family: 'Courier New', monospace;
|
|
136
|
+
direction: ltr;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.stats {
|
|
140
|
+
display: grid;
|
|
141
|
+
grid-template-columns: repeat(3, 1fr);
|
|
142
|
+
gap: 15px;
|
|
143
|
+
margin-bottom: 20px;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.stat-box {
|
|
147
|
+
background: var(--bg);
|
|
148
|
+
padding: 20px;
|
|
149
|
+
border-radius: 8px;
|
|
150
|
+
text-align: center;
|
|
151
|
+
border: 1px solid var(--border);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.stat-value {
|
|
155
|
+
font-size: 32px;
|
|
156
|
+
font-weight: bold;
|
|
157
|
+
color: var(--primary);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.stat-label {
|
|
161
|
+
color: var(--text-secondary);
|
|
162
|
+
font-size: 14px;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.api-key-display {
|
|
166
|
+
background: var(--bg);
|
|
167
|
+
padding: 16px;
|
|
168
|
+
border-radius: 8px;
|
|
169
|
+
border: 1px solid var(--border);
|
|
170
|
+
font-family: monospace;
|
|
171
|
+
display: flex;
|
|
172
|
+
justify-content: space-between;
|
|
173
|
+
align-items: center;
|
|
174
|
+
margin-bottom: 16px;
|
|
175
|
+
word-break: break-all;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.modal {
|
|
179
|
+
display: none;
|
|
180
|
+
position: fixed;
|
|
181
|
+
top: 0;
|
|
182
|
+
left: 0;
|
|
183
|
+
width: 100%;
|
|
184
|
+
height: 100%;
|
|
185
|
+
background: rgba(0,0,0,0.8);
|
|
186
|
+
z-index: 1000;
|
|
187
|
+
justify-content: center;
|
|
188
|
+
align-items: center;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.modal.active {
|
|
192
|
+
display: flex;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.modal-content {
|
|
196
|
+
background: var(--surface);
|
|
197
|
+
padding: 30px;
|
|
198
|
+
border-radius: 16px;
|
|
199
|
+
max-width: 500px;
|
|
200
|
+
width: 90%;
|
|
201
|
+
max-height: 90vh;
|
|
202
|
+
overflow-y: auto;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.pages-list {
|
|
206
|
+
list-style: none;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.page-item {
|
|
210
|
+
background: var(--bg);
|
|
211
|
+
border: 1px solid var(--border);
|
|
212
|
+
border-radius: 8px;
|
|
213
|
+
padding: 16px;
|
|
214
|
+
margin-bottom: 12px;
|
|
215
|
+
display: flex;
|
|
216
|
+
justify-content: space-between;
|
|
217
|
+
align-items: center;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.page-info h3 {
|
|
221
|
+
margin-bottom: 4px;
|
|
222
|
+
font-size: 16px;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.page-meta {
|
|
226
|
+
font-size: 12px;
|
|
227
|
+
color: var(--text-secondary);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.badge {
|
|
231
|
+
display: inline-block;
|
|
232
|
+
padding: 4px 8px;
|
|
233
|
+
border-radius: 4px;
|
|
234
|
+
font-size: 12px;
|
|
235
|
+
background: var(--primary);
|
|
236
|
+
color: white;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.badge.warning {
|
|
240
|
+
background: var(--warning);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.analysis-result {
|
|
244
|
+
background: var(--bg);
|
|
245
|
+
border-radius: 8px;
|
|
246
|
+
padding: 16px;
|
|
247
|
+
margin-top: 16px;
|
|
248
|
+
border-right: 4px solid var(--success);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.analysis-result.has-warnings {
|
|
252
|
+
border-right-color: var(--warning);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.warning-item {
|
|
256
|
+
color: var(--warning);
|
|
257
|
+
margin: 4px 0;
|
|
258
|
+
font-size: 14px;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.toast {
|
|
262
|
+
position: fixed;
|
|
263
|
+
bottom: 20px;
|
|
264
|
+
left: 20px;
|
|
265
|
+
background: var(--surface);
|
|
266
|
+
border: 1px solid var(--border);
|
|
267
|
+
padding: 16px 24px;
|
|
268
|
+
border-radius: 8px;
|
|
269
|
+
box-shadow: 0 10px 15px -3px rgba(0,0,0,0.5);
|
|
270
|
+
transform: translateY(100px);
|
|
271
|
+
opacity: 0;
|
|
272
|
+
transition: all 0.3s;
|
|
273
|
+
z-index: 2000;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.toast.show {
|
|
277
|
+
transform: translateY(0);
|
|
278
|
+
opacity: 1;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.toast.success { border-right: 4px solid var(--success); }
|
|
282
|
+
.toast.error { border-right: 4px solid var(--error); }
|
|
283
|
+
|
|
284
|
+
.hidden { display: none !important; }
|
|
285
|
+
|
|
286
|
+
.tabs {
|
|
287
|
+
display: flex;
|
|
288
|
+
gap: 10px;
|
|
289
|
+
margin-bottom: 20px;
|
|
290
|
+
border-bottom: 1px solid var(--border);
|
|
291
|
+
padding-bottom: 10px;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.tab {
|
|
295
|
+
padding: 8px 16px;
|
|
296
|
+
cursor: pointer;
|
|
297
|
+
border-radius: 6px;
|
|
298
|
+
transition: all 0.2s;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.tab.active {
|
|
302
|
+
background: var(--primary);
|
|
303
|
+
color: white;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.tab-content {
|
|
307
|
+
display: none;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.tab-content.active {
|
|
311
|
+
display: block;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
@media (max-width: 768px) {
|
|
315
|
+
.grid { grid-template-columns: 1fr; }
|
|
316
|
+
.stats { grid-template-columns: 1fr; }
|
|
317
|
+
.header-content { flex-direction: column; gap: 10px; }
|
|
318
|
+
}
|
|
319
|
+
</style>
|
|
320
|
+
</head>
|
|
321
|
+
<body>
|
|
322
|
+
|
|
323
|
+
<header>
|
|
324
|
+
<div class="header-content">
|
|
325
|
+
<div class="logo">NOHO Platform</div>
|
|
326
|
+
<div class="auth-section" id="authSection">
|
|
327
|
+
<button onclick="showModal('loginModal')">تسجيل الدخول</button>
|
|
328
|
+
<button class="secondary" onclick="showModal('registerModal')">إنشاء حساب</button>
|
|
329
|
+
</div>
|
|
330
|
+
<div class="auth-section hidden" id="userSection">
|
|
331
|
+
<span id="usernameDisplay"></span>
|
|
332
|
+
<button class="secondary" onclick="logout()">خروج</button>
|
|
333
|
+
</div>
|
|
334
|
+
</div>
|
|
335
|
+
</header>
|
|
336
|
+
|
|
337
|
+
<div class="container">
|
|
338
|
+
<!-- Guest View -->
|
|
339
|
+
<div id="guestView">
|
|
340
|
+
<div class="grid">
|
|
341
|
+
<div class="card" style="text-align: center; padding: 60px 20px;">
|
|
342
|
+
<h1 style="font-size: 48px; margin-bottom: 20px;">🚀</h1>
|
|
343
|
+
<h2>ابنِ صفحاتك الديناميكية</h2>
|
|
344
|
+
<p style="color: var(--text-secondary); margin: 20px 0;">
|
|
345
|
+
منصة متكاملة لإنشاء صفحات ويب ذكية مع AI، ربط API، وتحليل تلقائي للكود
|
|
346
|
+
</p>
|
|
347
|
+
<button onclick="showModal('registerModal')" style="font-size: 18px; padding: 15px 30px;">
|
|
348
|
+
ابدأ الآن مجاناً
|
|
349
|
+
</button>
|
|
350
|
+
</div>
|
|
351
|
+
</div>
|
|
352
|
+
</div>
|
|
353
|
+
|
|
354
|
+
<!-- Dashboard View -->
|
|
355
|
+
<div id="dashboardView" class="hidden">
|
|
356
|
+
<div class="stats">
|
|
357
|
+
<div class="stat-box">
|
|
358
|
+
<div class="stat-value" id="statPages">0</div>
|
|
359
|
+
<div class="stat-label">الصفحات</div>
|
|
360
|
+
</div>
|
|
361
|
+
<div class="stat-box">
|
|
362
|
+
<div class="stat-value" id="statViews">0</div>
|
|
363
|
+
<div class="stat-label">المشاهدات</div>
|
|
364
|
+
</div>
|
|
365
|
+
<div class="stat-box">
|
|
366
|
+
<div class="stat-value" id="statRequests">0</div>
|
|
367
|
+
<div class="stat-label">الطلبات</div>
|
|
368
|
+
</div>
|
|
369
|
+
</div>
|
|
370
|
+
|
|
371
|
+
<div class="grid">
|
|
372
|
+
<!-- API Key Section -->
|
|
373
|
+
<div class="card" style="grid-column: 1 / -1;">
|
|
374
|
+
<h2>🔑 مفتاح API</h2>
|
|
375
|
+
<p style="color: var(--text-secondary); margin-bottom: 16px;">
|
|
376
|
+
استخدم هذا المفتاح للوصول إلى API من أي لغة برمجة
|
|
377
|
+
</p>
|
|
378
|
+
<div class="api-key-display">
|
|
379
|
+
<span id="apiKeyDisplay">********</span>
|
|
380
|
+
<div>
|
|
381
|
+
<button class="secondary" onclick="toggleApiKey()" style="margin-left: 8px;">إظهار</button>
|
|
382
|
+
<button onclick="copyApiKey()">نسخ</button>
|
|
383
|
+
<button class="danger" onclick="regenerateKey()" style="margin-right: 8px;">تجديد</button>
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
<div style="background: var(--bg); padding: 16px; border-radius: 8px; direction: ltr; text-align: left; overflow-x: auto;">
|
|
387
|
+
<code style="color: var(--text-secondary);">
|
|
388
|
+
curl -X POST http://localhost:5000/api/pages \<br>
|
|
389
|
+
-H "X-API-Key: <span class="key-placeholder">YOUR_KEY</span>" \<br>
|
|
390
|
+
-d '{"route":"/hello","code":"res.send(\"Hello\")"}'
|
|
391
|
+
</code>
|
|
392
|
+
</div>
|
|
393
|
+
</div>
|
|
394
|
+
|
|
395
|
+
<!-- Create Page -->
|
|
396
|
+
<div class="card" style="grid-column: 1 / -1;">
|
|
397
|
+
<h2>✨ إنشاء صفحة جديدة</h2>
|
|
398
|
+
|
|
399
|
+
<div class="tabs">
|
|
400
|
+
<div class="tab active" onclick="switchTab('codeTab', this)">كود مخصص</div>
|
|
401
|
+
<div class="tab" onclick="switchTab('aiTab', this)">توليد بالذكاء الاصطناعي</div>
|
|
402
|
+
</div>
|
|
403
|
+
|
|
404
|
+
<div id="codeTab" class="tab-content active">
|
|
405
|
+
<div class="form-group">
|
|
406
|
+
<label>مسار الصفحة (Route)</label>
|
|
407
|
+
<input type="text" id="pageRoute" placeholder="/my-page" dir="ltr">
|
|
408
|
+
</div>
|
|
409
|
+
|
|
410
|
+
<div class="form-group">
|
|
411
|
+
<label>الكود (JavaScript)</label>
|
|
412
|
+
<textarea id="pageCode" placeholder="// مثال:
|
|
413
|
+
res.send(`
|
|
414
|
+
<h1>مرحباً بك في NOHO</h1>
|
|
415
|
+
<p>الوقت: ${new Date()}</p>
|
|
416
|
+
`);" dir="ltr"></textarea>
|
|
417
|
+
</div>
|
|
418
|
+
|
|
419
|
+
<button onclick="analyzeCode()">🔍 تحليل الكود</button>
|
|
420
|
+
<button onclick="createPage()" style="margin-right: 10px;">نشر الصفحة</button>
|
|
421
|
+
|
|
422
|
+
<div id="analysisResult"></div>
|
|
423
|
+
</div>
|
|
424
|
+
|
|
425
|
+
<div id="aiTab" class="tab-content">
|
|
426
|
+
<div class="form-group">
|
|
427
|
+
<label>وصف الصفحة المطلوبة</label>
|
|
428
|
+
<input type="text" id="aiPrompt" placeholder="صفحة عرض منتجات مع تصميم عصري">
|
|
429
|
+
</div>
|
|
430
|
+
<button onclick="generateWithAI()">✨ توليد الكود</button>
|
|
431
|
+
<div id="aiResult" style="margin-top: 20px;"></div>
|
|
432
|
+
</div>
|
|
433
|
+
</div>
|
|
434
|
+
|
|
435
|
+
<!-- My Pages -->
|
|
436
|
+
<div class="card" style="grid-column: 1 / -1;">
|
|
437
|
+
<h2>📄 صفحاتي</h2>
|
|
438
|
+
<ul class="pages-list" id="pagesList">
|
|
439
|
+
<li style="text-align: center; color: var(--text-secondary); padding: 40px;">
|
|
440
|
+
لا توجد صفحات بعد
|
|
441
|
+
</li>
|
|
442
|
+
</ul>
|
|
443
|
+
</div>
|
|
444
|
+
</div>
|
|
445
|
+
</div>
|
|
446
|
+
</div>
|
|
447
|
+
|
|
448
|
+
<!-- Login Modal -->
|
|
449
|
+
<div class="modal" id="loginModal">
|
|
450
|
+
<div class="modal-content">
|
|
451
|
+
<h2 style="margin-bottom: 20px;">تسجيل الدخول</h2>
|
|
452
|
+
<div class="form-group">
|
|
453
|
+
<label>البريد الإلكتروني</label>
|
|
454
|
+
<input type="email" id="loginEmail" dir="ltr">
|
|
455
|
+
</div>
|
|
456
|
+
<div class="form-group">
|
|
457
|
+
<label>كلمة المرور</label>
|
|
458
|
+
<input type="password" id="loginPassword">
|
|
459
|
+
</div>
|
|
460
|
+
<button onclick="login()" style="width: 100%;">دخول</button>
|
|
461
|
+
<button class="secondary" onclick="hideModal('loginModal')" style="width: 100%; margin-top: 10px;">إلغاء</button>
|
|
462
|
+
</div>
|
|
463
|
+
</div>
|
|
464
|
+
|
|
465
|
+
<!-- Register Modal -->
|
|
466
|
+
<div class="modal" id="registerModal">
|
|
467
|
+
<div class="modal-content">
|
|
468
|
+
<h2 style="margin-bottom: 20px;">إنشاء حساب جديد</h2>
|
|
469
|
+
<div class="form-group">
|
|
470
|
+
<label>اسم المستخدم (سيظهر في الرابط)</label>
|
|
471
|
+
<input type="text" id="regUsername" placeholder="your-name" dir="ltr">
|
|
472
|
+
</div>
|
|
473
|
+
<div class="form-group">
|
|
474
|
+
<label>البريد الإلكتروني</label>
|
|
475
|
+
<input type="email" id="regEmail" dir="ltr">
|
|
476
|
+
</div>
|
|
477
|
+
<div class="form-group">
|
|
478
|
+
<label>كلمة المرور</label>
|
|
479
|
+
<input type="password" id="regPassword">
|
|
480
|
+
</div>
|
|
481
|
+
<button onclick="register()" style="width: 100%;">إنشاء الحساب</button>
|
|
482
|
+
<button class="secondary" onclick="hideModal('registerModal')" style="width: 100%; margin-top: 10px;">إلغاء</button>
|
|
483
|
+
</div>
|
|
484
|
+
</div>
|
|
485
|
+
|
|
486
|
+
<!-- API Key Display Modal (After Register) -->
|
|
487
|
+
<div class="modal" id="apiKeyModal">
|
|
488
|
+
<div class="modal-content">
|
|
489
|
+
<h2 style="margin-bottom: 20px; color: var(--success);">✅ تم إنشاء الحساب!</h2>
|
|
490
|
+
<p style="margin-bottom: 16px;">احفظ مفتاح API هذا جيداً، لن يُعرض مرة أخرى:</p>
|
|
491
|
+
<div class="api-key-display" style="background: var(--bg); margin-bottom: 16px;">
|
|
492
|
+
<span id="newApiKey" style="font-size: 16px;"></span>
|
|
493
|
+
</div>
|
|
494
|
+
<button onclick="copyNewApiKey()" style="width: 100%; margin-bottom: 10px;">نسخ المفتاح</button>
|
|
495
|
+
<button onclick="closeApiKeyModal()" style="width: 100%;">متابعة للوحة التحكم</button>
|
|
496
|
+
</div>
|
|
497
|
+
</div>
|
|
498
|
+
|
|
499
|
+
<div class="toast" id="toast"></div>
|
|
500
|
+
|
|
501
|
+
<script>
|
|
502
|
+
// API URL - يعمل تلقائياً على نفس السيرفر
|
|
503
|
+
const API_URL = window.location.origin;
|
|
504
|
+
let currentUser = null;
|
|
505
|
+
let apiKeyVisible = false;
|
|
506
|
+
let storedApiKey = '';
|
|
507
|
+
|
|
508
|
+
// Auth Functions
|
|
509
|
+
async function register() {
|
|
510
|
+
const username = document.getElementById('regUsername').value.trim();
|
|
511
|
+
const email = document.getElementById('regEmail').value.trim();
|
|
512
|
+
const password = document.getElementById('regPassword').value;
|
|
513
|
+
|
|
514
|
+
if (!username || !email || !password) {
|
|
515
|
+
showToast('أكمل جميع الحقول', 'error');
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
if (password.length < 8) {
|
|
520
|
+
showToast('كلمة المرور يجب أن تكون 8 أحرف على الأقل', 'error');
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
try {
|
|
525
|
+
const res = await fetch(`${API_URL}/api/auth/register`, {
|
|
526
|
+
method: 'POST',
|
|
527
|
+
headers: { 'Content-Type': 'application/json' },
|
|
528
|
+
body: JSON.stringify({ username, email, password })
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
const data = await res.json();
|
|
532
|
+
if (data.success) {
|
|
533
|
+
storedApiKey = data.data.apiKey;
|
|
534
|
+
document.getElementById('newApiKey').textContent = storedApiKey;
|
|
535
|
+
hideModal('registerModal');
|
|
536
|
+
showModal('apiKeyModal');
|
|
537
|
+
} else {
|
|
538
|
+
showToast(data.error || 'خطأ في التسجيل', 'error');
|
|
539
|
+
}
|
|
540
|
+
} catch (e) {
|
|
541
|
+
showToast('خطأ في الاتصال بالسيرفر', 'error');
|
|
542
|
+
console.error(e);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
async function login() {
|
|
547
|
+
const email = document.getElementById('loginEmail').value.trim();
|
|
548
|
+
const password = document.getElementById('loginPassword').value;
|
|
549
|
+
|
|
550
|
+
if (!email || !password) {
|
|
551
|
+
showToast('أدخل البريد وكلمة المرور', 'error');
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
try {
|
|
556
|
+
const res = await fetch(`${API_URL}/api/auth/login`, {
|
|
557
|
+
method: 'POST',
|
|
558
|
+
headers: { 'Content-Type': 'application/json' },
|
|
559
|
+
body: JSON.stringify({ email, password })
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
const data = await res.json();
|
|
563
|
+
if (data.success) {
|
|
564
|
+
localStorage.setItem('noho_token', data.token);
|
|
565
|
+
currentUser = data.user;
|
|
566
|
+
hideModal('loginModal');
|
|
567
|
+
loadDashboard();
|
|
568
|
+
showToast('تم تسجيل الدخول', 'success');
|
|
569
|
+
} else {
|
|
570
|
+
showToast(data.error || 'بيانات غير صحيحة', 'error');
|
|
571
|
+
}
|
|
572
|
+
} catch (e) {
|
|
573
|
+
showToast('خطأ في الاتصال', 'error');
|
|
574
|
+
console.error(e);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function logout() {
|
|
579
|
+
localStorage.removeItem('noho_token');
|
|
580
|
+
currentUser = null;
|
|
581
|
+
location.reload();
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function closeApiKeyModal() {
|
|
585
|
+
hideModal('apiKeyModal');
|
|
586
|
+
// تسجيل الدخول تلقائياً بعد التسجيل
|
|
587
|
+
const email = document.getElementById('regEmail').value;
|
|
588
|
+
const password = document.getElementById('regPassword').value;
|
|
589
|
+
document.getElementById('loginEmail').value = email;
|
|
590
|
+
document.getElementById('loginPassword').value = password;
|
|
591
|
+
login();
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
async function loadDashboard() {
|
|
595
|
+
const token = localStorage.getItem('noho_token');
|
|
596
|
+
if (!token) return;
|
|
597
|
+
|
|
598
|
+
try {
|
|
599
|
+
const res = await fetch(`${API_URL}/api/auth/me`, {
|
|
600
|
+
headers: { 'Authorization': `Bearer ${token}` }
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
const data = await res.json();
|
|
604
|
+
|
|
605
|
+
if (data.success) {
|
|
606
|
+
currentUser = data.user;
|
|
607
|
+
|
|
608
|
+
// تحديث الواجهة
|
|
609
|
+
document.getElementById('guestView').classList.add('hidden');
|
|
610
|
+
document.getElementById('dashboardView').classList.remove('hidden');
|
|
611
|
+
document.getElementById('authSection').classList.add('hidden');
|
|
612
|
+
document.getElementById('userSection').classList.remove('hidden');
|
|
613
|
+
document.getElementById('usernameDisplay').textContent = data.user.username;
|
|
614
|
+
|
|
615
|
+
// تحديث الإحصائيات
|
|
616
|
+
document.getElementById('statPages').textContent = data.stats.totalPages;
|
|
617
|
+
document.getElementById('statViews').textContent = data.stats.pages.reduce((a,b) => a + b.views, 0);
|
|
618
|
+
document.getElementById('statRequests').textContent = data.stats.requests;
|
|
619
|
+
|
|
620
|
+
// حفظ مفتاح API
|
|
621
|
+
document.getElementById('apiKeyDisplay').dataset.fullKey = data.user.apiKey;
|
|
622
|
+
|
|
623
|
+
// تحميل الصفحات
|
|
624
|
+
loadPages();
|
|
625
|
+
} else {
|
|
626
|
+
localStorage.removeItem('noho_token');
|
|
627
|
+
}
|
|
628
|
+
} catch (e) {
|
|
629
|
+
console.error('Load dashboard error:', e);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
async function loadPages() {
|
|
634
|
+
const token = localStorage.getItem('noho_token');
|
|
635
|
+
if (!token) return;
|
|
636
|
+
|
|
637
|
+
try {
|
|
638
|
+
const res = await fetch(`${API_URL}/api/pages`, {
|
|
639
|
+
headers: { 'Authorization': `Bearer ${token}` }
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
const data = await res.json();
|
|
643
|
+
|
|
644
|
+
const list = document.getElementById('pagesList');
|
|
645
|
+
if (!data.pages || data.pages.length === 0) {
|
|
646
|
+
list.innerHTML = '<li style="text-align: center; color: var(--text-secondary); padding: 40px;">لا توجد صفحات بعد</li>';
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
list.innerHTML = data.pages.map(page => `
|
|
651
|
+
<li class="page-item">
|
|
652
|
+
<div class="page-info">
|
|
653
|
+
<h3>${page.shortRoute || page.route}</h3>
|
|
654
|
+
<div class="page-meta">
|
|
655
|
+
المشاهدات: ${page.views} |
|
|
656
|
+
التاريخ: ${new Date(page.createdAt).toLocaleDateString('ar-SA')}
|
|
657
|
+
${page.public ? '<span class="badge">عام</span>' : '<span class="badge warning">خاص</span>'}
|
|
658
|
+
</div>
|
|
659
|
+
</div>
|
|
660
|
+
<div>
|
|
661
|
+
<a href="${API_URL}${page.route}" target="_blank">
|
|
662
|
+
<button class="secondary">زيارة</button>
|
|
663
|
+
</a>
|
|
664
|
+
<button class="danger" onclick="deletePage('${page.id}')">حذف</button>
|
|
665
|
+
</div>
|
|
666
|
+
</li>
|
|
667
|
+
`).join('');
|
|
668
|
+
} catch (e) {
|
|
669
|
+
console.error('Load pages error:', e);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// Page Functions
|
|
674
|
+
async function analyzeCode() {
|
|
675
|
+
const code = document.getElementById('pageCode').value;
|
|
676
|
+
if (!code) return showToast('أدخل الكود أولاً', 'error');
|
|
677
|
+
|
|
678
|
+
const token = localStorage.getItem('noho_token');
|
|
679
|
+
if (!token) return showToast('سجل دخول أولاً', 'error');
|
|
680
|
+
|
|
681
|
+
try {
|
|
682
|
+
const res = await fetch(`${API_URL}/api/ai/analyze`, {
|
|
683
|
+
method: 'POST',
|
|
684
|
+
headers: {
|
|
685
|
+
'Content-Type': 'application/json',
|
|
686
|
+
'Authorization': `Bearer ${token}`
|
|
687
|
+
},
|
|
688
|
+
body: JSON.stringify({ code, context: 'web-page' })
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
const data = await res.json();
|
|
692
|
+
const resultDiv = document.getElementById('analysisResult');
|
|
693
|
+
|
|
694
|
+
if (data.success) {
|
|
695
|
+
resultDiv.innerHTML = `
|
|
696
|
+
<div class="analysis-result ${data.warnings && data.warnings.length > 0 ? 'has-warnings' : ''}">
|
|
697
|
+
<h4>نتيجة التحليل</h4>
|
|
698
|
+
${data.warnings ? data.warnings.map(w => `<div class="warning-item">⚠️ ${w}</div>`).join('') : ''}
|
|
699
|
+
${data.changes ? data.changes.map(c => `<div style="color: var(--success); margin: 4px 0;">✓ ${c}</div>`).join('') : ''}
|
|
700
|
+
${(!data.warnings || data.warnings.length === 0) ? '<div style="color: var(--success);">✓ الكود آمن</div>' : ''}
|
|
701
|
+
</div>
|
|
702
|
+
`;
|
|
703
|
+
}
|
|
704
|
+
} catch (e) {
|
|
705
|
+
showToast('خطأ في التحليل', 'error');
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
async function createPage() {
|
|
710
|
+
const route = document.getElementById('pageRoute').value.trim();
|
|
711
|
+
const code = document.getElementById('pageCode').value;
|
|
712
|
+
const token = localStorage.getItem('noho_token');
|
|
713
|
+
|
|
714
|
+
if (!route || !code) return showToast('أكمل جميع الحقول', 'error');
|
|
715
|
+
if (!token) return showToast('سجل دخول أولاً', 'error');
|
|
716
|
+
|
|
717
|
+
try {
|
|
718
|
+
const res = await fetch(`${API_URL}/api/pages`, {
|
|
719
|
+
method: 'POST',
|
|
720
|
+
headers: {
|
|
721
|
+
'Content-Type': 'application/json',
|
|
722
|
+
'Authorization': `Bearer ${token}`
|
|
723
|
+
},
|
|
724
|
+
body: JSON.stringify({ route, code, options: { public: true } })
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
const data = await res.json();
|
|
728
|
+
if (data.success) {
|
|
729
|
+
showToast('تم إنشاء الصفحة!', 'success');
|
|
730
|
+
document.getElementById('pageRoute').value = '';
|
|
731
|
+
document.getElementById('pageCode').value = '';
|
|
732
|
+
document.getElementById('analysisResult').innerHTML = '';
|
|
733
|
+
loadPages();
|
|
734
|
+
} else {
|
|
735
|
+
showToast(data.error || 'خطأ في الإنشاء', 'error');
|
|
736
|
+
}
|
|
737
|
+
} catch (e) {
|
|
738
|
+
showToast('خطأ في الاتصال', 'error');
|
|
739
|
+
console.error(e);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
async function generateWithAI() {
|
|
744
|
+
const prompt = document.getElementById('aiPrompt').value.trim();
|
|
745
|
+
if (!prompt) return showToast('أدخل وصفاً', 'error');
|
|
746
|
+
|
|
747
|
+
const token = localStorage.getItem('noho_token');
|
|
748
|
+
if (!token) return showToast('سجل دخول أولاً', 'error');
|
|
749
|
+
|
|
750
|
+
showToast('جاري التوليد...', 'success');
|
|
751
|
+
|
|
752
|
+
try {
|
|
753
|
+
const res = await fetch(`${API_URL}/api/ai/generate`, {
|
|
754
|
+
method: 'POST',
|
|
755
|
+
headers: {
|
|
756
|
+
'Content-Type': 'application/json',
|
|
757
|
+
'Authorization': `Bearer ${token}`
|
|
758
|
+
},
|
|
759
|
+
body: JSON.stringify({ description: prompt })
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
const data = await res.json();
|
|
763
|
+
if (data.success) {
|
|
764
|
+
document.getElementById('pageCode').value = data.code;
|
|
765
|
+
document.getElementById('aiResult').innerHTML = '<div style="color: var(--success);">✓ تم توليد الكود! انتقل لتبويب "كود مخصص" لمراجعته</div>';
|
|
766
|
+
switchTab('codeTab', document.querySelectorAll('.tab')[0]);
|
|
767
|
+
} else {
|
|
768
|
+
showToast(data.error || 'فشل التوليد', 'error');
|
|
769
|
+
}
|
|
770
|
+
} catch (e) {
|
|
771
|
+
showToast('خطأ في الاتصال', 'error');
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
async function deletePage(pageId) {
|
|
776
|
+
if (!confirm('هل أنت متأكد من الحذف؟')) return;
|
|
777
|
+
|
|
778
|
+
const token = localStorage.getItem('noho_token');
|
|
779
|
+
if (!token) return;
|
|
780
|
+
|
|
781
|
+
try {
|
|
782
|
+
const res = await fetch(`${API_URL}/api/pages/${pageId}`, {
|
|
783
|
+
method: 'DELETE',
|
|
784
|
+
headers: { 'Authorization': `Bearer ${token}` }
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
if (res.ok) {
|
|
788
|
+
showToast('تم الحذف', 'success');
|
|
789
|
+
loadPages();
|
|
790
|
+
}
|
|
791
|
+
} catch (e) {
|
|
792
|
+
showToast('خطأ في الحذف', 'error');
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// API Key Functions
|
|
797
|
+
function toggleApiKey() {
|
|
798
|
+
const display = document.getElementById('apiKeyDisplay');
|
|
799
|
+
const fullKey = display.dataset.fullKey;
|
|
800
|
+
|
|
801
|
+
if (apiKeyVisible) {
|
|
802
|
+
display.textContent = '••••••••••••';
|
|
803
|
+
apiKeyVisible = false;
|
|
804
|
+
} else {
|
|
805
|
+
display.textContent = fullKey || 'غير متوفر';
|
|
806
|
+
apiKeyVisible = true;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function copyApiKey() {
|
|
811
|
+
const key = document.getElementById('apiKeyDisplay').dataset.fullKey;
|
|
812
|
+
if (!key) return showToast('لا يوجد مفتاح', 'error');
|
|
813
|
+
|
|
814
|
+
navigator.clipboard.writeText(key).then(() => {
|
|
815
|
+
showToast('تم النسخ!', 'success');
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
function copyNewApiKey() {
|
|
820
|
+
navigator.clipboard.writeText(storedApiKey).then(() => {
|
|
821
|
+
showToast('تم النسخ!', 'success');
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
async function regenerateKey() {
|
|
826
|
+
if (!confirm('سيتم إنشاء مفتاح جديد والقديم سيتوقف؟')) return;
|
|
827
|
+
|
|
828
|
+
const token = localStorage.getItem('noho_token');
|
|
829
|
+
if (!token) return;
|
|
830
|
+
|
|
831
|
+
try {
|
|
832
|
+
const res = await fetch(`${API_URL}/api/auth/regenerate-key`, {
|
|
833
|
+
method: 'POST',
|
|
834
|
+
headers: { 'Authorization': `Bearer ${token}` }
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
const data = await res.json();
|
|
838
|
+
if (data.success) {
|
|
839
|
+
showToast('تم تجديد المفتاح', 'success');
|
|
840
|
+
loadDashboard();
|
|
841
|
+
}
|
|
842
|
+
} catch (e) {
|
|
843
|
+
showToast('خطأ في التجديد', 'error');
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// UI Helpers
|
|
848
|
+
function showModal(id) {
|
|
849
|
+
document.getElementById(id).classList.add('active');
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
function hideModal(id) {
|
|
853
|
+
document.getElementById(id).classList.remove('active');
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
function switchTab(tabId, tabElement) {
|
|
857
|
+
// إزالة active من جميع التبويبات
|
|
858
|
+
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
|
859
|
+
document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
|
|
860
|
+
|
|
861
|
+
// إضافة active للمحدد
|
|
862
|
+
if (tabElement) tabElement.classList.add('active');
|
|
863
|
+
document.getElementById(tabId).classList.add('active');
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
function showToast(message, type = 'success') {
|
|
867
|
+
const toast = document.getElementById('toast');
|
|
868
|
+
toast.textContent = message;
|
|
869
|
+
toast.className = `toast show ${type}`;
|
|
870
|
+
setTimeout(() => toast.classList.remove('show'), 3000);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// Init
|
|
874
|
+
window.onload = () => {
|
|
875
|
+
// التحقق من تسجيل الدخول تلقائياً
|
|
876
|
+
const token = localStorage.getItem('noho_token');
|
|
877
|
+
if (token) {
|
|
878
|
+
loadDashboard();
|
|
879
|
+
}
|
|
880
|
+
};
|
|
881
|
+
|
|
882
|
+
// Close modals on outside click
|
|
883
|
+
window.onclick = (e) => {
|
|
884
|
+
if (e.target.classList.contains('modal')) {
|
|
885
|
+
e.target.classList.remove('active');
|
|
886
|
+
}
|
|
887
|
+
};
|
|
888
|
+
</script>
|
|
889
|
+
|
|
890
|
+
</body>
|
|
891
|
+
</html>
|