From 04ecebe48358c0f929f99458cc2cb29c7f41437e Mon Sep 17 00:00:00 2001 From: Liam Galvin Date: Mon, 13 Nov 2023 12:27:47 +0000 Subject: [PATCH] Allow looping during workflows, and detect disconnected segments (#107) * Allow looping during workflows, and detect disconnected segments --- .github/workflows/docker.yml | 6 +- backend/server/api/workflows.go | 10 +- backend/server/bindings/client.go | 36 +-- backend/server/bindings/types.go | 8 +- backend/workflow/bus.go | 216 ++++++------------ backend/workflow/json.go | 12 + backend/workflow/runner.go | 18 +- backend/workflow/transmission/http.go | 6 +- backend/workflow/transmission/map.go | 8 +- backend/workspace/workspace.go | 8 +- frontend/package-lock.json | 45 ++++ frontend/package.json | 1 + frontend/src/components/AppDashboard.vue | 2 +- .../components/Workflow/WorkflowEditor.vue | 5 + frontend/src/lib/api/Client.ts | 36 +-- frontend/src/lib/api/workflow/index.ts | 1 + frontend/vite.config.ts | 5 +- frontend/yarn.lock | 42 +++- 18 files changed, 254 insertions(+), 211 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 4a846e8..e37b755 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -25,7 +25,7 @@ jobs: uses: actions/checkout@v4 # Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. - name: Log in to the Container registry - uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -33,14 +33,14 @@ jobs: # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels. - name: Extract metadata (tags, labels) for Docker id: meta - uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 + uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository. # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. - name: Build and push Docker image - uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 + uses: docker/build-push-action@v5 with: context: . push: true diff --git a/backend/server/api/workflows.go b/backend/server/api/workflows.go index 08ee45a..b42d084 100644 --- a/backend/server/api/workflows.go +++ b/backend/server/api/workflows.go @@ -17,6 +17,10 @@ import ( ) func (a *API) RunWorkflow(w *workflow.WorkflowM) { + go a.runWorkflow(w) +} + +func (a *API) runWorkflow(w *workflow.WorkflowM) { a.workflowMu.Lock() defer a.workflowMu.Unlock() if a.runningWorkflowID != uuid.Nil { @@ -38,7 +42,6 @@ func (a *API) RunWorkflow(w *workflow.WorkflowM) { go func() { for update := range updateChan { _ = a.eventTrigger(EventWorkflowUpdate, update.Pack()) - if n, err := flow.FindNode(update.Node); err == nil { _ = a.eventTrigger(EventWorkflowOutput, node.OutputM{ Node: update.Node.String(), @@ -66,9 +69,12 @@ func (a *API) RunWorkflow(w *workflow.WorkflowM) { } } -func (a *API) StopWorkflow(w *workflow.WorkflowM) { +func (a *API) StopWorkflow() { if a.workflowContextCancel != nil { + a.logger.Info("Stopping workflow!") a.workflowContextCancel() + } else { + a.logger.Info("No workflow to stop!") } } diff --git a/backend/server/bindings/client.go b/backend/server/bindings/client.go index 5cc311b..0bedcb0 100644 --- a/backend/server/bindings/client.go +++ b/backend/server/bindings/client.go @@ -42,6 +42,7 @@ func generateClient(summary Summary) error { markerMethodsStart, {'\n'}, methods, + {' ', ' '}, markerMethodsEnd, afterMethods, }, []byte{}) @@ -64,14 +65,14 @@ func generateClientMethods(summary Summary) ([]byte, []byte, error) { } if len(method.OutTypes) == 0 { - _, _ = fmt.Fprintf(buffer, ` %s(%s): Promise { - return new Promise((resolve, reject) => { - const receive = () => { - resolve(); - } - this.callMethod("%[1]s", [%[3]s], receive, reject); - }) - } + _, _ = fmt.Fprintf(buffer, ` %s(%s): Promise { + return new Promise((resolve, reject) => { + const receive = () => { + resolve() + } + this.callMethod('%[1]s', [%[3]s], receive, reject) + }) + } `, method.Name, @@ -88,20 +89,19 @@ func generateClientMethods(summary Summary) ([]byte, []byte, error) { panic(fmt.Sprintf("method %s has empty output type", method.Name)) } promiseTypes = append(promiseTypes, outType.TSProp()) - outputs += fmt.Sprintf("\n let output%d: %s = JSON.parse(args[%d])", i, outType.TSProp(), i) + outputs += fmt.Sprintf("\n const output%d: %s = JSON.parse(args[%d])", i, outType.TSProp(), i) outputList = append(outputList, fmt.Sprintf("output%d", i)) } promiseType := strings.Join(promiseTypes, ", ") - _, _ = fmt.Fprintf(buffer, ` %s(%s): Promise<%s> { - return new Promise<%[3]s>((resolve, reject) => { - const receive = (args: string[]) => { - %s - resolve(%s); - } - this.callMethod("%[1]s", [%[6]s], receive, reject); - }) - } + _, _ = fmt.Fprintf(buffer, ` %s(%s): Promise<%s> { + return new Promise<%[3]s>((resolve, reject) => { + const receive = (args: string[]) => {%s + resolve(%s) + } + this.callMethod('%[1]s', [%[6]s], receive, reject) + }) + } `, method.Name, diff --git a/backend/server/bindings/types.go b/backend/server/bindings/types.go index 3c87009..bc2d5ac 100644 --- a/backend/server/bindings/types.go +++ b/backend/server/bindings/types.go @@ -71,9 +71,13 @@ func generateTypes(summary Summary) error { _, _ = fmt.Fprint(file, "\n") } - for _, t := range types { - _, _ = fmt.Fprint(file, t.TSDefinition()+"\n\n") + for i, t := range types { + if i > 0 { + _, _ = fmt.Fprint(file, "\n\n") + } + _, _ = fmt.Fprint(file, t.TSDefinition()) } + _, _ = fmt.Fprint(file, "\n") if err := os.WriteFile(filepath.Join(basePath, dir, "index.ts"), file.Bytes(), 0644); err != nil { return fmt.Errorf("failed to write file '%s': %w", filepath.Join(dir, "index.ts"), err) diff --git a/backend/workflow/bus.go b/backend/workflow/bus.go index 517a36b..282eec7 100644 --- a/backend/workflow/bus.go +++ b/backend/workflow/bus.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" "sync" - "time" "github.com/ghostsecurity/reaper/backend/workflow/transmission" @@ -152,39 +151,66 @@ func (b *Bus) Run(ctx context.Context, output chan<- node.Output) error { var firstNodeError error var errMu sync.Mutex + // create an input channel for each node for _, n := range b.nodes { b.inputs[n.ID()] = make(chan node.Input) } + // build routes by creating a map of node:output to node:input b.buildRoutes() + // for each node... for _, n := range b.nodes { - var hasInputConnected bool - for _, l := range b.links { - if l.To.Node == n.ID() { - hasInputConnected = true - break + // ignoring the start node, check if the node is eventually linked back to the start node + // if not, we can mark it as complete as it will never be triggered + if b.start.ID() != n.ID() { + previous := b.getChainedInputNodes(n.ID(), nil) + var linkedToStart bool + for _, p := range previous { + if p == b.start.ID() { + linkedToStart = true + break + } + } + + var linkable bool + for _, input := range n.GetInputs() { + if input.Linkable { + linkable = true + break + } + } + + // we need to ignore nodes which have no inputs though, like requests, vars etc. + if !linkedToStart && linkable { + b.updateStatus(ctx, Update{ + Node: n.ID(), + Name: n.Name(), + Status: NodeStatusDisconnected, + Message: "Disconnected", + }) + b.closeNodeInput(n) + continue } - } - if !hasInputConnected && b.start.ID() != n.ID() { - b.updateStatus(ctx, Update{ - Node: n.ID(), - Name: n.Name(), - Status: NodeStatusSuccess, - Message: "Success (no inputs)", - }) - b.closeNodeInput(n) - continue } + // grab the input channel for this node b.inputsMu.RLock() in := b.inputs[n.ID()] b.inputsMu.RUnlock() + + // create an output channel for this node out := make(chan node.OutputInstance, 100) + + // count 2 jobs for the node - main execution and output handling wg.Add(2) + + // start the main work routine for the node go func(n node.Node, in chan node.Input, out chan node.OutputInstance) { defer wg.Done() + + // inject any static inputs if len(n.GetInjections()) > 0 { b.updateStatus(ctx, Update{ Node: n.ID(), @@ -200,7 +226,10 @@ func (b *Bus) Run(ctx context.Context, output chan<- node.Output) error { Message: "Waiting for input(s)...", }) } + defer close(out) + + // start work if err := n.Start(ctx, in, out, output); err != nil { if errors.Is(err, context.Canceled) { if !b.isAborted() { @@ -232,9 +261,10 @@ func (b *Bus) Run(ctx context.Context, output chan<- node.Output) error { } }(n, in, out) + + // start the output handling routine for the node go func(n node.Node, out chan node.OutputInstance) { defer wg.Done() - defer b.checkDeadlock(n.ID()) defer b.closeNodeOutput(n) for { msg, ok := <-out @@ -290,12 +320,11 @@ func (b *Bus) Run(ctx context.Context, output chan<- node.Output) error { } }() } - - b.checkDeadlock(n.ID()) } }(n, out) } + // grab the input channel for the start node b.inputsMu.RLock() startInput, ok := b.inputs[b.start.ID()] if !ok { @@ -305,12 +334,15 @@ func (b *Bus) Run(ctx context.Context, output chan<- node.Output) error { return fmt.Errorf("start node not found") } + // flag the start node as running b.updateStatus(ctx, Update{ Node: b.start.ID(), Name: b.start.Name(), Status: NodeStatusRunning, Message: "Running...", }) + + // write to the start node to kick off the workflow select { case <-ctx.Done(): b.inputsMu.RUnlock() @@ -323,43 +355,15 @@ func (b *Bus) Run(ctx context.Context, output chan<- node.Output) error { b.inputsMu.RUnlock() } - ticker := time.NewTicker(time.Second) - defer ticker.Stop() -SAFETY: - for { - select { - case <-func() chan struct{} { - c := make(chan struct{}) - go func() { - defer close(c) - wg.Wait() - }() - return c - }(): - break SAFETY - case <-ticker.C: - var remaining []uuid.UUID - b.inputsMu.RLock() - for n := range b.inputs { - remaining = append(remaining, n) - } - b.inputsMu.RUnlock() - for _, r := range remaining { - b.checkDeadlock(r) - } - } - } - for _, n := range b.nodes { - if old, ok := b.statuses[n.ID()]; !ok || (old.Status != NodeStatusSuccess && old.Status != NodeStatusError && old.Status != NodeStatusAborted) { - if firstNodeError == nil { - b.updateStatus(ctx, Update{ - Node: n.ID(), - Name: n.Name(), - Status: NodeStatusSuccess, - Message: "Success (defaulted)", - }) - } else { + + b.statusMu.Lock() + old, ok := b.statuses[n.ID()] + b.statusMu.Unlock() + + if !ok || (old.Status.IsFinal()) { + errMu.Lock() + if firstNodeError != nil { b.updateStatus(ctx, Update{ Node: n.ID(), Name: n.Name(), @@ -367,9 +371,11 @@ SAFETY: Message: "Aborted", }) } + errMu.Unlock() } } + wg.Wait() return firstNodeError } @@ -389,103 +395,19 @@ func (b *Bus) updateStatus(_ context.Context, update Update) { b.updateChan <- update } -func (b *Bus) checkDeadlock(fromOutputNode uuid.UUID) { - - b.inputsMu.RLock() - defer b.inputsMu.RUnlock() - - // first let's grab our remaining nodes which are running - var remainingNodes []uuid.UUID - for _, n := range b.nodes { - var linked bool - for _, link := range b.links { - if link.From.Node == fromOutputNode && link.To.Node == n.ID() { - linked = true - break - } - } - if !linked { - continue - } - if _, ok := b.inputs[n.ID()]; ok { - remainingNodes = append(remainingNodes, n.ID()) - } - } - - if len(remainingNodes) == 0 { - return - } - - // we need to look for circular dependencies which are strangled from all other inputs - for _, n := range remainingNodes { - chained, circ := b.getChainedInputNodes(n, nil) - if !circ || len(chained) == 0 { - continue - } - // we have a circular dependency, let's see if it's strangled - strangled := true - for _, chain := range chained { - if !b.isCircular(chain) { - if _, ok := b.inputs[chain]; ok { - // this chain is not strangled - strangled = false - break - } - } - } - if strangled { - b.inputsMu.RUnlock() - b.shutDownChain(chained) - b.inputsMu.RLock() - return - } - } - -} - -func (b *Bus) shutDownChain(chain []uuid.UUID) { - - var nodes []node.Node - - // check if anything in the chain is busy - if not, we can probably shut it down - for _, id := range chain { - var n node.Node - for _, no := range b.nodes { - if no.ID() == id { - n = no - break - } - } - if n == nil { - continue - } - if n.Busy() { - return - } - nodes = append(nodes, n) - } - - for _, n := range nodes { - b.closeNodeInput(n) - } -} - -func (b *Bus) isCircular(n uuid.UUID) bool { - _, circular := b.getChainedInputNodes(n, nil) - return circular -} - // returns all input nodes to a given node, and all input nodes to those nodes, and so on -func (b *Bus) getChainedInputNodes(from uuid.UUID, used []uuid.UUID) ([]uuid.UUID, bool) { +func (b *Bus) getChainedInputNodes(from uuid.UUID, used []uuid.UUID) []uuid.UUID { nodes := []uuid.UUID{ from, } used = append(used, from) - var circular bool for _, link := range b.links { if link.To.Node == from { // node has completed, not interested - if _, ok := b.inputs[link.From.Node]; !ok { + b.inputsMu.Lock() + _, ok := b.inputs[link.From.Node] + b.inputsMu.Unlock() + if !ok { continue } var exists bool @@ -496,13 +418,11 @@ func (b *Bus) getChainedInputNodes(from uuid.UUID, used []uuid.UUID) ([]uuid.UUI } } if exists { - circular = true continue } - chained, circ := b.getChainedInputNodes(link.From.Node, used) - circular = circular || circ + chained := b.getChainedInputNodes(link.From.Node, used) nodes = append(nodes, chained...) } } - return nodes, circular + return nodes } diff --git a/backend/workflow/json.go b/backend/workflow/json.go index 0ac5105..d234194 100644 --- a/backend/workflow/json.go +++ b/backend/workflow/json.go @@ -2,6 +2,7 @@ package workflow import ( "encoding/json" + "fmt" "github.com/google/uuid" @@ -13,14 +14,21 @@ import ( A lot of the strangeness in here existed to help wails create js bindings - we can start to simplify this now... */ +const jsonVersion = 1 + type WorkflowM struct { ID string `json:"id"` + Version int `json:"version"` Name string `json:"name"` Nodes []NodeM `json:"nodes"` Links []LinkM `json:"links"` Positioning map[string]Position `json:"positioning"` } +func (w WorkflowM) IsCurrentVersion() bool { + return w.Version == jsonVersion +} + type LinkDirectionM struct { Node string `json:"node"` Connector string `json:"connector"` @@ -45,6 +53,7 @@ func (w *Workflow) Pack() (*WorkflowM, error) { return &WorkflowM{ ID: w.ID.String(), + Version: jsonVersion, Name: w.Name, Nodes: mNodes, Links: toLinkMs(w.Links), @@ -84,6 +93,9 @@ func toUUIDOrNil(u string) uuid.UUID { } func (m *WorkflowM) Unpack() (*Workflow, error) { + if m.Version != jsonVersion { + return nil, fmt.Errorf("workflow version mismatch: %d != %d", m.Version, jsonVersion) + } w := Workflow{ ID: toUUIDOrNil(m.ID), Name: m.Name, diff --git a/backend/workflow/runner.go b/backend/workflow/runner.go index 7efb116..274bac5 100644 --- a/backend/workflow/runner.go +++ b/backend/workflow/runner.go @@ -4,9 +4,10 @@ import ( "errors" "fmt" - "github.com/ghostsecurity/reaper/backend/workflow/node" "github.com/google/uuid" "golang.org/x/net/context" + + "github.com/ghostsecurity/reaper/backend/workflow/node" ) type runner struct { @@ -47,13 +48,18 @@ var ChildNodeError = errors.New("child node error") type NodeStatus string const ( - NodeStatusPending NodeStatus = "pending" - NodeStatusRunning NodeStatus = "running" - NodeStatusSuccess NodeStatus = "success" - NodeStatusError NodeStatus = "error" - NodeStatusAborted NodeStatus = "aborted" + NodeStatusPending NodeStatus = "pending" + NodeStatusRunning NodeStatus = "running" + NodeStatusSuccess NodeStatus = "success" + NodeStatusError NodeStatus = "error" + NodeStatusAborted NodeStatus = "aborted" + NodeStatusDisconnected NodeStatus = "disconnected" ) +func (s NodeStatus) IsFinal() bool { + return s == NodeStatusSuccess || s == NodeStatusError || s == NodeStatusAborted || s == NodeStatusDisconnected +} + func (r *runner) Run(updateChan chan<- Update, output chan<- node.Output) error { if r.workflow == nil { diff --git a/backend/workflow/transmission/http.go b/backend/workflow/transmission/http.go index 24de949..e9303ce 100644 --- a/backend/workflow/transmission/http.go +++ b/backend/workflow/transmission/http.go @@ -119,10 +119,14 @@ type RequestResponsePairWithMap struct { } func NewRequestResponsePairWithMap(req packaging.HttpRequest, resp packaging.HttpResponse, params map[string]string) *RequestResponsePairWithMap { + clone := make(map[string]string, len(params)) + for k, v := range params { + clone[k] = v + } return &RequestResponsePairWithMap{ request: Request(req), response: Response(resp), - params: params, + params: clone, } } diff --git a/backend/workflow/transmission/map.go b/backend/workflow/transmission/map.go index 50d8891..fb59215 100644 --- a/backend/workflow/transmission/map.go +++ b/backend/workflow/transmission/map.go @@ -7,7 +7,11 @@ var _ Transmission = (*Map)(nil) type Map map[string]string func NewMap(data map[string]string) *Map { - m := Map(data) + clone := make(map[string]string, len(data)) + for k, v := range data { + clone[k] = v + } + m := Map(clone) return &m } @@ -19,7 +23,7 @@ func (t *Map) Map() map[string]string { if t == nil { return nil } - return map[string]string(*t) + return *t } func (t *Map) MarshalJSON() ([]byte, error) { diff --git a/backend/workspace/workspace.go b/backend/workspace/workspace.go index 7581148..13a9fbe 100644 --- a/backend/workspace/workspace.go +++ b/backend/workspace/workspace.go @@ -139,9 +139,13 @@ func loadFile(file string) (*Workspace, error) { return nil, fmt.Errorf("failed to decode workspace: %w", err) } - if workspace.Workflows == nil { - workspace.Workflows = []workflow.WorkflowM{} + filtered := make([]workflow.WorkflowM, 0) + for _, w := range workspace.Workflows { + if w.IsCurrentVersion() { + filtered = append(filtered, w) + } } + workspace.Workflows = filtered return &workspace, nil } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c71e993..26704ef 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -45,6 +45,7 @@ "tailwindcss": "^3.2.4", "typescript": "^4.9.4", "vite": "^3.2.7", + "vite-plugin-eslint": "^1.8.1", "vue-click-outside": "^1.1.0", "vue-router": "^4.1.6", "vue-tsc": "^0.39.5" @@ -672,6 +673,19 @@ "node": ">= 8" } }, + "node_modules/@rollup/pluginutils": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", + "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", + "dev": true, + "dependencies": { + "estree-walker": "^2.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/@tailwindcss/forms": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.3.tgz", @@ -706,6 +720,22 @@ "@types/chai": "*" } }, + "node_modules/@types/eslint": { + "version": "8.44.7", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.7.tgz", + "integrity": "sha512-f5ORu2hcBbKei97U73mf+l9t4zTGl74IqZ0GQk4oVea/VS8tQZYkUveSYojk+frraAVYId0V2WC9O4PTNru2FQ==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, "node_modules/@types/file-saver": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.7.tgz", @@ -9325,6 +9355,21 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/vite-plugin-eslint": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/vite-plugin-eslint/-/vite-plugin-eslint-1.8.1.tgz", + "integrity": "sha512-PqdMf3Y2fLO9FsNPmMX+//2BF5SF8nEWspZdgl4kSt7UvHDRHVVfHvxsD7ULYzZrJDGRxR81Nq7TOFgwMnUang==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^4.2.1", + "@types/eslint": "^8.4.5", + "rollup": "^2.77.2" + }, + "peerDependencies": { + "eslint": ">=7", + "vite": ">=2" + } + }, "node_modules/vitest": { "version": "0.26.3", "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.26.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index a63287b..8e7899d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -50,6 +50,7 @@ "tailwindcss": "^3.2.4", "typescript": "^4.9.4", "vite": "^3.2.7", + "vite-plugin-eslint": "^1.8.1", "vue-click-outside": "^1.1.0", "vue-router": "^4.1.6", "vue-tsc": "^0.39.5" diff --git a/frontend/src/components/AppDashboard.vue b/frontend/src/components/AppDashboard.vue index 42e8b93..f550692 100644 --- a/frontend/src/components/AppDashboard.vue +++ b/frontend/src/components/AppDashboard.vue @@ -381,7 +381,7 @@ function stopWorkflow(id: string) { if (flow === undefined) { return } - props.client.StopWorkflow(flow) + props.client.StopWorkflow() } function createWorkflowFromRequest(r: HttpRequest) { diff --git a/frontend/src/components/Workflow/WorkflowEditor.vue b/frontend/src/components/Workflow/WorkflowEditor.vue index 8e99dd0..4ee72aa 100644 --- a/frontend/src/components/Workflow/WorkflowEditor.vue +++ b/frontend/src/components/Workflow/WorkflowEditor.vue @@ -14,6 +14,7 @@ import { EyeSlashIcon, XCircleIcon, ArrowUpOnSquareIcon, + BoltSlashIcon, } from '@heroicons/vue/20/solid' import { uuid } from 'vue-uuid' import { WorkflowM, NodeM, Position, LinkM, LinkDirectionM } from '../../lib/api/workflow' @@ -605,6 +606,8 @@ function getStatusClass(id: string): string { return 'border-aurora-1' case 'aborted': return 'border-aurora-5' + case 'disconnected': + return 'border-aurora-3' case 'success': return 'border-aurora-4' default: @@ -678,6 +681,8 @@ function trackMover(id: string, el: any) { class="mr-2 h-4 w-4 text-aurora-3"/> +
{ + StopWorkflow(): Promise { return new Promise((resolve, reject) => { const receive = () => { resolve() } - this.callMethod('StopWorkflow', [a0], receive, reject) + this.callMethod('StopWorkflow', [], receive, reject) }) } @@ -424,18 +424,18 @@ export default class Client { // %METHODS:END% /* - Test(a: string): Promise { - let res: (value: string | PromiseLike) => void; - let rej: (reason?: any) => void; - return new Promise((resolve, reject) => { - res = resolve; - rej = reject; - const receive = (_: string[]) => { - let a: string = JSON.parse(args[0]); - res(a); - } - this.callMethod("Test", [a], receive, rej); - }) - - */ + Test(a: string): Promise { + let res: (value: string | PromiseLike) => void; + let rej: (reason?: any) => void; + return new Promise((resolve, reject) => { + res = resolve; + rej = reject; + const receive = (_: string[]) => { + let a: string = JSON.parse(args[0]); + res(a); + } + this.callMethod("Test", [a], receive, rej); + }) + + */ } diff --git a/frontend/src/lib/api/workflow/index.ts b/frontend/src/lib/api/workflow/index.ts index 77e7a17..9ba889d 100644 --- a/frontend/src/lib/api/workflow/index.ts +++ b/frontend/src/lib/api/workflow/index.ts @@ -32,6 +32,7 @@ export interface Position { export interface WorkflowM { id: string; + version: number; name: string; nodes: NodeM[]; links: LinkM[]; diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 05c1740..527748f 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,7 +1,8 @@ -import { defineConfig } from 'vite' +import {defineConfig} from 'vite' import vue from '@vitejs/plugin-vue' +import eslint from 'vite-plugin-eslint'; // https://vitejs.dev/config/ export default defineConfig({ - plugins: [vue()], + plugins: [vue(), eslint()], }) diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 9a734b2..aa2a1c2 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -517,6 +517,14 @@ resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== +"@rollup/pluginutils@^4.2.1": + version "4.2.1" + resolved "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz" + integrity sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ== + dependencies: + estree-walker "^2.0.1" + picomatch "^2.2.2" + "@sigstore/protobuf-specs@^0.1.0": version "0.1.0" @@ -557,12 +565,25 @@ resolved "https://registry.npmjs.org/@types/chai/-/chai-4.3.5.tgz" integrity sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng== +"@types/eslint@^8.4.5": + version "8.44.7" + resolved "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.7.tgz" + integrity sha512-f5ORu2hcBbKei97U73mf+l9t4zTGl74IqZ0GQk4oVea/VS8tQZYkUveSYojk+frraAVYId0V2WC9O4PTNru2FQ== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*": + version "1.0.5" + resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== + "@types/file-saver@^2.0.7": version "2.0.7" resolved "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.7.tgz" integrity sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A== -"@types/json-schema@^7.0.9": +"@types/json-schema@*", "@types/json-schema@^7.0.9": version "7.0.11" resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== @@ -1872,7 +1893,7 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4 resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -eslint@*, "eslint@^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8", "eslint@^6.0.0 || ^7.0.0 || ^8.0.0", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^6.2.0 || ^7.0.0 || ^8.0.0", "eslint@^7.32.0 || ^8.2.0", eslint@^8.50.0, eslint@>=6.0.0: +eslint@*, "eslint@^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8", "eslint@^6.0.0 || ^7.0.0 || ^8.0.0", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^6.2.0 || ^7.0.0 || ^8.0.0", "eslint@^7.32.0 || ^8.2.0", eslint@^8.50.0, eslint@>=6.0.0, eslint@>=7: version "8.50.0" resolved "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz" integrity sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg== @@ -1958,7 +1979,7 @@ estraverse@^5.2.0: resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== -estree-walker@^2.0.2: +estree-walker@^2.0.1, estree-walker@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz" integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== @@ -3679,7 +3700,7 @@ picocolors@^1.0.0: resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== @@ -3961,7 +3982,7 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" -rollup@^2.79.1: +rollup@^2.77.2, rollup@^2.79.1: version "2.79.1" resolved "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz" integrity sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw== @@ -4580,7 +4601,16 @@ vite-node@0.26.3: source-map-support "^0.5.21" vite "^3.0.0 || ^4.0.0" -vite@^3.0.0, "vite@^3.0.0 || ^4.0.0", vite@^3.2.7: +vite-plugin-eslint@^1.8.1: + version "1.8.1" + resolved "https://registry.npmjs.org/vite-plugin-eslint/-/vite-plugin-eslint-1.8.1.tgz" + integrity sha512-PqdMf3Y2fLO9FsNPmMX+//2BF5SF8nEWspZdgl4kSt7UvHDRHVVfHvxsD7ULYzZrJDGRxR81Nq7TOFgwMnUang== + dependencies: + "@rollup/pluginutils" "^4.2.1" + "@types/eslint" "^8.4.5" + rollup "^2.77.2" + +vite@^3.0.0, "vite@^3.0.0 || ^4.0.0", vite@^3.2.7, vite@>=2: version "3.2.7" resolved "https://registry.npmjs.org/vite/-/vite-3.2.7.tgz" integrity sha512-29pdXjk49xAP0QBr0xXqu2s5jiQIXNvE/xwd0vUizYT2Hzqe4BksNNoWllFVXJf4eLZ+UlVQmXfB4lWrc+t18g==