lula2 0.0.5 → 0.0.6

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 (108) hide show
  1. package/README.md +291 -8
  2. package/dist/_app/env.js +1 -0
  3. package/dist/_app/immutable/assets/0.DtiRW3lO.css +1 -0
  4. package/dist/_app/immutable/assets/DynamicControlEditor.BkVTzFZ-.css +1 -0
  5. package/dist/_app/immutable/chunks/7x_q-1ab.js +1 -0
  6. package/dist/_app/immutable/chunks/B19gt6-g.js +2 -0
  7. package/dist/_app/immutable/chunks/BR-0Dorr.js +1 -0
  8. package/dist/_app/immutable/chunks/B_3ksxz5.js +2 -0
  9. package/dist/_app/immutable/chunks/Bg_R1qWi.js +3 -0
  10. package/dist/_app/immutable/chunks/D3aNP_lg.js +1 -0
  11. package/dist/_app/immutable/chunks/D4Q_ObIy.js +1 -0
  12. package/dist/_app/immutable/chunks/DsnmJJEf.js +1 -0
  13. package/dist/_app/immutable/chunks/XY2j_owG.js +66 -0
  14. package/dist/_app/immutable/chunks/rzN25oDf.js +1 -0
  15. package/dist/_app/immutable/entry/app.r0uOd9qg.js +2 -0
  16. package/dist/_app/immutable/entry/start.DvoqR0rc.js +1 -0
  17. package/dist/_app/immutable/nodes/0.Ct6FAss_.js +1 -0
  18. package/dist/_app/immutable/nodes/1.DLoKuy8Q.js +1 -0
  19. package/dist/_app/immutable/nodes/2.IRkwSmiB.js +1 -0
  20. package/dist/_app/immutable/nodes/3.BrTg-ZHv.js +1 -0
  21. package/dist/_app/immutable/nodes/4.Blq-4WQS.js +9 -0
  22. package/dist/_app/version.json +1 -0
  23. package/dist/cli/commands/crawl.js +128 -0
  24. package/dist/cli/commands/ui.js +2769 -0
  25. package/dist/cli/commands/version.js +30 -0
  26. package/dist/cli/server/index.js +2713 -0
  27. package/dist/cli/server/server.js +2702 -0
  28. package/dist/cli/server/serverState.js +1199 -0
  29. package/dist/cli/server/spreadsheetRoutes.js +788 -0
  30. package/dist/cli/server/types.js +0 -0
  31. package/dist/cli/server/websocketServer.js +2625 -0
  32. package/dist/cli/utils/debug.js +24 -0
  33. package/dist/favicon.svg +1 -0
  34. package/dist/index.html +38 -0
  35. package/dist/index.js +2924 -37
  36. package/dist/lula.png +0 -0
  37. package/dist/lula2 +2 -0
  38. package/package.json +120 -72
  39. package/src/app.css +192 -0
  40. package/src/app.d.ts +13 -0
  41. package/src/app.html +13 -0
  42. package/src/lib/actions/fadeWhenScrollable.ts +39 -0
  43. package/src/lib/actions/modal.ts +230 -0
  44. package/src/lib/actions/tooltip.ts +82 -0
  45. package/src/lib/components/control-sets/ControlSetInfo.svelte +20 -0
  46. package/src/lib/components/control-sets/ControlSetSelector.svelte +46 -0
  47. package/src/lib/components/control-sets/index.ts +5 -0
  48. package/src/lib/components/controls/ControlDetailsPanel.svelte +235 -0
  49. package/src/lib/components/controls/ControlsList.svelte +608 -0
  50. package/src/lib/components/controls/DynamicControlEditor.svelte +298 -0
  51. package/src/lib/components/controls/MappingCard.svelte +105 -0
  52. package/src/lib/components/controls/MappingForm.svelte +188 -0
  53. package/src/lib/components/controls/index.ts +9 -0
  54. package/src/lib/components/controls/renderers/EditableFieldRenderer.svelte +103 -0
  55. package/src/lib/components/controls/renderers/FieldRenderer.svelte +49 -0
  56. package/src/lib/components/controls/renderers/index.ts +5 -0
  57. package/src/lib/components/controls/tabs/CustomFieldsTab.svelte +130 -0
  58. package/src/lib/components/controls/tabs/ImplementationTab.svelte +127 -0
  59. package/src/lib/components/controls/tabs/MappingsTab.svelte +182 -0
  60. package/src/lib/components/controls/tabs/OverviewTab.svelte +151 -0
  61. package/src/lib/components/controls/tabs/TimelineTab.svelte +41 -0
  62. package/src/lib/components/controls/tabs/index.ts +8 -0
  63. package/src/lib/components/controls/utils/ProcessedTextRenderer.svelte +63 -0
  64. package/src/lib/components/controls/utils/textProcessor.ts +164 -0
  65. package/src/lib/components/forms/DynamicControlForm.svelte +340 -0
  66. package/src/lib/components/forms/DynamicField.svelte +494 -0
  67. package/src/lib/components/forms/FormField.svelte +107 -0
  68. package/src/lib/components/forms/index.ts +6 -0
  69. package/src/lib/components/setup/ExistingControlSets.svelte +284 -0
  70. package/src/lib/components/setup/SpreadsheetImport.svelte +968 -0
  71. package/src/lib/components/setup/index.ts +5 -0
  72. package/src/lib/components/ui/Dropdown.svelte +107 -0
  73. package/src/lib/components/ui/EmptyState.svelte +80 -0
  74. package/src/lib/components/ui/FeatureToggle.svelte +50 -0
  75. package/src/lib/components/ui/SearchBar.svelte +73 -0
  76. package/src/lib/components/ui/StatusBadge.svelte +79 -0
  77. package/src/lib/components/ui/TabNavigation.svelte +48 -0
  78. package/src/lib/components/ui/Tooltip.svelte +120 -0
  79. package/src/lib/components/ui/index.ts +10 -0
  80. package/src/lib/components/version-control/DiffViewer.svelte +292 -0
  81. package/src/lib/components/version-control/TimelineItem.svelte +107 -0
  82. package/src/lib/components/version-control/YamlDiffViewer.svelte +428 -0
  83. package/src/lib/components/version-control/index.ts +6 -0
  84. package/src/lib/form-types.ts +57 -0
  85. package/src/lib/formatUtils.ts +17 -0
  86. package/src/lib/index.ts +5 -0
  87. package/src/lib/types.ts +180 -0
  88. package/src/lib/websocket.ts +359 -0
  89. package/src/routes/+layout.svelte +236 -0
  90. package/src/routes/+page.svelte +38 -0
  91. package/src/routes/control/[id]/+page.svelte +112 -0
  92. package/src/routes/setup/+page.svelte +241 -0
  93. package/src/stores/compliance.ts +95 -0
  94. package/src/styles/highlightjs.css +20 -0
  95. package/src/styles/modal.css +58 -0
  96. package/src/styles/tables.css +111 -0
  97. package/src/styles/tooltip.css +65 -0
  98. package/dist/controls/index.d.ts +0 -18
  99. package/dist/controls/index.d.ts.map +0 -1
  100. package/dist/controls/index.js +0 -18
  101. package/dist/crawl.d.ts +0 -62
  102. package/dist/crawl.d.ts.map +0 -1
  103. package/dist/crawl.js +0 -172
  104. package/dist/index.d.ts +0 -8
  105. package/dist/index.d.ts.map +0 -1
  106. package/src/controls/index.ts +0 -19
  107. package/src/crawl.ts +0 -227
  108. package/src/index.ts +0 -46
