@gxp-dev/tools 2.0.5

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.
Files changed (145) hide show
  1. package/.github/workflows/npm-publish.yml +48 -0
  2. package/CLAUDE.md +400 -0
  3. package/README.md +247 -0
  4. package/REFACTOR_PLAN.md +194 -0
  5. package/bin/gx-devtools.js +87 -0
  6. package/bin/lib/cli.js +251 -0
  7. package/bin/lib/commands/assets.js +337 -0
  8. package/bin/lib/commands/build.js +259 -0
  9. package/bin/lib/commands/datastore.js +433 -0
  10. package/bin/lib/commands/dev.js +328 -0
  11. package/bin/lib/commands/extensions.js +298 -0
  12. package/bin/lib/commands/index.js +35 -0
  13. package/bin/lib/commands/init.js +307 -0
  14. package/bin/lib/commands/publish.js +189 -0
  15. package/bin/lib/commands/socket.js +158 -0
  16. package/bin/lib/commands/ssl.js +47 -0
  17. package/bin/lib/constants.js +120 -0
  18. package/bin/lib/tui/App.tsx +600 -0
  19. package/bin/lib/tui/components/CommandInput.tsx +278 -0
  20. package/bin/lib/tui/components/GeminiPanel.tsx +161 -0
  21. package/bin/lib/tui/components/Header.tsx +27 -0
  22. package/bin/lib/tui/components/LogPanel.tsx +122 -0
  23. package/bin/lib/tui/components/TabBar.tsx +56 -0
  24. package/bin/lib/tui/components/WelcomeScreen.tsx +80 -0
  25. package/bin/lib/tui/index.tsx +63 -0
  26. package/bin/lib/tui/services/ExtensionService.ts +122 -0
  27. package/bin/lib/tui/services/GeminiService.ts +395 -0
  28. package/bin/lib/tui/services/ServiceManager.ts +336 -0
  29. package/bin/lib/tui/services/SocketService.ts +204 -0
  30. package/bin/lib/tui/services/ViteService.ts +107 -0
  31. package/bin/lib/tui/services/index.ts +13 -0
  32. package/bin/lib/utils/files.js +180 -0
  33. package/bin/lib/utils/index.js +17 -0
  34. package/bin/lib/utils/paths.js +138 -0
  35. package/bin/lib/utils/prompts.js +71 -0
  36. package/bin/lib/utils/ssl.js +233 -0
  37. package/browser-extensions/README.md +1 -0
  38. package/browser-extensions/chrome/background.js +857 -0
  39. package/browser-extensions/chrome/content.js +51 -0
  40. package/browser-extensions/chrome/devtools.html +9 -0
  41. package/browser-extensions/chrome/devtools.js +23 -0
  42. package/browser-extensions/chrome/icons/gx_off_128.png +0 -0
  43. package/browser-extensions/chrome/icons/gx_off_16.png +0 -0
  44. package/browser-extensions/chrome/icons/gx_off_32.png +0 -0
  45. package/browser-extensions/chrome/icons/gx_off_64.png +0 -0
  46. package/browser-extensions/chrome/icons/gx_on_128.png +0 -0
  47. package/browser-extensions/chrome/icons/gx_on_16.png +0 -0
  48. package/browser-extensions/chrome/icons/gx_on_32.png +0 -0
  49. package/browser-extensions/chrome/icons/gx_on_64.png +0 -0
  50. package/browser-extensions/chrome/inspector.js +1087 -0
  51. package/browser-extensions/chrome/manifest.json +70 -0
  52. package/browser-extensions/chrome/panel.html +638 -0
  53. package/browser-extensions/chrome/panel.js +862 -0
  54. package/browser-extensions/chrome/popup.html +399 -0
  55. package/browser-extensions/chrome/popup.js +515 -0
  56. package/browser-extensions/chrome/rules.json +1 -0
  57. package/browser-extensions/chrome/test-chrome.html +145 -0
  58. package/browser-extensions/chrome/test-mixed-content.html +190 -0
  59. package/browser-extensions/chrome/test-uri-pattern.html +199 -0
  60. package/browser-extensions/firefox/README.md +134 -0
  61. package/browser-extensions/firefox/background.js +804 -0
  62. package/browser-extensions/firefox/content.js +120 -0
  63. package/browser-extensions/firefox/debug-errors.html +229 -0
  64. package/browser-extensions/firefox/debug-https.html +113 -0
  65. package/browser-extensions/firefox/devtools.html +9 -0
  66. package/browser-extensions/firefox/devtools.js +24 -0
  67. package/browser-extensions/firefox/icons/gx_off_128.png +0 -0
  68. package/browser-extensions/firefox/icons/gx_off_16.png +0 -0
  69. package/browser-extensions/firefox/icons/gx_off_32.png +0 -0
  70. package/browser-extensions/firefox/icons/gx_off_64.png +0 -0
  71. package/browser-extensions/firefox/icons/gx_on_128.png +0 -0
  72. package/browser-extensions/firefox/icons/gx_on_16.png +0 -0
  73. package/browser-extensions/firefox/icons/gx_on_32.png +0 -0
  74. package/browser-extensions/firefox/icons/gx_on_64.png +0 -0
  75. package/browser-extensions/firefox/inspector.js +1087 -0
  76. package/browser-extensions/firefox/manifest.json +67 -0
  77. package/browser-extensions/firefox/panel.html +638 -0
  78. package/browser-extensions/firefox/panel.js +862 -0
  79. package/browser-extensions/firefox/popup.html +525 -0
  80. package/browser-extensions/firefox/popup.js +536 -0
  81. package/browser-extensions/firefox/test-gramercy.html +126 -0
  82. package/browser-extensions/firefox/test-imports.html +58 -0
  83. package/browser-extensions/firefox/test-masking.html +147 -0
  84. package/browser-extensions/firefox/test-uri-pattern.html +199 -0
  85. package/docs/DOCUSAURUS_IMPORT.md +378 -0
  86. package/docs/_category_.json +8 -0
  87. package/docs/app-manifest.md +272 -0
  88. package/docs/building-for-platform.md +315 -0
  89. package/docs/dev-tools.md +291 -0
  90. package/docs/getting-started.md +180 -0
  91. package/docs/gxp-store.md +305 -0
  92. package/docs/index.md +44 -0
  93. package/package.json +77 -0
  94. package/runtime/PortalContainer.vue +326 -0
  95. package/runtime/dev-tools/DevToolsModal.vue +217 -0
  96. package/runtime/dev-tools/LayoutSwitcher.vue +221 -0
  97. package/runtime/dev-tools/MockDataEditor.vue +621 -0
  98. package/runtime/dev-tools/SocketSimulator.vue +562 -0
  99. package/runtime/dev-tools/StoreInspector.vue +644 -0
  100. package/runtime/dev-tools/index.js +6 -0
  101. package/runtime/gxpStringsPlugin.js +428 -0
  102. package/runtime/index.html +22 -0
  103. package/runtime/main.js +32 -0
  104. package/runtime/mock-api/auth-middleware.js +97 -0
  105. package/runtime/mock-api/image-generator.js +221 -0
  106. package/runtime/mock-api/index.js +197 -0
  107. package/runtime/mock-api/response-generator.js +394 -0
  108. package/runtime/mock-api/route-generator.js +323 -0
  109. package/runtime/mock-api/socket-triggers.js +371 -0
  110. package/runtime/mock-api/spec-loader.js +300 -0
  111. package/runtime/server.js +180 -0
  112. package/runtime/stores/gxpPortalConfigStore.js +554 -0
  113. package/runtime/stores/index.js +6 -0
  114. package/runtime/vite-inspector-plugin.js +749 -0
  115. package/runtime/vite-source-tracker-plugin.js +232 -0
  116. package/runtime/vite.config.js +402 -0
  117. package/scripts/launch-chrome.js +90 -0
  118. package/scripts/pack-chrome.js +91 -0
  119. package/socket-events/AiSessionMessageCreated.json +18 -0
  120. package/socket-events/SocialStreamPostCreated.json +24 -0
  121. package/socket-events/SocialStreamPostVariantCompleted.json +23 -0
  122. package/template/README.md +332 -0
  123. package/template/app-manifest.json +32 -0
  124. package/template/dev-assets/images/avatar-placeholder.png +0 -0
  125. package/template/dev-assets/images/background-placeholder.jpg +0 -0
  126. package/template/dev-assets/images/banner-placeholder.jpg +0 -0
  127. package/template/dev-assets/images/icon-placeholder.png +0 -0
  128. package/template/dev-assets/images/logo-placeholder.png +0 -0
  129. package/template/dev-assets/images/product-placeholder.jpg +0 -0
  130. package/template/dev-assets/images/thumbnail-placeholder.jpg +0 -0
  131. package/template/env.example +51 -0
  132. package/template/gitignore +53 -0
  133. package/template/index.html +22 -0
  134. package/template/main.js +28 -0
  135. package/template/src/DemoPage.vue +459 -0
  136. package/template/src/Plugin.vue +38 -0
  137. package/template/src/stores/index.js +9 -0
  138. package/template/src/stores/test-data.json +173 -0
  139. package/template/theme-layouts/AdditionalStyling.css +0 -0
  140. package/template/theme-layouts/PrivateLayout.vue +39 -0
  141. package/template/theme-layouts/PublicLayout.vue +39 -0
  142. package/template/theme-layouts/SystemLayout.vue +39 -0
  143. package/template/vite.config.js +333 -0
  144. package/tsconfig.tui.json +21 -0
  145. package/vite.config.js +164 -0
