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,958 changes: 1,534 additions & 424 deletions portals/admin/src/main/webapp/package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion portals/admin/src/main/webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@
"react-spring": "8.0.27",
"sql-formatter": "4.0.2",
"swagger-client": "3.35.6",
"url": "0.11.3"
"url": "0.11.3",
"node-fetch": "^3.3.2",
"js-yaml": "^4.1.0"
},
"devDependencies": {
"@babel/core": "7.28.0",
Expand Down
8 changes: 8 additions & 0 deletions portals/admin/src/main/webapp/site/public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,7 @@
"Governance.Rulesets.AddEdit.confirm.overwrite.ok": "Overwrite",
"Governance.Rulesets.AddEdit.confirm.overwrite.title": "Confirm Overwrite",
"Governance.Rulesets.AddEdit.content.description": "Define the ruleset content in YAML or JSON format",
"Governance.Rulesets.AddEdit.content.external.description": "Only security headers can be updated for EXTERNAL rulesets.",
"Governance.Rulesets.AddEdit.content.title": "Ruleset Content",
"Governance.Rulesets.AddEdit.download.empty": "No content to download",
"Governance.Rulesets.AddEdit.edit.success": "Ruleset Updated Successfully",
Expand All @@ -819,6 +820,13 @@
"Governance.Rulesets.AddEdit.help.link": "Create and Manage Rulesets",
"Governance.Rulesets.AddEdit.title.edit": "Edit Ruleset - {name}",
"Governance.Rulesets.AddEdit.title.new": "Create New Ruleset",
"Governance.Rulesets.ExternalForm.headers.add": "Add Security Header",
"Governance.Rulesets.ExternalForm.headers.description": "Security headers can be edited.",
"Governance.Rulesets.ExternalForm.headers.empty": "No security headers were found in this ruleset.",
"Governance.Rulesets.ExternalForm.headers.key": "Header Key",
"Governance.Rulesets.ExternalForm.headers.title": "Security Headers",
"Governance.Rulesets.ExternalForm.headers.value": "Header Value",
"Governance.Rulesets.ExternalForm.headers.value.placeholder": "Enter new token",
"Governance.Rulesets.GenericForm.highconf.label": "High Confidence Threshold",
"Governance.Rulesets.GenericForm.highconf.tooltip": "APIs exceeding this higher threshold are flagged as near-certain duplicates.",
"Governance.Rulesets.GenericForm.lifecycle.mode.helper": "How the system responds to lifecycle violations",
Expand Down
8 changes: 8 additions & 0 deletions portals/admin/src/main/webapp/site/public/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,7 @@
"Governance.Rulesets.AddEdit.confirm.overwrite.ok": "Overwrite",
"Governance.Rulesets.AddEdit.confirm.overwrite.title": "Confirm Overwrite",
"Governance.Rulesets.AddEdit.content.description": "Define the ruleset content in YAML or JSON format",
"Governance.Rulesets.AddEdit.content.external.description": "Only security headers can be updated for EXTERNAL rulesets.",
"Governance.Rulesets.AddEdit.content.title": "Ruleset Content",
"Governance.Rulesets.AddEdit.download.empty": "No content to download",
"Governance.Rulesets.AddEdit.edit.success": "Ruleset Updated Successfully",
Expand All @@ -819,6 +820,13 @@
"Governance.Rulesets.AddEdit.help.link": "Create and Manage Rulesets",
"Governance.Rulesets.AddEdit.title.edit": "Edit Ruleset - {name}",
"Governance.Rulesets.AddEdit.title.new": "Create New Ruleset",
"Governance.Rulesets.ExternalForm.headers.add": "Add Security Header",
"Governance.Rulesets.ExternalForm.headers.description": "Security headers can be edited.",
"Governance.Rulesets.ExternalForm.headers.empty": "No security headers were found in this ruleset.",
"Governance.Rulesets.ExternalForm.headers.key": "Header Key",
"Governance.Rulesets.ExternalForm.headers.title": "Security Headers",
"Governance.Rulesets.ExternalForm.headers.value": "Header Value",
"Governance.Rulesets.ExternalForm.headers.value.placeholder": "Enter new token",
"Governance.Rulesets.GenericForm.highconf.label": "High Confidence Threshold",
"Governance.Rulesets.GenericForm.highconf.tooltip": "APIs exceeding this higher threshold are flagged as near-certain duplicates.",
"Governance.Rulesets.GenericForm.lifecycle.mode.helper": "How the system responds to lifecycle violations",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@

import * as monaco from 'monaco-editor';
import { Editor as MonacoEditor, loader } from '@monaco-editor/react';
import yaml from 'js-yaml';
import GenericRulesetForm from './GenericRulesetForm';
import ExternalRulesetForm from './ExternalRulesetForm';

// load Monaco from node_modules instead of CDN
loader.config({ monaco });
Expand All @@ -84,6 +86,8 @@
},
}));