@@ -0,0 +1,428 @@
1
+ <!-- SPDX-License-Identifier: Apache-2.0 -->
2
+ <!-- SPDX-FileCopyrightText: 2023-Present The Lula Authors -->
3
+
4
+ <script lang="ts">
5
+ import { formatValue } from '$lib/formatUtils';
6
+
7
+ interface Props {
8
+ yamlDiff: any; // YamlDiffResult
9
+ showToggle?: boolean;
10
+ }
11
+
12
+ let { yamlDiff, showToggle = true }: Props = $props();
13
+
14
+ let showDetailedView = $state(false); // Default to summary/compact view
15
+
16
+ function getChangeIcon(type: string) {
17
+ switch (type) {
18
+ case 'added':
19
+ return '+';
20
+ case 'removed':
21
+ return '-';
22
+ case 'modified':
23
+ return '~';
24
+ default:
25
+ return '•';
26
+ }
27
+ }
28
+
29
+ function getChangeColor(type: string) {
30
+ switch (type) {
31
+ case 'added':
32
+ return 'text-green-600 dark:text-green-400 bg-green-50 dark:bg-green-900/20';
33
+ case 'removed':
34
+ return 'text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-900/20';
35
+ case 'modified':
36
+ return 'text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-900/20';
37
+ default:
38
+ return 'text-gray-600 dark:text-gray-400 bg-gray-50 dark:bg-gray-900/20';
39
+ }
40
+ }
41
+
42
+ // Function to highlight character differences in text
43
+ function getTextDiff(oldText: string, newText: string) {
44
+ if (!oldText && !newText) return { oldHighlighted: '', newHighlighted: '' };
45
+ if (!oldText) return { oldHighlighted: '', newHighlighted: escapeHtml(newText) };
46
+ if (!newText) return { oldHighlighted: escapeHtml(oldText), newHighlighted: '' };
47
+
48
+ const oldStr = String(oldText);
49
+ const newStr = String(newText);
50
+
51
+ // Split on word boundaries, keeping separators
52
+ const oldTokens = oldStr.split(/(\s+)/);
53
+ const newTokens = newStr.split(/(\s+)/);
54
+
55
+ let oldHighlighted = '';
56
+ let newHighlighted = '';
57
+
58
+ // Simple LCS-style alignment
59
+ const oldLen = oldTokens.length;
60
+ const newLen = newTokens.length;
61
+
62
+ // Create a table to track matches
63
+ const dp = Array(oldLen + 1)
64
+ .fill(null)
65
+ .map(() => Array(newLen + 1).fill(0));
66
+
67
+ // Fill LCS table
68
+ for (let i = 1; i <= oldLen; i++) {
69
+ for (let j = 1; j <= newLen; j++) {
70
+ if (oldTokens[i - 1] === newTokens[j - 1]) {
71
+ dp[i][j] = dp[i - 1][j - 1] + 1;
72
+ } else {
73
+ dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
74
+ }
75
+ }
76
+ }
77
+
78
+ // Trace back to find the actual differences
79
+ let i = oldLen,
80
+ j = newLen;
81
+ const oldChanges = [];
82
+ const newChanges = [];
83
+
84
+ while (i > 0 || j > 0) {
85
+ if (i > 0 && j > 0 && oldTokens[i - 1] === newTokens[j - 1]) {
86
+ // Tokens match
87
+ oldChanges.unshift({ token: oldTokens[i - 1], changed: false });
88
+ newChanges.unshift({ token: newTokens[j - 1], changed: false });
89
+ i--;
90
+ j--;
91
+ } else if (i > 0 && (j === 0 || dp[i - 1][j] >= dp[i][j - 1])) {
92
+ // Token was deleted
93
+ oldChanges.unshift({ token: oldTokens[i - 1], changed: true });
94
+ i--;
95
+ } else if (j > 0) {
96
+ // Token was added
97
+ newChanges.unshift({ token: newTokens[j - 1], changed: true });
98
+ j--;
99
+ }
100
+ }
101
+
102
+ // Build highlighted strings
103
+ oldHighlighted = oldChanges
104
+ .map(({ token, changed }) =>
105
+ changed
106
+ ? `<mark class="bg-red-200 dark:bg-red-800 text-red-900 dark:text-red-100 px-0.5 rounded">${escapeHtml(token)}</mark>`
107
+ : escapeHtml(token)
108
+ )
109
+ .join('');
110
+
111
+ newHighlighted = newChanges
112
+ .map(({ token, changed }) =>
113
+ changed
114
+ ? `<mark class="bg-green-200 dark:bg-green-800 text-green-900 dark:text-green-100 px-0.5 rounded">${escapeHtml(token)}</mark>`
115
+ : escapeHtml(token)
116
+ )
117
+ .join('');
118
+
119
+ return { oldHighlighted, newHighlighted };
120
+ }
121
+
122
+ // Helper function to escape HTML
123
+ function escapeHtml(text: string): string {
124
+ const div = document.createElement('div');
125
+ div.textContent = text;
126
+ return div.innerHTML;
127
+ }
128
+
129
+ // Helper function to truncate text while preserving highlights
130
+ function truncateWithHighlight(htmlText: string, maxLength: number): string {
131
+ if (!htmlText) return '';
132
+
133
+ // Remove HTML tags to check actual text length
134
+ const tempDiv = document.createElement('div');
135
+ tempDiv.innerHTML = htmlText;
136
+ const textContent = tempDiv.textContent || tempDiv.innerText || '';
137
+
138
+ if (textContent.length <= maxLength) {
139
+ return htmlText;
140
+ }
141
+
142
+ // If we need to truncate and there are highlights, find the best section
143
+ if (htmlText.includes('<mark')) {
144
+ // Find all highlights and their positions
145
+ const markRegex = /<mark[^>]*>([^<]*)<\/mark>/g;
146
+ const highlights = [];
147
+ let match;
148
+
149
+ while ((match = markRegex.exec(htmlText)) !== null) {
150
+ highlights.push({
151
+ start: match.index,
152
+ end: match.index + match[0].length,
153
+ text: match[1],
154
+ fullMatch: match[0]
155
+ });
156
+ }
157
+
158
+ if (highlights.length > 0) {
159
+ // Find the first substantial highlight (not just whitespace)
160
+ const goodHighlight = highlights.find((h) => h.text.trim().length > 0) || highlights[0];
161
+
162
+ // Calculate context window around this highlight
163
+ const contextSize = Math.floor((maxLength - goodHighlight.text.length) / 2);
164
+
165
+ // Find text boundaries in the original HTML
166
+ const beforeText = htmlText.substring(0, goodHighlight.start);
167
+ const afterText = htmlText.substring(goodHighlight.end);
168
+
169
+ // Convert HTML positions to text positions for better calculation
170
+ const beforeDiv = document.createElement('div');
171
+ beforeDiv.innerHTML = beforeText;
172
+ const beforePlainText = beforeDiv.textContent || '';
173
+
174
+ const afterDiv = document.createElement('div');
175
+ afterDiv.innerHTML = afterText;
176
+ const afterPlainText = afterDiv.textContent || '';
177
+
178
+ // Calculate how much context to show
179
+ const beforeContextLength = Math.min(beforePlainText.length, contextSize);
180
+ const afterContextLength = Math.min(afterPlainText.length, contextSize);
181
+
182
+ // Find word boundaries for cleaner truncation
183
+ const beforeStart = Math.max(0, beforePlainText.length - beforeContextLength);
184
+ const afterEnd = Math.min(afterPlainText.length, afterContextLength);
185
+
186
+ // Find corresponding positions in HTML (simplified approach)
187
+ let beforeTruncated = '';
188
+ let afterTruncated = '';
189
+
190
+ if (beforeStart < beforePlainText.length) {
191
+ // Try to find a reasonable cut point in the HTML
192
+ const ratio = beforeStart / beforePlainText.length;
193
+ const htmlCutPoint = Math.floor(beforeText.length * ratio);
194
+ beforeTruncated = '...' + beforeText.substring(htmlCutPoint);
195
+ } else {
196
+ beforeTruncated = beforeText;
197
+ }
198
+
199
+ if (afterEnd < afterPlainText.length) {
200
+ const ratio = afterEnd / afterPlainText.length;
201
+ const htmlCutPoint = Math.floor(afterText.length * ratio);
202
+ afterTruncated = afterText.substring(0, htmlCutPoint) + '...';
203
+ } else {
204
+ afterTruncated = afterText;
205
+ }
206
+
207
+ return beforeTruncated + goodHighlight.fullMatch + afterTruncated;
208
+ }
209
+ }
210
+
211
+ // Fallback: simple word-boundary truncation
212
+ const words = textContent.split(/\s+/);
213
+ let result = '';
214
+ let currentLength = 0;
215
+
216
+ for (const word of words) {
217
+ if (currentLength + word.length + (result ? 1 : 0) > maxLength) {
218
+ break;
219
+ }
220
+ if (result) result += ' ';
221
+ result += word;
222
+ currentLength += word.length + (result.length > word.length ? 1 : 0);
223
+ }
224
+
225
+ return escapeHtml(result) + (currentLength < textContent.length ? '...' : '');
226
+ }
227
+ </script>
228
+
229
+ <div
230
+ class="yaml-diff-viewer bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm overflow-hidden"
231
+ >
232
+ <div class="bg-gray-100 dark:bg-gray-900 px-4 py-2 border-b border-gray-200 dark:border-gray-700">
233
+ <div class="flex items-center justify-between">
234
+ <div class="flex items-center space-x-2">
235
+ <svg class="w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
236
+ <path
237
+ stroke-linecap="round"
238
+ stroke-linejoin="round"
239
+ stroke-width="2"
240
+ d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"
241
+ />
242
+ </svg>
243
+ <span class="text-sm font-medium text-gray-700 dark:text-gray-300">Changes</span>
244
+ <span
245
+ class="text-xs text-gray-500 dark:text-gray-400 bg-gray-200 dark:bg-gray-700 px-2 py-0.5 rounded-full"
246
+ >
247
+ {yamlDiff?.summary || 'No changes'}
248
+ </span>
249
+ </div>
250
+
251
+ {#if showToggle}
252
+ <button
253
+ onclick={() => (showDetailedView = !showDetailedView)}
254
+ class="inline-flex items-center px-2 py-1 text-xs font-medium text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 bg-gray-200 dark:bg-gray-700 rounded hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors"
255
+ >
256
+ <svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
257
+ <path
258
+ stroke-linecap="round"
259
+ stroke-linejoin="round"
260
+ stroke-width="2"
261
+ d="M13 10V3L4 14h7v7l9-11h-7z"
262
+ />
263
+ </svg>
264
+ {showDetailedView ? 'Summary' : 'Details'}
265
+ </button>
266
+ {/if}
267
+ </div>
268
+ </div>
269
+
270
+ <div class="max-h-96 overflow-y-auto">
271
+ {#if yamlDiff?.changes && yamlDiff.changes.length > 0}
272
+ {#if showDetailedView}
273
+ <!-- Detailed view with full context -->
274
+ <div class="divide-y divide-gray-200 dark:divide-gray-600">
275
+ {#each yamlDiff.changes as change}
276
+ <div class="p-3 {getChangeColor(change.type)}">
277
+ <div class="flex items-start space-x-3">
278
+ <div
279
+ class="flex-shrink-0 w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold"
280
+ >
281
+ {getChangeIcon(change.type)}
282
+ </div>
283
+
284
+ <div class="flex-1 min-w-0">
285
+ <div class="flex items-center space-x-2 mb-1">
286
+ <span class="text-sm font-medium text-gray-900 dark:text-white">
287
+ {change.description}
288
+ </span>
289
+ <span
290
+ class="text-xs text-gray-500 dark:text-gray-400 font-mono bg-gray-100 dark:bg-gray-800 px-2 py-0.5 rounded"
291
+ >
292
+ {change.path}
293
+ </span>
294
+ </div>
295
+
296
+ {#if change.type === 'modified'}
297
+ <div class="text-sm space-y-1">
298
+ <div class="flex items-start space-x-2">
299
+ <span class="text-red-600 dark:text-red-400 font-mono text-xs">-</span>
300
+ <code
301
+ class="text-red-700 dark:text-red-300 bg-red-100 dark:bg-red-900/30 px-1 py-0.5 rounded text-xs"
302
+ >
303
+ {formatValue(change.oldValue)}
304
+ </code>
305
+ </div>
306
+ <div class="flex items-start space-x-2">
307
+ <span class="text-green-600 dark:text-green-400 font-mono text-xs">+</span>
308
+ <code
309
+ class="text-green-700 dark:text-green-300 bg-green-100 dark:bg-green-900/30 px-1 py-0.5 rounded text-xs"
310
+ >
311
+ {formatValue(change.newValue)}
312
+ </code>
313
+ </div>
314
+ </div>
315
+ {:else if change.type === 'added'}
316
+ <div class="text-sm">
317
+ <code
318
+ class="text-green-700 dark:text-green-300 bg-green-100 dark:bg-green-900/30 px-1 py-0.5 rounded text-xs"
319
+ >
320
+ {formatValue(change.newValue)}
321
+ </code>
322
+ </div>
323
+ {:else if change.type === 'removed'}
324
+ <div class="text-sm">
325
+ <code
326
+ class="text-red-700 dark:text-red-300 bg-red-100 dark:bg-red-900/30 px-1 py-0.5 rounded text-xs"
327
+ >
328
+ {formatValue(change.oldValue)}
329
+ </code>
330
+ </div>
331
+ {/if}
332
+ </div>
333
+ </div>
334
+ </div>
335
+ {/each}
336
+ </div>
337
+ {:else}
338
+ <!-- Summary view - more compact -->
339
+ <div class="p-4 space-y-2">
340
+ {#each yamlDiff.changes as change}
341
+ <div class="flex items-center space-x-2 text-sm">
342
+ <span
343
+ class="w-5 h-5 rounded-full flex items-center justify-center text-xs font-bold {getChangeColor(
344
+ change.type
345
+ )}"
346
+ >
347
+ {getChangeIcon(change.type)}
348
+ </span>
349
+
350
+ <span class="font-medium text-gray-900 dark:text-white">
351
+ {change.description}
352
+ </span>
353
+
354
+ {#if change.type === 'modified' && change.oldValue !== change.newValue}
355
+ {@const diff = getTextDiff(
356
+ formatValue(change.oldValue),
357
+ formatValue(change.newValue)
358
+ )}
359
+ {@const oldTruncated = truncateWithHighlight(
360
+ diff.oldHighlighted || escapeHtml(formatValue(change.oldValue)),
361
+ 80
362
+ )}
363
+ {@const newTruncated = truncateWithHighlight(
364
+ diff.newHighlighted || escapeHtml(formatValue(change.newValue)),
365
+ 80
366
+ )}
367
+ <div class="flex items-center space-x-2 text-xs ml-2 mt-1">
368
+ <span class="text-red-600 dark:text-red-400 font-mono">−</span>
369
+ <code
370
+ class="text-red-600 dark:text-red-400 bg-red-100 dark:bg-red-900/30 px-2 py-1 rounded"
371
+ >
372
+ {@html oldTruncated}
373
+ </code>
374
+ </div>
375
+ <div class="flex items-center space-x-2 text-xs ml-2 mt-1">
376
+ <span class="text-green-600 dark:text-green-400 font-mono">+</span>
377
+ <code
378
+ class="text-green-600 dark:text-green-400 bg-green-100 dark:bg-green-900/30 px-2 py-1 rounded"
379
+ >
380
+ {@html newTruncated}
381
+ </code>
382
+ </div>
383
+ {:else if change.type === 'added' && change.newValue !== undefined}
384
+ <div class="flex items-center space-x-1 text-xs ml-2">
385
+ <code
386
+ class="text-green-600 dark:text-green-400 bg-green-100 dark:bg-green-900/30 px-1 py-0.5 rounded"
387
+ >
388
+ {typeof change.newValue === 'string' && change.newValue.length > 20
389
+ ? change.newValue.substring(0, 20) + '...'
390
+ : formatValue(change.newValue)}
391
+ </code>
392
+ </div>
393
+ {:else if change.type === 'removed' && change.oldValue !== undefined}
394
+ <div class="flex items-center space-x-1 text-xs ml-2">
395
+ <code
396
+ class="text-red-600 dark:text-red-400 bg-red-100 dark:bg-red-900/30 px-1 py-0.5 rounded"
397
+ >
398
+ {typeof change.oldValue === 'string' && change.oldValue.length > 20
399
+ ? change.oldValue.substring(0, 20) + '...'
400
+ : formatValue(change.oldValue)}
401
+ </code>
402
+ </div>
403
+ {/if}
404
+ </div>
405
+ {/each}
406
+ </div>
407
+ {/if}
408
+ {:else}
409
+ <div class="p-6 text-center text-gray-500 dark:text-gray-400">
410
+ <svg class="mx-auto h-8 w-8 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
411
+ <path
412
+ stroke-linecap="round"
413
+ stroke-linejoin="round"
414
+ stroke-width="2"
415
+ d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
416
+ />
417
+ </svg>
418
+ <p class="text-sm">No changes detected</p>
419
+ </div>
420
+ {/if}
421
+ </div>
422
+ </div>
423
+
424
+ <style>
425
+ .yaml-diff-viewer {
426
+ font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
427
+ }
428
+ </style>
@@ -0,0 +1,6 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2023-Present The Lula Authors
3
+
4
+ export { default as DiffViewer } from './DiffViewer.svelte';
5
+ export { default as TimelineItem } from './TimelineItem.svelte';
6
+ export { default as YamlDiffViewer } from './YamlDiffViewer.svelte';
@@ -0,0 +1,57 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2023-Present The Lula Authors
3
+
4
+ export interface FieldDefinition {
5
+ id: string;
6
+ label: string;
7
+ type:
8
+ | 'text'
9
+ | 'textarea'
10
+ | 'select'
11
+ | 'boolean'
12
+ | 'date'
13
+ | 'number'
14
+ | 'string-array'
15
+ | 'object-array';
16
+ description?: string;
17
+ placeholder?: string;
18
+ required?: boolean;
19
+ group?: string;
20
+ rows?: number; // for textarea
21
+ options?: string[]; // for select
22
+ validation?: ValidationRule[];
23
+ helpText?: string;
24
+ arrayItemType?: 'string' | 'object'; // for array types
25
+ arraySchema?: any; // schema for object array items
26
+ }
27
+
28
+ export interface ValidationRule {
29
+ type: 'minLength' | 'maxLength' | 'pattern';
30
+ value: number; // Made required to fix TypeScript errors
31
+ pattern?: string;
32
+ message?: string;
33
+ }
34
+
35
+ export interface ControlSchema {
36
+ name: string;
37
+ version: string;
38
+ fields: FieldDefinition[];
39
+ groups?: FieldGroup[];
40
+ }
41
+
42
+ export interface FieldGroup {
43
+ id: string;
44
+ label: string;
45
+ description?: string;
46
+ }
47
+
48
+ export interface ValidationError {
49
+ field: string;
50
+ message: string;
51
+ }
52
+
53
+ export interface ValidationResult {
54
+ valid: boolean;
55
+ errors: ValidationError[];
56
+ warnings: ValidationError[];
57
+ }
@@ -0,0 +1,17 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2023-Present The Lula Authors
3
+
4
+ import * as YAML from 'yaml';
5
+
6
+ /**
7
+ * Format a value for display in diffs
8
+ */
9
+ export function formatValue(value: any): string {
10
+ if (value === null) return 'null';
11
+ if (value === undefined) return 'undefined';
12
+ if (typeof value === 'string') return `"${value}"`;
13
+ if (typeof value === 'object') {
14
+ return YAML.stringify(value).trim();
15
+ }
16
+ return String(value);
17
+ }
@@ -0,0 +1,5 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2023-Present The Lula Authors
3
+
4
+ // Types (base types first)
5
+ export type * from './types';
@@ -0,0 +1,180 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2023-Present The Lula Authors
3
+ export interface Control {
4
+ id: string;
5
+ title: string;
6
+ family: string;
7
+ class?: string;
8
+ sort_id?: string;
9
+ statement?: string;
10
+ guidance?: string;
11
+ objectives?: string[];
12
+ assessment_methods?: string[];
13
+ properties?: { [key: string]: string };
14
+ links?: Array<{
15
+ href: string;
16
+ rel?: string;
17
+ text?: string;
18
+ }>;
19
+ parameters?: Array<{
20
+ id: string;
21
+ label?: string;
22
+ usage?: string;
23
+ values?: string[];
24
+ guidelines?: string[];
25
+ constraints?: string[];
26
+ }>;
27
+ enhancements?: Control[];
28
+ // Allow dynamic field access for form components
29
+ [key: string]: any;
30
+ }
31
+
32
+ export interface Mapping {
33
+ uuid: string;
34
+ control_id: string;
35
+ justification: string;
36
+ source_entries: SourceEntry[];
37
+ status: 'planned' | 'implemented' | 'verified';
38
+ created_by?: string;
39
+ }
40
+
41
+ export interface SourceEntry {
42
+ location: string;
43
+ shasum?: string;
44
+ }
45
+
46
+ export interface ControlWithMappings extends Control {
47
+ mappings: Mapping[];
48
+ }
49
+
50
+ export interface SearchResult {
51
+ controls: Control[];
52
+ mappings: Mapping[];
53
+ }
54
+
55
+ export interface Stats {
56
+ controls: number;
57
+ mappings: number;
58
+ families: number;
59
+ familyList: string[];
60
+ }
61
+
62
+ export interface FieldSchema {
63
+ type: string;
64
+ ui_type:
65
+ | 'short_text'
66
+ | 'medium_text'
67
+ | 'textarea'
68
+ | 'select'
69
+ | 'date'
70
+ | 'number'
71
+ | 'boolean'
72
+ | 'long_text';
73
+ is_array: boolean;
74
+ max_length?: number;
75
+ usage_count?: number;
76
+ usage_percentage?: number;
77
+ required: boolean;
78
+ visible: boolean;
79
+ show_in_table?: boolean;
80
+ editable: boolean;
81
+ display_order: number;
82
+ category: 'core' | 'content' | 'metadata' | 'compliance' | 'custom';
83
+ tab?: 'overview' | 'implementation' | 'custom' | 'hidden';
84
+ examples?: any[];
85
+ options?: string[];
86
+ original_name?: string;
87
+ }
88
+
89
+ export interface ControlSet {
90
+ id: string;
91
+ name: string;
92
+ title?: string;
93
+ description?: string;
94
+ version?: string;
95
+ lastModified?: string;
96
+ path?: string;
97
+ families?: Array<{
98
+ id: string;
99
+ name: string;
100
+ control_count?: number;
101
+ }>;
102
+ project?: {
103
+ framework?: {
104
+ baseline?: string;
105
+ name?: string;
106
+ };
107
+ };
108
+ statistics?: {
109
+ total_controls?: number;
110
+ families?: number;
111
+ };
112
+ metadata?: {
113
+ source?: string;
114
+ baseline?: string;
115
+ revision?: string;
116
+ [key: string]: any;
117
+ };
118
+ fieldSchema?: {
119
+ fields: Record<string, FieldSchema>;
120
+ total_controls?: number;
121
+ analyzed_at?: string;
122
+ };
123
+ field_schema?: {
124
+ fields: Record<string, FieldSchema>;
125
+ total_controls?: number;
126
+ analyzed_at?: string;
127
+ };
128
+ }
129
+
130
+ export interface ControlSetInfo {
131
+ currentSet: ControlSet;
132
+ availableSets: ControlSet[];
133
+ }
134
+
135
+ export interface GitCommit {
136
+ hash: string;
137
+ shortHash: string;
138
+ author: string;
139
+ authorEmail: string;
140
+ date: string;
141
+ message: string;
142
+ changes: {
143
+ insertions: number;
144
+ deletions: number;
145
+ files: number;
146
+ };
147
+ diff?: string; // The actual file diff
148
+ yamlDiff?: any; // Intelligent YAML diff (YamlDiffResult)
149
+ type?: string; // 'control' or 'mapping' - added by unified endpoint
150
+ fileType?: string; // 'Control File' or 'Mappings' - added by unified endpoint
151
+ isPending?: boolean; // true for uncommitted changes
152
+ isStaged?: boolean; // true for staged changes (optional since user doesn't care about staged vs unstaged)
153
+ }
154
+
155
+ export interface GitFileHistory {
156
+ filePath: string;
157
+ commits: GitCommit[];
158
+ totalCommits: number;
159
+ firstCommit: GitCommit | null;
160
+ lastCommit: GitCommit | null;
161
+ }
162
+
163
+ export interface ControlWithHistory extends Control {
164
+ history?: GitFileHistory;
165
+ }
166
+
167
+ export interface UnifiedHistory {
168
+ commits: GitCommit[];
169
+ totalCommits: number;
170
+ controlCommits: number;
171
+ mappingCommits: number;
172
+ controlFilePath?: string;
173
+ mappingFilePath?: string;
174
+ }
175
+
176
+ export interface ControlCompleteData {
177
+ control: Control;
178
+ mappings: Mapping[];
179
+ unifiedHistory: UnifiedHistory;
180
+ }