lula2 0.4.0 → 0.5.0-nightly.1
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.Dfpe5goI.css +1 -0
- package/dist/_app/immutable/chunks/{d2wclEM1.js → B3DV5AB9.js} +1 -1
- package/dist/_app/immutable/chunks/{DTNJlR4l.js → BBW9xKix.js} +19 -19
- package/dist/_app/immutable/chunks/{B8sFn9qB.js → BIY1u1I9.js} +1 -1
- package/dist/_app/immutable/chunks/{DPYPcVpy.js → BN4ish10.js} +1 -1
- package/dist/_app/immutable/chunks/{D6AXzSy_.js → BtwnwKFn.js} +1 -1
- package/dist/_app/immutable/chunks/{CgOk0Ct0.js → C2de20AA.js} +3 -3
- package/dist/_app/immutable/chunks/Cyp5c8fY.js +1 -0
- package/dist/_app/immutable/chunks/{DL7cUWpq.js → D6NghQtU.js} +1 -1
- package/dist/_app/immutable/chunks/{AMsv4DTs.js → DBN1r830.js} +1 -1
- package/dist/_app/immutable/entry/{app.DCeR7c2L.js → app.ChNLcnhL.js} +2 -2
- package/dist/_app/immutable/entry/start.CuWGVoVi.js +1 -0
- package/dist/_app/immutable/nodes/0.DnmE0r6A.js +2 -0
- package/dist/_app/immutable/nodes/{1.DZuIkAUv.js → 1.CPF_x4ZW.js} +1 -1
- package/dist/_app/immutable/nodes/{2.CaMZrOwr.js → 2.dJ7_0KZr.js} +1 -1
- package/dist/_app/immutable/nodes/{3.BCB0DxLi.js → 3.BhR2ddch.js} +1 -1
- package/dist/_app/immutable/nodes/{4.BlOrY_m9.js → 4.Djg06sYy.js} +1 -1
- package/dist/_app/version.json +1 -1
- package/dist/cli/commands/ui.js +265 -49
- package/dist/cli/server/index.js +265 -49
- package/dist/cli/server/server.js +265 -49
- package/dist/cli/server/spreadsheetRoutes.js +282 -59
- package/dist/cli/server/websocketServer.js +265 -49
- package/dist/index.html +10 -10
- package/dist/index.js +265 -49
- package/package.json +22 -23
- package/src/lib/components/dialogs/ExportColumnDialog.svelte +163 -0
- package/src/lib/components/dialogs/index.ts +4 -0
- package/src/routes/+layout.svelte +67 -5
- package/src/routes/control/[id]/+page.svelte +0 -1
- package/dist/_app/immutable/assets/0.D6CB7gA7.css +0 -1
- package/dist/_app/immutable/chunks/CBRQrpza.js +0 -1
- package/dist/_app/immutable/entry/start.Ca2qbK08.js +0 -1
- package/dist/_app/immutable/nodes/0.B2WQMmWy.js +0 -1
|
@@ -2967,7 +2967,10 @@ function extractFamilyFromControlId(controlId) {
|
|
|
2967
2967
|
}
|
|
2968
2968
|
return controlId.substring(0, 2).toUpperCase();
|
|
2969
2969
|
}
|
|
2970
|
-
function exportAsCSV(controls, metadata, res) {
|
|
2970
|
+
function exportAsCSV(controls, metadata, mappingsColumn, res) {
|
|
2971
|
+
return exportAsCSVWithMapping(controls, metadata, { mappings: mappingsColumn }, res);
|
|
2972
|
+
}
|
|
2973
|
+
function exportAsCSVWithMapping(controls, metadata, columnMappings, res) {
|
|
2971
2974
|
const fieldSchema = metadata?.fieldSchema?.fields || {};
|
|
2972
2975
|
const controlIdField = metadata?.control_id_field || "id";
|
|
2973
2976
|
const allFields = /* @__PURE__ */ new Set();
|
|
@@ -2975,49 +2978,91 @@ function exportAsCSV(controls, metadata, res) {
|
|
|
2975
2978
|
Object.keys(control).forEach((key) => allFields.add(key));
|
|
2976
2979
|
});
|
|
2977
2980
|
const fieldMapping = [];
|
|
2981
|
+
const usedDisplayNames = /* @__PURE__ */ new Set();
|
|
2978
2982
|
if (allFields.has(controlIdField)) {
|
|
2979
2983
|
const idSchema = fieldSchema[controlIdField];
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
+
const displayName = idSchema?.original_name || "Control ID";
|
|
2985
|
+
let isMappingColumn = false;
|
|
2986
|
+
if (columnMappings["mappings"]) {
|
|
2987
|
+
const targetDisplayName = columnMappings["mappings"];
|
|
2988
|
+
if (displayName.toLowerCase() === targetDisplayName.toLowerCase()) {
|
|
2989
|
+
isMappingColumn = true;
|
|
2990
|
+
}
|
|
2991
|
+
}
|
|
2992
|
+
fieldMapping.push({ fieldName: controlIdField, displayName, isMappingColumn });
|
|
2993
|
+
usedDisplayNames.add(displayName.toLowerCase());
|
|
2984
2994
|
allFields.delete(controlIdField);
|
|
2985
2995
|
} else if (allFields.has("id")) {
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2996
|
+
let isMappingColumn = false;
|
|
2997
|
+
const displayName = "Control ID";
|
|
2998
|
+
if (columnMappings["mappings"]) {
|
|
2999
|
+
const targetDisplayName = columnMappings["mappings"];
|
|
3000
|
+
if (displayName.toLowerCase() === targetDisplayName.toLowerCase()) {
|
|
3001
|
+
isMappingColumn = true;
|
|
3002
|
+
}
|
|
3003
|
+
}
|
|
3004
|
+
fieldMapping.push({ fieldName: "id", displayName, isMappingColumn });
|
|
3005
|
+
usedDisplayNames.add("control id");
|
|
2990
3006
|
allFields.delete("id");
|
|
2991
3007
|
}
|
|
2992
3008
|
if (allFields.has("family")) {
|
|
2993
3009
|
const familySchema = fieldSchema["family"];
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
});
|
|
3010
|
+
const displayName = familySchema?.original_name || "Family";
|
|
3011
|
+
fieldMapping.push({ fieldName: "family", displayName });
|
|
3012
|
+
usedDisplayNames.add(displayName.toLowerCase());
|
|
2998
3013
|
allFields.delete("family");
|
|
2999
3014
|
}
|
|
3000
3015
|
Array.from(allFields).filter((field) => field !== "mappings" && field !== "mappings_count").sort().forEach((field) => {
|
|
3001
3016
|
const schema = fieldSchema[field];
|
|
3002
|
-
const
|
|
3003
|
-
|
|
3017
|
+
const defaultDisplayName = schema?.original_name || field.replace(/-/g, " ").replace(/\b\w/g, (l) => l.toUpperCase());
|
|
3018
|
+
let isMappingColumn = false;
|
|
3019
|
+
let finalDisplayName = defaultDisplayName;
|
|
3020
|
+
if (columnMappings["mappings"]) {
|
|
3021
|
+
const targetDisplayName = columnMappings["mappings"];
|
|
3022
|
+
if (defaultDisplayName.toLowerCase() === targetDisplayName.toLowerCase()) {
|
|
3023
|
+
isMappingColumn = true;
|
|
3024
|
+
finalDisplayName = targetDisplayName;
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
if (!usedDisplayNames.has(finalDisplayName.toLowerCase())) {
|
|
3028
|
+
fieldMapping.push({
|
|
3029
|
+
fieldName: field,
|
|
3030
|
+
displayName: finalDisplayName,
|
|
3031
|
+
isMappingColumn
|
|
3032
|
+
});
|
|
3033
|
+
usedDisplayNames.add(finalDisplayName.toLowerCase());
|
|
3034
|
+
}
|
|
3004
3035
|
});
|
|
3005
|
-
if (allFields.has("
|
|
3006
|
-
fieldMapping.push({ fieldName: "mappings_count", displayName: "Mappings Count" });
|
|
3007
|
-
}
|
|
3008
|
-
if (allFields.has("mappings")) {
|
|
3036
|
+
if (allFields.has("mappings") && (!columnMappings["mappings"] || columnMappings["mappings"] === "Mappings")) {
|
|
3009
3037
|
fieldMapping.push({ fieldName: "mappings", displayName: "Mappings" });
|
|
3010
3038
|
}
|
|
3011
3039
|
const csvRows = [];
|
|
3012
3040
|
csvRows.push(fieldMapping.map((field) => `"${field.displayName}"`).join(","));
|
|
3013
3041
|
controls.forEach((control) => {
|
|
3014
|
-
const row = fieldMapping.map(({ fieldName }) => {
|
|
3015
|
-
|
|
3042
|
+
const row = fieldMapping.map(({ fieldName, isMappingColumn }) => {
|
|
3043
|
+
let value;
|
|
3044
|
+
if (isMappingColumn) {
|
|
3045
|
+
const mappingsValue = control["mappings"];
|
|
3046
|
+
if (Array.isArray(mappingsValue) && mappingsValue.length > 0) {
|
|
3047
|
+
const mappingsStr = mappingsValue.map((m) => m.description || m.justification || "").filter((desc) => desc && desc.trim() !== "").join("\n");
|
|
3048
|
+
if (mappingsStr.trim() !== "") {
|
|
3049
|
+
value = mappingsStr;
|
|
3050
|
+
} else {
|
|
3051
|
+
value = control[fieldName];
|
|
3052
|
+
}
|
|
3053
|
+
} else {
|
|
3054
|
+
value = control[fieldName];
|
|
3055
|
+
}
|
|
3056
|
+
} else {
|
|
3057
|
+
value = control[fieldName];
|
|
3058
|
+
}
|
|
3016
3059
|
if (value === void 0 || value === null) return '""';
|
|
3017
3060
|
if (fieldName === "mappings" && Array.isArray(value)) {
|
|
3018
|
-
const mappingsStr = value.map(
|
|
3019
|
-
|
|
3020
|
-
|
|
3061
|
+
const mappingsStr = value.map((m) => {
|
|
3062
|
+
const justification = m.description || m.justification || "";
|
|
3063
|
+
const status = m.status || "Unknown";
|
|
3064
|
+
return justification.trim() !== "" ? justification : `[${status}]`;
|
|
3065
|
+
}).join("\n");
|
|
3021
3066
|
return `"${mappingsStr.replace(/"/g, '""')}"`;
|
|
3022
3067
|
}
|
|
3023
3068
|
if (Array.isArray(value)) return `"${value.join("; ").replace(/"/g, '""')}"`;
|
|
@@ -3032,32 +3077,101 @@ function exportAsCSV(controls, metadata, res) {
|
|
|
3032
3077
|
res.setHeader("Content-Disposition", `attachment; filename="${fileName}"`);
|
|
3033
3078
|
res.send(csvContent);
|
|
3034
3079
|
}
|
|
3035
|
-
async function exportAsExcel(controls, metadata, res) {
|
|
3080
|
+
async function exportAsExcel(controls, metadata, mappingsColumn, res) {
|
|
3081
|
+
return await exportAsExcelWithMapping(controls, metadata, { mappings: mappingsColumn }, res);
|
|
3082
|
+
}
|
|
3083
|
+
async function exportAsExcelWithMapping(controls, metadata, columnMappings, res) {
|
|
3036
3084
|
const fieldSchema = metadata?.fieldSchema?.fields || {};
|
|
3037
3085
|
const controlIdField = metadata?.control_id_field || "id";
|
|
3038
|
-
const
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3086
|
+
const allFields = /* @__PURE__ */ new Set();
|
|
3087
|
+
controls.forEach((control) => {
|
|
3088
|
+
Object.keys(control).forEach((key) => allFields.add(key));
|
|
3089
|
+
});
|
|
3090
|
+
const fieldMapping = [];
|
|
3091
|
+
const usedDisplayNames = /* @__PURE__ */ new Set();
|
|
3092
|
+
if (allFields.has(controlIdField)) {
|
|
3093
|
+
const idSchema = fieldSchema[controlIdField];
|
|
3094
|
+
const displayName = idSchema?.original_name || "Control ID";
|
|
3095
|
+
let isMappingColumn = false;
|
|
3096
|
+
if (columnMappings["mappings"]) {
|
|
3097
|
+
const targetDisplayName = columnMappings["mappings"];
|
|
3098
|
+
if (displayName.toLowerCase() === targetDisplayName.toLowerCase()) {
|
|
3099
|
+
isMappingColumn = true;
|
|
3100
|
+
}
|
|
3046
3101
|
}
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3102
|
+
fieldMapping.push({ fieldName: controlIdField, displayName, isMappingColumn });
|
|
3103
|
+
usedDisplayNames.add(displayName.toLowerCase());
|
|
3104
|
+
allFields.delete(controlIdField);
|
|
3105
|
+
} else if (allFields.has("id")) {
|
|
3106
|
+
let isMappingColumn = false;
|
|
3107
|
+
const displayName = "Control ID";
|
|
3108
|
+
if (columnMappings["mappings"]) {
|
|
3109
|
+
const targetDisplayName = columnMappings["mappings"];
|
|
3110
|
+
if (displayName.toLowerCase() === targetDisplayName.toLowerCase()) {
|
|
3111
|
+
isMappingColumn = true;
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
fieldMapping.push({ fieldName: "id", displayName, isMappingColumn });
|
|
3115
|
+
usedDisplayNames.add("control id");
|
|
3116
|
+
allFields.delete("id");
|
|
3117
|
+
}
|
|
3118
|
+
if (allFields.has("family")) {
|
|
3119
|
+
const familySchema = fieldSchema["family"];
|
|
3120
|
+
const displayName = familySchema?.original_name || "Family";
|
|
3121
|
+
fieldMapping.push({ fieldName: "family", displayName });
|
|
3122
|
+
usedDisplayNames.add(displayName.toLowerCase());
|
|
3123
|
+
allFields.delete("family");
|
|
3124
|
+
}
|
|
3125
|
+
Array.from(allFields).filter((field) => field !== "mappings" && field !== "mappings_count").sort().forEach((field) => {
|
|
3126
|
+
const schema = fieldSchema[field];
|
|
3127
|
+
const defaultDisplayName = schema?.original_name || field.replace(/-/g, " ").replace(/\b\w/g, (l) => l.toUpperCase());
|
|
3128
|
+
let isMappingColumn = false;
|
|
3129
|
+
let finalDisplayName = defaultDisplayName;
|
|
3130
|
+
if (columnMappings["mappings"]) {
|
|
3131
|
+
const targetDisplayName = columnMappings["mappings"];
|
|
3132
|
+
if (defaultDisplayName.toLowerCase() === targetDisplayName.toLowerCase()) {
|
|
3133
|
+
isMappingColumn = true;
|
|
3134
|
+
finalDisplayName = targetDisplayName;
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
if (!usedDisplayNames.has(finalDisplayName.toLowerCase())) {
|
|
3138
|
+
fieldMapping.push({
|
|
3139
|
+
fieldName: field,
|
|
3140
|
+
displayName: finalDisplayName,
|
|
3141
|
+
isMappingColumn
|
|
3142
|
+
});
|
|
3143
|
+
usedDisplayNames.add(finalDisplayName.toLowerCase());
|
|
3051
3144
|
}
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3145
|
+
});
|
|
3146
|
+
if (allFields.has("mappings") && (!columnMappings["mappings"] || columnMappings["mappings"] === "Mappings")) {
|
|
3147
|
+
fieldMapping.push({ fieldName: "mappings", displayName: "Mappings" });
|
|
3148
|
+
}
|
|
3149
|
+
const worksheetData = controls.map((control) => {
|
|
3150
|
+
const exportControl = {};
|
|
3151
|
+
fieldMapping.forEach(({ fieldName, displayName, isMappingColumn }) => {
|
|
3152
|
+
let value;
|
|
3153
|
+
if (isMappingColumn) {
|
|
3154
|
+
const mappingsValue = control["mappings"];
|
|
3155
|
+
if (Array.isArray(mappingsValue) && mappingsValue.length > 0) {
|
|
3156
|
+
const mappingsStr = mappingsValue.map((m) => m.description || m.justification || "").filter((desc) => desc && desc.trim() !== "").join("\n");
|
|
3157
|
+
if (mappingsStr.trim() !== "") {
|
|
3158
|
+
value = mappingsStr;
|
|
3159
|
+
} else {
|
|
3160
|
+
value = control[fieldName];
|
|
3161
|
+
}
|
|
3162
|
+
} else {
|
|
3163
|
+
value = control[fieldName];
|
|
3164
|
+
}
|
|
3165
|
+
} else {
|
|
3166
|
+
value = control[fieldName];
|
|
3167
|
+
}
|
|
3168
|
+
if (fieldName === "mappings" && Array.isArray(value)) {
|
|
3169
|
+
const mappingsStr = value.map((m) => {
|
|
3170
|
+
const justification = m.description || m.justification || "";
|
|
3171
|
+
const status = m.status || "Unknown";
|
|
3172
|
+
return justification.trim() !== "" ? justification : `[${status}]`;
|
|
3173
|
+
}).join("\n");
|
|
3174
|
+
exportControl[displayName] = mappingsStr;
|
|
3061
3175
|
} else if (Array.isArray(value)) {
|
|
3062
3176
|
exportControl[displayName] = value.join("; ");
|
|
3063
3177
|
} else if (typeof value === "object" && value !== null) {
|
|
@@ -3471,6 +3585,7 @@ var init_spreadsheetRoutes = __esm({
|
|
|
3471
3585
|
router.get("/export-controls", async (req, res) => {
|
|
3472
3586
|
try {
|
|
3473
3587
|
const format = req.query.format || "csv";
|
|
3588
|
+
const mappingsColumn = req.query.mappingsColumn || "Mappings";
|
|
3474
3589
|
const state = getServerState();
|
|
3475
3590
|
const fileStore = state.fileStore;
|
|
3476
3591
|
if (!fileStore) {
|
|
@@ -3480,7 +3595,8 @@ var init_spreadsheetRoutes = __esm({
|
|
|
3480
3595
|
const mappings = await fileStore.loadMappings();
|
|
3481
3596
|
let metadata = {};
|
|
3482
3597
|
try {
|
|
3483
|
-
const
|
|
3598
|
+
const controlSetPath = getCurrentControlSetPath();
|
|
3599
|
+
const metadataPath2 = join4(controlSetPath, "lula.yaml");
|
|
3484
3600
|
if (existsSync3(metadataPath2)) {
|
|
3485
3601
|
const metadataContent = readFileSync3(metadataPath2, "utf8");
|
|
3486
3602
|
metadata = yaml4.load(metadataContent);
|
|
@@ -3508,10 +3624,10 @@ var init_spreadsheetRoutes = __esm({
|
|
|
3508
3624
|
debug(`Exporting ${controlsWithMappings.length} controls as ${format}`);
|
|
3509
3625
|
switch (format.toLowerCase()) {
|
|
3510
3626
|
case "csv":
|
|
3511
|
-
return exportAsCSV(controlsWithMappings, metadata, res);
|
|
3627
|
+
return exportAsCSV(controlsWithMappings, metadata, mappingsColumn, res);
|
|
3512
3628
|
case "excel":
|
|
3513
3629
|
case "xlsx":
|
|
3514
|
-
return await exportAsExcel(controlsWithMappings, metadata, res);
|
|
3630
|
+
return await exportAsExcel(controlsWithMappings, metadata, mappingsColumn, res);
|
|
3515
3631
|
case "json":
|
|
3516
3632
|
return exportAsJSON(controlsWithMappings, metadata, res);
|
|
3517
3633
|
default:
|
|
@@ -3522,6 +3638,106 @@ var init_spreadsheetRoutes = __esm({
|
|
|
3522
3638
|
res.status(500).json({ error: error.message });
|
|
3523
3639
|
}
|
|
3524
3640
|
});
|
|
3641
|
+
router.get("/export-column-headers", async (req, res) => {
|
|
3642
|
+
try {
|
|
3643
|
+
let metadata = {};
|
|
3644
|
+
try {
|
|
3645
|
+
const controlSetPath = getCurrentControlSetPath();
|
|
3646
|
+
const metadataPath2 = join4(controlSetPath, "lula.yaml");
|
|
3647
|
+
if (existsSync3(metadataPath2)) {
|
|
3648
|
+
const metadataContent = readFileSync3(metadataPath2, "utf8");
|
|
3649
|
+
metadata = yaml4.load(metadataContent);
|
|
3650
|
+
} else {
|
|
3651
|
+
return res.status(404).json({ error: `No lula.yaml file found in control set path: ${controlSetPath}` });
|
|
3652
|
+
}
|
|
3653
|
+
} catch {
|
|
3654
|
+
return res.status(500).json({ error: "Failed to read lula.yaml file" });
|
|
3655
|
+
}
|
|
3656
|
+
const fieldSchema = metadata?.fieldSchema?.fields || {};
|
|
3657
|
+
const controlIdField = metadata?.control_id_field || "id";
|
|
3658
|
+
const columnHeaders = [];
|
|
3659
|
+
if (fieldSchema[controlIdField]) {
|
|
3660
|
+
const idSchema = fieldSchema[controlIdField];
|
|
3661
|
+
const displayName = idSchema?.original_name || "Control ID";
|
|
3662
|
+
columnHeaders.push({
|
|
3663
|
+
value: displayName,
|
|
3664
|
+
label: displayName
|
|
3665
|
+
});
|
|
3666
|
+
} else {
|
|
3667
|
+
columnHeaders.push({ value: "Control ID", label: "Control ID" });
|
|
3668
|
+
}
|
|
3669
|
+
if (fieldSchema["family"]) {
|
|
3670
|
+
const familySchema = fieldSchema["family"];
|
|
3671
|
+
const displayName = familySchema?.original_name || "Family";
|
|
3672
|
+
columnHeaders.push({
|
|
3673
|
+
value: displayName,
|
|
3674
|
+
label: displayName
|
|
3675
|
+
});
|
|
3676
|
+
}
|
|
3677
|
+
Object.entries(fieldSchema).forEach(([fieldName, schema]) => {
|
|
3678
|
+
if (fieldName === controlIdField || fieldName === "family" || fieldName === "mappings" || fieldName === "mappings_count") {
|
|
3679
|
+
return;
|
|
3680
|
+
}
|
|
3681
|
+
const displayName = schema?.original_name || fieldName.replace(/-/g, " ").replace(/\b\w/g, (l) => l.toUpperCase());
|
|
3682
|
+
columnHeaders.push({
|
|
3683
|
+
value: displayName,
|
|
3684
|
+
label: displayName
|
|
3685
|
+
});
|
|
3686
|
+
});
|
|
3687
|
+
columnHeaders.push({ value: "Mappings", label: "Mappings (Default)" });
|
|
3688
|
+
res.json({
|
|
3689
|
+
columnHeaders,
|
|
3690
|
+
defaultColumn: "Mappings"
|
|
3691
|
+
});
|
|
3692
|
+
} catch (error) {
|
|
3693
|
+
res.status(500).json({ error: error.message });
|
|
3694
|
+
}
|
|
3695
|
+
});
|
|
3696
|
+
router.post("/export-csv", async (req, res) => {
|
|
3697
|
+
try {
|
|
3698
|
+
const { format = "csv", _columns = [], columnMappings = {} } = req.body;
|
|
3699
|
+
const state = getServerState();
|
|
3700
|
+
const fileStore = state.fileStore;
|
|
3701
|
+
if (!fileStore) {
|
|
3702
|
+
return res.status(500).json({ error: "No control set loaded" });
|
|
3703
|
+
}
|
|
3704
|
+
const controls = await fileStore.loadAllControls();
|
|
3705
|
+
const mappings = await fileStore.loadMappings();
|
|
3706
|
+
let metadata = {};
|
|
3707
|
+
try {
|
|
3708
|
+
const controlSetPath = getCurrentControlSetPath();
|
|
3709
|
+
const metadataPath2 = join4(controlSetPath, "lula.yaml");
|
|
3710
|
+
if (existsSync3(metadataPath2)) {
|
|
3711
|
+
const metadataContent = readFileSync3(metadataPath2, "utf8");
|
|
3712
|
+
metadata = yaml4.load(metadataContent);
|
|
3713
|
+
}
|
|
3714
|
+
} catch (err) {
|
|
3715
|
+
debug("Could not load metadata:", err);
|
|
3716
|
+
}
|
|
3717
|
+
if (!controls || controls.length === 0) {
|
|
3718
|
+
return res.status(404).json({ error: "No controls found" });
|
|
3719
|
+
}
|
|
3720
|
+
const controlIdField = metadata?.control_id_field || "id";
|
|
3721
|
+
const controlsWithMappings = controls.map((control) => {
|
|
3722
|
+
const controlId = control[controlIdField] || control.id;
|
|
3723
|
+
const controlMappings = mappings.filter((m) => m.control_id === controlId);
|
|
3724
|
+
return {
|
|
3725
|
+
...control,
|
|
3726
|
+
mappings_count: controlMappings.length,
|
|
3727
|
+
mappings: controlMappings.map((m) => ({
|
|
3728
|
+
uuid: m.uuid,
|
|
3729
|
+
status: m.status,
|
|
3730
|
+
description: m.justification || ""
|
|
3731
|
+
}))
|
|
3732
|
+
};
|
|
3733
|
+
});
|
|
3734
|
+
debug(`Exporting ${controlsWithMappings.length} controls as ${format} with column mappings`);
|
|
3735
|
+
return exportAsCSVWithMapping(controlsWithMappings, metadata, columnMappings, res);
|
|
3736
|
+
} catch (error) {
|
|
3737
|
+
console.error("Export error:", error);
|
|
3738
|
+
res.status(500).json({ error: error.message });
|
|
3739
|
+
}
|
|
3740
|
+
});
|
|
3525
3741
|
router.post("/parse-excel", upload.single("file"), async (req, res) => {
|
|
3526
3742
|
try {
|
|
3527
3743
|
if (!req.file) {
|