Overview
Implement the Frontend Variable Customization UI to complete Phase 1 of the hybrid scanning architecture. This provides the user interface for framework selection, variable customization, template management, and scan configuration.
Phase 1 Progress : 6/7 tasks completed → 7/7 after this issue
Problem Statement
The backend APIs (PR #105 ) provide complete framework discovery, variable management, and template operations, but users have no UI to:
Browse available compliance frameworks
Customize XCCDF variables with validation
Save and reuse scan configurations as templates
Configure scans with framework/template selection
Solution: Complete Frontend UI
Implement React/TypeScript components integrated with Material-UI v5 and the scan configuration APIs.
Architecture: Page Organization
1. /content - Compliance Content Hub
Add two new sections:
A. /content/frameworks (NEW)
Framework discovery and browsing
Variable definitions viewer
Framework metadata display
B. /content/templates (NEW)
Template list (user's own + public)
Template editor (CRUD operations)
Template history/audit logs
Template statistics
2. /scans/config - Enhanced Scan Configuration
Modify existing page:
Framework/version selection
Template selection (dropdown)
Variable customization (dynamic form)
Target selection (existing hosts only)
Save as template option
3. /host-groups - Template Quick Actions
Add to existing page:
"Scan with Template" dropdown menu
Quick template application to groups
Implementation Tasks
1. New Pages (2 pages)
A. Frameworks Page (frontend/src/pages/Content/FrameworksPage.tsx)
Features :
Grid/list view of available frameworks
Framework cards with metadata (rule count, variable count)
Click to view framework details
Search/filter frameworks
API Calls :
GET /scan-config/frameworks
Components :
< FrameworksPage >
< PageHeader title = "Compliance Frameworks" / >
< SearchBar onSearch = { handleSearch} / >
< Grid >
< FrameworkCard
framework = "nist"
displayName = "NIST 800-53"
versions = { [ "rev4" , "rev5" ] }
ruleCount = { 487 }
variableCount = { 62 }
onViewDetails = { handleViewDetails}
/ >
...
< / Grid >
< / FrameworksPage >
B. Framework Details Page (frontend/src/pages/Content/FrameworkDetailPage.tsx)
Features :
Framework overview (description, versions)
Variable list grouped by category
Variable details (type, default, constraints)
"Create Template from Framework" button
API Calls :
GET /scan-config/frameworks/{framework}/{version}
GET /scan-config/frameworks/{framework}/{version}/variables
Components :
< FrameworkDetailPage >
< Breadcrumbs : Home > Content > Frameworks > NIST 800 - 53 rev5 >
< FrameworkHeader framework = { framework } version = { version } / >
< Tabs >
< Tab label = "Overview" >
< FrameworkOverview / >
< / Tab >
< Tab label = "Variables (62)" >
< VariableList
variables = { variables}
groupBy = "category"
/ >
< / Tab >
< Tab label = "Rules (487)" >
< RuleList framework = { framework } version = { version } / >
< / Tab >
< / Tabs >
< Button onClick = { handleCreateTemplate} >
Create Template from Framework
< / B u t t o n >
< / F r a m e w o r k D e t a i l P a g e >
C. Templates Page (frontend/src/pages/Content/TemplatesPage.tsx)
Features :
List user's templates + public templates
Filter by framework, tags, visibility
Template cards with metadata
CRUD actions (Edit, Delete, Clone, Set Default)
Template statistics
API Calls :
GET /scan-config/templates
POST /scan-config/templates/{id}/clone
DELETE /scan-config/templates/{id}
POST /scan-config/templates/{id}/set-default
Components :
< TemplatesPage >
< PageHeader
title = "Scan Configuration Templates"
action = { < Button onClick = { handleNew} > New Template < / B u t t o n > }
/ >
< FilterBar
frameworks = { frameworks}
onFilter = { handleFilter}
/ >
< Section title = "My Templates (8)" >
< TemplateCard
template = { template}
isDefault = { template . is_default}
onEdit = { handleEdit}
onClone = { handleClone}
onDelete = { handleDelete}
onSetDefault = { handleSetDefault}
onUse = { handleUse}
/ >
< / Section >
< Section title = "Public Templates (12)" >
< TemplateCard
template = { publicTemplate}
isPublic
onClone = { handleClone}
onView = { handleView}
/ >
< / Section >
< / TemplatesPage >
D. Template Editor Page (frontend/src/pages/Content/TemplateEditorPage.tsx)
Features :
Template name, description, tags
Framework/version selection
Variable customization form
Rule filter options
Save/Update/Cancel actions
Audit log tab (for existing templates)
API Calls :
POST /scan-config/templates (create)
PUT /scan-config/templates/{id} (update)
GET /scan-config/templates/{id} (load)
POST /scan-config/frameworks/{framework}/{version}/validate (validation)
Components :
< TemplateEditorPage >
< PageHeader title = { isEdit ? "Edit Template" : "New Template" } / >
< Form onSubmit = { handleSubmit} >
< TextField label = "Name" value = { name } onChange = { setName } / >
< TextField label = "Description" multiline value = { description } / >
< FrameworkSelector
value = { framework}
onChange = { handleFrameworkChange}
/ >
< VariableCustomizer
framework = { framework}
version = { version}
initialValues = { template ?. variable_overrides}
onChange = { handleVariablesChange}
onValidate = { handleValidate}
/ >
< RuleFilterEditor
value = { ruleFilter}
onChange = { setRuleFilter}
/ >
< TagInput value = { tags } onChange = { setTags } / >
< FormControlLabel
control = { < Checkbox checked = { isDefault } onChange = { setIsDefault } / > }
label = "Set as default template"
/ >
< FormControlLabel
control = { < Checkbox checked = { isPublic } onChange = { setIsPublic } / > }
label = "Make public"
/ >
< Button onClick = { handleCancel} > Cancel < / B u t t o n >
< Button type = "submit" variant = "contained" > Save Template < / B u t t o n >
< / F o r m >
{ isEdit && (
< Tabs >
< Tab label = "Configuration" / >
< Tab label = "History" >
< AuditLog templateId = { template . template_id } / >
< / Tab >
< / Tabs >
) }
< / TemplateEditorPage >
2. Modified Pages (2 pages)
A. Enhanced /scans/config (frontend/src/pages/Scans/ConfigPage.tsx)
Modifications :
Add "From Template" tab
Add framework/version selection
Add variable customization accordion
Add "Save as template" checkbox
Integrate with host selection (existing hosts only)
API Calls :
GET /scan-config/frameworks
GET /scan-config/frameworks/{framework}/{version}/variables
POST /scan-config/frameworks/{framework}/{version}/validate
GET /scan-config/templates
POST /scan-config/templates/{id}/apply
POST /scans/execute
Components :
< ScanConfigPage >
< Tabs value = { activeTab } onChange = { setActiveTab } >
< Tab label = "Quick Scan" / >
< Tab label = "From Template" / >
< Tab label = "Advanced" / >
< / Tabs >
{ activeTab = = = 0 && (
< QuickScanTab >
< FrameworkSelector
onChange = { handleFrameworkChange}
/ >
< Accordion >
< AccordionSummary >
Show Variables ( { variables . length} )
< / A c c o r d i o n S u m m a r y >
< AccordionDetails >
< VariableCustomizer
framework = { framework}
version = { version}
onChange = { handleVariablesChange}
/ >
< / AccordionDetails >
< / Accordion >
< TargetSelector
hosts = { hosts }
hostGroups = { hostGroups }
onSelectHost = { setSelectedHost }
onSelectGroup = { setSelectedGroup }
onAddNewHost = { ( ) => navigate ( '/hosts/new' ) }
/ >
< FormControlLabel
control = { < Checkbox / > }
label = "Save as template"
/ >
< Button onClick = { handleRunScan} > Run Scan < / B u t t o n >
< / Q u i c k S c a n T a b >
) }
{ activeTab === 1 && (
< TemplateTab >
< TemplateSelector
templates = { templates}
onChange = { handleTemplateChange}
/ >
< TemplatePreview template = { selectedTemplate} / >
< Accordion >
< AccordionSummary > Override Variables < / A c c o r d i o n S u m m a r y >
< AccordionDetails >
< VariableCustomizer
framework = { selectedTemplate . framework}
version = { selectedTemplate . framework_version}
initialValues = { selectedTemplate . variable_overrides}
onChange = { handleOverrides}
/ >
< / AccordionDetails >
< / Accordion >
< TargetSelector ... / >
< Button onClick = { handleRunScan} > Run Scan < / B u t t o n >
< / T e m p l a t e T a b >
) }
< / ScanConfigPage >
B. Enhanced /host-groups (frontend/src/pages/HostGroups/HostGroupsPage.tsx)
Modifications :
Add "Scan with Template" dropdown to each host group card
Quick template application flow
Components :
< HostGroupCard >
< CardHeader
title = { group . name }
subheader = { `${group . host_count } hosts`}
action = {
< Menu >
< MenuItem onClick = { handleViewHosts } > View Hosts < / MenuItem >
< MenuItem onClick = { handleEdit } > Edit Group < / M e n u I t e m >
< Divider / >
< MenuItem >
< ListItemText primary = "Scan with Template" / >
< ArrowRight / >
< / MenuItem >
{ /* Submenu */ }
< SubMenu >
{ templates . map ( t => (
< MenuItem onClick = { ( ) => handleScanWithTemplate ( group , t ) } >
{ t . name }
< / M e n u I t e m >
) ) }
< Divider / >
< MenuItem onClick = { handleCustomScan} > Custom Configuration ...< / M e n u I t e m >
< / S u b M e n u >
< / M e n u >
}
/ >
< / HostGroupCard >
3. Reusable Components (12 new components)
A. FrameworkSelector (frontend/src/components/Frameworks/FrameworkSelector.tsx)
Purpose : Dropdown for framework + version selection
interface FrameworkSelectorProps {
value ?: { framework : string ; version : string }
onChange : ( framework : string , version : string ) => void
disabled ?: boolean
}
export const FrameworkSelector = ( { value, onChange, disabled } ) => {
const { data : frameworks } = useFrameworks ( )
const [ selectedFramework , setSelectedFramework ] = useState ( value ?. framework )
const [ selectedVersion , setSelectedVersion ] = useState ( value ?. version )
const selectedFrameworkData = frameworks ?. find ( f => f . framework === selectedFramework )
return (
< Box >
< Autocomplete
options = { frameworks | | [ ] }
getOptionLabel = { ( f ) => f . display_name }
value = { selectedFrameworkData}
onChange = { ( e , f ) => {
setSelectedFramework ( f ?. framework )
onChange ( f ?. framework , f ?. versions [ 0 ] )
} }
disabled = { disabled}
renderInput = { ( params ) => < TextField { ...params } label = "Framework" / > }
/ >
{ selectedFramework && (
< Autocomplete
options = { selectedFrameworkData ?. versions | | [ ] }
value = { selectedVersion }
onChange = { ( e , v ) => {
setSelectedVersion ( v )
onChange ( selectedFramework , v )
} }
disabled = { disabled }
renderInput = { ( params ) => < TextField { ...params } label = "Version" / > }
/ >
) }
< / Box >
)
}
B. VariableCustomizer (frontend/src/components/Variables/VariableCustomizer.tsx)
Purpose : Dynamic form for variable customization with validation
Features :
Grouped by category (collapsible accordions)
Type-specific inputs (number, select, text)
Real-time validation
Default value display
Constraint indicators
interface VariableCustomizerProps {
framework : string
version : string
initialValues ?: Record < string , string >
onChange : ( variables : Record < string , string > ) => void
onValidate ?: ( isValid : boolean , errors : Record < string , string > ) => void
}
export const VariableCustomizer = ( {
framework,
version,
initialValues,
onChange,
onValidate
} ) => {
const { data : variables } = useFrameworkVariables ( framework , version )
const [ values , setValues ] = useState ( initialValues || { } )
const [ errors , setErrors ] = useState < Record < string , string > > ( { } )
// Group variables by category
const groupedVariables = useMemo ( ( ) => {
return groupBy ( variables , 'category' )
} , [ variables ] )
// Validate on change
const handleChange = async ( varId : string , value : any ) => {
const newValues = { ...values , [ varId ] : value }
setValues ( newValues )
onChange ( newValues )
// Validate
const validation = await frameworkService . validateVariables (
framework ,
version ,
newValues
)
setErrors ( validation . errors || { } )
onValidate ?.( validation . valid , validation . errors )
}
return (
< Box >
{ Object. entries ( groupedVariables ) . map ( ( [ category , vars ] ) => (
< Accordion key = { category} >
< AccordionSummary expandIcon = { < ExpandMoreIcon / > } >
< Typography > { category | | 'General' } ( { vars . length } ) < / Typography >
< / AccordionSummary >
< AccordionDetails >
< Stack spacing = { 2 } >
{ vars . map ( variable => (
< VariableInput
key = { variable . id }
variable = { variable }
value = { values [ variable . id ] }
onChange = { ( v ) => handleChange ( variable . id , v ) }
error = { errors [ variable . id ] }
/ >
) ) }
< / Stack >
< / AccordionDetails >
< / Accordion >
) ) }
< / Box >
)
}
C. VariableInput (frontend/src/components/Variables/VariableInput.tsx)
Purpose : Type-specific input for a single variable
interface VariableInputProps {
variable : VariableDefinition
value : any
onChange : ( value : any ) => void
error ?: string
}
export const VariableInput = ( { variable, value, onChange, error } ) => {
const currentValue = value ?? variable . default
// Render based on type and constraints
if ( variable . type === 'number' ) {
const { lower_bound, upper_bound } = variable . constraints || { }
return (
< Box >
< Typography variant = "subtitle2" > { variable . title} < / T y p o g r a p h y >
< Typography variant = "caption" color = "text.secondary" >
{ variable . description}
< / T y p o g r a p h y >
{ lower_bound !== undefined && upper_bound !== undefined ? (
< Box >
< Slider
value = { Number ( currentValue ) }
onChange = { ( e , v ) => onChange ( v ) }
min = { lower_bound }
max = { upper_bound }
marks
valueLabelDisplay = "auto"
/ >
< TextField
type = "number"
value = { currentValue }
onChange = { ( e ) => onChange ( e . target . value ) }
inputProps = { { min : lower_bound , max : upper_bound } }
error= { ! ! error }
helperText = { error || `Range: ${ lower_bound } -${ upper_bound } ` }
/ >
< / Box >
) : (
< TextField
type = "number"
value = { currentValue }
onChange = { ( e ) => onChange ( e . target . value ) }
error = { ! ! error }
helperText = { error | | `Default: ${variable . default } `}
/ >
) }
< / Box >
)
}
if ( variable . type === 'boolean' ) {
return (
< FormControlLabel
control = {
< Switch
checked = { currentValue = == 'true' | | currentValue = == true }
onChange = { ( e ) => onChange ( e . target . checked ) }
/ >
}
label = {
< Box >
< Typography variant = "subtitle2" > { variable . title } < / Typography >
< Typography variant = "caption " color = "text.secondary" >
{ variable . description}
< / T y p o g r a p h y >
< / B o x >
}
/ >
)
}
if ( variable . constraints ?. choices ) {
return (
< FormControl fullWidth error = { ! ! error } >
< InputLabel > { variable . title} < / I n p u t L a b e l >
< Select
value = { currentValue}
onChange = { ( e ) => onChange ( e . target . value ) }
label = { variable . title}
>
{ variable. constraints . choices . map ( choice => (
< MenuItem key = { choice } value = { choice } > { choice} < / M e n u I t e m >
) ) }
< / S e l e c t >
< FormHelperText >
{ error || variable . description }
< / F o r m H e l p e r T e x t >
< / F o r m C o n t r o l >
)
}
// Default: text input
return (
< TextField
fullWidth
label = { variable . title }
value = { currentValue }
onChange = { ( e ) => onChange ( e . target . value ) }
helperText = { error | | variable . description }
error = { ! ! error }
inputProps = {
variable . constraints ?. match
? { pattern : variable . constraints . match }
: { }
}
/ >
)
}
D. TemplateSelector (frontend/src/components/Templates/TemplateSelector.tsx)
interface TemplateSelectorProps {
value ?: string
onChange : ( templateId : string ) => void
framework ?: string
}
export const TemplateSelector = ( { value, onChange, framework } ) => {
const { data : templates } = useTemplates ( { framework } )
// Group by user's vs public
const myTemplates = templates ?. filter ( t => t . created_by === currentUser . username )
const publicTemplates = templates ?. filter ( t => t . is_public )
return (
< Autocomplete
options = { [
{ label : 'My Templates' , options : myTemplates || [ ] } ,
{ label : 'Public Templates' , options : publicTemplates || [ ] }
] }
groupBy = { ( option ) => option . label }
getOptionLabel = { ( t ) => t . name }
value = { templates ?. find ( t => t . template_id === value ) }
onChange = { ( e , t ) => onChange ( t ?. template_id ) }
renderInput = { ( params ) => < TextField { ...params } label = "Select Template" / > }
renderOption = { ( props , template ) => (
< li { ...props } >
< Box >
< Typography >
{ template . name}
{ template . is_default && < StarIcon fontSize = "small" color = "primary" / > }
< / Typography >
< Typography variant = "caption" color = "text.secondary" >
{ template . framework } { template . framework_version } |
{ Object . keys ( template . variable_overrides ) . length } variables
< / Typography >
< / Box >
< / li >
) }
/ >
)
}
E. TemplateCard (frontend/src/components/Templates/TemplateCard.tsx)
interface TemplateCardProps {
template : ScanTemplate
onEdit ?: ( ) => void
onDelete ?: ( ) => void
onClone ?: ( ) => void
onSetDefault ?: ( ) => void
onUse ?: ( ) => void
isPublic ?: boolean
}
export const TemplateCard = ( { template, onEdit, onDelete, onClone, onSetDefault, onUse } ) => {
return (
< Card >
< CardHeader
title = {
< Box display = "flex" alignItems = "center" gap = { 1 } >
{ template . name }
{ template . is_default & & < Chip label = "Default" size = "small" color = "primary" / > }
{ template . is_public & & < PublicIcon fontSize = "small" / > }
< / Box >
}
subheader = { `${template . framework } ${template . framework_version } `}
action = {
< IconButton >
< MoreVertIcon / >
< / IconButton >
}
/ >
< CardContent >
< Typography variant = "body2 " color = "text . secondary ">
{ template . description }
< / Typography >
< Box mt = { 2 } >
< Typography variant = "caption ">
{ Object . keys ( template . variable_overrides ) . length } variables customized
< / Typography >
< / Box >
< Box mt = { 1 } >
{ template . tags . map ( tag => (
< Chip key = { tag } label = { tag } size = "small" sx = { { mr : 0.5 } } / >
) ) }
< / Box >
< Box mt = { 2 } display = "flex" gap = { 1 } >
< Typography variant = "caption" color = "text.secondary" >
Created : { formatDate ( template . created_at ) }
< / T y p o g r a p h y >
< / B o x >
< / C a r d C o n t e n t >
< CardActions >
{ onUse && < Button size = "small" onClick = { onUse } > Use Template < / B u t t o n > }
{ onEdit && < Button size = "small" onClick = { onEdit } > Edit < / B u t t o n > }
{ onClone && < Button size = "small" onClick = { onClone } > Clone < / B u t t o n > }
{ onSetDefault && ! template . is_default && (
< Button size = "small" onClick = { onSetDefault } > Set Default < / B u t t o n >
) }
{ onDelete & & < Button size = "small" color = "error" onClick = { onDelete } > Delete < / Button > }
< / CardActions >
< / Card >
)
}
4. API Services (2 new services)
A. Framework Service (frontend/src/services/frameworkService.ts)
import { api } from './api'
export const frameworkService = {
listFrameworks : async ( ) => {
const response = await api . get ( '/scan-config/frameworks' )
return response . data
} ,
getFrameworkDetails : async ( framework : string , version : string ) => {
const response = await api . get ( `/scan-config/frameworks/${ framework } /${ version } ` )
return response . data
} ,
getVariables : async ( framework : string , version : string ) => {
const response = await api . get ( `/scan-config/frameworks/${ framework } /${ version } /variables` )
return response . data
} ,
validateVariables : async (
framework : string ,
version : string ,
variables : Record < string , any >
) => {
const response = await api . post (
`/scan-config/frameworks/${ framework } /${ version } /validate` ,
{ variables }
)
return response . data
}
}
B. Template Service (frontend/src/services/templateService.ts)
export const templateService = {
list : async ( params ?: { framework ?: string ; tags ?: string } ) => {
const response = await api . get ( '/scan-config/templates' , { params } )
return response . data
} ,
get : async ( id : string ) => {
const response = await api . get ( `/scan-config/templates/${ id } ` )
return response . data
} ,
create : async ( data : CreateTemplateRequest ) => {
const response = await api . post ( '/scan-config/templates' , data )
return response . data
} ,
update : async ( id : string , data : UpdateTemplateRequest ) => {
const response = await api . put ( `/scan-config/templates/${ id } ` , data )
return response . data
} ,
delete : async ( id : string ) => {
await api . delete ( `/scan-config/templates/${ id } ` )
} ,
apply : async ( id : string , target : any , additionalOverrides ?: Record < string , string > ) => {
const response = await api . post ( `/scan-config/templates/${ id } /apply` , {
target,
variable_overrides : additionalOverrides
} )
return response . data
} ,
clone : async ( id : string , newName : string ) => {
const response = await api . post (
`/scan-config/templates/${ id } /clone?new_name=${ encodeURIComponent ( newName ) } `
)
return response . data
} ,
setDefault : async ( id : string ) => {
const response = await api . post ( `/scan-config/templates/${ id } /set-default` )
return response . data
} ,
getStatistics : async ( ) => {
const response = await api . get ( '/scan-config/statistics' )
return response . data
}
}
5. React Hooks (4 new hooks)
A. useFrameworks (frontend/src/hooks/useFrameworks.ts)
import { useQuery } from '@tanstack/react-query'
import { frameworkService } from '@/services/frameworkService'
export const useFrameworks = ( ) => {
return useQuery ( {
queryKey : [ 'frameworks' ] ,
queryFn : ( ) => frameworkService . listFrameworks ( ) ,
staleTime : 5 * 60 * 1000 // 5 minutes
} )
}
export const useFrameworkDetails = ( framework : string , version : string ) => {
return useQuery ( {
queryKey : [ 'framework' , framework , version ] ,
queryFn : ( ) => frameworkService . getFrameworkDetails ( framework , version ) ,
enabled : ! ! framework && ! ! version
} )
}
export const useFrameworkVariables = ( framework : string , version : string ) => {
return useQuery ( {
queryKey : [ 'framework-variables' , framework , version ] ,
queryFn : ( ) => frameworkService . getVariables ( framework , version ) ,
enabled : ! ! framework && ! ! version
} )
}
B. useTemplates (frontend/src/hooks/useTemplates.ts)
import { useQuery , useMutation , useQueryClient } from '@tanstack/react-query'
import { templateService } from '@/services/templateService'
export const useTemplates = ( filters ?: { framework ?: string ; tags ?: string } ) => {
return useQuery ( {
queryKey : [ 'templates' , filters ] ,
queryFn : ( ) => templateService . list ( filters )
} )
}
export const useTemplate = ( id : string ) => {
return useQuery ( {
queryKey : [ 'template' , id ] ,
queryFn : ( ) => templateService . get ( id ) ,
enabled : ! ! id
} )
}
export const useCreateTemplate = ( ) => {
const queryClient = useQueryClient ( )
return useMutation ( {
mutationFn : templateService . create ,
onSuccess : ( ) => {
queryClient . invalidateQueries ( { queryKey : [ 'templates' ] } )
}
} )
}
export const useUpdateTemplate = ( ) => {
const queryClient = useQueryClient ( )
return useMutation ( {
mutationFn : ( { id, data } : { id : string ; data : any } ) =>
templateService . update ( id , data ) ,
onSuccess : ( _ , variables ) => {
queryClient . invalidateQueries ( { queryKey : [ 'templates' ] } )
queryClient . invalidateQueries ( { queryKey : [ 'template' , variables . id ] } )
}
} )
}
export const useDeleteTemplate = ( ) => {
const queryClient = useQueryClient ( )
return useMutation ( {
mutationFn : templateService . delete ,
onSuccess : ( ) => {
queryClient . invalidateQueries ( { queryKey : [ 'templates' ] } )
}
} )
}
6. TypeScript Types (frontend/src/types/scanConfig.ts)
export interface Framework {
framework : string
display_name : string
versions : string [ ]
description : string
rule_count : number
variable_count : number
categories ?: string [ ]
severities ?: Record < string , number >
}
export interface VariableConstraint {
lower_bound ?: number
upper_bound ?: number
choices ?: string [ ]
match ?: string
}
export interface VariableDefinition {
id : string
title : string
description : string
type : 'string' | 'number' | 'boolean'
default : any
constraints ?: VariableConstraint
interactive : boolean
category ?: string
}
export interface ScanTemplate {
template_id : string
name : string
description ?: string
framework : string
framework_version : string
target_type : string
variable_overrides : Record < string , string >
rule_filter ?: Record < string , any >
created_by : string
created_at : string
updated_at : string
is_default : boolean
is_public : boolean
tags : string [ ]
version : number
shared_with : string [ ]
}
export interface CreateTemplateRequest {
name : string
description ?: string
framework : string
framework_version : string
target_type : string
variable_overrides : Record < string , string >
rule_filter ?: Record < string , any >
tags : string [ ]
is_public : boolean
}
export interface ValidationResult {
valid : boolean
errors : Record < string , string >
warnings : Record < string , string >
}
7. Navigation Updates
A. Sidebar (frontend/src/components/Layout/Sidebar.tsx)
Add menu items:
{
title : 'Content' ,
items : [
{ label : 'Compliance Rules' , path : '/content/rules' , icon : < RuleIcon / > } ,
{ label : 'Frameworks' , path : '/content/frameworks' , icon : < AccountTreeIcon / > } , / / NEW
{ label : 'Templates' , path : '/content/templates' , icon : < BookmarkIcon / > } , / / NEW
]
}
B. Routes (frontend/src/App.tsx)
< Route path = "/content/frameworks" element = { < FrameworksPage / > } / >
< Route path = "/content/frameworks/:framework/:version" element = { < FrameworkDetailPage / > } / >
< Route path = "/content/templates" element = { < TemplatesPage / > } / >
< Route path = "/content/templates/new" element = { < TemplateEditorPage / > } / >
< Route path = "/content/templates/:id" element = { < TemplateEditorPage / > } / >
Estimated Effort
5-7 days (as planned)
Pages: 2 days (4 new pages)
Components: 2 days (12 components)
Services/Hooks: 0.5 day
Integration: 1 day
Testing: 0.5 day
Polish: 1 day
Acceptance Criteria
Testing Strategy
Unit Tests
Variable validation logic
Type-specific input rendering
Template CRUD operations
Framework selector state management
Integration Tests
Complete scan configuration workflow
Template creation from framework
Template application to scan
Variable override merging
E2E Tests
Browse frameworks → View details → Create template
Configure new scan → Customize variables → Save as template → Execute scan
Load template → Override variables → Execute scan
Clone public template → Edit → Use for scan
Related Issues
Phase 1 Completion
After this issue:
✅ Phase 1 Complete (7/7 tasks - 100%) :
✅ Enhanced ComplianceRule Model (PR [Phase 1] Add XCCDFVariable Model and XCCDF Variables Support #95 )
✅ Enhanced SCAP Converter (PR [Phase 1] Enhanced SCAP Converter with Variable and Remediation Extraction #97 )
✅ XCCDF Generator (PR [Phase 1] XCCDF Data-Stream Generator from MongoDB #99 )
✅ Scan Service (PR [Phase 1 Issue #4] MongoDB-Based Scan Service with Multi-Scanner Routing #101 )
✅ ORSA Remediation Engine (PR [Phase 1 Issue #5] ORSA Remediation Engine - Ansible & Bash Executors #103 )
✅ Scan Configuration API (PR [Phase 1 Issue #6] Scan Configuration API - Framework Discovery & Template Management #105 )
✅ Frontend Variable Customization UI (this issue)
🎉 Phase 1: XCCDF Variables + Hybrid Scanning Engine - COMPLETE
Overview
Implement the Frontend Variable Customization UI to complete Phase 1 of the hybrid scanning architecture. This provides the user interface for framework selection, variable customization, template management, and scan configuration.
Phase 1 Progress: 6/7 tasks completed → 7/7 after this issue
Problem Statement
The backend APIs (PR #105) provide complete framework discovery, variable management, and template operations, but users have no UI to:
Solution: Complete Frontend UI
Implement React/TypeScript components integrated with Material-UI v5 and the scan configuration APIs.
Architecture: Page Organization
1.
/content- Compliance Content HubAdd two new sections:
A.
/content/frameworks(NEW)B.
/content/templates(NEW)2.
/scans/config- Enhanced Scan ConfigurationModify existing page:
3.
/host-groups- Template Quick ActionsAdd to existing page:
Implementation Tasks
1. New Pages (2 pages)
A. Frameworks Page (
frontend/src/pages/Content/FrameworksPage.tsx)Features:
API Calls:
GET /scan-config/frameworksComponents:
B. Framework Details Page (
frontend/src/pages/Content/FrameworkDetailPage.tsx)Features:
API Calls:
GET /scan-config/frameworks/{framework}/{version}GET /scan-config/frameworks/{framework}/{version}/variablesComponents:
C. Templates Page (
frontend/src/pages/Content/TemplatesPage.tsx)Features:
API Calls:
GET /scan-config/templatesPOST /scan-config/templates/{id}/cloneDELETE /scan-config/templates/{id}POST /scan-config/templates/{id}/set-defaultComponents:
D. Template Editor Page (
frontend/src/pages/Content/TemplateEditorPage.tsx)Features:
API Calls:
POST /scan-config/templates(create)PUT /scan-config/templates/{id}(update)GET /scan-config/templates/{id}(load)POST /scan-config/frameworks/{framework}/{version}/validate(validation)Components:
2. Modified Pages (2 pages)
A. Enhanced
/scans/config(frontend/src/pages/Scans/ConfigPage.tsx)Modifications:
API Calls:
GET /scan-config/frameworksGET /scan-config/frameworks/{framework}/{version}/variablesPOST /scan-config/frameworks/{framework}/{version}/validateGET /scan-config/templatesPOST /scan-config/templates/{id}/applyPOST /scans/executeComponents:
B. Enhanced
/host-groups(frontend/src/pages/HostGroups/HostGroupsPage.tsx)Modifications:
Components:
3. Reusable Components (12 new components)
A. FrameworkSelector (
frontend/src/components/Frameworks/FrameworkSelector.tsx)Purpose: Dropdown for framework + version selection
B. VariableCustomizer (
frontend/src/components/Variables/VariableCustomizer.tsx)Purpose: Dynamic form for variable customization with validation
Features:
C. VariableInput (
frontend/src/components/Variables/VariableInput.tsx)Purpose: Type-specific input for a single variable
D. TemplateSelector (
frontend/src/components/Templates/TemplateSelector.tsx)E. TemplateCard (
frontend/src/components/Templates/TemplateCard.tsx)4. API Services (2 new services)
A. Framework Service (
frontend/src/services/frameworkService.ts)B. Template Service (
frontend/src/services/templateService.ts)5. React Hooks (4 new hooks)
A. useFrameworks (
frontend/src/hooks/useFrameworks.ts)B. useTemplates (
frontend/src/hooks/useTemplates.ts)6. TypeScript Types (
frontend/src/types/scanConfig.ts)7. Navigation Updates
A. Sidebar (
frontend/src/components/Layout/Sidebar.tsx)Add menu items:
B. Routes (
frontend/src/App.tsx)Estimated Effort
5-7 days (as planned)
Acceptance Criteria
Testing Strategy
Unit Tests
Integration Tests
E2E Tests
Related Issues
Phase 1 Completion
After this issue:
✅ Phase 1 Complete (7/7 tasks - 100%):
🎉 Phase 1: XCCDF Variables + Hybrid Scanning Engine - COMPLETE