@@ -0,0 +1,562 @@
1
+ <template>
2
+ <div class="socket-simulator">
3
+ <div class="simulator-header">
4
+ <div class="connection-status" :class="{ connected: isConnected }">
5
+ <span class="status-dot"></span>
6
+ {{ isConnected ? 'Connected' : 'Disconnected' }}
7
+ </div>
8
+ <span class="socket-port">Port: {{ socketPort }}</span>
9
+ </div>
10
+
11
+ <div class="event-list">
12
+ <h4>Available Events</h4>
13
+ <p class="helper-text">
14
+ Click an event to send it. Events are loaded from <code>socket-events/</code> directory.
15
+ </p>
16
+
17
+ <div v-if="events.length === 0" class="empty-state">
18
+ <p>No socket events found.</p>
19
+ <p class="hint">Create JSON files in <code>socket-events/</code> to add events.</p>
20
+ </div>
21
+
22
+ <div v-else class="events-grid">
23
+ <div
24
+ v-for="event in events"
25
+ :key="event.name"
26
+ class="event-card"
27
+ @click="selectEvent(event)"
28
+ :class="{ selected: selectedEvent?.name === event.name }"
29
+ >
30
+ <div class="event-name">{{ event.name }}</div>
31
+ <div class="event-type">{{ event.event }}</div>
32
+ </div>
33
+ </div>
34
+ </div>
35
+
36
+ <div v-if="selectedEvent" class="event-editor">
37
+ <h4>Event Details: {{ selectedEvent.name }}</h4>
38
+
39
+ <div class="editor-field">
40
+ <label>Event Type:</label>
41
+ <input v-model="editableEvent.event" class="field-input" />
42
+ </div>
43
+
44
+ <div class="editor-field">
45
+ <label>Channel:</label>
46
+ <input v-model="editableEvent.channel" class="field-input" />
47
+ </div>
48
+
49
+ <div class="editor-field">
50
+ <label>Data (JSON):</label>
51
+ <textarea
52
+ v-model="editableEventData"
53
+ class="field-textarea"
54
+ rows="8"
55
+ @input="validateJson"
56
+ ></textarea>
57
+ <span v-if="jsonError" class="json-error">{{ jsonError }}</span>
58
+ </div>
59
+
60
+ <div class="editor-actions">
61
+ <button class="btn btn-primary" @click="sendEvent" :disabled="!!jsonError">
62
+ Send Event
63
+ </button>
64
+ <button class="btn btn-secondary" @click="resetEvent">
65
+ Reset
66
+ </button>
67
+ </div>
68
+ </div>
69
+
70
+ <div class="event-log">
71
+ <div class="log-header">
72
+ <h4>Event Log</h4>
73
+ <button class="btn-clear" @click="clearLog">Clear</button>
74
+ </div>
75
+ <div class="log-entries">
76
+ <div v-if="eventLog.length === 0" class="empty-log">
77
+ No events sent yet
78
+ </div>
79
+ <div
80
+ v-for="(entry, index) in eventLog"
81
+ :key="index"
82
+ class="log-entry"
83
+ :class="entry.type"
84
+ >
85
+ <span class="log-time">{{ entry.time }}</span>
86
+ <span class="log-direction">{{ entry.direction }}</span>
87
+ <span class="log-event">{{ entry.event }}</span>
88
+ <span class="log-status">{{ entry.status }}</span>
89
+ </div>
90
+ </div>
91
+ </div>
92
+ </div>
93
+ </template>
94
+
95
+ <script setup>
96
+ import { ref, reactive, computed, onMounted } from 'vue';
97
+
98
+ const props = defineProps({
99
+ store: {
100
+ type: Object,
101
+ required: true
102
+ }
103
+ });
104
+
105
+ const socketPort = ref(3069);
106
+ const isConnected = ref(false);
107
+ const events = ref([]);
108
+ const selectedEvent = ref(null);
109
+ const editableEvent = reactive({
110
+ event: '',
111
+ channel: '',
112
+ data: {}
113
+ });
114
+ const editableEventData = ref('');
115
+ const jsonError = ref('');
116
+ const eventLog = ref([]);
117
+
118
+ // Default events if none are loaded
119
+ const defaultEvents = [
120
+ {
121
+ name: 'AiSessionMessageCreated',
122
+ event: 'AiSessionMessageCreated',
123
+ channel: 'private.ai_session.1',
124
+ data: {
125
+ id: 1,
126
+ message: 'Test AI response',
127
+ session_id: 1,
128
+ created_at: new Date().toISOString()
129
+ }
130
+ },
131
+ {
132
+ name: 'SocialStreamPostCreated',
133
+ event: 'SocialStreamPostCreated',
134
+ channel: 'private.social_stream.1',
135
+ data: {
136
+ id: 1,
137
+ content: 'Test social post',
138
+ author: 'Test User',
139
+ created_at: new Date().toISOString()
140
+ }
141
+ },
142
+ {
143
+ name: 'StateChange',
144
+ event: 'state-change',
145
+ channel: 'broadcast',
146
+ data: {
147
+ key: 'test_key',
148
+ value: 'test_value',
149
+ timestamp: new Date().toISOString()
150
+ }
151
+ }
152
+ ];
153
+
154
+ onMounted(() => {
155
+ // Load events - in a real implementation, this would load from socket-events directory
156
+ events.value = defaultEvents;
157
+
158
+ // Check socket connection
159
+ checkConnection();
160
+ });
161
+
162
+ function checkConnection() {
163
+ // Try to connect to socket server
164
+ const socket = props.store?.sockets?.primary;
165
+ isConnected.value = !!socket;
166
+ }
167
+
168
+ function selectEvent(event) {
169
+ selectedEvent.value = event;
170
+ editableEvent.event = event.event;
171
+ editableEvent.channel = event.channel;
172
+ editableEvent.data = { ...event.data };
173
+ editableEventData.value = JSON.stringify(event.data, null, 2);
174
+ jsonError.value = '';
175
+ }
176
+
177
+ function validateJson() {
178
+ try {
179
+ JSON.parse(editableEventData.value);
180
+ jsonError.value = '';
181
+ } catch (e) {
182
+ jsonError.value = 'Invalid JSON: ' + e.message;
183
+ }
184
+ }
185
+
186
+ function resetEvent() {
187
+ if (selectedEvent.value) {
188
+ selectEvent(selectedEvent.value);
189
+ }
190
+ }
191
+
192
+ async function sendEvent() {
193
+ if (jsonError.value) return;
194
+
195
+ let data;
196
+ try {
197
+ data = JSON.parse(editableEventData.value);
198
+ } catch {
199
+ return;
200
+ }
201
+
202
+ const eventPayload = {
203
+ event: editableEvent.event,
204
+ channel: editableEvent.channel,
205
+ data: data
206
+ };
207
+
208
+ // Log the send attempt
209
+ addLogEntry('send', editableEvent.event, 'pending');
210
+
211
+ try {
212
+ // Try to send via the store's socket
213
+ const socket = props.store?.sockets?.primary;
214
+ if (socket && socket.broadcast) {
215
+ socket.broadcast(editableEvent.event, data);
216
+ updateLastLogEntry('success');
217
+ console.log('[DevTools] Socket event sent:', eventPayload);
218
+ } else {
219
+ // Fallback: try to send via HTTP to the socket server
220
+ const response = await fetch(`https://localhost:${socketPort.value}/emit`, {
221
+ method: 'POST',
222
+ headers: {
223
+ 'Content-Type': 'application/json'
224
+ },
225
+ body: JSON.stringify(eventPayload)
226
+ });
227
+
228
+ if (response.ok) {
229
+ updateLastLogEntry('success');
230
+ console.log('[DevTools] Socket event sent via HTTP:', eventPayload);
231
+ } else {
232
+ throw new Error('HTTP request failed');
233
+ }
234
+ }
235
+ } catch (err) {
236
+ updateLastLogEntry('error');
237
+ console.error('[DevTools] Failed to send event:', err);
238
+ }
239
+ }
240
+
241
+ function addLogEntry(direction, event, status) {
242
+ const now = new Date();
243
+ const time = now.toLocaleTimeString('en-US', { hour12: false });
244
+
245
+ eventLog.value.unshift({
246
+ time,
247
+ direction: direction === 'send' ? '→' : '←',
248
+ event,
249
+ status,
250
+ type: status
251
+ });
252
+
253
+ // Keep only last 50 entries
254
+ if (eventLog.value.length > 50) {
255
+ eventLog.value.pop();
256
+ }
257
+ }
258
+
259
+ function updateLastLogEntry(status) {
260
+ if (eventLog.value.length > 0) {
261
+ eventLog.value[0].status = status;
262
+ eventLog.value[0].type = status;
263
+ }
264
+ }
265
+
266
+ function clearLog() {
267
+ eventLog.value = [];
268
+ }
269
+ </script>
270
+
271
+ <style scoped>
272
+ .socket-simulator {
273
+ display: flex;
274
+ flex-direction: column;
275
+ gap: 20px;
276
+ }
277
+
278
+ .simulator-header {
279
+ display: flex;
280
+ justify-content: space-between;
281
+ align-items: center;
282
+ padding: 12px 16px;
283
+ background: #2d2d2d;
284
+ border-radius: 8px;
285
+ }
286
+
287
+ .connection-status {
288
+ display: flex;
289
+ align-items: center;
290
+ gap: 8px;
291
+ font-size: 13px;
292
+ color: #ff6b6b;
293
+ }
294
+
295
+ .connection-status.connected {
296
+ color: #51cf66;
297
+ }
298
+
299
+ .status-dot {
300
+ width: 8px;
301
+ height: 8px;
302
+ border-radius: 50%;
303
+ background: currentColor;
304
+ }
305
+
306
+ .socket-port {
307
+ font-size: 12px;
308
+ color: #888;
309
+ font-family: 'SF Mono', Monaco, monospace;
310
+ }
311
+
312
+ .event-list h4,
313
+ .event-editor h4,
314
+ .event-log h4 {
315
+ margin: 0 0 8px 0;
316
+ font-size: 13px;
317
+ color: #e0e0e0;
318
+ }
319
+
320
+ .helper-text {
321
+ font-size: 12px;
322
+ color: #888;
323
+ margin: 0 0 12px 0;
324
+ }
325
+
326
+ .helper-text code,
327
+ .hint code {
328
+ background: #3d3d3d;
329
+ padding: 2px 6px;
330
+ border-radius: 3px;
331
+ font-family: 'SF Mono', Monaco, monospace;
332
+ font-size: 11px;
333
+ }
334
+
335
+ .empty-state {
336
+ padding: 20px;
337
+ text-align: center;
338
+ color: #666;
339
+ background: #2d2d2d;
340
+ border-radius: 8px;
341
+ }
342
+
343
+ .empty-state p {
344
+ margin: 0;
345
+ }
346
+
347
+ .hint {
348
+ font-size: 11px;
349
+ margin-top: 8px !important;
350
+ }
351
+
352
+ .events-grid {
353
+ display: grid;
354
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
355
+ gap: 12px;
356
+ }
357
+
358
+ .event-card {
359
+ background: #2d2d2d;
360
+ border-radius: 8px;
361
+ padding: 12px;
362
+ cursor: pointer;
363
+ border: 2px solid transparent;
364
+ transition: all 0.2s;
365
+ }
366
+
367
+ .event-card:hover {
368
+ border-color: #3d3d3d;
369
+ background: #333;
370
+ }
371
+
372
+ .event-card.selected {
373
+ border-color: #61dafb;
374
+ background: #2a3a4a;
375
+ }
376
+
377
+ .event-name {
378
+ font-size: 13px;
379
+ font-weight: 500;
380
+ color: #e0e0e0;
381
+ margin-bottom: 4px;
382
+ }
383
+
384
+ .event-type {
385
+ font-size: 11px;
386
+ color: #888;
387
+ font-family: 'SF Mono', Monaco, monospace;
388
+ }
389
+
390
+ .event-editor {
391
+ background: #2d2d2d;
392
+ border-radius: 8px;
393
+ padding: 16px;
394
+ }
395
+
396
+ .editor-field {
397
+ margin-bottom: 12px;
398
+ }
399
+
400
+ .editor-field label {
401
+ display: block;
402
+ font-size: 12px;
403
+ color: #888;
404
+ margin-bottom: 4px;
405
+ }
406
+
407
+ .field-input,
408
+ .field-textarea {
409
+ width: 100%;
410
+ background: #1e1e1e;
411
+ border: 1px solid #3d3d3d;
412
+ color: #e0e0e0;
413
+ padding: 8px 12px;
414
+ border-radius: 4px;
415
+ font-family: 'SF Mono', Monaco, monospace;
416
+ font-size: 12px;
417
+ box-sizing: border-box;
418
+ }
419
+
420
+ .field-input:focus,
421
+ .field-textarea:focus {
422
+ outline: none;
423
+ border-color: #61dafb;
424
+ }
425
+
426
+ .field-textarea {
427
+ resize: vertical;
428
+ min-height: 100px;
429
+ }
430
+
431
+ .json-error {
432
+ display: block;
433
+ color: #ff6b6b;
434
+ font-size: 11px;
435
+ margin-top: 4px;
436
+ }
437
+
438
+ .editor-actions {
439
+ display: flex;
440
+ gap: 8px;
441
+ margin-top: 16px;
442
+ }
443
+
444
+ .btn {
445
+ padding: 8px 16px;
446
+ border: none;
447
+ border-radius: 4px;
448
+ cursor: pointer;
449
+ font-size: 12px;
450
+ transition: all 0.2s;
451
+ }
452
+
453
+ .btn-primary {
454
+ background: #61dafb;
455
+ color: #1e1e1e;
456
+ }
457
+
458
+ .btn-primary:hover {
459
+ background: #4fc3f7;
460
+ }
461
+
462
+ .btn-primary:disabled {
463
+ background: #3d3d3d;
464
+ color: #666;
465
+ cursor: not-allowed;
466
+ }
467
+
468
+ .btn-secondary {
469
+ background: #3d3d3d;
470
+ color: #e0e0e0;
471
+ }
472
+
473
+ .btn-secondary:hover {
474
+ background: #4d4d4d;
475
+ }
476
+
477
+ .event-log {
478
+ background: #2d2d2d;
479
+ border-radius: 8px;
480
+ overflow: hidden;
481
+ }
482
+
483
+ .log-header {
484
+ display: flex;
485
+ justify-content: space-between;
486
+ align-items: center;
487
+ padding: 12px 16px;
488
+ border-bottom: 1px solid #3d3d3d;
489
+ }
490
+
491
+ .log-header h4 {
492
+ margin: 0;
493
+ }
494
+
495
+ .btn-clear {
496
+ background: transparent;
497
+ border: none;
498
+ color: #888;
499
+ cursor: pointer;
500
+ font-size: 11px;
501
+ }
502
+
503
+ .btn-clear:hover {
504
+ color: #e0e0e0;
505
+ }
506
+
507
+ .log-entries {
508
+ max-height: 200px;
509
+ overflow-y: auto;
510
+ }
511
+
512
+ .empty-log {
513
+ padding: 20px;
514
+ text-align: center;
515
+ color: #666;
516
+ font-size: 12px;
517
+ }
518
+
519
+ .log-entry {
520
+ display: flex;
521
+ gap: 12px;
522
+ padding: 8px 16px;
523
+ font-size: 11px;
524
+ font-family: 'SF Mono', Monaco, monospace;
525
+ border-bottom: 1px solid #252525;
526
+ }
527
+
528
+ .log-entry:last-child {
529
+ border-bottom: none;
530
+ }
531
+
532
+ .log-time {
533
+ color: #666;
534
+ min-width: 70px;
535
+ }
536
+
537
+ .log-direction {
538
+ color: #61dafb;
539
+ }
540
+
541
+ .log-event {
542
+ color: #e0e0e0;
543
+ flex: 1;
544
+ }
545
+
546
+ .log-status {
547
+ min-width: 60px;
548
+ text-align: right;
549
+ }
550
+
551
+ .log-entry.success .log-status {
552
+ color: #51cf66;
553
+ }
554
+
555
+ .log-entry.error .log-status {
556
+ color: #ff6b6b;
557
+ }
558
+
559
+ .log-entry.pending .log-status {
560
+ color: #ffd43b;
561
+ }
562
+ </style>