const DEFAULT_EXTERNAL_RULE_TYPE = 'API_DEFINITION';

function reducer(state, { field, value }) {
const nextState = cloneDeep(state);
switch (field) {
Expand All @@ -110,6 +114,7 @@
const [openConfirmDialog, setOpenConfirmDialog] = useState(false);
const [pendingFile, setPendingFile] = useState(null);
const [rulesetValidationError, setRulesetValidationError] = useState(null);
const [externalHeaderChanges, setExternalHeaderChanges] = useState(false);
const intl = useIntl();
const { match: { params: { id } }, history } = props;

Expand Down Expand Up @@ -139,6 +144,7 @@
: CONSTS.RULESET_TYPES;

const isGenericRuleset = ruleCategory === 'GENERIC';
const isExternalRuleset = ruleCategory === 'EXTERNAL';

useEffect(() => {
const restApi = new GovernanceAPI();
Expand All @@ -162,6 +168,33 @@
.then((contentResult) => {
const { text } = contentResult;
dispatch({ field: 'rulesetContent', value: text });
// Parse YAML content and populate general details if not present
try {
const parsed = yaml.load(text) || {};
if (parsed.name) {
dispatch({ field: 'name', value: parsed.name });
}
if (parsed.description) {
dispatch({ field: 'description', value: parsed.description });
}
if (parsed.documentationLink) {
dispatch({ field: 'documentationLink', value: parsed.documentationLink });
}
if (parsed.provider) {
dispatch({ field: 'provider', value: parsed.provider });
}
if (parsed.ruleType) {
dispatch({ field: 'ruleType', value: parsed.ruleType });
}
if (parsed.artifactType) {
dispatch({ field: 'artifactType', value: parsed.artifactType });
}
if (parsed.ruleCategory) {
dispatch({ field: 'ruleCategory', value: parsed.ruleCategory });
}
} catch (e) {
// ignore parse errors and keep existing metadata
}

Check warning on line 197 in portals/admin/src/main/webapp/source/src/app/components/Governance/RulesetCatalog/AddEditRuleset.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Handle this exception or don't catch it at all.

See more on https://sonarcloud.io/project/issues?id=wso2_apim-apps&issues=AZ4DRCjlCmeI1QNEQdv1&open=AZ4DRCjlCmeI1QNEQdv1&pullRequest=1345
})
.catch((error) => {
const { response } = error;
Expand All @@ -181,7 +214,13 @@
if (artifactType === 'MCP' && ruleType === 'API_DEFINITION') {
dispatch({ field: 'ruleType', value: '' });
}
}, [artifactType, ruleType]);
}, [artifactType]); // Remove ruleType from dependency array

useEffect(() => {
if (isExternalRuleset && !ruleType) {
dispatch({ field: 'ruleType', value: DEFAULT_EXTERNAL_RULE_TYPE });
}
}, [isExternalRuleset]); // Remove ruleType from dependency array

const onChange = (e) => {
dispatch({ field: e.target.name, value: e.target.value });
Expand Down Expand Up @@ -275,6 +314,88 @@
Utils.downloadContent(rulesetContent, `${name || 'ruleset'}.yaml`);
};

let rulesetEditor;
if (isGenericRuleset) {
rulesetEditor = (
<GenericRulesetForm
rulesetContent={rulesetContent}
onContentChange={handleEditorChange}
rulesetName={name}
/>
);
} else if (isExternalRuleset) {
rulesetEditor = (
<ExternalRulesetForm
rulesetContent={rulesetContent}
onContentChange={handleEditorChange}
onChangeDetected={setExternalHeaderChanges}
/>
);
} else {
rulesetEditor = (
<Paper variant='outlined'>
<EditorToolbar>
<Box sx={{ display: 'flex', gap: 1 }}>
<Button
component='label'
variant='contained'
startIcon={<CloudUploadIcon />}
size='small'
>
<FormattedMessage
id='Governance.Rulesets.AddEdit.button.upload'
defaultMessage='Upload File'
/>
<input
type='file'
hidden
accept='.yaml,.yml,.json'
onChange={handleFileUpload}
/>
</Button>
<Button
variant='contained'
startIcon={<CloudDownloadIcon />}
size='small'
onClick={handleDownload}
>
<FormattedMessage
id='Governance.Rulesets.AddEdit.button.download'
defaultMessage='Download'
/>
</Button>
</Box>
</EditorToolbar>
<EditorContainer>
<MonacoEditor
height='100%'
defaultLanguage='yaml'
value={rulesetContent}
onChange={handleEditorChange}
theme='light'
options={{
minimap: { enabled: false },
lineNumbers: 'on',
scrollBeyondLastLine: false,
}}
/>
</EditorContainer>
</Paper>
);
}

const rulesetContentDescription = isExternalRuleset ? (
<FormattedMessage
id='Governance.Rulesets.AddEdit.content.external.description'
defaultMessage='Only security headers can be updated for EXTERNAL rulesets.'
/>
) : (
<FormattedMessage
id='Governance.Rulesets.AddEdit.content.description'
defaultMessage='Define the ruleset content in YAML or JSON format'
/>
);

const hasErrors = (fieldName, fieldValue, validatingActive) => {
let error = false;
if (!validatingActive) {
Expand Down Expand Up @@ -311,6 +432,9 @@
break;

case 'ruleType':
if (isExternalRuleset) {
break;
}
if (!fieldValue) {
error = intl.formatMessage({
id: 'Governance.Rulesets.AddEdit.form.ruletype.required',
Expand Down Expand Up @@ -385,11 +509,30 @@
setSaving(true);

const file = new File([rulesetContent], `${name}.yaml`);

// Determine effective rule category
let effectiveRuleCategory;
if (isExternalRuleset) {
effectiveRuleCategory = 'EXTERNAL';
} else {
effectiveRuleCategory = ruleCategory || 'SPECTRAL';
}

// Determine effective rule type
let effectiveRuleType;
if (isExternalRuleset) {
effectiveRuleType = DEFAULT_EXTERNAL_RULE_TYPE;
} else if (ruleType === 'GENERIC') {
effectiveRuleType = 'API_DEFINITION';
} else {
effectiveRuleType = ruleType;
}

const body = {
...state,
provider: AuthManager.getUser().name,
ruleCategory: ruleCategory || 'SPECTRAL',
ruleType: ruleType === 'GENERIC' ? 'API_DEFINITION' : ruleType,
ruleCategory: effectiveRuleCategory,
ruleType: effectiveRuleType,
rulesetContent: file,
};

Expand Down Expand Up @@ -506,6 +649,7 @@
name='name'
value={name}
onChange={onChange}
disabled={isExternalRuleset}
label={(
<span>
<FormattedMessage
Expand All @@ -525,6 +669,7 @@
name='description'
value={description}
onChange={onChange}
disabled={isExternalRuleset}
label={(
<FormattedMessage
id='Governance.Rulesets.AddEdit.form.description'
Expand All @@ -546,6 +691,7 @@
name='documentationLink'
value={documentationLink}
onChange={onChange}
disabled={isExternalRuleset}
label={(
<FormattedMessage
id='Governance.Rulesets.AddEdit.form.documentation'
Expand All @@ -565,6 +711,7 @@
name='artifactType'
value={artifactType}
onChange={onChange}
disabled={isExternalRuleset}
label={(
<FormattedMessage
id='Governance.Rulesets.AddEdit.form.artifact.type'
Expand All @@ -591,6 +738,7 @@
name='ruleType'
value={ruleType}
onChange={onChange}
disabled={isExternalRuleset}
label={(
<FormattedMessage
id='Governance.Rulesets.AddEdit.form.ruleset.type'
Expand Down Expand Up @@ -630,72 +778,14 @@
/>
</Typography>
<Typography color='inherit' variant='caption' component='p'>
<FormattedMessage
id='Governance.Rulesets.AddEdit.content.description'
defaultMessage='Define the ruleset content in YAML or JSON format'
/>
{rulesetContentDescription}
</Typography>
</Grid>
)}

<Grid item xs={12} md={12} lg={12}>
<Box component='div' m={1}>
{isGenericRuleset ? (
<GenericRulesetForm
rulesetContent={rulesetContent}
onContentChange={handleEditorChange}
rulesetName={name}
/>
) : (
<Paper variant='outlined'>
<EditorToolbar>
<Box sx={{ display: 'flex', gap: 1 }}>
<Button
component='label'
variant='contained'
startIcon={<CloudUploadIcon />}
size='small'
>
<FormattedMessage
id='Governance.Rulesets.AddEdit.button.upload'
defaultMessage='Upload File'
/>
<input
type='file'
hidden
accept='.yaml,.yml,.json'
onChange={handleFileUpload}
/>
</Button>
<Button
variant='contained'
startIcon={<CloudDownloadIcon />}
size='small'
onClick={handleDownload}
>
<FormattedMessage
id='Governance.Rulesets.AddEdit.button.download'
defaultMessage='Download'
/>
</Button>
</Box>
</EditorToolbar>
<EditorContainer>
<MonacoEditor
height='100%'
defaultLanguage='yaml'
value={rulesetContent}
onChange={handleEditorChange}
theme='light'
options={{
minimap: { enabled: false },
lineNumbers: 'on',
scrollBeyondLastLine: false,
}}
/>
</EditorContainer>
</Paper>
)}
{rulesetEditor}
</Box>
{validating && hasErrors('rulesetContent', rulesetContent, true) && (
<Box sx={{ color: 'error.main', pl: 2, pb: 1 }}>
Expand All @@ -718,7 +808,7 @@
variant='contained'
color='primary'
onClick={formSave}
disabled={saving}
disabled={saving || (isExternalRuleset && !externalHeaderChanges)}
>
{(() => {
if (saving) {
Expand Down
Loading