import * as React from 'react'; import { useEffect, useState, useRef } from 'react'; import Container from '@mui/material/Container'; import Typography from '@mui/material/Typography'; import Box from '@mui/material/Box'; import FileUploadIcon from '@mui/icons-material/FileUpload'; import { DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors, DragOverlay } from "@dnd-kit/core"; import { arrayMove, SortableContext, sortableKeyboardCoordinates, rectSortingStrategy } from "@dnd-kit/sortable"; import type { DragStartEvent, DragEndEvent, UniqueIdentifier } from "@dnd-kit/core"; import { Module } from './types'; import { modules, steps, module_types, empty_config } from './schema.json'; import { Stack, Button, } from '@mui/material'; import Grid from '@mui/material/Grid2'; import { parseDocument, Document, YAMLSeq, YAMLMap, Scalar } from 'yaml' import StepCard from './StepCard'; function FileDrop({ setYamlFile }: { setYamlFile: React.Dispatch> }) { const [showError, setShowError] = useState(false); const [label, setLabel] = useState(<>Drag and drop your orchestration.yaml file here, or click to select a file.); const wrapperRef = useRef(null); function openYAMLFile(event: any) { let file = event.target.files[0]; if (file.type.indexOf('yaml') === -1) { setShowError(true); setLabel(<>Invalid type, only YAML files are accepted.) return; } let reader = new FileReader(); reader.onload = function (e) { let contents = e.target ? e.target.result : ''; try { let document = parseDocument(contents as string); if (document.errors.length > 0) { // not a valid yaml file setShowError(true); setLabel(<>Invalid file. Make sure your Orchestration is a valid YAML file with a 'steps' section in it.) return; } else { setShowError(false); setLabel(<>File loaded successfully.) } // do some basic validation of 'steps' let steps = document.get('steps'); if (!steps) { setShowError(true); setLabel(<>Invalid file. Your orchestration file must have a 'steps' section in it.) return; } const replacements = { feeder: 'feeders', formatter: 'formatters', archivers: 'extractors', }; let error = false; for (let stepType of Object.keys(replacements)) { if (steps.get(stepType) !== undefined) { setShowError(true); setLabel(<>Invalid file. Your orchestration file appears to be in the old (v0.12) format with a '{stepType}' section.
You should manually update your orchestration file first (hint: {stepType} → {replacements[stepType]})); error = true; return; } }; setYamlFile(document); } catch (e) { console.error(e); } } reader.readAsText(file); } return ( <>
{ e.currentTarget.style.backgroundColor = 'var(--mui-palette-LinearProgress-infoBg)'; }} onDragLeave={(e) => { e.currentTarget.style.backgroundColor = ''; }} onDrop={(e) => { e.currentTarget.style.backgroundColor = ''; }} > {label}
); } function ModuleTypes({ stepType, setEnabledModules, enabledModules, configValues }: { stepType: string, setEnabledModules: any, enabledModules: any, configValues: any }) { const [showError, setShowError] = useState(false); const [activeId, setActiveId] = useState(); const [items, setItems] = useState([]); useEffect(() => { setItems(enabledModules[stepType].map(([name, enabled]: [string, boolean]) => name)); } , [enabledModules]); const toggleModule = (event: any) => { // make sure that 'feeder' and 'formatter' types only have one value let name = event.target.id; let checked = event.target.checked; if (stepType === 'feeders' || stepType === 'formatters') { // check how many modules of this type are enabled const checkedModules = enabledModules[stepType].filter(([m, enabled]: [string, boolean]) => { return (m !== name && enabled) || (checked && m === name) }); if (checkedModules.length > 1) { setShowError(true); } else { setShowError(false); } } else { setShowError(false); } let newEnabledModules = { ...enabledModules }; newEnabledModules[stepType] = enabledModules[stepType].map(([m, enabled]: [string, boolean]) => { return (m === name) ? [m, checked] : [m, enabled]; }); setEnabledModules(newEnabledModules); } const sensors = useSensors( useSensor(PointerSensor), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }) ); const handleDragStart = (event: DragStartEvent) => { setActiveId(event.active.id); }; const handleDragEnd = (event: DragEndEvent) => { setActiveId(undefined); const { active, over } = event; if (active.id !== over?.id) { const oldIndex = items.indexOf(active.id as string); const newIndex = items.indexOf(over?.id as string); let newArray = arrayMove(items, oldIndex, newIndex); // set it also on steps let newEnabledModules = { ...enabledModules }; newEnabledModules[stepType] = enabledModules[stepType].sort((a, b) => { return newArray.indexOf(a[0]) - newArray.indexOf(b[0]); }) setEnabledModules(newEnabledModules); } }; return ( <> {stepType} Select the {stepType} you wish to enable. Drag to reorder. {showError ? Only one {stepType.slice(0,-1)} can be enabled at a time. : null} {items.map((name: string) => { let m: Module = modules[name]; return ( ); })} {activeId ? (
) : null}
); } export default function App() { const [yamlFile, setYamlFile] = useState(new Document()); const [enabledModules, setEnabledModules] = useState<{}>(Object.fromEntries(Object.keys(steps).map(type => [type, steps[type].map((name: string) => [name, false])]))); const [configValues, setConfigValues] = useState<{ [key: string]: { [key: string ]: any } }>( Object.keys(modules).reduce((acc, module) => { acc[module] = {}; return acc; }, {}) ); const saveSettings = function (copy: boolean = false) { // edit the yamlFile // generate the steps config let stepsConfig = enabledModules; let finalYamlFile: Document = null; if (!yamlFile || yamlFile.contents == null) { // create the yaml file from finalYamlFile = parseDocument(empty_config as string); } else { finalYamlFile = yamlFile; } // set the steps module_types.forEach((type: string) => { let stepType = type + 's'; let existingSteps = finalYamlFile.getIn(['steps', stepType]) as YAMLSeq; stepsConfig[stepType].forEach(([name, enabled]: [string, boolean]) => { let index = existingSteps.items.findIndex((item) => { return (item.value || item) === name }); let stepItem = finalYamlFile.getIn(['steps', stepType], true) as YAMLSeq; if (enabled && index === -1) { finalYamlFile.addIn(['steps', stepType], name); stepItem.commentBefore = stepItem.commentBefore?.replace("\n - " + name, ''); stepItem.comment = stepItem.comment?.replace("\n - " + name, ''); } else if (!enabled && index !== -1) { // set the value to empty and add a comment before with the commented value finalYamlFile.deleteIn(['steps', stepType, index]); stepItem.commentBefore += "\n - " + name; finalYamlFile.setIn(['steps', stepType], stepItem); } }); // sort the items existingSteps.items.sort((a: Scalar | string, b: Scalar | string) => { return (stepsConfig[stepType].findIndex((val: [string, boolean]) => {return val[0] === (a.value || a)}) - stepsConfig[stepType].findIndex((val: [string, boolean]) => {return val[0] === (b.value || b)})) }); existingSteps.flow = existingSteps.items.length ? false : true; }); // set all other settings // loop through each item that isn't 'steps' in the finalYamlFile and check if it exists in configValues Object.keys(configValues).forEach((module_name: string) => { // get an existing key let existingConfig = finalYamlFile.get(module_name, true) as YAMLMap; if (existingConfig) { Object.keys(configValues[module_name]).forEach((config_name: string) => { let existingConfigYAML = existingConfig.get(config_name, true) as Scalar; if (existingConfigYAML) { existingConfigYAML.value = configValues[module_name][config_name]; existingConfig.set(config_name, existingConfigYAML); } else { existingConfig.set(config_name, configValues[module_name][config_name]); } }); finalYamlFile.set(module_name, existingConfig); } else { if (configValues[module_name] && Object.keys(configValues[module_name]).length > 0) { finalYamlFile.set(module_name, configValues[module_name]); } } }); if (copy) { navigator.clipboard.writeText(String(finalYamlFile)).then(() => { alert("Settings copied to clipboard."); }); } else { // offer the file for download const blob = new Blob([String(finalYamlFile)], { type: 'application/x-yaml' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'orchestration.yaml'; a.click(); } } useEffect(() => { // load the configs, and set the default values if they exist let newConfigValues = {}; Object.keys(modules).map((module: string) => { let m = modules[module]; let configs = m.configs; if (!configs) { return; } newConfigValues[module] = {}; Object.keys(configs).map((config: string) => { let config_args = configs[config]; if (config_args.default !== undefined) { newConfigValues[module][config] = config_args.default; } }); }) setConfigValues(newConfigValues); }, []); useEffect(() => { if (!yamlFile || yamlFile.contents == null) { return; } let settings = yamlFile.toJS(); // make a deep copy of settings let stepSettings = settings['steps']; let newEnabledModules = Object.fromEntries(Object.keys(steps).map((type: string) => { return [type, steps[type].map((name: string) => { return [name, stepSettings[type].indexOf(name) !== -1]; }).sort((a, b) => { let aIndex = stepSettings[type].indexOf(a[0]); let bIndex = stepSettings[type].indexOf(b[0]); if (aIndex === -1 && bIndex === -1) { return a - b; } if (bIndex === -1) { return -1; } if (aIndex === -1) { return 1; } return aIndex - bIndex; })]; }).sort((a, b) => { return module_types.indexOf(a[0]) - module_types.indexOf(b[0]); })); setEnabledModules(newEnabledModules); // set the config values let newConfigValues = settings; delete newConfigValues['steps']; setConfigValues(Object.keys(modules).reduce((acc, module) => { acc[module] = newConfigValues[module] || {}; return acc; }, {})); }, [yamlFile]); return ( 1. Select your orchestration.yaml settings file. Or skip this step to start from scratch 2. Choose the Modules you wish to enable/disable {Object.keys(steps).map((stepType: string) => { return ( ); })} 3. Configure your Enabled Modules Next to each module you've enabled, you can click 'Configure' to set the module's settings. 4. Save your settings ); }