Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
944ca22
feat(myopencre): add initial MyOpenCRE page and CSV template download
PRAteek-singHWY Dec 15, 2025
c3a747e
Merge branch 'main' into myopencre-csv-download
PRAteek-singHWY Dec 16, 2025
54235ad
feat(myopencre): add initial MyOpenCRE page and CSV template download
PRAteek-singHWY Dec 17, 2025
64c8b44
Merge branch 'main' into myopencre-csv-download
PRAteek-singHWY Dec 17, 2025
a5f56ef
Resolve nav conflicts and standardize MyOpenCRE links
PRAteek-singHWY Dec 26, 2025
a4f988f
chore: resolve merge conflicts with upstream main
PRAteek-singHWY Dec 26, 2025
35d5a0f
chore: resolve header merge conflict
PRAteek-singHWY Dec 26, 2025
b285409
fix(header): restore sr-only utility for accessibility
PRAteek-singHWY Dec 26, 2025
8c929ce
Merge branch 'main' into myopencre-csv-download
PRAteek-singHWY Jan 3, 2026
3ddf7af
Merge branch 'main' into myopencre-csv-download
PRAteek-singHWY Jan 3, 2026
a83b918
chore(osib): update OWASP name to Open Worldwide Application Security…
PRAteek-singHWY Jan 3, 2026
735eb7b
feat(myopencre): enable CSV download of all CREs
PRAteek-singHWY Dec 17, 2025
57118a0
feat(myopencre): add CSV upload UI and wire to existing import endpoint
PRAteek-singHWY Dec 18, 2025
fcd3dbd
fix: correct SCSS block after merge conflict
PRAteek-singHWY Dec 26, 2025
197653b
feat(myopencre): add export-compatible CSV import validation
PRAteek-singHWY Dec 28, 2025
2bba24d
Merge branch 'main' into myopencre-csv-import-validation
PRAteek-singHWY Jan 3, 2026
d3e1ab4
refactor(csv-import): move CSV validation into spreadsheet parser
PRAteek-singHWY Jan 4, 2026
c81609f
refactor(csv): move import validation to spreadsheet parser entry point
PRAteek-singHWY Jan 4, 2026
cb089b8
style(csv): format spreadsheet parser with black
PRAteek-singHWY Jan 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.11
3.11.9
11 changes: 11 additions & 0 deletions application/frontend/src/pages/MyOpenCRE/MyOpenCRE.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.myopencre-section {
margin-top: 2rem;
}

.myopencre-upload {
margin-top: 1.5rem;
}

.myopencre-disabled {
opacity: 0.7;
}
172 changes: 172 additions & 0 deletions application/frontend/src/pages/MyOpenCRE/MyOpenCRE.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import './MyOpenCRE.scss';

import React, { useState } from 'react';
import { Button, Container, Form, Header, Message } from 'semantic-ui-react';

import { useEnvironment } from '../../hooks';

export const MyOpenCRE = () => {
const { apiUrl } = useEnvironment();

// Upload enabled only for local/dev
const isUploadEnabled = apiUrl !== '/rest/v1';

const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState<any | null>(null);

/* ------------------ CSV DOWNLOAD ------------------ */

const downloadCreCsv = async () => {
try {
const response = await fetch(`${apiUrl}/cre_csv`, {
method: 'GET',
headers: { Accept: 'text/csv' },
});

if (!response.ok) {
throw new Error(`HTTP error ${response.status}`);
}

const blob = await response.blob();
const url = window.URL.createObjectURL(blob);

const link = document.createElement('a');
link.href = url;
link.download = 'opencre-cre-mapping.csv';
document.body.appendChild(link);
link.click();

document.body.removeChild(link);
window.URL.revokeObjectURL(url);
} catch (err) {
console.error('CSV download failed:', err);
alert('Failed to download CRE CSV');
}
};

/* ------------------ FILE SELECTION ------------------ */

const onFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setError(null);
setSuccess(null);

if (!e.target.files || e.target.files.length === 0) return;

const file = e.target.files[0];

if (!file.name.toLowerCase().endsWith('.csv')) {
setError('Please upload a valid CSV file.');
e.target.value = '';
setSelectedFile(null);
return;
}

setSelectedFile(file);
};

/* ------------------ CSV UPLOAD ------------------ */

const uploadCsv = async () => {
if (!selectedFile) return;

setLoading(true);
setError(null);
setSuccess(null);

const formData = new FormData();
formData.append('cre_csv', selectedFile);

try {
const response = await fetch(`${apiUrl}/cre_csv_import`, {
method: 'POST',
body: formData,
});

if (response.status === 403) {
throw new Error(
'CSV import is disabled on hosted environments. Run OpenCRE locally with CRE_ALLOW_IMPORT=true.'
);
}

if (!response.ok) {
const text = await response.text();
throw new Error(text || 'CSV import failed');
}

const result = await response.json();
setSuccess(result);
setSelectedFile(null);
} catch (err: any) {
setError(err.message || 'Unexpected error during import');
} finally {
setLoading(false);
}
};

/* ------------------ UI ------------------ */

