Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions application/frontend/src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,6 @@ export const GRAPH = '/graph';
export const BROWSEROOT = '/root_cres';
export const GAP_ANALYSIS = '/map_analysis';
export const EXPLORER = '/explorer';
export const MYOPENCRE = '/myopencre';

export const GA_STRONG_UPPER_LIMIT = 2; // remember to change this in the Python code too
53 changes: 53 additions & 0 deletions application/frontend/src/pages/MyOpenCRE/myopencre.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Main container for the entire page
.myopencre-page-container {
padding: 2em;
min-height: 90vh;
display: flex;
align-items: center;
justify-content: center;
background-color: #f7f7f7; // A light grey background for the whole page
}

// Styling for the main page header
.page-header {
margin-bottom: 1.5em !important; // Add more space below the header
color: #333;
}

// The main "card" or container for the form
.form-container {
background-color: #ffffff !important;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08) !important;
text-align: left; // Align text inside the card to the left
}

// Style for the descriptive paragraph
.description-text {
font-size: 1.1em;
color: #555;
margin-bottom: 2em;
line-height: 1.6;
}

// Styling for the file input field to make it more prominent
.file-input {
margin-bottom: 1.5em !important;
.label {
font-size: 1.1em !important;
}
}

// Style for the submit button
.submit-button {
transition: background-color 0.2s ease-in-out !important;

&:hover {
background-color: #1a69c4 !important; // A slightly darker blue on hover
}
}

// Container for the loading/error messages below the form
.indicator-container {
min-height: 50px; // Reserve space so the layout doesn't jump
margin-top: 1.5em;
}
212 changes: 212 additions & 0 deletions application/frontend/src/pages/MyOpenCRE/myopencre.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import './myopencre.scss';

import React, { useEffect, useState } from 'react';
import { Button, Container, Divider, Form, Grid, Header, Icon, Message, Segment } from 'semantic-ui-react';

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

export const MyOpenCRE = () => {
const { apiUrl } = useEnvironment();
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string>('');

const [suggestionFile, setSuggestionFile] = useState<File | null>(null);
const [importFile, setImportFile] = useState<File | null>(null);
const [importSuccess, setImportSuccess] = useState<string>('');

// NEW: Robust handler for the template download
const handleTemplateDownload = () => {
setLoading(true);
setError('');
setImportSuccess('');

fetch(`${apiUrl}/cre_csv`, {})
.then((response) => {
if (!response.ok) {
throw new Error(`Server responded with status: ${response.status}`);
}
return response.blob();
})
.then((blob) => {
setLoading(false);
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'CRE-Catalogue.csv'; // The correct filename from the backend
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
})
.catch((err) => {
setLoading(false);
setError(`Failed to download template: ${err.message}`);
});
};

// Handlers for the AI Suggestion form
const handleSuggestionFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.files && event.target.files.length > 0) {
setSuggestionFile(event.target.files[0]);
}
};

const handleSuggestionUpload = (event: React.FormEvent) => {
event.preventDefault();
if (!suggestionFile) return;

setLoading(true);
setError('');
setImportSuccess('');

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

fetch(`${apiUrl}/cre_csv/suggest`, {
method: 'POST',
body: formData,
})
.then((response) => response.blob())
.then((blob) => {
setLoading(false);
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'cre-suggestions.csv';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
})
.catch((err) => {
setLoading(false);
setError(`Failed to analyze file: ${err.message}`);
});
};

const handleImportFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.files && event.target.files.length > 0) {
setImportFile(event.target.files[0]);
}
};

const handleFullImport = (event: React.FormEvent) => {
event.preventDefault();
if (!importFile) return;

setLoading(true);
setError('');
setImportSuccess('');

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

fetch(`${apiUrl}/cre_csv_import`, {
method: 'POST',
body: formData,
})
.then((response) => {
if (!response.ok) throw new Error(`Server error: ${response.status}`);
return response.json();
})
.then((data) => {
setLoading(false);
setImportSuccess(
`Successfully imported! New CREs: ${data.new_cres.length}, New Standards: ${data.new_standards}`
);
if ('Notification' in window && Notification.permission === 'granted') {
new Notification('OpenCRE Import Complete', { body: 'Your new mappings have been processed.' });
}
})
.catch((err) => {
setLoading(false);
setError(`Import failed: ${err.message}`);
});
};

// Request notification permission on component mount
useEffect(() => {
if ('Notification' in window && Notification.permission !== 'granted') {
Notification.requestPermission();
}
}, []);

