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>
73 lines
2.5 KiB
TypeScript
73 lines
2.5 KiB
TypeScript
import { ShieldAlert } from 'lucide-react'
|
||
import { cn } from '@/lib/utils'
|
||
import type { ScriptTemplateListItem } from '@/types'
|
||
|
||
const COMPLEXITY_CLASSES: Record<ScriptTemplateListItem['complexity'], string> = {
|
||
beginner: 'text-emerald-400 bg-emerald-400/10',
|
||
intermediate: 'text-amber-400 bg-amber-400/10',
|
||
advanced: 'text-rose-500 bg-rose-500/10',
|
||
}
|
||
|
||
interface Props {
|
||
template: ScriptTemplateListItem
|
||
onConfigure: (id: string) => void
|
||
}
|
||
|
||
export function TemplateCard({ template, onConfigure }: Props) {
|
||
return (
|
||
<div
|
||
className={cn(
|
||
'w-full px-4 py-3 rounded-xl border transition-all',
|
||
'border-border bg-transparent'
|
||
)}
|
||
>
|
||
<div className="flex items-start justify-between gap-2 mb-1">
|
||
<span className="text-sm font-medium text-foreground line-clamp-1">
|
||
{template.name}
|
||
</span>
|
||
<div className="flex items-center gap-1.5 shrink-0">
|
||
{template.requires_elevation && (
|
||
<span title="Requires administrator elevation">
|
||
<ShieldAlert size={13} className="text-amber-400" />
|
||
</span>
|
||
)}
|
||
<span className={cn('font-label text-[0.625rem] uppercase tracking-wide px-1.5 py-0.5 rounded', COMPLEXITY_CLASSES[template.complexity])}>
|
||
{template.complexity}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
{template.description && (
|
||
<p className="text-xs text-muted-foreground line-clamp-2 mb-2">
|
||
{template.description}
|
||
</p>
|
||
)}
|
||
|
||
<div className="flex items-center justify-between gap-3">
|
||
<div className="flex items-center gap-3 text-[0.625rem] text-muted-foreground font-label">
|
||
<span>{template.usage_count}× used</span>
|
||
{template.tags.length > 0 && (
|
||
<div className="flex gap-1 flex-wrap">
|
||
{template.tags.slice(0, 3).map(tag => (
|
||
<span key={tag} className="bg-white/5 border border-border rounded px-1.5 py-0.5">
|
||
{tag}
|
||
</span>
|
||
))}
|
||
{template.tags.length > 3 && (
|
||
<span className="text-muted-foreground">+{template.tags.length - 3}</span>
|
||
)}
|
||
</div>
|
||
)}
|
||
</div>
|
||
<button
|
||
type="button"
|
||
onClick={() => onConfigure(template.id)}
|
||
className="shrink-0 bg-primary/10 border border-primary/20 text-primary text-xs px-2.5 py-1 rounded-md hover:bg-primary/20 transition-colors"
|
||
>
|
||
Configure →
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|