return (
<Container style={{ marginTop: '3rem' }}>
<Header as="h1">MyOpenCRE</Header>

<p>
MyOpenCRE allows you to map your own security standard (e.g. SOC2) to OpenCRE Common Requirements
using a CSV spreadsheet.
</p>

<p>
Start by downloading the CRE catalogue below, then map your standard’s controls or sections to CRE IDs
in the spreadsheet.
</p>

<div className="myopencre-section">
<Button primary onClick={downloadCreCsv}>
Download CRE Catalogue (CSV)
</Button>
</div>

<div className="myopencre-section myopencre-upload">
<Header as="h3">Upload Mapping CSV</Header>

<p>Upload your completed mapping spreadsheet to import your standard into OpenCRE.</p>

{!isUploadEnabled && (
<Message info className="myopencre-disabled">
CSV upload is disabled on hosted environments due to resource constraints.
<br />
Please run OpenCRE locally to enable standard imports.
</Message>
)}

{error && <Message negative>{error}</Message>}

{success && (
<Message positive>
<strong>Import successful</strong>
<ul>
<li>New CREs added: {success.new_cres?.length ?? 0}</li>
<li>Standards imported: {success.new_standards}</li>
</ul>
</Message>
)}

<Form>
<Form.Field>
<input type="file" accept=".csv" disabled={!isUploadEnabled || loading} onChange={onFileChange} />
</Form.Field>

<Button
primary
loading={loading}
disabled={!isUploadEnabled || !selectedFile || loading}
onClick={uploadCsv}
>
Upload CSV
</Button>
</Form>
</div>
</Container>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,7 @@
color: var(--muted-foreground);
height: 1rem;
width: 1rem;
pointer-events: none;
z-index: 1;
}

input {
padding: 0.5rem 1rem 0.5rem 2.5rem;
width: 16rem;
Expand Down
7 changes: 7 additions & 0 deletions application/frontend/src/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { ExplorerCircles } from './pages/Explorer/visuals/circles/circles';
import { ExplorerForceGraph } from './pages/Explorer/visuals/force-graph/forceGraph';
import { GapAnalysis } from './pages/GapAnalysis/GapAnalysis';
import { MembershipRequired } from './pages/MembershipRequired/MembershipRequired';
import { MyOpenCRE } from './pages/MyOpenCRE/MyOpenCRE';
import { SearchName } from './pages/Search/SearchName';
import { StandardSection } from './pages/Standard/StandardSection';

Expand All @@ -31,6 +32,12 @@ export interface IRoute {
}

export const ROUTES: IRoute[] = [
{
path: '/myopencre',
component: MyOpenCRE,
showFilter: false,
},

{
path: INDEX,
component: SearchPage,
Expand Down
15 changes: 15 additions & 0 deletions application/frontend/src/scaffolding/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Button } from 'semantic-ui-react';

import { ClearFilterButton } from '../../components/FilterButton/FilterButton';
import { useLocationFromOutsideRoute } from '../../hooks/useLocationFromOutsideRoute';
import { MyOpenCRE } from '../../pages/MyOpenCRE/MyOpenCRE';
import { SearchBar } from '../../pages/Search/components/SearchBar';

export const Header = () => {
Expand Down Expand Up @@ -68,6 +69,11 @@ export const Header = () => {
<NavLink to="/explorer" className="nav-link" activeClassName="nav-link--active">
Explorer
</NavLink>

<NavLink to="/myopencre" className="nav-link" activeClassName="nav-link--active">
MyOpenCRE
</NavLink>

</div>

<div>
Expand Down Expand Up @@ -186,6 +192,15 @@ export const Header = () => {
>
Explorer
</NavLink>

<NavLink
to="/myopencre"
className="nav-link"
activeClassName="nav-link--active"
onClick={closeMobileMenu}
>
MyOpenCRE
</NavLink>
</div>

<div className="mobile-auth">
Expand Down
2 changes: 0 additions & 2 deletions application/frontend/src/scaffolding/Header/header.scss
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,6 @@
display: block;
}
}

.sr-only {
position: absolute;
width: 1px;
Expand All @@ -331,7 +330,6 @@
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
2 changes: 1 addition & 1 deletion application/frontend/www/bundle.js

Large diffs are not rendered by default.

7 changes: 0 additions & 7 deletions application/frontend/www/bundle.js.LICENSE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,6 @@ object-assign
* SPDX-License-Identifier: MIT
*/

/**
* @license lucide-react v0.536.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/

/**
* Prism: Lightweight, robust, elegant syntax highlighting
*
Expand Down
2 changes: 1 addition & 1 deletion application/frontend/www/index.html
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<!doctype html><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=noshrink-to-fit=no"/><title>Open CRE</title><link rel="icon" type="image/svg+xml" href="/logo.svg"/><link rel="alternate icon" href="/favicon.ico"/><link rel="stylesheet" href="//cdn.jsdelivr.net/npm/[email protected]/dist/semantic.min.css"/><script defer="defer" src="/bundle.js"></script></head><body><div id="mount"></div></body>
<!doctype html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=noshrink-to-fit=no"><title>Open CRE</title><link rel="stylesheet" href="//cdn.jsdelivr.net/npm/[email protected]/dist/semantic.min.css"/><script defer="defer" src="/bundle.js"></script></head><body><div id="mount"></div></body>
Loading