feat: Script Generator Phase 1+2 — backend, engine, API, frontend, template editor, parameter detector
Complete Script Generator feature including: Backend: - ScriptCategory, ScriptTemplate, ScriptGeneration models - ScriptTemplateEngine with substitution, filters, sanitization - CRUD + share API endpoints with permission checks - Integration tests for permissions and sharing - Migration 057 with AD User Management seed templates Frontend — Script Library: - Browse templates with category tabs and search - Configure pane with parameter form and script generation - Script preview with live substitution and copy/download - scriptGeneratorStore Zustand store Frontend — Template Editor: - Full CRUD form with metadata, script body (Monaco Editor), parameters - ParameterSchemaBuilder with visual builder + JSON toggle - ScriptManagePage with routing and nav link Frontend — Parameter Detector: - Client-side PowerShell parameter detection engine - Detects script-level param() blocks and variable assignments - Type inference from PS type annotations and value patterns - ParameterDetectorStepper one-by-one review UI with accept/skip Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit was merged in pull request #105.
This commit is contained in:
98
frontend/src/pages/ScriptLibraryPage.tsx
Normal file
98
frontend/src/pages/ScriptLibraryPage.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Terminal, Settings } from 'lucide-react'
|
||||
import { useScriptGeneratorStore } from '@/store/scriptGeneratorStore'
|
||||
import { usePermissions } from '@/hooks/usePermissions'
|
||||
import { ScriptFilterBar } from '@/components/scripts/ScriptFilterBar'
|
||||
import { ScriptTemplateList } from '@/components/scripts/ScriptTemplateList'
|
||||
import { ScriptConfigurePane } from '@/components/scripts/ScriptConfigurePane'
|
||||
import { ScriptPreview } from '@/components/scripts/ScriptPreview'
|
||||
|
||||
export default function ScriptLibraryPage() {
|
||||
const [paneMode, setPaneMode] = useState<'browse' | 'configure'>('browse')
|
||||
// inputValue owned here so it survives Configure ↔ Browse transitions
|
||||
const [inputValue, setInputValue] = useState('')
|
||||
|
||||
const loadCategories = useScriptGeneratorStore(s => s.loadCategories)
|
||||
const loadTemplates = useScriptGeneratorStore(s => s.loadTemplates)
|
||||
const setSearch = useScriptGeneratorStore(s => s.setSearch)
|
||||
const selectTemplate = useScriptGeneratorStore(s => s.selectTemplate)
|
||||
const clearOutput = useScriptGeneratorStore(s => s.clearOutput)
|
||||
const selectedTemplate = useScriptGeneratorStore(s => s.selectedTemplate)
|
||||
|
||||
const { isEngineer } = usePermissions()
|
||||
const canGenerate = isEngineer
|
||||
|
||||
useEffect(() => {
|
||||
loadCategories().then(() => loadTemplates())
|
||||
}, [loadCategories, loadTemplates])
|
||||
|
||||
const onClearSearch = () => {
|
||||
setInputValue('')
|
||||
setSearch('')
|
||||
}
|
||||
|
||||
const onConfigure = (id: string) => {
|
||||
selectTemplate(id)
|
||||
setPaneMode('configure')
|
||||
}
|
||||
|
||||
const onBack = () => {
|
||||
clearOutput()
|
||||
setPaneMode('browse')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 p-6 h-full">
|
||||
{/* Page header */}
|
||||
<div>
|
||||
<h1 className="text-2xl font-heading font-bold text-foreground">Script Library</h1>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Browse PowerShell templates, fill in parameters, and generate ready-to-run scripts.
|
||||
</p>
|
||||
{isEngineer && (
|
||||
<Link
|
||||
to="/scripts/manage"
|
||||
className="inline-flex items-center gap-1.5 text-xs text-primary bg-primary/10 hover:bg-primary/15 px-2.5 py-1 rounded-full transition-colors mt-2 group"
|
||||
>
|
||||
<Settings size={12} className="group-hover:rotate-90 transition-transform duration-300" />
|
||||
Manage Templates
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Two-column layout */}
|
||||
<div className="grid grid-cols-[320px_1fr] gap-4 flex-1 min-h-0">
|
||||
{/* Left pane — Browse or Configure mode */}
|
||||
{paneMode === 'browse' ? (
|
||||
<div className="glass-card-static flex flex-col overflow-hidden">
|
||||
<div className="p-2 pb-0">
|
||||
<ScriptFilterBar inputValue={inputValue} setInputValue={setInputValue} />
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<ScriptTemplateList
|
||||
inputValue={inputValue}
|
||||
onClearSearch={onClearSearch}
|
||||
onConfigure={onConfigure}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<ScriptConfigurePane canGenerate={canGenerate} onBack={onBack} />
|
||||
)}
|
||||
|
||||
{/* Right pane — read-only ScriptPreview */}
|
||||
{selectedTemplate === null ? (
|
||||
<div className="glass-card-static h-full flex flex-col items-center justify-center gap-3 text-center p-8">
|
||||
<Terminal size={40} className="text-muted-foreground/40" />
|
||||
<p className="text-sm text-muted-foreground">Select a template to get started</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="glass-card-static h-full overflow-y-auto p-4">
|
||||
<ScriptPreview />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user