This commit is contained in:
dengqichen 2025-01-20 14:56:55 +08:00
parent 6b1e9c6b4c
commit ee70c571e8
2 changed files with 339 additions and 196 deletions

View File

@ -13,5 +13,54 @@ loader.config({
}
});
// 注册 Groovy 语言支持
loader.init().then((monaco) => {
monaco.languages.register({ id: 'groovy' });
monaco.languages.setMonarchTokensProvider('groovy', {
keywords: [
'def', 'class', 'interface', 'trait', 'extends', 'implements',
'package', 'import', 'new', 'null', 'true', 'false',
'if', 'else', 'for', 'while', 'do', 'switch', 'case',
'break', 'continue', 'return', 'throw', 'try', 'catch',
'finally', 'this', 'super', 'abstract', 'static', 'final',
'public', 'protected', 'private', 'void'
],
operators: [
'=', '>', '<', '!', '~', '?', ':',
'==', '<=', '>=', '!=', '&&', '||', '++', '--',
'+', '-', '*', '/', '&', '|', '^', '%', '<<',
'>>', '>>>', '+=', '-=', '*=', '/=', '&=', '|=',
'^=', '%=', '<<=', '>>=', '>>>='
],
symbols: /[=><!~?:&|+\-*\/\^%]+/,
tokenizer: {
root: [
[/[a-zA-Z_$][\w$]*/, {
cases: {
'@keywords': 'keyword',
'@default': 'identifier'
}
}],
[/".*?"/, 'string'],
[/'.*?'/, 'string'],
[/\/\/.*$/, 'comment'],
[/\/\*/, 'comment', '@comment'],
[/[0-9]+/, 'number'],
[/@symbols/, {
cases: {
'@operators': 'operator',
'@default': ''
}
}]
],
comment: [
[/[^/*]+/, 'comment'],
[/\*\//, 'comment', '@pop'],
[/[/*]/, 'comment']
]
}
});
});
export const Editor = MonacoEditor;
export default Editor;

View File

@ -26,11 +26,12 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {PlusCircle, X} from 'lucide-react';
import {PlusCircle, X, Maximize2} from 'lucide-react';
import {useForm, useFieldArray} from "react-hook-form";
import {getApplicationList} from '../../../Application/List/service';
import {getExternalSystemList, getJenkinsViewList, getJenkinsJobList} from '../service';
import type {ExternalSystem} from '@/pages/Deploy/External/types';
import { Editor } from "@/components/Editor";
interface Application {
id: number;
@ -58,6 +59,7 @@ interface FormValues {
viewId?: number;
jobId?: number;
envs: EnvVariable[];
script: string;
}
interface DeploymentConfigModalProps {
@ -71,6 +73,8 @@ const DeploymentConfigModal: React.FC<DeploymentConfigModalProps> = ({open, onCa
const [externalSystems, setExternalSystems] = useState<ExternalSystem[]>([]);
const [jenkinsViews, setJenkinsViews] = useState<JenkinsView[]>([]);
const [jenkinsJobs, setJenkinsJobs] = useState<JenkinsJob[]>([]);
const [fullscreenEditor, setFullscreenEditor] = useState(false);
const [isFullscreen, setIsFullscreen] = useState(false);
const form = useForm<FormValues>({
defaultValues: {
@ -79,6 +83,7 @@ const DeploymentConfigModal: React.FC<DeploymentConfigModalProps> = ({open, onCa
viewId: undefined,
jobId: undefined,
envs: [{key: '', value: ''}],
script: '',
}
});
@ -137,204 +142,293 @@ const DeploymentConfigModal: React.FC<DeploymentConfigModalProps> = ({open, onCa
});
return (
<Dialog open={open} onOpenChange={(open) => !open && onCancel()}>
<DialogContent className="max-w-[800px] h-[75vh]">
<DialogHeader className="pb-4">
<DialogTitle></DialogTitle>
</DialogHeader>
<Form {...form}>
<form onSubmit={handleSubmit} className="space-y-4 flex flex-col h-full">
<ScrollArea className="flex-1 -mx-6">
<div className="px-6 space-y-4">
<div className="grid grid-cols-2 gap-4">
<FormField
control={form.control}
name="applicationId"
render={({field}) => (
<FormItem>
<FormLabel></FormLabel>
<Select
onValueChange={(value) => field.onChange(Number(value))}
value={field.value?.toString()}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="请选择应用" />
</SelectTrigger>
</FormControl>
<SelectContent>
{applications.map((app) => (
<SelectItem key={app.id} value={String(app.id)}>
{app.appName}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="externalSystemId"
render={({field}) => (
<FormItem>
<FormLabel></FormLabel>
<Select
onValueChange={(value) => handleExternalSystemChange(Number(value))}
value={field.value?.toString()}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="请选择三方系统" />
</SelectTrigger>
</FormControl>
<SelectContent>
{externalSystems.map((system) => (
<SelectItem key={system.id} value={String(system.id)}>
{system.name}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
</div>
<Separator className="my-4" />
<div className="grid grid-cols-2 gap-4">
<FormField
control={form.control}
name="viewId"
render={({field}) => (
<FormItem>
<FormLabel>Jenkins视图选择</FormLabel>
<Select
disabled={!form.getValues('externalSystemId')}
onValueChange={(value) => handleViewChange(Number(value))}
value={field.value?.toString()}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="请选择 View" />
</SelectTrigger>
</FormControl>
<SelectContent>
{jenkinsViews.map((view) => (
<SelectItem key={view.id} value={String(view.id)}>
{view.viewName}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="jobId"
render={({field}) => (
<FormItem>
<FormLabel>Jenkins任务选择</FormLabel>
<Select
disabled={!form.getValues('viewId')}
onValueChange={(value) => field.onChange(Number(value))}
value={field.value?.toString()}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="请选择 Job" />
</SelectTrigger>
</FormControl>
<SelectContent>
{jenkinsJobs.map((job) => (
<SelectItem key={job.id} value={String(job.id)}>
{job.jobName}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
</div>
<Separator className="my-4" />
<div className="space-y-4">
<div className="flex items-center justify-between bg-background z-10 py-2">
<FormLabel></FormLabel>
<Button
type="button"
variant="outline"
size="sm"
onClick={() => append({key: '', value: ''})}
>
<PlusCircle className="h-4 w-4 mr-2" />
</Button>
</div>
<ScrollArea className="h-[200px] rounded-md border">
<div className="space-y-4 p-4">
{fields.map((field, index) => (
<div key={field.id} className="flex gap-4 items-start">
<FormField
control={form.control}
name={`envs.${index}.key`}
render={({field}) => (
<FormItem className="flex-1">
<FormControl>
<Input placeholder="变量名" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name={`envs.${index}.value`}
render={({field}) => (
<FormItem className="flex-1">
<FormControl>
<Input placeholder="变量值" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button
type="button"
variant="ghost"
size="icon"
onClick={() => remove(index)}
className="mt-2"
<>
<Dialog open={open} onOpenChange={(open) => !open && onCancel()}>
<DialogContent className="max-w-[800px] h-[90vh] flex flex-col">
<DialogHeader className="flex-shrink-0">
<DialogTitle></DialogTitle>
</DialogHeader>
<Form {...form}>
<form onSubmit={handleSubmit} className="flex flex-col flex-1 overflow-hidden">
<ScrollArea className="flex-1 -mr-6 pr-6">
<div className="space-y-6">
<div className="grid grid-cols-2 gap-4">
<FormField
control={form.control}
name="applicationId"
render={({field}) => (
<FormItem>
<FormLabel></FormLabel>
<Select
onValueChange={(value) => field.onChange(Number(value))}
value={field.value?.toString()}
>
<X className="h-4 w-4" />
</Button>
</div>
))}
<FormControl>
<SelectTrigger>
<SelectValue placeholder="请选择应用" />
</SelectTrigger>
</FormControl>
<SelectContent>
{applications.map((app) => (
<SelectItem key={app.id} value={String(app.id)}>
{app.appName}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="externalSystemId"
render={({field}) => (
<FormItem>
<FormLabel></FormLabel>
<Select
onValueChange={(value) => handleExternalSystemChange(Number(value))}
value={field.value?.toString()}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="请选择三方系统" />
</SelectTrigger>
</FormControl>
<SelectContent>
{externalSystems.map((system) => (
<SelectItem key={system.id} value={String(system.id)}>
{system.name}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
</div>
<Separator className="my-4" />
<div className="grid grid-cols-2 gap-4">
<FormField
control={form.control}
name="viewId"
render={({field}) => (
<FormItem>
<FormLabel>Jenkins视图选择</FormLabel>
<Select
disabled={!form.getValues('externalSystemId')}
onValueChange={(value) => handleViewChange(Number(value))}
value={field.value?.toString()}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="请选择 View" />
</SelectTrigger>
</FormControl>
<SelectContent>
{jenkinsViews.map((view) => (
<SelectItem key={view.id} value={String(view.id)}>
{view.viewName}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="jobId"
render={({field}) => (
<FormItem>
<FormLabel>Jenkins任务选择</FormLabel>
<Select
disabled={!form.getValues('viewId')}
onValueChange={(value) => field.onChange(Number(value))}
value={field.value?.toString()}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="请选择 Job" />
</SelectTrigger>
</FormControl>
<SelectContent>
{jenkinsJobs.map((job) => (
<SelectItem key={job.id} value={String(job.id)}>
{job.jobName}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
</div>
<Separator className="my-4" />
<div className="space-y-4">
<div className="flex items-center justify-between bg-background z-10 py-2">
<FormLabel></FormLabel>
<Button
type="button"
variant="outline"
size="sm"
onClick={() => append({key: '', value: ''})}
>
<PlusCircle className="h-4 w-4 mr-2" />
</Button>
</div>
</ScrollArea>
<ScrollArea className="h-[200px] rounded-md border">
<div className="space-y-4 p-4">
{fields.map((field, index) => (
<div key={field.id} className="flex gap-4 items-start">
<FormField
control={form.control}
name={`envs.${index}.key`}
render={({field}) => (
<FormItem className="flex-1">
<FormControl>
<Input placeholder="变量名" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name={`envs.${index}.value`}
render={({field}) => (
<FormItem className="flex-1">
<FormControl>
<Input placeholder="变量值" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button
type="button"
variant="ghost"
size="icon"
onClick={() => remove(index)}
className="mt-2"
>
<X className="h-4 w-4" />
</Button>
</div>
))}
</div>
</ScrollArea>
</div>
<Separator className="my-4" />
<div className="space-y-4">
<FormField
control={form.control}
name="script"
render={({field}) => (
<FormItem className="relative">
<FormLabel></FormLabel>
<FormControl>
<div className="relative">
<Button
type="button"
variant="outline"
size="sm"
className="absolute right-2 top-2 z-10"
onClick={() => setIsFullscreen(true)}
>
<Maximize2 className="h-4 w-4"/>
</Button>
<Button
type="button"
variant="outline"
size="sm"
className="absolute right-14 top-2 z-10"
>
</Button>
<Editor
height="300px"
language="groovy"
theme="vs-dark"
value={field.value}
onChange={field.onChange}
options={{
minimap: { enabled: false },
scrollBeyondLastLine: false,
fontSize: 14,
lineNumbers: 'on',
renderLineHighlight: 'all',
automaticLayout: true
}}
/>
</div>
</FormControl>
<FormMessage/>
</FormItem>
)}
/>
</div>
</div>
</ScrollArea>
<div className="flex-shrink-0 border-t bg-background mt-6">
<DialogFooter className="pt-4">
<Button variant="outline" onClick={onCancel}>
</Button>
<Button onClick={form.handleSubmit(handleSubmit)}>
</Button>
</DialogFooter>
</div>
</ScrollArea>
<DialogFooter className="flex-shrink-0">
<Button variant="outline" onClick={onCancel}>
</Button>
<Button onClick={form.handleSubmit(handleSubmit)}>
</Button>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
</form>
</Form>
</DialogContent>
</Dialog>
{isFullscreen && (
<Dialog open={isFullscreen} onOpenChange={setIsFullscreen}>
<DialogContent className="max-w-[100vw] w-[100vw] h-[100vh] p-0">
<div className="h-full flex flex-col">
<div className="flex-shrink-0 p-4 border-b">
<DialogTitle></DialogTitle>
</div>
<div className="flex-1 p-4">
<FormField
control={form.control}
name="script"
render={({ field }) => (
<Editor
height="calc(100vh - 120px)"
language="groovy"
theme="vs-dark"
value={field.value}
onChange={(value) => field.onChange(value ?? '')}
options={{
minimap: { enabled: true },
scrollBeyondLastLine: false,
fontSize: 14,
lineNumbers: 'on',
folding: true
}}
/>
)}
/>
</div>
</div>
</DialogContent>
</Dialog>
)}
</>
);
};