return (
<div className="myopencre-page-container">
<Grid textAlign="center" verticalAlign="middle">
<Grid.Column style={{ maxWidth: 700 }}>
<Header as="h1" icon textAlign="center" className="page-header">
<Icon name="sync alternate" circular />
<Header.Content>MyOpenCRE Workspace</Header.Content>
</Header>

{/* Section 1: Download Template */}
<Segment padded="very" className="workspace-segment">
<Header as="h2" icon="download" content="Step 1: Get the Template" />
<p>
Download the complete, up-to-date list of all CREs. Use this file to map your own security
standards by filling in the standard-related columns for each CRE.
</p>
{/* UPDATED: This is now a standard button with an onClick handler */}
<Button onClick={handleTemplateDownload} primary size="large" loading={loading}>
<Icon name="download" /> Download CRE Mapping Template
</Button>
</Segment>

{/* Section 2: AI Suggestions (Feature Flagged) */}
{process.env.REACT_APP_ENABLE_AI_SUGGESTIONS === 'true' && (
<Segment padded="very" className="workspace-segment">
<Header as="h2" icon="magic" content="Step 2 (Optional): Get AI Suggestions" />
<p>
Have a CSV with descriptions but missing CREs? Upload it here, and our AI will analyze it and
return a new file with high-confidence mapping suggestions.
</p>
<Form onSubmit={handleSuggestionUpload}>
<Form.Input
type="file"
label="Select a .csv File for Analysis"
accept=".csv"
onChange={handleSuggestionFileChange}
/>
<Button fluid size="large" type="submit" disabled={!suggestionFile || loading} color="teal">
<Icon name="cogs" /> Analyze and Download Suggestions
</Button>
</Form>
</Segment>
)}

{/* Section 3: Final Import (Feature Flagged) */}
{process.env.REACT_APP_ENABLE_FULL_IMPORT === 'true' && (
<Segment padded="very" className="workspace-segment">
<Header as="h2" icon="upload" content="Step 3: Import Your Mappings" />
<p>
Once your spreadsheet is complete, upload it here to import your new standard mappings into
the OpenCRE database.
</p>
<Form onSubmit={handleFullImport}>
<Form.Input
type="file"
label="Select a .csv File to Import"
accept=".csv"
onChange={handleImportFileChange}
/>
<Button fluid size="large" type="submit" disabled={!importFile || loading} color="green">
<Icon name="upload" /> Upload and Import to OpenCRE
</Button>
</Form>
</Segment>
)}

{/* Indicator section for loading, errors, and success messages */}
<div className="indicator-container">
<LoadingAndErrorIndicator loading={loading} error={error} />
{importSuccess && !error && (
<Message positive header="Import Successful" content={importSuccess} />
)}
</div>
</Grid.Column>
</Grid>
</div>
);
};
82 changes: 61 additions & 21 deletions application/frontend/src/pages/chatbot/chatbot.scss
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
// }
// }


// .chat-container {
// width: 1000px;
// display: flex;
Expand All @@ -46,33 +45,74 @@
// margin-bottom: 1rem;
// }

.chat-input {
background-color: #c8e6c9;
// Main container for the chat window
.chat-window {
border: 1px solid #ddd;
border-radius: 10px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 1rem;
// display: flex;
// justify-content: space-between;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
background-color: #ffffff;
}


.message-card {
background-color: white;
border-radius: 10px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 1rem;
// The scrollable area for messages
#chat-messages {
height: 60vh;
overflow-y: auto;
padding: 10px;
margin-bottom: 1rem;
width: 100%;
height: 100%;
border-bottom: 1px solid #eee;
}

// Wrapper for each message to ensure proper line breaks between floats
.message-row {
clear: both;
padding-bottom: 1rem; // This creates space between messages
}

// Input form styling
.chat-input {
padding-top: 1rem;
}

// Common styles for BOTH user and assistant message bubbles
.message-bubble {
padding: 0.8rem 1.2rem;
border-radius: 15px;
width: fit-content; // Bubble shrinks to fit the content
max-width: 80%; // Prevents bubbles from being too wide on large screens
text-align: left;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);

// Styling for the text inside the bubble
.comment .content {
.author {
text-transform: capitalize;
}
.text {
color: #333; // Darker text for readability
p {
margin: 0.5em 0; // Better spacing for paragraphs
}
}
}
}

// Specific style for the USER's message bubble (right side)
.message-bubble.user {
background-color: #dcf8c6; // A pleasant green
border: 1px solid #cde8ba;
}

.user .message-card {
margin-left: auto;
background-color: #e3f2fd;
height: 100%;
// Specific style for the ASSISTANT's message bubble (left side)
.message-bubble.assistant {
background-color: #f1f0f0; // A neutral light grey
border: 1px solid #e0e0e0;
}

.assistant .message-card {
margin-right: auto;
background-color: #c8e6c9;
// Footer styling
.chatbot-footer {
margin-top: 1rem;
font-size: 0.9em;
color: #666;
text-align: center;
}
Loading