lula2 0.3.2 → 0.3.4-nightly.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/_app/immutable/assets/0.D6CB7gA7.css +1 -0
- package/dist/_app/immutable/chunks/BITsSGhD.js +65 -0
- package/dist/_app/immutable/chunks/{DVAnYkTd.js → BcKIo18J.js} +3 -3
- package/dist/_app/immutable/chunks/BhjtS45v.js +2 -0
- package/dist/_app/immutable/chunks/{BtuEtkd3.js → BkFSJLu9.js} +1 -1
- package/dist/_app/immutable/chunks/CHdfnWSV.js +1 -0
- package/dist/_app/immutable/chunks/{152nb-LI.js → CzIDw1HQ.js} +1 -1
- package/dist/_app/immutable/chunks/{1spjHGNy.js → DknPZycG.js} +1 -1
- package/dist/_app/immutable/chunks/{DY3-lqhI.js → DwdPeWTx.js} +1 -1
- package/dist/_app/immutable/chunks/{CNOPXlDW.js → vNis_B2V.js} +1 -1
- package/dist/_app/immutable/entry/app.rFT55pRO.js +2 -0
- package/dist/_app/immutable/entry/start.BWvTyytY.js +1 -0
- package/dist/_app/immutable/nodes/{0.Bw6pYYFU.js → 0.CMylckYT.js} +1 -1
- package/dist/_app/immutable/nodes/{1.DbCtPiQe.js → 1.DPLsosR-.js} +1 -1
- package/dist/_app/immutable/nodes/{2.CWnpuE-a.js → 2.DvLMOmaz.js} +1 -1
- package/dist/_app/immutable/nodes/{3.hZ7uQfPx.js → 3.hM9U_7ZV.js} +1 -1
- package/dist/_app/immutable/nodes/4.DNVoxjQw.js +11 -0
- package/dist/_app/version.json +1 -1
- package/dist/cli/commands/ui.js +17 -19
- package/dist/cli/server/index.js +17 -19
- package/dist/cli/server/server.js +17 -19
- package/dist/cli/server/serverState.js +3 -1
- package/dist/cli/server/spreadsheetRoutes.js +4 -8
- package/dist/cli/server/websocketServer.js +17 -19
- package/dist/index.html +10 -10
- package/dist/index.js +17 -19
- package/package.json +21 -22
- package/src/lib/components/control-sets/ControlSetSelector.svelte +1 -1
- package/src/lib/components/controls/ControlDetailsPanel.svelte +1 -1
- package/src/lib/components/controls/ControlsList.svelte +4 -48
- package/src/lib/components/controls/DynamicControlEditor.svelte +2 -2
- package/src/lib/components/controls/MappingCard.svelte +1 -1
- package/src/lib/components/controls/MappingForm.svelte +1 -1
- package/src/lib/components/controls/renderers/EditableFieldRenderer.svelte +1 -1
- package/src/lib/components/controls/renderers/FieldRenderer.svelte +2 -2
- package/src/lib/components/controls/tabs/CustomFieldsTab.svelte +3 -3
- package/src/lib/components/controls/tabs/ImplementationTab.svelte +3 -3
- package/src/lib/components/controls/tabs/MappingsTab.svelte +1 -1
- package/src/lib/components/controls/tabs/OverviewTab.svelte +4 -4
- package/src/lib/components/controls/tabs/TimelineTab.svelte +3 -4
- package/src/lib/components/controls/utils/ProcessedTextRenderer.svelte +4 -4
- package/src/lib/components/forms/DynamicControlForm.svelte +10 -10
- package/src/lib/components/forms/DynamicField.svelte +4 -4
- package/src/lib/components/forms/FormField.svelte +1 -1
- package/src/lib/components/setup/ExistingControlSets.svelte +1 -2
- package/src/lib/components/setup/SpreadsheetImport.svelte +112 -115
- package/src/lib/components/ui/CustomDropdown.svelte +1 -1
- package/src/lib/components/ui/FilterBuilder.svelte +4 -4
- package/src/lib/components/ui/TabNavigation.svelte +1 -1
- package/src/lib/components/version-control/DiffViewer.svelte +1 -1
- package/src/lib/components/version-control/YamlDiffViewer.svelte +2 -2
- package/src/stores/compliance.ts +2 -69
- package/dist/_app/immutable/assets/0.CLKu6Q8_.css +0 -1
- package/dist/_app/immutable/chunks/C113Bo4B.js +0 -2
- package/dist/_app/immutable/chunks/C57TYu27.js +0 -65
- package/dist/_app/immutable/chunks/Dfk9QG8E.js +0 -1
- package/dist/_app/immutable/entry/app.D0O4A2k5.js +0 -2
- package/dist/_app/immutable/entry/start.DtHY97nW.js +0 -1
- package/dist/_app/immutable/nodes/4.j9xYp9Vt.js +0 -12
|
@@ -128,8 +128,8 @@
|
|
|
128
128
|
{#if readonly}
|
|
129
129
|
<!-- View Mode: Clean minimal layout -->
|
|
130
130
|
<div class="space-y-6">
|
|
131
|
-
{#each Object.entries(fieldGroups) as [
|
|
132
|
-
{#each [fields] as fieldList}
|
|
131
|
+
{#each Object.entries(fieldGroups) as [_groupName, fields], index (index)}
|
|
132
|
+
{#each [fields] as fieldList, a (a)}
|
|
133
133
|
{@const importantFields = fieldList.filter((f) =>
|
|
134
134
|
['id', 'title', 'priority', 'status'].includes(f.id)
|
|
135
135
|
)}
|
|
@@ -147,7 +147,7 @@
|
|
|
147
147
|
{#if importantFields.length > 0}
|
|
148
148
|
<div class="pb-4 border-b border-gray-200 dark:border-gray-700">
|
|
149
149
|
<div class="flex flex-wrap items-center gap-6">
|
|
150
|
-
{#each importantFields as field}
|
|
150
|
+
{#each importantFields as field, index (index)}
|
|
151
151
|
{@const value = control[field.id]}
|
|
152
152
|
{#if value !== undefined && value !== null && value !== ''}
|
|
153
153
|
<div class="flex items-center space-x-3">
|
|
@@ -181,7 +181,7 @@
|
|
|
181
181
|
{/if}
|
|
182
182
|
|
|
183
183
|
<!-- Content sections -->
|
|
184
|
-
{#each contentFields as field}
|
|
184
|
+
{#each contentFields as field, index (index)}
|
|
185
185
|
{@const value = control[field.id]}
|
|
186
186
|
|
|
187
187
|
{#if field.type === 'textarea'}
|
|
@@ -206,7 +206,7 @@
|
|
|
206
206
|
</span>
|
|
207
207
|
</h3>
|
|
208
208
|
<div class="space-y-2">
|
|
209
|
-
{#each value as item}
|
|
209
|
+
{#each value as item, index (index)}
|
|
210
210
|
<div class="flex items-start space-x-3 py-2">
|
|
211
211
|
<div class="flex-shrink-0 w-1.5 h-1.5 bg-blue-600 rounded-full mt-2"></div>
|
|
212
212
|
<div class="text-gray-900 dark:text-white leading-relaxed">
|
|
@@ -226,13 +226,13 @@
|
|
|
226
226
|
</span>
|
|
227
227
|
</h3>
|
|
228
228
|
<div class="space-y-3">
|
|
229
|
-
{#each value as item, index}
|
|
229
|
+
{#each value as item, index (index)}
|
|
230
230
|
<div
|
|
231
231
|
class="bg-gray-50 dark:bg-gray-800 rounded-lg p-4 border border-gray-200 dark:border-gray-700"
|
|
232
232
|
>
|
|
233
233
|
{#if field.arraySchema}
|
|
234
234
|
<dl class="space-y-2">
|
|
235
|
-
{#each Object.entries(field.arraySchema) as [key, schema]}
|
|
235
|
+
{#each Object.entries(field.arraySchema) as [key, schema], index (index)}
|
|
236
236
|
{@const schemaObj = schema as any}
|
|
237
237
|
{#if item[key]}
|
|
238
238
|
<div class="flex justify-between">
|
|
@@ -281,7 +281,7 @@
|
|
|
281
281
|
{:else}
|
|
282
282
|
<!-- Edit Mode: Enhanced form layout -->
|
|
283
283
|
<div class="space-y-10">
|
|
284
|
-
{#each Object.entries(fieldGroups) as [groupName, fields]}
|
|
284
|
+
{#each Object.entries(fieldGroups) as [groupName, fields], index (index)}
|
|
285
285
|
<section
|
|
286
286
|
class="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-xl shadow-sm overflow-hidden"
|
|
287
287
|
>
|
|
@@ -300,7 +300,7 @@
|
|
|
300
300
|
<div class="p-8">
|
|
301
301
|
<!-- Improved grid layout for simple fields -->
|
|
302
302
|
<div class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-8 mb-10">
|
|
303
|
-
{#each fields as field}
|
|
303
|
+
{#each fields as field, index (index)}
|
|
304
304
|
{#if !['textarea', 'string-array', 'object-array'].includes(field.type)}
|
|
305
305
|
<div class="space-y-2">
|
|
306
306
|
<DynamicField
|
|
@@ -317,7 +317,7 @@
|
|
|
317
317
|
|
|
318
318
|
<!-- Enhanced full-width fields with better spacing -->
|
|
319
319
|
<div class="space-y-10">
|
|
320
|
-
{#each fields as field}
|
|
320
|
+
{#each fields as field, index (index)}
|
|
321
321
|
{#if ['textarea', 'string-array', 'object-array'].includes(field.type)}
|
|
322
322
|
<div
|
|
323
323
|
class="bg-gray-50 dark:bg-gray-800 rounded-xl p-6 border border-gray-200 dark:border-gray-700"
|
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
{#if !field.required}
|
|
141
141
|
<option value="">-- Select an option --</option>
|
|
142
142
|
{/if}
|
|
143
|
-
{#each field.options as option}
|
|
143
|
+
{#each field.options as option (option)}
|
|
144
144
|
<option value={option}>{option}</option>
|
|
145
145
|
{/each}
|
|
146
146
|
</select>
|
|
@@ -221,7 +221,7 @@
|
|
|
221
221
|
</button>
|
|
222
222
|
</div>
|
|
223
223
|
{:else}
|
|
224
|
-
{#each ensureArray() as item, index}
|
|
224
|
+
{#each ensureArray() as item, index (index)}
|
|
225
225
|
<div
|
|
226
226
|
class="group relative p-4 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm hover:shadow-md transition-shadow"
|
|
227
227
|
>
|
|
@@ -349,7 +349,7 @@
|
|
|
349
349
|
</button>
|
|
350
350
|
</div>
|
|
351
351
|
{:else}
|
|
352
|
-
{#each ensureArray() as item, index}
|
|
352
|
+
{#each ensureArray() as item, index (index)}
|
|
353
353
|
<div
|
|
354
354
|
class="group relative bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-xl shadow-sm hover:shadow-md transition-all duration-200"
|
|
355
355
|
>
|
|
@@ -409,7 +409,7 @@
|
|
|
409
409
|
<div class="p-4">
|
|
410
410
|
{#if field.arraySchema}
|
|
411
411
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
412
|
-
{#each Object.entries(field.arraySchema) as [key, schemaObj]}
|
|
412
|
+
{#each Object.entries(field.arraySchema) as [key, schemaObj], index (index)}
|
|
413
413
|
{@const schema = schemaObj as any}
|
|
414
414
|
<div class="space-y-2">
|
|
415
415
|
<label
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { onMount } from 'svelte';
|
|
3
2
|
import { createEventDispatcher } from 'svelte';
|
|
4
3
|
import { appState } from '$lib/websocket';
|
|
5
4
|
|
|
@@ -166,7 +165,7 @@
|
|
|
166
165
|
</div>
|
|
167
166
|
{:else if controlSets.length > 0}
|
|
168
167
|
<div class="space-y-3">
|
|
169
|
-
{#each controlSets as controlSet}
|
|
168
|
+
{#each controlSets as controlSet, index (index)}
|
|
170
169
|
{@const isCurrent = isCurrentControlSet(controlSet)}
|
|
171
170
|
<div
|
|
172
171
|
on:click={() => !isCurrent && selectControlSet(controlSet)}
|
|
@@ -13,8 +13,6 @@
|
|
|
13
13
|
let sampleData: any[] = [];
|
|
14
14
|
let controlCount = 0;
|
|
15
15
|
let rowPreviews: { row: number; preview: string }[] = [];
|
|
16
|
-
// Make availableFields reactive to both fields and fieldConfigs changes
|
|
17
|
-
$: availableFields = fields.filter((f) => fields.includes(f));
|
|
18
16
|
|
|
19
17
|
// Field configuration for tabs
|
|
20
18
|
type TabAssignment = 'overview' | 'implementation' | 'mappings' | 'custom' | null;
|
|
@@ -312,49 +310,47 @@
|
|
|
312
310
|
if (draggedField && fieldConfigs.has(draggedField)) {
|
|
313
311
|
const config = fieldConfigs.get(draggedField)!;
|
|
314
312
|
|
|
315
|
-
//
|
|
316
|
-
if (
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
.
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
313
|
+
// Special handling for mappings tab
|
|
314
|
+
if (tab === 'mappings') {
|
|
315
|
+
// Add to justificationFields if not already there
|
|
316
|
+
if (!justificationFields.includes(draggedField)) {
|
|
317
|
+
justificationFields = [...justificationFields, draggedField];
|
|
318
|
+
}
|
|
319
|
+
} else {
|
|
320
|
+
config.tab = tab;
|
|
321
|
+
|
|
322
|
+
// If dropping at a specific position, update display orders
|
|
323
|
+
if (targetIndex !== undefined && tab !== null) {
|
|
324
|
+
// Get all fields in this tab
|
|
325
|
+
const tabFields = Array.from(fieldConfigs.entries())
|
|
326
|
+
.filter(([_, cfg]) => cfg.tab === tab)
|
|
327
|
+
.sort((a, b) => a[1].displayOrder - b[1].displayOrder);
|
|
328
|
+
|
|
329
|
+
// Remove the dragged field from the list if it was already in this tab
|
|
330
|
+
const filteredFields = tabFields.filter(([field]) => field !== draggedField);
|
|
331
|
+
|
|
332
|
+
// Insert at the target position
|
|
333
|
+
filteredFields.splice(targetIndex, 0, [draggedField, config]);
|
|
334
|
+
|
|
335
|
+
// Update display orders for all fields in this tab
|
|
336
|
+
filteredFields.forEach(([field, cfg], index) => {
|
|
337
|
+
cfg.displayOrder = index;
|
|
338
|
+
fieldConfigs.set(field, cfg);
|
|
339
|
+
});
|
|
340
|
+
} else if (tab !== null) {
|
|
341
|
+
// If no specific position, add to end
|
|
342
|
+
const maxOrder = Math.max(
|
|
343
|
+
0,
|
|
344
|
+
...Array.from(fieldConfigs.values())
|
|
345
|
+
.filter((cfg) => cfg.tab === tab)
|
|
346
|
+
.map((cfg) => cfg.displayOrder)
|
|
347
|
+
);
|
|
348
|
+
config.displayOrder = maxOrder + 1;
|
|
349
|
+
}
|
|
339
350
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
cfg.displayOrder = index;
|
|
343
|
-
fieldConfigs.set(field, cfg);
|
|
344
|
-
});
|
|
345
|
-
} else if (tab !== null) {
|
|
346
|
-
// If no specific position, add to end
|
|
347
|
-
const maxOrder = Math.max(
|
|
348
|
-
0,
|
|
349
|
-
...Array.from(fieldConfigs.values())
|
|
350
|
-
.filter((cfg) => cfg.tab === tab)
|
|
351
|
-
.map((cfg) => cfg.displayOrder)
|
|
352
|
-
);
|
|
353
|
-
config.displayOrder = maxOrder + 1;
|
|
351
|
+
fieldConfigs.set(draggedField, config);
|
|
352
|
+
fieldConfigs = fieldConfigs; // Trigger reactivity
|
|
354
353
|
}
|
|
355
|
-
|
|
356
|
-
fieldConfigs.set(draggedField, config);
|
|
357
|
-
fieldConfigs = fieldConfigs; // Trigger reactivity
|
|
358
354
|
}
|
|
359
355
|
draggedField = null;
|
|
360
356
|
dragOverTab = null;
|
|
@@ -428,7 +424,7 @@
|
|
|
428
424
|
|
|
429
425
|
// Add field schema configuration - include all fields that are assigned to a tab
|
|
430
426
|
const fieldSchema = Array.from(fieldConfigs.entries())
|
|
431
|
-
.filter(([
|
|
427
|
+
.filter(([_field, config]) => config.tab !== null)
|
|
432
428
|
.map(([field, config]) => ({
|
|
433
429
|
fieldName: cleanFieldName(field),
|
|
434
430
|
...config
|
|
@@ -624,7 +620,7 @@
|
|
|
624
620
|
}}
|
|
625
621
|
class="bg-white border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:text-white"
|
|
626
622
|
>
|
|
627
|
-
{#each sheets as sheet}
|
|
623
|
+
{#each sheets as sheet (sheet)}
|
|
628
624
|
<option value={sheet}>{sheet}</option>
|
|
629
625
|
{/each}
|
|
630
626
|
</select>
|
|
@@ -646,7 +642,7 @@
|
|
|
646
642
|
on:change={loadSheetData}
|
|
647
643
|
class="bg-white border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:text-white"
|
|
648
644
|
>
|
|
649
|
-
{#each rowPreviews as preview}
|
|
645
|
+
{#each rowPreviews as preview (preview.row)}
|
|
650
646
|
<option value={preview.row}>
|
|
651
647
|
Row {preview.row}: {preview.preview}
|
|
652
648
|
</option>
|
|
@@ -673,7 +669,7 @@
|
|
|
673
669
|
required
|
|
674
670
|
>
|
|
675
671
|
<option value="" disabled>Select Control ID field</option>
|
|
676
|
-
{#each fields as field}
|
|
672
|
+
{#each fields as field (field)}
|
|
677
673
|
{@const exampleValue =
|
|
678
674
|
sampleData.length > 0 && sampleData[0][field]
|
|
679
675
|
? String(sampleData[0][field]).slice(0, 30)
|
|
@@ -785,7 +781,7 @@
|
|
|
785
781
|
>
|
|
786
782
|
{#each Array.from(fieldConfigs.entries())
|
|
787
783
|
.filter(([_field, config]) => config.tab === 'overview')
|
|
788
|
-
.sort((a, b) => a[1].displayOrder - b[1].displayOrder) as [field, _config],
|
|
784
|
+
.sort((a, b) => a[1].displayOrder - b[1].displayOrder) as [field, _config], _index (field)}
|
|
789
785
|
<div
|
|
790
786
|
draggable="true"
|
|
791
787
|
on:dragstart={(e) => handleFieldDragStart(e, field)}
|
|
@@ -805,7 +801,7 @@
|
|
|
805
801
|
<span class="truncate">{field}</span>
|
|
806
802
|
</div>
|
|
807
803
|
{/each}
|
|
808
|
-
{#if Array.from(fieldConfigs.entries()).filter(([
|
|
804
|
+
{#if Array.from(fieldConfigs.entries()).filter(([_field, config]) => config.tab === 'overview').length === 0}
|
|
809
805
|
<p class="text-xs text-gray-400 dark:text-gray-500 text-center py-4">
|
|
810
806
|
Drop fields here
|
|
811
807
|
</p>
|
|
@@ -835,8 +831,8 @@
|
|
|
835
831
|
aria-label="Implementation tab drop zone"
|
|
836
832
|
>
|
|
837
833
|
{#each Array.from(fieldConfigs.entries())
|
|
838
|
-
.filter(([
|
|
839
|
-
.sort((a, b) => a[1].displayOrder - b[1].displayOrder) as [field, _config],
|
|
834
|
+
.filter(([_field, config]) => config.tab === 'implementation')
|
|
835
|
+
.sort((a, b) => a[1].displayOrder - b[1].displayOrder) as [field, _config], _index (field)}
|
|
840
836
|
<div
|
|
841
837
|
draggable="true"
|
|
842
838
|
on:dragstart={(e) => handleFieldDragStart(e, field)}
|
|
@@ -856,7 +852,56 @@
|
|
|
856
852
|
<span class="truncate">{field}</span>
|
|
857
853
|
</div>
|
|
858
854
|
{/each}
|
|
859
|
-
{#if Array.from(fieldConfigs.entries()).filter(([
|
|
855
|
+
{#if Array.from(fieldConfigs.entries()).filter(([_field, config]) => config.tab === 'implementation').length === 0}
|
|
856
|
+
<p class="text-xs text-gray-400 dark:text-gray-500 text-center py-4">
|
|
857
|
+
Drop fields here
|
|
858
|
+
</p>
|
|
859
|
+
{/if}
|
|
860
|
+
</div>
|
|
861
|
+
</div>
|
|
862
|
+
|
|
863
|
+
<!-- Custom Tab Column -->
|
|
864
|
+
<div
|
|
865
|
+
class="border border-purple-300 dark:border-purple-700 rounded-lg bg-white dark:bg-gray-800"
|
|
866
|
+
>
|
|
867
|
+
<div
|
|
868
|
+
class="p-3 border-b border-purple-200 dark:border-purple-800 bg-purple-50 dark:bg-purple-900/20 rounded-t-lg"
|
|
869
|
+
>
|
|
870
|
+
<h4 class="text-sm font-semibold text-purple-700 dark:text-purple-300">Custom Tab</h4>
|
|
871
|
+
<p class="text-xs text-purple-600 dark:text-purple-400 mt-1">Additional fields</p>
|
|
872
|
+
</div>
|
|
873
|
+
<div
|
|
874
|
+
class="p-3 min-h-[400px] max-h-[600px] overflow-y-auto space-y-2 transition-colors
|
|
875
|
+
{dragOverTab === 'custom' ? 'bg-purple-50 dark:bg-purple-900/10' : ''}"
|
|
876
|
+
on:dragover={(e) => handleTabDragOver(e, 'custom')}
|
|
877
|
+
on:dragleave={handleTabDragLeave}
|
|
878
|
+
on:drop={(e) => handleTabDrop(e, 'custom')}
|
|
879
|
+
role="region"
|
|
880
|
+
aria-label="Custom fields drop zone"
|
|
881
|
+
>
|
|
882
|
+
{#each Array.from(fieldConfigs.entries())
|
|
883
|
+
.filter(([_field, config]) => config.tab === 'custom')
|
|
884
|
+
.sort((a, b) => a[1].displayOrder - b[1].displayOrder) as [field, _config], _index (field)}
|
|
885
|
+
<div
|
|
886
|
+
draggable="true"
|
|
887
|
+
on:dragstart={(e) => handleFieldDragStart(e, field)}
|
|
888
|
+
on:dragend={handleFieldDragEnd}
|
|
889
|
+
on:dragover={(e) => handleFieldDragOver(e, field)}
|
|
890
|
+
on:dragleave={handleFieldDragLeave}
|
|
891
|
+
on:drop={(e) => handleFieldDrop(e, field, 'custom')}
|
|
892
|
+
role="button"
|
|
893
|
+
aria-label="{field} field in Custom tab"
|
|
894
|
+
tabindex="0"
|
|
895
|
+
class="flex items-center px-3 py-2 bg-purple-100 dark:bg-purple-900/30 text-purple-800 dark:text-purple-300 rounded text-sm cursor-move hover:bg-purple-200 dark:hover:bg-purple-800/30 transition-colors
|
|
896
|
+
{dragOverField === field && draggedField !== field
|
|
897
|
+
? 'border-t-2 border-purple-500'
|
|
898
|
+
: ''}"
|
|
899
|
+
>
|
|
900
|
+
<Draggable class="w-3 h-3 mr-2 flex-shrink-0" />
|
|
901
|
+
<span class="truncate">{field}</span>
|
|
902
|
+
</div>
|
|
903
|
+
{/each}
|
|
904
|
+
{#if Array.from(fieldConfigs.entries()).filter(([_field, config]) => config.tab === 'custom').length === 0}
|
|
860
905
|
<p class="text-xs text-gray-400 dark:text-gray-500 text-center py-4">
|
|
861
906
|
Drop fields here
|
|
862
907
|
</p>
|
|
@@ -889,22 +934,23 @@
|
|
|
889
934
|
<div class="space-y-2">
|
|
890
935
|
{#if justificationFields.length > 0}
|
|
891
936
|
<!-- Display justification fields -->
|
|
892
|
-
{#each justificationFields as field,
|
|
937
|
+
{#each justificationFields as field, _index}
|
|
893
938
|
<div
|
|
894
|
-
draggable="
|
|
895
|
-
on:dragstart={(e) => handleFieldDragStart(e, field)}
|
|
896
|
-
on:dragend={handleFieldDragEnd}
|
|
897
|
-
on:dragover={(e) => handleFieldDragOver(e, field)}
|
|
898
|
-
on:dragleave={handleFieldDragLeave}
|
|
899
|
-
on:drop={(e) => handleFieldDrop(e, field, 'mappings')}
|
|
939
|
+
draggable="false"
|
|
900
940
|
role="button"
|
|
901
|
-
aria-label="{field} field in Mappings tab"
|
|
902
941
|
tabindex="0"
|
|
903
|
-
class="flex items-center px-3 py-2 bg-orange-100 dark:bg-orange-900/30 text-orange-800 dark:text-orange-300 rounded text-sm
|
|
904
|
-
{dragOverField === field && draggedField !== field ? 'border-t-2 border-orange-500' : ''}"
|
|
942
|
+
class="flex items-center px-3 py-2 bg-orange-100 dark:bg-orange-900/30 text-orange-800 dark:text-orange-300 rounded text-sm hover:bg-orange-200 dark:hover:bg-orange-800/30 transition-colors"
|
|
905
943
|
>
|
|
906
|
-
<Draggable class="w-3 h-3 mr-2 flex-shrink-0" />
|
|
907
944
|
<span class="truncate">{field}</span>
|
|
945
|
+
<button
|
|
946
|
+
class="ml-auto text-gray-400 hover:text-red-500 dark:text-gray-500 dark:hover:text-red-400"
|
|
947
|
+
title="Remove from mappings"
|
|
948
|
+
on:click|stopPropagation={() => {
|
|
949
|
+
justificationFields = justificationFields.filter((f) => f !== field);
|
|
950
|
+
}}
|
|
951
|
+
>
|
|
952
|
+
×
|
|
953
|
+
</button>
|
|
908
954
|
</div>
|
|
909
955
|
{/each}
|
|
910
956
|
{:else}
|
|
@@ -945,55 +991,6 @@
|
|
|
945
991
|
</div>
|
|
946
992
|
</div>
|
|
947
993
|
</div>
|
|
948
|
-
|
|
949
|
-
<!-- Custom Tab Column -->
|
|
950
|
-
<div
|
|
951
|
-
class="border border-purple-300 dark:border-purple-700 rounded-lg bg-white dark:bg-gray-800"
|
|
952
|
-
>
|
|
953
|
-
<div
|
|
954
|
-
class="p-3 border-b border-purple-200 dark:border-purple-800 bg-purple-50 dark:bg-purple-900/20 rounded-t-lg"
|
|
955
|
-
>
|
|
956
|
-
<h4 class="text-sm font-semibold text-purple-700 dark:text-purple-300">Custom Tab</h4>
|
|
957
|
-
<p class="text-xs text-purple-600 dark:text-purple-400 mt-1">Additional fields</p>
|
|
958
|
-
</div>
|
|
959
|
-
<div
|
|
960
|
-
class="p-3 min-h-[400px] max-h-[600px] overflow-y-auto space-y-2 transition-colors
|
|
961
|
-
{dragOverTab === 'custom' ? 'bg-purple-50 dark:bg-purple-900/10' : ''}"
|
|
962
|
-
on:dragover={(e) => handleTabDragOver(e, 'custom')}
|
|
963
|
-
on:dragleave={handleTabDragLeave}
|
|
964
|
-
on:drop={(e) => handleTabDrop(e, 'custom')}
|
|
965
|
-
role="region"
|
|
966
|
-
aria-label="Custom fields drop zone"
|
|
967
|
-
>
|
|
968
|
-
{#each Array.from(fieldConfigs.entries())
|
|
969
|
-
.filter(([field, config]) => config.tab === 'custom')
|
|
970
|
-
.sort((a, b) => a[1].displayOrder - b[1].displayOrder) as [field, config], index (field)}
|
|
971
|
-
<div
|
|
972
|
-
draggable="true"
|
|
973
|
-
on:dragstart={(e) => handleFieldDragStart(e, field)}
|
|
974
|
-
on:dragend={handleFieldDragEnd}
|
|
975
|
-
on:dragover={(e) => handleFieldDragOver(e, field)}
|
|
976
|
-
on:dragleave={handleFieldDragLeave}
|
|
977
|
-
on:drop={(e) => handleFieldDrop(e, field, 'custom')}
|
|
978
|
-
role="button"
|
|
979
|
-
aria-label="{field} field in Custom tab"
|
|
980
|
-
tabindex="0"
|
|
981
|
-
class="flex items-center px-3 py-2 bg-purple-100 dark:bg-purple-900/30 text-purple-800 dark:text-purple-300 rounded text-sm cursor-move hover:bg-purple-200 dark:hover:bg-purple-800/30 transition-colors
|
|
982
|
-
{dragOverField === field && draggedField !== field
|
|
983
|
-
? 'border-t-2 border-purple-500'
|
|
984
|
-
: ''}"
|
|
985
|
-
>
|
|
986
|
-
<Draggable class="w-3 h-3 mr-2 flex-shrink-0" />
|
|
987
|
-
<span class="truncate">{field}</span>
|
|
988
|
-
</div>
|
|
989
|
-
{/each}
|
|
990
|
-
{#if Array.from(fieldConfigs.entries()).filter(([field, config]) => config.tab === 'custom').length === 0}
|
|
991
|
-
<p class="text-xs text-gray-400 dark:text-gray-500 text-center py-4">
|
|
992
|
-
Drop fields here
|
|
993
|
-
</p>
|
|
994
|
-
{/if}
|
|
995
|
-
</div>
|
|
996
|
-
</div>
|
|
997
994
|
</div>
|
|
998
995
|
</div>
|
|
999
996
|
|
|
@@ -1011,15 +1008,15 @@
|
|
|
1011
1008
|
class="text-xs text-gray-700 uppercase bg-gray-100 dark:bg-gray-600 dark:text-gray-400"
|
|
1012
1009
|
>
|
|
1013
1010
|
<tr>
|
|
1014
|
-
{#each fields.slice(0, 5) as field}
|
|
1011
|
+
{#each fields.slice(0, 5) as field (field)}
|
|
1015
1012
|
<th class="px-4 py-2">{field}</th>
|
|
1016
1013
|
{/each}
|
|
1017
1014
|
</tr>
|
|
1018
1015
|
</thead>
|
|
1019
1016
|
<tbody>
|
|
1020
|
-
{#each sampleData as row}
|
|
1017
|
+
{#each sampleData as row, i (i)}
|
|
1021
1018
|
<tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
|
|
1022
|
-
{#each fields.slice(0, 5) as field}
|
|
1019
|
+
{#each fields.slice(0, 5) as field (field)}
|
|
1023
1020
|
<td class="px-4 py-2">{row[field] || ''}</td>
|
|
1024
1021
|
{/each}
|
|
1025
1022
|
</tr>
|
|
@@ -191,7 +191,7 @@
|
|
|
191
191
|
<p class="text-sm text-gray-500 dark:text-gray-400">No filters</p>
|
|
192
192
|
{:else}
|
|
193
193
|
<div class="space-y-2">
|
|
194
|
-
{#each $activeFilters as filter, index}
|
|
194
|
+
{#each $activeFilters as filter, index (index)}
|
|
195
195
|
<div
|
|
196
196
|
class="flex items-center justify-between bg-gray-50 dark:bg-gray-700 p-2 rounded-md"
|
|
197
197
|
>
|
|
@@ -262,7 +262,7 @@
|
|
|
262
262
|
>
|
|
263
263
|
Overview Fields
|
|
264
264
|
</div>
|
|
265
|
-
{#each fieldsByTab.overview as field}
|
|
265
|
+
{#each fieldsByTab.overview as field (field)}
|
|
266
266
|
<button
|
|
267
267
|
class={twMerge(
|
|
268
268
|
'w-full text-left px-3 py-2 text-sm',
|
|
@@ -288,7 +288,7 @@
|
|
|
288
288
|
>
|
|
289
289
|
Implementation Fields
|
|
290
290
|
</div>
|
|
291
|
-
{#each fieldsByTab.implementation as field}
|
|
291
|
+
{#each fieldsByTab.implementation as field (field)}
|
|
292
292
|
<button
|
|
293
293
|
class={twMerge(
|
|
294
294
|
'w-full text-left px-3 py-2 text-sm',
|
|
@@ -315,7 +315,7 @@
|
|
|
315
315
|
>
|
|
316
316
|
Custom Fields
|
|
317
317
|
</div>
|
|
318
|
-
{#each fieldsByTab.custom as field}
|
|
318
|
+
{#each fieldsByTab.custom as field (field)}
|
|
319
319
|
<button
|
|
320
320
|
class={twMerge(
|
|
321
321
|
'w-full text-left px-3 py-2 text-sm',
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
<nav class="border-b border-gray-200 dark:border-gray-700 flex-shrink-0">
|
|
23
23
|
<div class="flex space-x-4">
|
|
24
|
-
{#each tabs as tab}
|
|
24
|
+
{#each tabs as tab, index (index)}
|
|
25
25
|
<button
|
|
26
26
|
onclick={() => !tab.disabled && onSelect(tab.id)}
|
|
27
27
|
disabled={tab.disabled}
|
|
@@ -272,7 +272,7 @@
|
|
|
272
272
|
{#if showDetailedView}
|
|
273
273
|
<!-- Detailed view with full context -->
|
|
274
274
|
<div class="divide-y divide-gray-200 dark:divide-gray-600">
|
|
275
|
-
{#each yamlDiff.changes as change}
|
|
275
|
+
{#each yamlDiff.changes as change, i (i)}
|
|
276
276
|
<div class="p-3 {getChangeColor(change.type)}">
|
|
277
277
|
<div class="flex items-start space-x-3">
|
|
278
278
|
<div
|
|
@@ -337,7 +337,7 @@
|
|
|
337
337
|
{:else}
|
|
338
338
|
<!-- Summary view - more compact -->
|
|
339
339
|
<div class="p-4 space-y-2">
|
|
340
|
-
{#each yamlDiff.changes as change}
|
|
340
|
+
{#each yamlDiff.changes as change (change)}
|
|
341
341
|
<div class="flex items-center space-x-2 text-sm">
|
|
342
342
|
<span
|
|
343
343
|
class="w-5 h-5 rounded-full flex items-center justify-center text-xs font-bold {getChangeColor(
|
package/src/stores/compliance.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// SPDX-FileCopyrightText: 2023-Present The Lula Authors
|
|
3
3
|
|
|
4
|
-
import type { Control,
|
|
4
|
+
import type { Control, Mapping } from '$lib/types';
|
|
5
5
|
import { appState } from '$lib/websocket';
|
|
6
|
-
import {
|
|
6
|
+
import { get, writable } from 'svelte/store';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Shared filter operator options used across the application
|
|
@@ -57,73 +57,6 @@ export const searchTerm = writable('');
|
|
|
57
57
|
export const selectedControl = writable<Control | null>(null);
|
|
58
58
|
export const activeFilters = writable<FilterCondition[]>([]);
|
|
59
59
|
|
|
60
|
-
export const filteredControls = derived(
|
|
61
|
-
[controls, searchTerm, activeFilters],
|
|
62
|
-
([$controls, $searchTerm, $activeFilters]) => {
|
|
63
|
-
let results = $controls;
|
|
64
|
-
|
|
65
|
-
// Apply search term
|
|
66
|
-
if ($searchTerm) {
|
|
67
|
-
const term = $searchTerm.toLowerCase();
|
|
68
|
-
results = results.filter((c) => JSON.stringify(c).toLowerCase().includes(term));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Apply advanced filters
|
|
72
|
-
if ($activeFilters.length > 0) {
|
|
73
|
-
results = results.filter((control) => {
|
|
74
|
-
// Cast to ControlWithDynamicFields for dynamic field access
|
|
75
|
-
const dynamicControl = control as Record<string, unknown>;
|
|
76
|
-
|
|
77
|
-
// Control must match all filters
|
|
78
|
-
return $activeFilters.every((filter) => {
|
|
79
|
-
const fieldValue = dynamicControl[filter.fieldName];
|
|
80
|
-
|
|
81
|
-
// For exists/not_exists operators, we just need to check if the field has a value
|
|
82
|
-
if (filter.operator === 'exists') {
|
|
83
|
-
return fieldValue !== undefined && fieldValue !== null && fieldValue !== '';
|
|
84
|
-
} else if (filter.operator === 'not_exists') {
|
|
85
|
-
return fieldValue === undefined || fieldValue === null || fieldValue === '';
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// For other operators, convert values to strings for comparison
|
|
89
|
-
const fieldValueStr = String(fieldValue).toLowerCase();
|
|
90
|
-
const filterValueStr =
|
|
91
|
-
filter.value !== undefined ? String(filter.value).toLowerCase() : '';
|
|
92
|
-
|
|
93
|
-
switch (filter.operator) {
|
|
94
|
-
case 'equals':
|
|
95
|
-
return fieldValueStr === filterValueStr;
|
|
96
|
-
|
|
97
|
-
case 'not_equals':
|
|
98
|
-
return fieldValueStr !== filterValueStr;
|
|
99
|
-
|
|
100
|
-
case 'includes':
|
|
101
|
-
return fieldValueStr.includes(filterValueStr);
|
|
102
|
-
|
|
103
|
-
case 'not_includes':
|
|
104
|
-
return !fieldValueStr.includes(filterValueStr);
|
|
105
|
-
|
|
106
|
-
default:
|
|
107
|
-
return true;
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return results;
|
|
114
|
-
}
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
export const controlsWithMappings = derived(
|
|
118
|
-
[controls, mappings],
|
|
119
|
-
([$controls, $mappings]): ControlWithMappings[] => {
|
|
120
|
-
return $controls.map((control) => ({
|
|
121
|
-
...control,
|
|
122
|
-
mappings: $mappings.filter((m) => m.control_id === control.id)
|
|
123
|
-
}));
|
|
124
|
-
}
|
|
125
|
-
);
|
|
126
|
-
|
|
127
60
|
// Store actions - mostly for local state management
|
|
128
61
|
// All server operations now go through WebSocket
|
|
129
62
|
export const complianceStore = {
|