mirror of
https://github.com/SecurityBrewery/catalyst.git
synced 2026-02-20 12:05:27 +01:00
Add playbook editor (#702)
This commit is contained in:
@@ -9,6 +9,7 @@
|
|||||||
"lint": "vue-cli-service lint"
|
"lint": "vue-cli-service lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@crinkles/digl": "^2.0.2",
|
||||||
"@koumoul/vjsf": "2.21.1",
|
"@koumoul/vjsf": "2.21.1",
|
||||||
"@mdi/font": "7.1.96",
|
"@mdi/font": "7.1.96",
|
||||||
"@mdi/util": "0.3.2",
|
"@mdi/util": "0.3.2",
|
||||||
@@ -23,6 +24,7 @@
|
|||||||
"axios": "1.2.2",
|
"axios": "1.2.2",
|
||||||
"chart.js": "2.9.4",
|
"chart.js": "2.9.4",
|
||||||
"core-js": "3.27.2",
|
"core-js": "3.27.2",
|
||||||
|
"d3": "^7.8.0",
|
||||||
"graphlib": "2.1.8",
|
"graphlib": "2.1.8",
|
||||||
"json-schema-editor-vue": "2.2.1",
|
"json-schema-editor-vue": "2.2.1",
|
||||||
"just-kebab-case": "4.2.0",
|
"just-kebab-case": "4.2.0",
|
||||||
@@ -30,6 +32,7 @@
|
|||||||
"less-loader": "11.1.0",
|
"less-loader": "11.1.0",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"luxon": "3.2.1",
|
"luxon": "3.2.1",
|
||||||
|
"panzoom": "^9.4.3",
|
||||||
"register-service-worker": "1.7.2",
|
"register-service-worker": "1.7.2",
|
||||||
"splitpanes": "2.4.1",
|
"splitpanes": "2.4.1",
|
||||||
"swagger-ui": "4.13.0",
|
"swagger-ui": "4.13.0",
|
||||||
|
|||||||
273
ui/src/components/playbookeditor/EditTask.vue
Normal file
273
ui/src/components/playbookeditor/EditTask.vue
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
<template>
|
||||||
|
<v-card data-app outlined>
|
||||||
|
<v-card-title class="d-flex">
|
||||||
|
<span>Edit Task</span>
|
||||||
|
<v-spacer/>
|
||||||
|
<v-dialog v-model="deleteDialog" max-width="400">
|
||||||
|
<template #activator="{ on }">
|
||||||
|
<v-btn outlined v-on="on" class="mr-2" small>
|
||||||
|
<v-icon>mdi-delete</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
<v-card>
|
||||||
|
<v-card-title class="headline">Delete Task</v-card-title>
|
||||||
|
<v-card-text>Are you sure you want to delete this task?</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer/>
|
||||||
|
<v-btn color="blue darken-1" text @click="deleteDialog = false">Cancel</v-btn>
|
||||||
|
<v-btn color="blue darken-1" text @click="deleteTask">Delete</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
<v-btn @click="close" outlined small>
|
||||||
|
<v-icon>mdi-close</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<v-text-field
|
||||||
|
v-model="task.name"
|
||||||
|
label="Name"
|
||||||
|
variant="underlined"/>
|
||||||
|
<v-textarea
|
||||||
|
v-model="task.description"
|
||||||
|
label="Description"
|
||||||
|
auto-grow
|
||||||
|
rows="1"
|
||||||
|
variant="underlined"/>
|
||||||
|
<v-select
|
||||||
|
v-model="task.type"
|
||||||
|
:items="['input','automation','task']"
|
||||||
|
label="Type"
|
||||||
|
variant="underlined"/>
|
||||||
|
|
||||||
|
<AdvancedJSONSchemaEditor
|
||||||
|
v-if="task.type === 'input'"
|
||||||
|
:schema="task.schema"
|
||||||
|
@save="task.schema = JSON.parse($event)" />
|
||||||
|
|
||||||
|
<v-select
|
||||||
|
v-if="task.type === 'automation'"
|
||||||
|
v-model="task.automation"
|
||||||
|
:items="automations"
|
||||||
|
item-text="id"
|
||||||
|
item-value="id"
|
||||||
|
label="Automation"
|
||||||
|
variant="underlined"/>
|
||||||
|
|
||||||
|
<v-list v-if="task.type === 'automation'">
|
||||||
|
<v-subheader class="pa-0" style="padding-inline-start: 0 !important;">Payload Mapping</v-subheader>
|
||||||
|
<v-toolbar v-for="(expr, key) in task.payload" :key="key" class="next-row" flat dense>
|
||||||
|
{{ key }}:
|
||||||
|
<v-text-field
|
||||||
|
v-model="task.payload[key]"
|
||||||
|
label="Expression"
|
||||||
|
variant="solo"
|
||||||
|
clearable
|
||||||
|
hide-details
|
||||||
|
density="compact"
|
||||||
|
bg-color="surface"
|
||||||
|
/>
|
||||||
|
<v-btn @click="deletePayloadMapping(key)" color="error" class="pa-0 ma-0" icon>
|
||||||
|
<v-icon>mdi-delete</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-toolbar>
|
||||||
|
<v-toolbar class="next-row" flat dense>
|
||||||
|
<v-text-field
|
||||||
|
v-model="newPayloadMapping"
|
||||||
|
label="Payload Field"
|
||||||
|
variant="solo"
|
||||||
|
bg-color="surface"
|
||||||
|
hide-details
|
||||||
|
density="compact"
|
||||||
|
/>:
|
||||||
|
<v-text-field
|
||||||
|
v-model="newExpression"
|
||||||
|
label="CAQL Expression"
|
||||||
|
variant="solo"
|
||||||
|
hide-details
|
||||||
|
density="compact"
|
||||||
|
bg-color="surface"
|
||||||
|
/>
|
||||||
|
<v-btn
|
||||||
|
@click="addPayloadMapping"
|
||||||
|
:disabled="!newPayloadMapping || !newExpression"
|
||||||
|
class="pa-0 ma-0"
|
||||||
|
icon>
|
||||||
|
<v-icon>mdi-plus</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-toolbar>
|
||||||
|
</v-list>
|
||||||
|
|
||||||
|
<v-list v-if="task.next || possibleNexts.length > 1">
|
||||||
|
<v-subheader class="pa-0" style="padding-inline-start: 0 !important;">Next Task(s)</v-subheader>
|
||||||
|
<v-toolbar v-for="(expr, key) in task.next" :key="key" class="next-row" flat dense>
|
||||||
|
If
|
||||||
|
<v-text-field
|
||||||
|
v-model="task.next[key]"
|
||||||
|
label="Condition (leave empty to always run)"
|
||||||
|
variant="solo"
|
||||||
|
clearable
|
||||||
|
hide-details
|
||||||
|
density="compact"
|
||||||
|
bg-color="surface"
|
||||||
|
/>
|
||||||
|
run
|
||||||
|
<span class="font-weight-black">
|
||||||
|
{{ playbook.tasks[key].name }}
|
||||||
|
</span>
|
||||||
|
<v-btn @click="deleteNext(key)" color="error" class="pa-0 ma-0" icon>
|
||||||
|
<v-icon>mdi-delete</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-toolbar>
|
||||||
|
<v-toolbar v-if="possibleNexts.length > 0" class="next-row" flat dense>
|
||||||
|
If
|
||||||
|
<v-text-field
|
||||||
|
v-model="newCondition"
|
||||||
|
label="Condition (leave empty to always run)"
|
||||||
|
variant="solo"
|
||||||
|
clearable
|
||||||
|
hide-details
|
||||||
|
density="compact"
|
||||||
|
bg-color="surface"
|
||||||
|
/>
|
||||||
|
run
|
||||||
|
<v-select
|
||||||
|
v-model="newNext"
|
||||||
|
item-text="name"
|
||||||
|
item-value="key"
|
||||||
|
:items="possibleNexts"
|
||||||
|
variant="solo"
|
||||||
|
bg-color="surface"
|
||||||
|
hide-details
|
||||||
|
density="compact"
|
||||||
|
/>
|
||||||
|
<v-btn
|
||||||
|
@click="addNext"
|
||||||
|
:disabled="!newNext"
|
||||||
|
class="pa-0 ma-0"
|
||||||
|
icon>
|
||||||
|
<v-icon>mdi-plus</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-toolbar>
|
||||||
|
</v-list>
|
||||||
|
<v-switch
|
||||||
|
v-if="parents.length > 1"
|
||||||
|
label="Join (Require all previous tasks to be completed)"
|
||||||
|
v-model="task.join"
|
||||||
|
color="primary"/>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {defineProps, ref, watch, defineEmits, del, set, onMounted, computed} from "vue";
|
||||||
|
import AdvancedJSONSchemaEditor from "@/components/AdvancedJSONSchemaEditor.vue";
|
||||||
|
import {API} from "@/services/api";
|
||||||
|
import {AutomationResponse} from "@/client";
|
||||||
|
|
||||||
|
interface Task {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
type: string;
|
||||||
|
next: Record<string, string>;
|
||||||
|
payload: Record<string, string>;
|
||||||
|
join: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
value: Task;
|
||||||
|
possibleNexts: Array<Record<string, string>>;
|
||||||
|
parents: Array<string>;
|
||||||
|
playbook: object;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits(["input", "delete", "close"]);
|
||||||
|
|
||||||
|
const deleteDialog = ref(false);
|
||||||
|
const deleteTask = () => emit("delete");
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
emit("close");
|
||||||
|
};
|
||||||
|
|
||||||
|
const task = ref(props.value);
|
||||||
|
watch(() => props.value, (value) => {
|
||||||
|
task.value = value;
|
||||||
|
});
|
||||||
|
watch(task, (value) => {
|
||||||
|
emit("input", value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// const task = computed({
|
||||||
|
// get: () => {
|
||||||
|
// console.log("get", props.value);
|
||||||
|
// return props.value;
|
||||||
|
// },
|
||||||
|
// set: (value) => {
|
||||||
|
// console.log("set", value);
|
||||||
|
// emit("input", value);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
const deleteNext = (key: string) => {
|
||||||
|
del(task.value.next, key);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deletePayloadMapping = (key: string) => {
|
||||||
|
del(task.value.payload, key);
|
||||||
|
};
|
||||||
|
|
||||||
|
const newNext = ref('');
|
||||||
|
const newCondition = ref('');
|
||||||
|
|
||||||
|
const newPayloadMapping = ref('');
|
||||||
|
const newExpression = ref('');
|
||||||
|
|
||||||
|
watch(() => props.possibleNexts, () => {
|
||||||
|
if (props.possibleNexts.length > 0) {
|
||||||
|
newNext.value = props.possibleNexts[0].key;
|
||||||
|
}
|
||||||
|
}, {deep: true, immediate: true});
|
||||||
|
|
||||||
|
|
||||||
|
const addNext = () => {
|
||||||
|
if (task.value.next === undefined) {
|
||||||
|
// task.value.next = {};
|
||||||
|
set(task.value, 'next', {});
|
||||||
|
}
|
||||||
|
// task.value.next[newNext.value] = newCondition.value;
|
||||||
|
set(task.value.next, newNext.value, newCondition.value);
|
||||||
|
newNext.value = "";
|
||||||
|
newCondition.value = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
const addPayloadMapping = () => {
|
||||||
|
if (task.value.payload === undefined) {
|
||||||
|
// task.value.payload = {};
|
||||||
|
set(task.value, 'payload', {});
|
||||||
|
}
|
||||||
|
// task.value.payload[newPayloadMapping.value] = newExpression.value;
|
||||||
|
set(task.value.payload, newPayloadMapping.value, newExpression.value);
|
||||||
|
newPayloadMapping.value = "";
|
||||||
|
newExpression.value = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
const automations = ref<Array<AutomationResponse>>([]);
|
||||||
|
onMounted(() => {
|
||||||
|
API.listAutomations().then((response) => {
|
||||||
|
automations.value = response.data;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.next-row {
|
||||||
|
padding-top: 10px;
|
||||||
|
background: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.next-row .v-toolbar__content {
|
||||||
|
gap: 5px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
80
ui/src/components/playbookeditor/NewTask.vue
Normal file
80
ui/src/components/playbookeditor/NewTask.vue
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<template>
|
||||||
|
<v-card outlined>
|
||||||
|
<v-card-title>
|
||||||
|
Create a new step
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn @click="close" outlined small>
|
||||||
|
<v-icon>mdi-close</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<v-form v-model="valid">
|
||||||
|
<v-text-field
|
||||||
|
v-model="newTask.name"
|
||||||
|
label="Name"
|
||||||
|
:rules="[val => (val || '').length > 0 || 'This field is required']"
|
||||||
|
variant="underlined"/>
|
||||||
|
<v-textarea
|
||||||
|
v-model="newTask.description"
|
||||||
|
label="Description"
|
||||||
|
auto-grow
|
||||||
|
rows="1"
|
||||||
|
variant="underlined"/>
|
||||||
|
<v-text-field
|
||||||
|
v-model="newTask.key"
|
||||||
|
label="Key (generated automatically)"
|
||||||
|
readonly
|
||||||
|
disabled
|
||||||
|
:rules="[val => (val || '').length > 0 || 'This field is required']"
|
||||||
|
variant="underlined"/>
|
||||||
|
</v-form>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer/>
|
||||||
|
<v-btn color="primary" @click="createTask" :disabled="!valid">Create</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {defineProps, ref, watch, defineEmits} from "vue";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
playbook: any;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const valid = ref(false);
|
||||||
|
const newTask = ref({
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
key: '',
|
||||||
|
next: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(newTask, (val) => {
|
||||||
|
if (val.name) {
|
||||||
|
const newKeyBase = val.name.toLowerCase().replace(/ /g, '_');
|
||||||
|
let newKey = newKeyBase;
|
||||||
|
if (!(newKey in props.playbook.tasks)) {
|
||||||
|
newTask.value.key = newKey;
|
||||||
|
} else {
|
||||||
|
let i = 1;
|
||||||
|
while (newKey in props.playbook.tasks) {
|
||||||
|
newKey = newKeyBase + '_' + i;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
newTask.value.key = newKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {deep: true});
|
||||||
|
|
||||||
|
const emit = defineEmits(["createTask", "close"]);
|
||||||
|
|
||||||
|
const createTask = () => {
|
||||||
|
emit('createTask', newTask.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
emit("close");
|
||||||
|
};
|
||||||
|
</script>
|
||||||
164
ui/src/components/playbookeditor/PanZoom.vue
Normal file
164
ui/src/components/playbookeditor/PanZoom.vue
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
<template>
|
||||||
|
<div id="graphwrapper">
|
||||||
|
<div id="gab">
|
||||||
|
<slot name="actionbar"/>
|
||||||
|
</div>
|
||||||
|
<v-toolbar
|
||||||
|
id="gtb"
|
||||||
|
class="ma-2"
|
||||||
|
floating
|
||||||
|
dense
|
||||||
|
>
|
||||||
|
<v-btn @click.prevent.stop="reset" title="Reset" icon>
|
||||||
|
<v-icon color="#000">mdi-image-filter-center-focus</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
<v-btn @click.prevent.stop="zoomIn" title="Zoom in" :disabled="isMaxZoom" icon>
|
||||||
|
<v-icon color="#000">mdi-magnify-plus-outline</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
<v-btn @click.prevent.stop="zoomOut" title="Zoom out" :disabled="isMinZoom" icon>
|
||||||
|
<v-icon color="#000">mdi-magnify-minus-outline</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
<slot name="toolbar"/>
|
||||||
|
</v-toolbar>
|
||||||
|
<div id="panzoom">
|
||||||
|
<slot/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {computed, onMounted, ref, defineProps, defineExpose} from "vue";
|
||||||
|
import createPanZoom from "panzoom";
|
||||||
|
import * as panzoom from "panzoom";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
config: panzoom.PanZoomOptions
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const panZoom = ref<panzoom.PanZoom | null>(null);
|
||||||
|
|
||||||
|
const zoomLevel = ref<number>(1);
|
||||||
|
|
||||||
|
const minZoom = ref<number>(0.5);
|
||||||
|
const maxZoom = ref<number>(1.5);
|
||||||
|
|
||||||
|
const isMaxZoom = computed(() => {
|
||||||
|
if (zoomLevel.value) {
|
||||||
|
return zoomLevel.value >= maxZoom.value;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const isMinZoom = computed(() => {
|
||||||
|
if (zoomLevel.value) {
|
||||||
|
return zoomLevel.value <= minZoom.value;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const initialZoom = ref<number>(1);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const canvas = document.getElementById("panzoom")
|
||||||
|
if (!canvas) {
|
||||||
|
throw new Error("No element with id panzoom")
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstChild = canvas.firstElementChild
|
||||||
|
if (!firstChild) {
|
||||||
|
throw new Error("No child element")
|
||||||
|
}
|
||||||
|
|
||||||
|
const startX = canvas.getBoundingClientRect().width / 2 - firstChild.getBoundingClientRect().width / 2
|
||||||
|
const startY = canvas.getBoundingClientRect().height / 2 - firstChild.getBoundingClientRect().height / 2
|
||||||
|
|
||||||
|
initialZoom.value = props.config.initialZoom ? props.config.initialZoom : 1
|
||||||
|
minZoom.value = props.config.initialZoom / 2
|
||||||
|
maxZoom.value = props.config.initialZoom * 2
|
||||||
|
|
||||||
|
panZoom.value = createPanZoom(canvas, {
|
||||||
|
...props.config,
|
||||||
|
zoomDoubleClickSpeed: 1, // disable double click zoom
|
||||||
|
autocenter: true,
|
||||||
|
initialX: startX,
|
||||||
|
initialY: startY,
|
||||||
|
minZoom: minZoom.value,
|
||||||
|
maxZoom: maxZoom.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
panZoom.value.on("zoom", (e: panzoom.PanZoom) => {
|
||||||
|
zoomLevel.value = e.getTransform().scale;
|
||||||
|
});
|
||||||
|
|
||||||
|
reset(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
const ZOOM_FACTOR = 0.255
|
||||||
|
|
||||||
|
const zoomIn = () => {
|
||||||
|
if (!panZoom.value) return
|
||||||
|
const currentZoom = panZoom.value.getTransform().scale
|
||||||
|
panZoom.value.smoothZoomAbs(0, 0, currentZoom + ZOOM_FACTOR)
|
||||||
|
}
|
||||||
|
|
||||||
|
const zoomOut = () => {
|
||||||
|
if (!panZoom.value) return
|
||||||
|
const currentZoom = panZoom.value.getTransform().scale
|
||||||
|
panZoom.value.smoothZoomAbs(0, 0, currentZoom - ZOOM_FACTOR)
|
||||||
|
}
|
||||||
|
|
||||||
|
const reset = (smooth = true) => {
|
||||||
|
if (!panZoom.value) return
|
||||||
|
|
||||||
|
const canvas = document.getElementById("panzoom")
|
||||||
|
if (!canvas) {
|
||||||
|
throw new Error("No element with id panzoom")
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstChild = canvas.firstElementChild
|
||||||
|
if (!firstChild) {
|
||||||
|
throw new Error("No child element")
|
||||||
|
}
|
||||||
|
|
||||||
|
const startX = canvas.getBoundingClientRect().width / 2 - firstChild.getBoundingClientRect().width / 2
|
||||||
|
const startY = canvas.getBoundingClientRect().height / 2 - firstChild.getBoundingClientRect().height / 2
|
||||||
|
|
||||||
|
panZoom.value.pause()
|
||||||
|
if (smooth) {
|
||||||
|
panZoom.value.smoothZoomAbs(0, 0, initialZoom.value)
|
||||||
|
panZoom.value.smoothMoveTo(startX * 0.5, startY)
|
||||||
|
} else {
|
||||||
|
panZoom.value.zoomTo(0, 0, initialZoom.value)
|
||||||
|
panZoom.value.moveTo(startX * 0.5, startY)
|
||||||
|
}
|
||||||
|
panZoom.value.resume()
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
reset,
|
||||||
|
zoomIn,
|
||||||
|
zoomOut,
|
||||||
|
zoomLevel,
|
||||||
|
isMaxZoom,
|
||||||
|
isMinZoom,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#graphwrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gtb .v-toolbar__content {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#panzoom {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
235
ui/src/components/playbookeditor/PlaybookEditor.vue
Normal file
235
ui/src/components/playbookeditor/PlaybookEditor.vue
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
<template>
|
||||||
|
<v-row style="position: relative; min-height: 600px">
|
||||||
|
<v-col :cols="(selectedStep && selectedStep in playbook.tasks) || showNewDialog ? 8 :12">
|
||||||
|
<v-card style="overflow: hidden" outlined>
|
||||||
|
<v-card-text>
|
||||||
|
<PanZoom ref="panZoomPanel" :config="panZoomConfig">
|
||||||
|
<template #default>
|
||||||
|
<PlaybookGraph
|
||||||
|
v-if="playbook"
|
||||||
|
:playbook="playbook"
|
||||||
|
:horizontal="horizontal"
|
||||||
|
:selected="selectedStep"
|
||||||
|
@update:selected="showNewDialog = false; selectedStep = $event"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #actionbar>
|
||||||
|
<v-btn @click="showNewDialog = true; selectedStep = ''" large rounded>
|
||||||
|
<v-icon color="#000">mdi-plus</v-icon>
|
||||||
|
New Step
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
<template #toolbar>
|
||||||
|
<v-btn @click="toggleOrientation" label="Toggle Orientation" icon>
|
||||||
|
<v-icon color="#000">mdi-format-rotate-90</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</PanZoom>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
<v-col
|
||||||
|
v-if="(selectedStep && selectedStep in playbook.tasks) || showNewDialog"
|
||||||
|
cols="4"
|
||||||
|
>
|
||||||
|
<EditTask
|
||||||
|
v-if="selectedStep && selectedStep in playbook.tasks"
|
||||||
|
:value="playbook.tasks[selectedStep]"
|
||||||
|
@input="updateTask"
|
||||||
|
:possibleNexts="possibleNexts"
|
||||||
|
:parents="parents"
|
||||||
|
:playbook="playbook"
|
||||||
|
@delete="deleteTask"
|
||||||
|
@close="unselectStep"/>
|
||||||
|
<NewTask
|
||||||
|
v-else-if="showNewDialog"
|
||||||
|
:playbook="playbook"
|
||||||
|
@createTask="createTask"
|
||||||
|
@close="showNewDialog = false"/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {computed, nextTick, ref, defineProps, set, defineEmits, del} from "vue";
|
||||||
|
import PlaybookGraph from "@/components/playbookeditor/PlaybookGraph.vue";
|
||||||
|
import PanZoom from "@/components/playbookeditor/PanZoom.vue";
|
||||||
|
import EditTask from "@/components/playbookeditor/EditTask.vue";
|
||||||
|
import NewTask from "@/components/playbookeditor/NewTask.vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
'value': {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['input']);
|
||||||
|
|
||||||
|
const playbook = computed({
|
||||||
|
get: () => props.value,
|
||||||
|
set: (value) => {
|
||||||
|
emit('input', value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// selected step
|
||||||
|
const selectedStep = ref("")
|
||||||
|
const unselectStep = () => {
|
||||||
|
selectedStep.value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateTask = (task: any) => {
|
||||||
|
set(playbook.value.tasks, selectedStep.value, task);
|
||||||
|
emit('input', playbook.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteTask = () => {
|
||||||
|
const parents = Array<any>();
|
||||||
|
for (const task in playbook.value.tasks) {
|
||||||
|
if (playbook.value.tasks[task].next && playbook.value.tasks[task].next[selectedStep.value]) {
|
||||||
|
parents.push(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const children = Array<any>();
|
||||||
|
if (playbook.value.tasks[selectedStep.value].next) {
|
||||||
|
for (const next in playbook.value.tasks[selectedStep.value].next) {
|
||||||
|
children.push(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const parent of parents) {
|
||||||
|
del(playbook.value.tasks[parent].next, selectedStep.value);
|
||||||
|
for (const child of children) {
|
||||||
|
set(playbook.value.tasks[parent].next, child, playbook.value.tasks[selectedStep.value].next[child]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
del(playbook.value.tasks, selectedStep.value);
|
||||||
|
|
||||||
|
// for (const task in playbook.value.tasks) {
|
||||||
|
// if (playbook.value.tasks[task].next && playbook.value.tasks[task].next[selectedStep.value]) {
|
||||||
|
// del(playbook.value.tasks[task].next, selectedStep.value);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// del(playbook.value.tasks, selectedStep.value);
|
||||||
|
emit('input', playbook.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const panZoomConfig = ref({})
|
||||||
|
|
||||||
|
const panZoomPanel = ref(null);
|
||||||
|
|
||||||
|
const horizontal = ref(false);
|
||||||
|
const toggleOrientation = () => {
|
||||||
|
horizontal.value = !horizontal.value;
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
panZoomPanel.value?.reset(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const showNewDialog = ref(false);
|
||||||
|
const createTask = (task: any) => {
|
||||||
|
const t = {
|
||||||
|
name: task.name,
|
||||||
|
description: task.description,
|
||||||
|
type: 'task',
|
||||||
|
next: {}
|
||||||
|
};
|
||||||
|
set(playbook.value.tasks, task.key, t);
|
||||||
|
selectedStep.value = task.key;
|
||||||
|
};
|
||||||
|
|
||||||
|
// edit task
|
||||||
|
const possibleNexts = computed(() => {
|
||||||
|
if (!selectedStep.value) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let nexts = Object.keys(playbook.value.tasks);
|
||||||
|
nexts = nexts.filter((n) => n !== selectedStep.value);
|
||||||
|
|
||||||
|
// remove any nexts that are already in the list
|
||||||
|
if (playbook.value.tasks[selectedStep.value] && 'next' in playbook.value.tasks[selectedStep.value]) {
|
||||||
|
for (const next in playbook.value.tasks[selectedStep.value].next) {
|
||||||
|
nexts = nexts.filter((n) => n !== next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove parents recursively
|
||||||
|
const parents = findAncestor(selectedStep.value);
|
||||||
|
for (const parent of parents) {
|
||||||
|
nexts = nexts.filter((n) => n !== parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: Array<Record<string, string>> = [];
|
||||||
|
for (const next of nexts) {
|
||||||
|
if (next && playbook.value.tasks[next].name) {
|
||||||
|
result.push({"key": next, "name": playbook.value.tasks[next].name});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
const findAncestor = (step: string): Array<string> => {
|
||||||
|
const parents: Array<string> = [];
|
||||||
|
for (const task in playbook.value.tasks) {
|
||||||
|
for (const next in playbook.value.tasks[task].next) {
|
||||||
|
if (next === step) {
|
||||||
|
if (!parents.includes(task)) {
|
||||||
|
parents.push(task);
|
||||||
|
parents.push(...findAncestor(task));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parents;
|
||||||
|
};
|
||||||
|
|
||||||
|
const parents = computed(() => {
|
||||||
|
const parents: Array<string> = [];
|
||||||
|
for (const task in playbook.value.tasks) {
|
||||||
|
for (const next in playbook.value.tasks[task].next) {
|
||||||
|
if (next === selectedStep.value) {
|
||||||
|
if (!parents.includes(task)) {
|
||||||
|
parents.push(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parents;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#graphwrapper {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gab {
|
||||||
|
position: absolute;
|
||||||
|
left: 10px;
|
||||||
|
top: 20px;
|
||||||
|
border-radius: 30px;
|
||||||
|
z-index: 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gtb {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 20px;
|
||||||
|
border-radius: 30px;
|
||||||
|
z-index: 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gtb .v-toolbar__content > .v-btn:first-child {
|
||||||
|
margin-inline-start: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gtb .v-toolbar__content > .v-btn:last-child {
|
||||||
|
margin-inline-end: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
511
ui/src/components/playbookeditor/PlaybookGraph.vue
Normal file
511
ui/src/components/playbookeditor/PlaybookGraph.vue
Normal file
@@ -0,0 +1,511 @@
|
|||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
class="pe-graph"
|
||||||
|
:width="width * scale"
|
||||||
|
:height="height * scale"
|
||||||
|
:viewBox="`${minX - config.graphPadding} ${minY - config.graphPadding} ${maxX - minX + config.boxWidth + config.graphPadding * 2} ${maxY - minY + config.boxHeight + config.graphPadding * 2}`"
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<path
|
||||||
|
id="cog"
|
||||||
|
d="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z"/>
|
||||||
|
<path
|
||||||
|
id="clipboard-outline"
|
||||||
|
d="M19,3H14.82C14.4,1.84 13.3,1 12,1C10.7,1 9.6,1.84 9.18,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3M12,3A1,1 0 0,1 13,4A1,1 0 0,1 12,5A1,1 0 0,1 11,4A1,1 0 0,1 12,3M7,7H17V5H19V19H5V5H7V7Z"/>
|
||||||
|
<path
|
||||||
|
id="keyboard"
|
||||||
|
d="M19,10H17V8H19M19,13H17V11H19M16,10H14V8H16M16,13H14V11H16M16,17H8V15H16M7,10H5V8H7M7,13H5V11H7M8,11H10V13H8M8,8H10V10H8M11,11H13V13H11M11,8H13V10H11M20,5H4C2.89,5 2,5.89 2,7V17A2,2 0 0,0 4,19H20A2,2 0 0,0 22,17V7C22,5.89 21.1,5 20,5Z"/>
|
||||||
|
<path
|
||||||
|
id="star"
|
||||||
|
d="M12,17.27L18.18,21L16.54,13.97L22,9.24L14.81,8.62L12,2L9.19,8.62L2,9.24L7.45,13.97L5.82,21L12,17.27Z"/>
|
||||||
|
</defs>
|
||||||
|
<g class="pe-links">
|
||||||
|
<path v-for="(link, index) in links" :key="index" :d="link.path"/>
|
||||||
|
</g>
|
||||||
|
<g class="pe-activelinks">
|
||||||
|
<path
|
||||||
|
v-for="(link, index) in links"
|
||||||
|
:key="index"
|
||||||
|
:d="link.path"
|
||||||
|
:class="{
|
||||||
|
'hovered': !selectedNode && (link.source === hoverNode || link.target === hoverNode),
|
||||||
|
'selected': link.source === selectedNode || link.target === selectedNode
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g class="pe-nodes">
|
||||||
|
<g
|
||||||
|
v-for="(node, index) in positionedNodes"
|
||||||
|
:key="index">
|
||||||
|
<g
|
||||||
|
v-if="node.type === 'start'"
|
||||||
|
:transform="`translate(${props.horizontal ? node.x + config.boxWidth : node.x + (config.boxWidth / 2)}, ${node.y})`"
|
||||||
|
class="start"
|
||||||
|
>
|
||||||
|
<circle
|
||||||
|
:transform="`translate(0, ${config.boxHeight / 2})`"
|
||||||
|
r="20"
|
||||||
|
/>
|
||||||
|
<g
|
||||||
|
class="icon"
|
||||||
|
:transform="`translate(${0 - 12}, ${(config.boxHeight - 24) / 2})`"
|
||||||
|
>
|
||||||
|
<use href="#star" fill="white"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
v-else
|
||||||
|
:transform="`translate(${node.x}, ${node.y})`"
|
||||||
|
:class="{
|
||||||
|
'pe-node': true,
|
||||||
|
'start': node.type === 'start',
|
||||||
|
'hovered': node.id === hoverNode,
|
||||||
|
'selected': node.id === selectedNode,
|
||||||
|
'unhovered': hoverNode && !selectedNode && node.id !== hoverNode,
|
||||||
|
'unselected': selectedNode && node.id !== selectedNode,
|
||||||
|
}"
|
||||||
|
@mouseover="hoverNode = node.id"
|
||||||
|
@mouseout="hoverNode = null"
|
||||||
|
@click="selectedNode === node.id ? selectedNode = null : selectedNode = node.id"
|
||||||
|
>
|
||||||
|
<rect
|
||||||
|
class="pe-box"
|
||||||
|
:width="config.boxWidth"
|
||||||
|
:height="config.boxHeight"
|
||||||
|
:rx="config.boxRadius"
|
||||||
|
:ry="config.boxRadius"
|
||||||
|
/>
|
||||||
|
<g
|
||||||
|
class="pe-icon"
|
||||||
|
:transform="`translate(10, ${(config.boxHeight - 24) / 2})`"
|
||||||
|
>
|
||||||
|
<use v-if="node.type === 'automation'" href="#cog"/>
|
||||||
|
<use v-else-if="node.type === 'start'" href="#star"/>
|
||||||
|
<use v-else-if="node.type === 'input'" href="#keyboard"/>
|
||||||
|
<use v-else href="#clipboard-outline"/>
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
class="pe-text"
|
||||||
|
:x="config.boxWidth / 2"
|
||||||
|
:y="config.boxHeight / 2"
|
||||||
|
>
|
||||||
|
{{ node.label ? node.label : node.id }}
|
||||||
|
</text>
|
||||||
|
<g
|
||||||
|
class="add"
|
||||||
|
:transform="`translate(${config.boxWidth - 34}, ${(config.boxHeight - 24) / 2})`"
|
||||||
|
>
|
||||||
|
<use href="#star" fill="none"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g class="pe-connectors">
|
||||||
|
<g
|
||||||
|
v-for="(link, index) in links"
|
||||||
|
:key="index"
|
||||||
|
:class="{
|
||||||
|
'pe-connector': true,
|
||||||
|
'hovered': !selectedNode && (link.source === hoverNode || link.target === hoverNode),
|
||||||
|
'selected': link.source === selectedNode || link.target === selectedNode
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<circle
|
||||||
|
v-if="link.source !== 'start'"
|
||||||
|
:cx="link.start.x"
|
||||||
|
:cy="link.start.y"
|
||||||
|
:r="4"/>
|
||||||
|
<circle
|
||||||
|
:cx="link.end.x"
|
||||||
|
:cy="link.end.y"
|
||||||
|
:r="7"/>
|
||||||
|
<circle
|
||||||
|
:cx="link.end.x"
|
||||||
|
:cy="link.end.y"
|
||||||
|
:r="5"
|
||||||
|
fill="white"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import * as d3 from "d3";
|
||||||
|
import {digl} from "@crinkles/digl";
|
||||||
|
import {Edge as DiglEdge, Node as DiglNode, Position, Rank} from "@crinkles/digl/dist/types";
|
||||||
|
import {computed, defineEmits, ref, Ref, defineProps, ComputedRef} from "vue";
|
||||||
|
|
||||||
|
interface Config {
|
||||||
|
graphPadding: number;
|
||||||
|
boxWidth: number;
|
||||||
|
boxHeight: number;
|
||||||
|
boxMarginX: number;
|
||||||
|
boxMarginY: number;
|
||||||
|
boxRadius: number;
|
||||||
|
lineDistance: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
playbook: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
selected: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
horizontal: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
scale: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const config = ref({
|
||||||
|
graphPadding: 10,
|
||||||
|
boxWidth: 220,
|
||||||
|
boxHeight: 40,
|
||||||
|
boxMarginX: 60,
|
||||||
|
boxMarginY: 70,
|
||||||
|
boxRadius: 20,
|
||||||
|
lineDistance: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
interface Edge extends DiglEdge {
|
||||||
|
label?: { text: string, x: number, y: number };
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Node extends DiglNode {
|
||||||
|
type: 'automation' | 'input' | 'task' | 'start';
|
||||||
|
}
|
||||||
|
|
||||||
|
const edges = computed(() => {
|
||||||
|
const edges: Array<Edge> = [];
|
||||||
|
|
||||||
|
for (const key in props.playbook.tasks) {
|
||||||
|
for (const next in props.playbook.tasks[key].next) {
|
||||||
|
edges.push({
|
||||||
|
source: key,
|
||||||
|
target: next,
|
||||||
|
label: props.playbook.tasks[key].next[next]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rootNodes = nodes.value.filter(node => edges.every(edge => edge.target !== node.id));
|
||||||
|
for (const node of rootNodes) {
|
||||||
|
if (node.id !== 'start') {
|
||||||
|
edges.push({
|
||||||
|
source: 'start',
|
||||||
|
target: node.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return edges;
|
||||||
|
});
|
||||||
|
|
||||||
|
const nodes: ComputedRef<Array<Node>> = computed(() => {
|
||||||
|
const nodes = [{
|
||||||
|
id: "start",
|
||||||
|
label: "Start",
|
||||||
|
type: "start" as 'automation' | 'input' | 'task' | 'start',
|
||||||
|
}];
|
||||||
|
for (const key in props.playbook.tasks) {
|
||||||
|
nodes.push({
|
||||||
|
id: key,
|
||||||
|
label: props.playbook.tasks[key].name,
|
||||||
|
type: props.playbook.tasks[key].type
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return nodes;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const hoverNode: Ref<string | null> = ref(null);
|
||||||
|
|
||||||
|
const emits = defineEmits(['update:selected']);
|
||||||
|
const selectedNode = computed({
|
||||||
|
get: () => props.selected,
|
||||||
|
set: (newVal) => {
|
||||||
|
emits('update:selected', newVal);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const rankses: Ref<Array<Array<Rank>>> = computed(() => {
|
||||||
|
return digl(edges.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
const ranks = computed(() => {
|
||||||
|
return rankses.value.length > 0 ? rankses.value[0] : [];
|
||||||
|
});
|
||||||
|
|
||||||
|
const positionedNodes = computed(() => {
|
||||||
|
return positioning(config.value, props.horizontal, nodes.value, ranks.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
const width = computed(() => {
|
||||||
|
return maxX.value - minX.value + config.value.graphPadding * 2 + config.value.boxWidth;
|
||||||
|
});
|
||||||
|
|
||||||
|
const height = computed(() => {
|
||||||
|
return maxY.value - minY.value + config.value.graphPadding * 2 + config.value.boxHeight;
|
||||||
|
});
|
||||||
|
|
||||||
|
const minX = computed(() => {
|
||||||
|
if (props.horizontal) {
|
||||||
|
return Math.min(...positionedNodes.value.map(node => node.x)) + config.value.boxWidth - 22;
|
||||||
|
}
|
||||||
|
return Math.min(...positionedNodes.value.map(node => node.x));
|
||||||
|
});
|
||||||
|
|
||||||
|
const minY = computed(() => {
|
||||||
|
if (!props.horizontal) {
|
||||||
|
return Math.min(...positionedNodes.value.map(node => node.y)) + config.value.boxHeight - 30;
|
||||||
|
}
|
||||||
|
return Math.min(...positionedNodes.value.map(node => node.y));
|
||||||
|
});
|
||||||
|
|
||||||
|
const maxX = computed(() => {
|
||||||
|
return Math.max(...positionedNodes.value.map(node => node.x));
|
||||||
|
});
|
||||||
|
|
||||||
|
const maxY = computed(() => {
|
||||||
|
return Math.max(...positionedNodes.value.map(node => node.y)) + 50;
|
||||||
|
});
|
||||||
|
|
||||||
|
const links = computed(() => {
|
||||||
|
return edges.value.map(edge => {
|
||||||
|
const source = positionedNodes.value.find(node => node.id === edge.source);
|
||||||
|
const target = positionedNodes.value.find(node => node.id === edge.target);
|
||||||
|
|
||||||
|
if (!source || !target) return;
|
||||||
|
|
||||||
|
// index within rank
|
||||||
|
const sourceIndex = ranks.value.find(rank => rank.includes(edge.source))?.indexOf(edge.source);
|
||||||
|
if (sourceIndex === undefined) {
|
||||||
|
throw new Error(`sourceIndex is undefined for ${edge.source}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = props.horizontal ?
|
||||||
|
horizontalConnectionLine(source, target, sourceIndex, config.value) :
|
||||||
|
verticalConnectionLine(source, target, sourceIndex, config.value);
|
||||||
|
|
||||||
|
const start = props.horizontal ?
|
||||||
|
{x: source.x + config.value.boxWidth, y: source.y + config.value.boxHeight / 2} :
|
||||||
|
{x: source.x + config.value.boxWidth / 2, y: source.y + config.value.boxHeight};
|
||||||
|
|
||||||
|
const end = props.horizontal ?
|
||||||
|
{x: target.x, y: target.y + config.value.boxHeight / 2} :
|
||||||
|
{x: target.x + config.value.boxWidth / 2, y: target.y};
|
||||||
|
|
||||||
|
return {
|
||||||
|
source: edge.source,
|
||||||
|
target: edge.target,
|
||||||
|
path: path.toString(),
|
||||||
|
start: start,
|
||||||
|
end: end,
|
||||||
|
};
|
||||||
|
}).filter(link => link);
|
||||||
|
});
|
||||||
|
|
||||||
|
interface PositionedNode extends Node, Position {
|
||||||
|
}
|
||||||
|
|
||||||
|
function positioning(
|
||||||
|
config: Config,
|
||||||
|
horizontal: boolean,
|
||||||
|
nodes: Node[],
|
||||||
|
ranks: Rank[]
|
||||||
|
): PositionedNode[] {
|
||||||
|
const _nodes: PositionedNode[] = [];
|
||||||
|
const _h = horizontal;
|
||||||
|
|
||||||
|
ranks.forEach((rank, i) => {
|
||||||
|
const xStart = _h
|
||||||
|
? (config.boxWidth + config.boxMarginX) * i
|
||||||
|
: -0.5 * (rank.length - 1) * (config.boxWidth + config.boxMarginX);
|
||||||
|
const yStart = _h
|
||||||
|
? -0.5 * (rank.length - 1) * (config.boxHeight + config.boxMarginY)
|
||||||
|
: (config.boxHeight + config.boxMarginY) * i;
|
||||||
|
|
||||||
|
rank.forEach((nodeId, nIndex) => {
|
||||||
|
const _node: Node = nodes.find((n) => n.id == nodeId) as Node;
|
||||||
|
if (!_node) return;
|
||||||
|
const x = _h ? xStart : xStart + (config.boxWidth + config.boxMarginX) * nIndex;
|
||||||
|
const y = _h ? yStart + (config.boxHeight + config.boxMarginY) * nIndex : yStart;
|
||||||
|
_nodes.push({..._node, x, y});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return _nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function verticalConnectionLine(source: PositionedNode, target: PositionedNode, sourceIndex: number, config: Config) {
|
||||||
|
const sourceBottomCenter = {
|
||||||
|
x: source.x + config.boxWidth / 2,
|
||||||
|
y: source.y + config.boxHeight,
|
||||||
|
};
|
||||||
|
|
||||||
|
const targetTopCenter = {
|
||||||
|
x: target.x + config.boxWidth / 2,
|
||||||
|
y: target.y,
|
||||||
|
};
|
||||||
|
|
||||||
|
const path = d3.path();
|
||||||
|
path.moveTo(sourceBottomCenter.x, sourceBottomCenter.y);
|
||||||
|
|
||||||
|
const lineCurve = config.boxMarginY / 2;
|
||||||
|
|
||||||
|
if (sourceBottomCenter.x == targetTopCenter.x) {
|
||||||
|
path.lineTo(targetTopCenter.x, targetTopCenter.y);
|
||||||
|
} else if (sourceBottomCenter.x < targetTopCenter.x) {
|
||||||
|
if (target.y !== source.y + config.boxHeight + config.boxMarginY) {
|
||||||
|
path.lineTo(sourceBottomCenter.x, target.y - config.boxMarginY);
|
||||||
|
}
|
||||||
|
sourceBottomCenter.y = target.y - config.boxMarginY;
|
||||||
|
path.quadraticCurveTo(
|
||||||
|
sourceBottomCenter.x, sourceBottomCenter.y + lineCurve + sourceIndex * config.lineDistance,
|
||||||
|
sourceBottomCenter.x + lineCurve, sourceBottomCenter.y + lineCurve + sourceIndex * config.lineDistance,
|
||||||
|
);
|
||||||
|
path.lineTo(targetTopCenter.x - lineCurve, targetTopCenter.y - lineCurve + sourceIndex * config.lineDistance);
|
||||||
|
path.quadraticCurveTo(
|
||||||
|
targetTopCenter.x, targetTopCenter.y - lineCurve + sourceIndex * config.lineDistance,
|
||||||
|
targetTopCenter.x, targetTopCenter.y,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (target.y !== source.y + config.boxHeight + config.boxMarginY) {
|
||||||
|
path.lineTo(sourceBottomCenter.x, target.y - config.boxMarginY);
|
||||||
|
}
|
||||||
|
sourceBottomCenter.y = target.y - config.boxMarginY;
|
||||||
|
path.quadraticCurveTo(
|
||||||
|
sourceBottomCenter.x, sourceBottomCenter.y + lineCurve + sourceIndex * config.lineDistance,
|
||||||
|
sourceBottomCenter.x - lineCurve, sourceBottomCenter.y + lineCurve + sourceIndex * config.lineDistance,
|
||||||
|
);
|
||||||
|
path.lineTo(targetTopCenter.x + lineCurve, targetTopCenter.y - lineCurve + sourceIndex * config.lineDistance);
|
||||||
|
path.quadraticCurveTo(
|
||||||
|
targetTopCenter.x, targetTopCenter.y - lineCurve + sourceIndex * config.lineDistance,
|
||||||
|
targetTopCenter.x, targetTopCenter.y,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
function horizontalConnectionLine(source: PositionedNode, target: PositionedNode, sourceIndex: number, config: Config) {
|
||||||
|
const sourceRightCenter = {
|
||||||
|
x: source.x + config.boxWidth,
|
||||||
|
y: source.y + config.boxHeight / 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
const targetLeftCenter = {
|
||||||
|
x: target.x,
|
||||||
|
y: target.y + config.boxHeight / 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
const path = d3.path();
|
||||||
|
path.moveTo(sourceRightCenter.x, sourceRightCenter.y);
|
||||||
|
|
||||||
|
const lineCurve = config.boxMarginX / 2;
|
||||||
|
|
||||||
|
if (sourceRightCenter.y == targetLeftCenter.y) {
|
||||||
|
path.lineTo(targetLeftCenter.x, targetLeftCenter.y);
|
||||||
|
} else if (sourceRightCenter.y < targetLeftCenter.y) {
|
||||||
|
if (target.x !== source.x + config.boxWidth + config.boxMarginX) {
|
||||||
|
path.lineTo(target.x - config.boxMarginX, sourceRightCenter.y);
|
||||||
|
}
|
||||||
|
sourceRightCenter.x = target.x - config.boxMarginX;
|
||||||
|
path.quadraticCurveTo(
|
||||||
|
sourceRightCenter.x + lineCurve + sourceIndex * config.lineDistance, sourceRightCenter.y,
|
||||||
|
sourceRightCenter.x + lineCurve + sourceIndex * config.lineDistance, sourceRightCenter.y + lineCurve,
|
||||||
|
);
|
||||||
|
path.lineTo(targetLeftCenter.x - lineCurve + sourceIndex * config.lineDistance, targetLeftCenter.y - lineCurve);
|
||||||
|
path.quadraticCurveTo(
|
||||||
|
targetLeftCenter.x - lineCurve + sourceIndex * config.lineDistance, targetLeftCenter.y,
|
||||||
|
targetLeftCenter.x, targetLeftCenter.y,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (target.x !== source.x + config.boxWidth + config.boxMarginX) {
|
||||||
|
path.lineTo(target.x - config.boxMarginX, sourceRightCenter.y);
|
||||||
|
}
|
||||||
|
sourceRightCenter.x = target.x - config.boxMarginX;
|
||||||
|
path.quadraticCurveTo(
|
||||||
|
sourceRightCenter.x + lineCurve + sourceIndex * config.lineDistance, sourceRightCenter.y,
|
||||||
|
sourceRightCenter.x + lineCurve + sourceIndex * config.lineDistance, sourceRightCenter.y - lineCurve,
|
||||||
|
);
|
||||||
|
path.lineTo(targetLeftCenter.x - lineCurve + sourceIndex * config.lineDistance, targetLeftCenter.y + lineCurve);
|
||||||
|
path.quadraticCurveTo(
|
||||||
|
targetLeftCenter.x - lineCurve + sourceIndex * config.lineDistance, targetLeftCenter.y,
|
||||||
|
targetLeftCenter.x, targetLeftCenter.y,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
svg .pe-node.unhovered {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg .pe-node .pe-box {
|
||||||
|
fill: white;
|
||||||
|
stroke: #4C566A;
|
||||||
|
stroke-width: 1px;
|
||||||
|
filter: drop-shadow(0 1.5px 1.5px rgba(0, 0, 0, .15));
|
||||||
|
}
|
||||||
|
|
||||||
|
svg .pe-node.selected .pe-box {
|
||||||
|
stroke: #88C0D0;
|
||||||
|
stroke-width: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg .pe-node text {
|
||||||
|
text-anchor: middle;
|
||||||
|
dominant-baseline: middle;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg .pe-node:hover .pe-box {
|
||||||
|
filter: drop-shadow(0 0 0.1rem #333);
|
||||||
|
}
|
||||||
|
|
||||||
|
svg .pe-node:hover .pe-box,
|
||||||
|
svg .pe-node:hover text {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg .pe-node:hover .add use {
|
||||||
|
/* fill: red !important; */
|
||||||
|
}
|
||||||
|
|
||||||
|
svg .pe-links path {
|
||||||
|
stroke: black;
|
||||||
|
fill: none;
|
||||||
|
stroke-width: 2;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
filter: drop-shadow(0 2px 2px rgba(0, 0, 0, .15));
|
||||||
|
}
|
||||||
|
|
||||||
|
svg .pe-activelinks path {
|
||||||
|
stroke: none;
|
||||||
|
fill: none;
|
||||||
|
stroke-width: 2;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg .pe-activelinks path.selected,
|
||||||
|
svg .pe-activelinks path.hovered {
|
||||||
|
stroke-width: 3;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
stroke: #88C0D0;
|
||||||
|
filter: drop-shadow(0 0 0.1rem #88C0D0);
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="fill-height">
|
<div class="network-graph fill-height">
|
||||||
<v-card
|
<v-card
|
||||||
v-if="selected !== undefined"
|
v-if="selected !== undefined"
|
||||||
class="mt-3 ml-3 px-0"
|
class="mt-3 ml-3 px-0"
|
||||||
@@ -184,33 +184,33 @@ export default Vue.extend({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.node,
|
.network-graph .node,
|
||||||
.node.selected {
|
.network-graph .node.selected {
|
||||||
stroke: #388E3C !important;
|
stroke: #388E3C !important;
|
||||||
}
|
}
|
||||||
.node.event {
|
.network-graph .node.event {
|
||||||
stroke: #D32F2F !important;
|
stroke: #D32F2F !important;
|
||||||
}
|
}
|
||||||
.node.center {
|
.network-graph .node.center {
|
||||||
stroke: #FFEB3B !important;
|
stroke: #FFEB3B !important;
|
||||||
fill: #FFEB3B !important;
|
fill: #FFEB3B !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme--dark .node-label,
|
.theme--dark .network-graph .node-label,
|
||||||
.theme--dark .node-label.event {
|
.theme--dark .network-graph .node-label.event {
|
||||||
fill: #ffffff !important;
|
fill: #ffffff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.node-label,
|
.network-graph .node-label,
|
||||||
.node-label.event {
|
.network-graph .node-label.event {
|
||||||
fill: #000000 !important;
|
fill: #000000 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link {
|
.network-graph .link {
|
||||||
stroke: #424242 !important;
|
stroke: #424242 !important;
|
||||||
}
|
}
|
||||||
.link.selected,
|
.network-graph .link.selected,
|
||||||
.link:hover,.node:hover{
|
.network-graph .link:hover,.node:hover{
|
||||||
stroke: #FFEB3B !important;
|
stroke: #FFEB3B !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -19,25 +19,35 @@
|
|||||||
<v-alert v-else-if="error" color="warning">
|
<v-alert v-else-if="error" color="warning">
|
||||||
{{ error }}
|
{{ error }}
|
||||||
</v-alert>
|
</v-alert>
|
||||||
<div v-else class="px-4 overflow-scroll">
|
|
||||||
<vue-pipeline
|
|
||||||
v-if="pipelineData"
|
|
||||||
ref="pipeline"
|
|
||||||
:x="50"
|
|
||||||
:y="55"
|
|
||||||
:data="pipelineData"
|
|
||||||
:showArrow="true"
|
|
||||||
:ystep="70"
|
|
||||||
:xstep="100"
|
|
||||||
lineStyle="default"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<v-subheader class="pl-0 py-0" style="height: 20px; font-size: 12px">
|
|
||||||
Playbook
|
|
||||||
</v-subheader>
|
|
||||||
<div class="flex-grow-1 flex-shrink-1 overflow-scroll">
|
<div class="flex-grow-1 flex-shrink-1 overflow-scroll">
|
||||||
<Editor v-model="playbook.yaml" @input="updatePipeline" lang="yaml" :readonly="readonly"></Editor>
|
<v-tabs
|
||||||
|
v-model="tab"
|
||||||
|
background-color="transparent"
|
||||||
|
>
|
||||||
|
<v-tab>Graph (experimental)</v-tab>
|
||||||
|
<v-tab>YAML</v-tab>
|
||||||
|
</v-tabs>
|
||||||
|
<v-tabs-items v-model="tab" style="background: transparent">
|
||||||
|
<v-tab-item>
|
||||||
|
<v-text-field
|
||||||
|
v-model="playbook.name"
|
||||||
|
label="Name"
|
||||||
|
outlined
|
||||||
|
dense
|
||||||
|
:readonly="readonly"
|
||||||
|
class="mt-4"
|
||||||
|
/>
|
||||||
|
<PlaybookEditor
|
||||||
|
v-if="playbookJSON"
|
||||||
|
v-model="playbookJSON" />
|
||||||
|
</v-tab-item>
|
||||||
|
<v-tab-item>
|
||||||
|
<v-card class="py-2">
|
||||||
|
<Editor v-model="playbookYAML" lang="yaml" :readonly="readonly"></Editor>
|
||||||
|
</v-card>
|
||||||
|
</v-tab-item>
|
||||||
|
</v-tabs-items>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<v-row v-if="!readonly" class="px-3 my-6 flex-grow-0 flex-shrink-0">
|
<v-row v-if="!readonly" class="px-3 my-6 flex-grow-0 flex-shrink-0">
|
||||||
@@ -62,6 +72,7 @@ import Editor from "../components/Editor.vue";
|
|||||||
import {alg, Graph} from "graphlib";
|
import {alg, Graph} from "graphlib";
|
||||||
import yaml from 'yaml';
|
import yaml from 'yaml';
|
||||||
import Ajv from "ajv";
|
import Ajv from "ajv";
|
||||||
|
import PlaybookEditor from "@/components/playbookeditor/PlaybookEditor.vue";
|
||||||
|
|
||||||
const playbookSchema = {
|
const playbookSchema = {
|
||||||
type: "object",
|
type: "object",
|
||||||
@@ -100,8 +111,10 @@ interface State {
|
|||||||
playbook?: PlaybookTemplate;
|
playbook?: PlaybookTemplate;
|
||||||
g: Record<string, any>;
|
g: Record<string, any>;
|
||||||
selected: any;
|
selected: any;
|
||||||
pipelineData: any;
|
|
||||||
error: string;
|
error: string;
|
||||||
|
tab: number;
|
||||||
|
playbookYAML: string;
|
||||||
|
playbookJSON: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TaskWithID {
|
interface TaskWithID {
|
||||||
@@ -140,17 +153,26 @@ const inityaml = "name: VirusTotal hash check\n" +
|
|||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
name: "Playbook",
|
name: "Playbook",
|
||||||
components: { Editor },
|
components: { Editor, PlaybookEditor },
|
||||||
data: (): State => ({
|
data: (): State => ({
|
||||||
playbook: undefined,
|
playbook: undefined,
|
||||||
g: {},
|
g: {},
|
||||||
selected: undefined,
|
selected: undefined,
|
||||||
pipelineData: undefined,
|
|
||||||
error: "",
|
error: "",
|
||||||
|
tab: 1,
|
||||||
|
playbookJSON: undefined,
|
||||||
|
playbookYAML: inityaml
|
||||||
}),
|
}),
|
||||||
watch: {
|
watch: {
|
||||||
'$route': function () {
|
'$route': function () {
|
||||||
this.loadPlaybook();
|
this.loadPlaybook();
|
||||||
|
},
|
||||||
|
tab: function (value) {
|
||||||
|
if (value === 0) {
|
||||||
|
this.playbookJSON = yaml.parse(this.playbookYAML);
|
||||||
|
} else {
|
||||||
|
this.playbookYAML = yaml.stringify(this.playbookJSON);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -198,77 +220,26 @@ export default Vue.extend({
|
|||||||
}
|
}
|
||||||
return tasks;
|
return tasks;
|
||||||
},
|
},
|
||||||
updatePipeline: function () {
|
|
||||||
if (this.playbook) {
|
|
||||||
this.pipeline(this.playbook.yaml);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
pipeline: function(playbookYAML: string) {
|
|
||||||
try {
|
|
||||||
let playbook = yaml.parse(playbookYAML);
|
|
||||||
|
|
||||||
this.error = "";
|
|
||||||
|
|
||||||
let g = new Graph();
|
|
||||||
|
|
||||||
for (const stepKey in playbook.tasks) {
|
|
||||||
g.setNode(stepKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.lodash.forEach(playbook.tasks, (task: Task, stepKey: string) => {
|
|
||||||
if ("next" in task) {
|
|
||||||
this.lodash.forEach(task.next, (condition, nextKey) => {
|
|
||||||
g.setEdge(stepKey, nextKey);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let tasks = this.tasks(g, playbook);
|
|
||||||
let elements = [] as Array<any>;
|
|
||||||
this.lodash.forEach(tasks, task => {
|
|
||||||
elements.push({
|
|
||||||
id: task.id,
|
|
||||||
name: task.task.name,
|
|
||||||
next: [],
|
|
||||||
status: "unknown"
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.lodash.forEach(tasks, (task: TaskWithID) => {
|
|
||||||
if ("next" in task.task) {
|
|
||||||
this.lodash.forEach(task.task.next, (condition, nextKey) => {
|
|
||||||
let nextID = this.lodash.findIndex(elements, ["id", nextKey]);
|
|
||||||
let stepID = this.lodash.findIndex(elements, ["id", task.id]);
|
|
||||||
if (nextID !== -1) {
|
|
||||||
// TODO: invalid schema
|
|
||||||
elements[stepID].next.push({index: nextID});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.pipelineData = undefined;
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.pipelineData = this.lodash.values(elements);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
catch (e: unknown) {
|
|
||||||
console.log(e);
|
|
||||||
this.error = this.lodash.toString(e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
save() {
|
save() {
|
||||||
if (this.playbook === undefined) {
|
if (this.playbook === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let playbook = this.playbook;
|
||||||
|
if (this.tab === 0) {
|
||||||
|
let jsonData = this.playbookJSON;
|
||||||
|
jsonData["name"] = playbook.name;
|
||||||
|
playbook.yaml = yaml.stringify(jsonData);
|
||||||
|
} else {
|
||||||
|
playbook.yaml = this.playbookYAML;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.$route.params.id == 'new') {
|
if (this.$route.params.id == 'new') {
|
||||||
let playbook = this.playbook;
|
|
||||||
// playbook.id = kebabCase(playbook.name);
|
// playbook.id = kebabCase(playbook.name);
|
||||||
API.createPlaybook(playbook).then(() => {
|
API.createPlaybook(playbook).then(() => {
|
||||||
this.$store.dispatch("alertSuccess", { name: "Playbook created" });
|
this.$store.dispatch("alertSuccess", { name: "Playbook created" });
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
API.updatePlaybook(this.$route.params.id, this.playbook).then(() => {
|
API.updatePlaybook(this.$route.params.id, playbook).then(() => {
|
||||||
this.$store.dispatch("alertSuccess", { name: "Playbook saved" });
|
this.$store.dispatch("alertSuccess", { name: "Playbook saved" });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -279,9 +250,13 @@ export default Vue.extend({
|
|||||||
}
|
}
|
||||||
if (this.$route.params.id == 'new') {
|
if (this.$route.params.id == 'new') {
|
||||||
this.playbook = { name: "MyPlaybook", yaml: inityaml }
|
this.playbook = { name: "MyPlaybook", yaml: inityaml }
|
||||||
|
this.playbookJSON = yaml.parse(this.playbook.yaml);
|
||||||
|
this.playbookYAML = this.playbook.yaml;
|
||||||
} else {
|
} else {
|
||||||
API.getPlaybook(this.$route.params.id).then((response) => {
|
API.getPlaybook(this.$route.params.id).then((response) => {
|
||||||
this.playbook = response.data;
|
this.playbook = response.data;
|
||||||
|
this.playbookJSON = yaml.parse(this.playbook.yaml);
|
||||||
|
this.playbookYAML = this.playbook.yaml;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -290,7 +265,7 @@ export default Vue.extend({
|
|||||||
return this.lodash.includes(this.$store.state.settings.roles, s);
|
return this.lodash.includes(this.$store.state.settings.roles, s);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.loadPlaybook();
|
this.loadPlaybook();
|
||||||
|
|||||||
@@ -295,19 +295,13 @@
|
|||||||
<v-icon small>mdi-close-circle</v-icon>
|
<v-icon small>mdi-close-circle</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-card-subtitle>
|
</v-card-subtitle>
|
||||||
<div style="overflow-x: scroll">
|
<div style="overflow-x: scroll; text-align: center">
|
||||||
<vue-pipeline
|
<PlaybookGraph
|
||||||
v-if="showPipelines"
|
v-if="playbook"
|
||||||
ref="pipeline"
|
:playbook="playbook"
|
||||||
:x="50"
|
horizontal
|
||||||
:y="55"
|
:scale="0.3"
|
||||||
:data="pipeline(playbook)"
|
style="margin: 0 auto"
|
||||||
:showArrow="true"
|
|
||||||
:ystep="70"
|
|
||||||
:xstep="90"
|
|
||||||
lineStyle="default"
|
|
||||||
@select="select"
|
|
||||||
class="mx-4"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<v-list dense color="cards" class="tasks py-0">
|
<v-list dense color="cards" class="tasks py-0">
|
||||||
@@ -319,7 +313,6 @@
|
|||||||
link
|
link
|
||||||
@click="selectTask(taskwithid, playbookid)"
|
@click="selectTask(taskwithid, playbookid)"
|
||||||
>
|
>
|
||||||
<!--template v-slot:activator-->
|
|
||||||
<v-list-item-icon>
|
<v-list-item-icon>
|
||||||
<v-icon
|
<v-icon
|
||||||
:class="{
|
:class="{
|
||||||
@@ -863,6 +856,8 @@ import {DateTime} from "luxon";
|
|||||||
import VueMarkdown from "vue-markdown";
|
import VueMarkdown from "vue-markdown";
|
||||||
import JSONHTML from "../components/JSONHTML.vue";
|
import JSONHTML from "../components/JSONHTML.vue";
|
||||||
import TicketNew from "@/views/TicketNew.vue";
|
import TicketNew from "@/views/TicketNew.vue";
|
||||||
|
import yaml from "yaml";
|
||||||
|
import PlaybookGraph from "@/components/playbookeditor/PlaybookGraph.vue";
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
valid: boolean;
|
valid: boolean;
|
||||||
@@ -929,6 +924,7 @@ interface TaskWithID {
|
|||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
name: "Ticket",
|
name: "Ticket",
|
||||||
components: {
|
components: {
|
||||||
|
PlaybookGraph,
|
||||||
TicketNew,
|
TicketNew,
|
||||||
Dashboard,
|
Dashboard,
|
||||||
ArtifactSnippet,
|
ArtifactSnippet,
|
||||||
@@ -999,6 +995,9 @@ export default Vue.extend({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
yaml() {
|
||||||
|
return yaml
|
||||||
|
},
|
||||||
schema: function() {
|
schema: function() {
|
||||||
if (this.ticket !== undefined && this.ticket.schema !== undefined) {
|
if (this.ticket !== undefined && this.ticket.schema !== undefined) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
|||||||
309
ui/yarn.lock
309
ui/yarn.lock
@@ -1002,6 +1002,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
|
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
|
||||||
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
|
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
|
||||||
|
|
||||||
|
"@crinkles/digl@^2.0.2":
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@crinkles/digl/-/digl-2.0.2.tgz#add3574124c29dbb3e262624d412d64e8b5d1b54"
|
||||||
|
integrity sha512-lwdBaMIcZrs0PGHsbua0wjbSgzWtYvIzBhaS7Xr4UZm+QAh727IQqoOVk12EYT41t6XfdZVdkwYN6aeYA3q4jw==
|
||||||
|
|
||||||
"@cypress/request@^2.88.10":
|
"@cypress/request@^2.88.10":
|
||||||
version "2.88.10"
|
version "2.88.10"
|
||||||
resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.10.tgz#b66d76b07f860d3a4b8d7a0604d020c662752cce"
|
resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.10.tgz#b66d76b07f860d3a4b8d7a0604d020c662752cce"
|
||||||
@@ -2851,6 +2856,13 @@ alphanum-sort@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
|
resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
|
||||||
integrity sha512-0FcBfdcmaumGPQ0qPn7Q5qTgz/ooXgIyp1rf8ik5bGX8mpE2YHjC0P/eyQvxu1GURYQgq9ozf2mteQ5ZD9YiyQ==
|
integrity sha512-0FcBfdcmaumGPQ0qPn7Q5qTgz/ooXgIyp1rf8ik5bGX8mpE2YHjC0P/eyQvxu1GURYQgq9ozf2mteQ5ZD9YiyQ==
|
||||||
|
|
||||||
|
amator@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/amator/-/amator-1.1.0.tgz#08c6b60bc93aec2b61bbfc0c4d677d30323cc0f1"
|
||||||
|
integrity sha512-V5+aH8pe+Z3u/UG3L3pG3BaFQGXAyXHVQDroRwjPHdh08bcUEchAVsU1MCuJSCaU5o60wTK6KaE6te5memzgYw==
|
||||||
|
dependencies:
|
||||||
|
bezier-easing "^2.0.3"
|
||||||
|
|
||||||
ansi-colors@^3.0.0:
|
ansi-colors@^3.0.0:
|
||||||
version "3.2.4"
|
version "3.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf"
|
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf"
|
||||||
@@ -3444,6 +3456,11 @@ bcrypt-pbkdf@^1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tweetnacl "^0.14.3"
|
tweetnacl "^0.14.3"
|
||||||
|
|
||||||
|
bezier-easing@^2.0.3:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/bezier-easing/-/bezier-easing-2.1.0.tgz#c04dfe8b926d6ecaca1813d69ff179b7c2025d86"
|
||||||
|
integrity sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig==
|
||||||
|
|
||||||
bfj@^6.1.1:
|
bfj@^6.1.1:
|
||||||
version "6.1.2"
|
version "6.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/bfj/-/bfj-6.1.2.tgz#325c861a822bcb358a41c78a33b8e6e2086dde7f"
|
resolved "https://registry.yarnpkg.com/bfj/-/bfj-6.1.2.tgz#325c861a822bcb358a41c78a33b8e6e2086dde7f"
|
||||||
@@ -4267,6 +4284,11 @@ commander@2.17.x:
|
|||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
|
||||||
integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==
|
integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==
|
||||||
|
|
||||||
|
commander@7:
|
||||||
|
version "7.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
|
||||||
|
integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
|
||||||
|
|
||||||
commander@^2.12.1, commander@^2.18.0, commander@^2.19.0, commander@^2.20.0, commander@^2.9.0:
|
commander@^2.12.1, commander@^2.18.0, commander@^2.19.0, commander@^2.20.0, commander@^2.9.0:
|
||||||
version "2.20.3"
|
version "2.20.3"
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||||
@@ -4879,16 +4901,108 @@ cypress@11.2.0:
|
|||||||
untildify "^4.0.0"
|
untildify "^4.0.0"
|
||||||
yauzl "^2.10.0"
|
yauzl "^2.10.0"
|
||||||
|
|
||||||
|
"d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3, d3-array@^3.2.0:
|
||||||
|
version "3.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.1.tgz#39331ea706f5709417d31bbb6ec152e0328b39b3"
|
||||||
|
integrity sha512-gUY/qeHq/yNqqoCKNq4vtpFLdoCdvyNpWoC/KNjhGbhDuQpAM9sIQQKkXSNpXa9h5KySs/gzm7R88WkUutgwWQ==
|
||||||
|
dependencies:
|
||||||
|
internmap "1 - 2"
|
||||||
|
|
||||||
|
d3-axis@3:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-3.0.0.tgz#c42a4a13e8131d637b745fc2973824cfeaf93322"
|
||||||
|
integrity sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==
|
||||||
|
|
||||||
|
d3-brush@3:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-3.0.0.tgz#6f767c4ed8dcb79de7ede3e1c0f89e63ef64d31c"
|
||||||
|
integrity sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==
|
||||||
|
dependencies:
|
||||||
|
d3-dispatch "1 - 3"
|
||||||
|
d3-drag "2 - 3"
|
||||||
|
d3-interpolate "1 - 3"
|
||||||
|
d3-selection "3"
|
||||||
|
d3-transition "3"
|
||||||
|
|
||||||
|
d3-chord@3:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-chord/-/d3-chord-3.0.1.tgz#d156d61f485fce8327e6abf339cb41d8cbba6966"
|
||||||
|
integrity sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==
|
||||||
|
dependencies:
|
||||||
|
d3-path "1 - 3"
|
||||||
|
|
||||||
d3-collection@1:
|
d3-collection@1:
|
||||||
version "1.0.7"
|
version "1.0.7"
|
||||||
resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.7.tgz#349bd2aa9977db071091c13144d5e4f16b5b310e"
|
resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.7.tgz#349bd2aa9977db071091c13144d5e4f16b5b310e"
|
||||||
integrity sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==
|
integrity sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==
|
||||||
|
|
||||||
|
"d3-color@1 - 3", d3-color@3:
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2"
|
||||||
|
integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==
|
||||||
|
|
||||||
|
d3-contour@4:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-4.0.0.tgz#5a1337c6da0d528479acdb5db54bc81a0ff2ec6b"
|
||||||
|
integrity sha512-7aQo0QHUTu/Ko3cP9YK9yUTxtoDEiDGwnBHyLxG5M4vqlBkO/uixMRele3nfsfj6UXOcuReVpVXzAboGraYIJw==
|
||||||
|
dependencies:
|
||||||
|
d3-array "^3.2.0"
|
||||||
|
|
||||||
|
d3-delaunay@6:
|
||||||
|
version "6.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-6.0.2.tgz#7fd3717ad0eade2fc9939f4260acfb503f984e92"
|
||||||
|
integrity sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ==
|
||||||
|
dependencies:
|
||||||
|
delaunator "5"
|
||||||
|
|
||||||
d3-dispatch@1:
|
d3-dispatch@1:
|
||||||
version "1.0.6"
|
version "1.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-1.0.6.tgz#00d37bcee4dd8cd97729dd893a0ac29caaba5d58"
|
resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-1.0.6.tgz#00d37bcee4dd8cd97729dd893a0ac29caaba5d58"
|
||||||
integrity sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==
|
integrity sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==
|
||||||
|
|
||||||
|
"d3-dispatch@1 - 3", d3-dispatch@3:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e"
|
||||||
|
integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==
|
||||||
|
|
||||||
|
"d3-drag@2 - 3", d3-drag@3:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba"
|
||||||
|
integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==
|
||||||
|
dependencies:
|
||||||
|
d3-dispatch "1 - 3"
|
||||||
|
d3-selection "3"
|
||||||
|
|
||||||
|
"d3-dsv@1 - 3", d3-dsv@3:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-3.0.1.tgz#c63af978f4d6a0d084a52a673922be2160789b73"
|
||||||
|
integrity sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==
|
||||||
|
dependencies:
|
||||||
|
commander "7"
|
||||||
|
iconv-lite "0.6"
|
||||||
|
rw "1"
|
||||||
|
|
||||||
|
"d3-ease@1 - 3", d3-ease@3:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4"
|
||||||
|
integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==
|
||||||
|
|
||||||
|
d3-fetch@3:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-fetch/-/d3-fetch-3.0.1.tgz#83141bff9856a0edb5e38de89cdcfe63d0a60a22"
|
||||||
|
integrity sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==
|
||||||
|
dependencies:
|
||||||
|
d3-dsv "1 - 3"
|
||||||
|
|
||||||
|
d3-force@3:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-3.0.0.tgz#3e2ba1a61e70888fe3d9194e30d6d14eece155c4"
|
||||||
|
integrity sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==
|
||||||
|
dependencies:
|
||||||
|
d3-dispatch "1 - 3"
|
||||||
|
d3-quadtree "1 - 3"
|
||||||
|
d3-timer "1 - 3"
|
||||||
|
|
||||||
d3-force@^1.0.6:
|
d3-force@^1.0.6:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-1.2.1.tgz#fd29a5d1ff181c9e7f0669e4bd72bdb0e914ec0b"
|
resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-1.2.1.tgz#fd29a5d1ff181c9e7f0669e4bd72bdb0e914ec0b"
|
||||||
@@ -4899,16 +5013,168 @@ d3-force@^1.0.6:
|
|||||||
d3-quadtree "1"
|
d3-quadtree "1"
|
||||||
d3-timer "1"
|
d3-timer "1"
|
||||||
|
|
||||||
|
"d3-format@1 - 3", d3-format@3:
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641"
|
||||||
|
integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==
|
||||||
|
|
||||||
|
d3-geo@3:
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-3.1.0.tgz#74fd54e1f4cebd5185ac2039217a98d39b0a4c0e"
|
||||||
|
integrity sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==
|
||||||
|
dependencies:
|
||||||
|
d3-array "2.5.0 - 3"
|
||||||
|
|
||||||
|
d3-hierarchy@3:
|
||||||
|
version "3.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz#b01cd42c1eed3d46db77a5966cf726f8c09160c6"
|
||||||
|
integrity sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==
|
||||||
|
|
||||||
|
"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@3:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d"
|
||||||
|
integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==
|
||||||
|
dependencies:
|
||||||
|
d3-color "1 - 3"
|
||||||
|
|
||||||
|
"d3-path@1 - 3", d3-path@3, d3-path@^3.1.0:
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526"
|
||||||
|
integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==
|
||||||
|
|
||||||
|
d3-polygon@3:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-polygon/-/d3-polygon-3.0.1.tgz#0b45d3dd1c48a29c8e057e6135693ec80bf16398"
|
||||||
|
integrity sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==
|
||||||
|
|
||||||
d3-quadtree@1:
|
d3-quadtree@1:
|
||||||
version "1.0.7"
|
version "1.0.7"
|
||||||
resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-1.0.7.tgz#ca8b84df7bb53763fe3c2f24bd435137f4e53135"
|
resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-1.0.7.tgz#ca8b84df7bb53763fe3c2f24bd435137f4e53135"
|
||||||
integrity sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==
|
integrity sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==
|
||||||
|
|
||||||
|
"d3-quadtree@1 - 3", d3-quadtree@3:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz#6dca3e8be2b393c9a9d514dabbd80a92deef1a4f"
|
||||||
|
integrity sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==
|
||||||
|
|
||||||
|
d3-random@3:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-3.0.1.tgz#d4926378d333d9c0bfd1e6fa0194d30aebaa20f4"
|
||||||
|
integrity sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==
|
||||||
|
|
||||||
|
d3-scale-chromatic@3:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz#15b4ceb8ca2bb0dcb6d1a641ee03d59c3b62376a"
|
||||||
|
integrity sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==
|
||||||
|
dependencies:
|
||||||
|
d3-color "1 - 3"
|
||||||
|
d3-interpolate "1 - 3"
|
||||||
|
|
||||||
|
d3-scale@4:
|
||||||
|
version "4.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396"
|
||||||
|
integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==
|
||||||
|
dependencies:
|
||||||
|
d3-array "2.10.0 - 3"
|
||||||
|
d3-format "1 - 3"
|
||||||
|
d3-interpolate "1.2.0 - 3"
|
||||||
|
d3-time "2.1.1 - 3"
|
||||||
|
d3-time-format "2 - 4"
|
||||||
|
|
||||||
|
"d3-selection@2 - 3", d3-selection@3:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31"
|
||||||
|
integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==
|
||||||
|
|
||||||
|
d3-shape@3:
|
||||||
|
version "3.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5"
|
||||||
|
integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==
|
||||||
|
dependencies:
|
||||||
|
d3-path "^3.1.0"
|
||||||
|
|
||||||
|
"d3-time-format@2 - 4", d3-time-format@4:
|
||||||
|
version "4.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a"
|
||||||
|
integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==
|
||||||
|
dependencies:
|
||||||
|
d3-time "1 - 3"
|
||||||
|
|
||||||
|
"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@3:
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7"
|
||||||
|
integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==
|
||||||
|
dependencies:
|
||||||
|
d3-array "2 - 3"
|
||||||
|
|
||||||
d3-timer@1:
|
d3-timer@1:
|
||||||
version "1.0.10"
|
version "1.0.10"
|
||||||
resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.10.tgz#dfe76b8a91748831b13b6d9c793ffbd508dd9de5"
|
resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.10.tgz#dfe76b8a91748831b13b6d9c793ffbd508dd9de5"
|
||||||
integrity sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==
|
integrity sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==
|
||||||
|
|
||||||
|
"d3-timer@1 - 3", d3-timer@3:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0"
|
||||||
|
integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==
|
||||||
|
|
||||||
|
"d3-transition@2 - 3", d3-transition@3:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f"
|
||||||
|
integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==
|
||||||
|
dependencies:
|
||||||
|
d3-color "1 - 3"
|
||||||
|
d3-dispatch "1 - 3"
|
||||||
|
d3-ease "1 - 3"
|
||||||
|
d3-interpolate "1 - 3"
|
||||||
|
d3-timer "1 - 3"
|
||||||
|
|
||||||
|
d3-zoom@3:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3"
|
||||||
|
integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==
|
||||||
|
dependencies:
|
||||||
|
d3-dispatch "1 - 3"
|
||||||
|
d3-drag "2 - 3"
|
||||||
|
d3-interpolate "1 - 3"
|
||||||
|
d3-selection "2 - 3"
|
||||||
|
d3-transition "2 - 3"
|
||||||
|
|
||||||
|
d3@^7.8.0:
|
||||||
|
version "7.8.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3/-/d3-7.8.0.tgz#c9441f0ea9266b1003a97c2ffd53e79e9e14b1fc"
|
||||||
|
integrity sha512-a5rNemRadWkEfqnY5NsD4RdCP9vn8EIJ4I5Rl14U0uKH1SXqcNmk/h9aGaAF1O98lz6L9M0IeUcuPa9GUYbI5A==
|
||||||
|
dependencies:
|
||||||
|
d3-array "3"
|
||||||
|
d3-axis "3"
|
||||||
|
d3-brush "3"
|
||||||
|
d3-chord "3"
|
||||||
|
d3-color "3"
|
||||||
|
d3-contour "4"
|
||||||
|
d3-delaunay "6"
|
||||||
|
d3-dispatch "3"
|
||||||
|
d3-drag "3"
|
||||||
|
d3-dsv "3"
|
||||||
|
d3-ease "3"
|
||||||
|
d3-fetch "3"
|
||||||
|
d3-force "3"
|
||||||
|
d3-format "3"
|
||||||
|
d3-geo "3"
|
||||||
|
d3-hierarchy "3"
|
||||||
|
d3-interpolate "3"
|
||||||
|
d3-path "3"
|
||||||
|
d3-polygon "3"
|
||||||
|
d3-quadtree "3"
|
||||||
|
d3-random "3"
|
||||||
|
d3-scale "4"
|
||||||
|
d3-scale-chromatic "3"
|
||||||
|
d3-selection "3"
|
||||||
|
d3-shape "3"
|
||||||
|
d3-time "3"
|
||||||
|
d3-time-format "4"
|
||||||
|
d3-timer "3"
|
||||||
|
d3-transition "3"
|
||||||
|
d3-zoom "3"
|
||||||
|
|
||||||
dashdash@^1.12.0:
|
dashdash@^1.12.0:
|
||||||
version "1.14.1"
|
version "1.14.1"
|
||||||
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
|
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
|
||||||
@@ -5088,6 +5354,13 @@ del@^4.1.1:
|
|||||||
pify "^4.0.1"
|
pify "^4.0.1"
|
||||||
rimraf "^2.6.3"
|
rimraf "^2.6.3"
|
||||||
|
|
||||||
|
delaunator@5:
|
||||||
|
version "5.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-5.0.0.tgz#60f052b28bd91c9b4566850ebf7756efe821d81b"
|
||||||
|
integrity sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==
|
||||||
|
dependencies:
|
||||||
|
robust-predicates "^3.0.0"
|
||||||
|
|
||||||
delayed-stream@~1.0.0:
|
delayed-stream@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||||
@@ -7041,7 +7314,7 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24:
|
|||||||
dependencies:
|
dependencies:
|
||||||
safer-buffer ">= 2.1.2 < 3"
|
safer-buffer ">= 2.1.2 < 3"
|
||||||
|
|
||||||
iconv-lite@^0.6.3:
|
iconv-lite@0.6, iconv-lite@^0.6.3:
|
||||||
version "0.6.3"
|
version "0.6.3"
|
||||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
|
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
|
||||||
integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
|
integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
|
||||||
@@ -7222,6 +7495,11 @@ internal-slot@^1.0.3:
|
|||||||
has "^1.0.3"
|
has "^1.0.3"
|
||||||
side-channel "^1.0.4"
|
side-channel "^1.0.4"
|
||||||
|
|
||||||
|
"internmap@1 - 2":
|
||||||
|
version "2.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009"
|
||||||
|
integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==
|
||||||
|
|
||||||
interpret@^1.0.0:
|
interpret@^1.0.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
|
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
|
||||||
@@ -9424,6 +9702,11 @@ neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1, neo-async@^2.6.2:
|
|||||||
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
|
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
|
||||||
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
|
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
|
||||||
|
|
||||||
|
ngraph.events@^1.2.2:
|
||||||
|
version "1.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/ngraph.events/-/ngraph.events-1.2.2.tgz#3ceb92d676a04a4e7ce60a09fa8e17a4f0346d7f"
|
||||||
|
integrity sha512-JsUbEOzANskax+WSYiAPETemLWYXmixuPAlmZmhIbIj6FH/WDgEGCGnRwUQBK0GjOnVm8Ui+e5IJ+5VZ4e32eQ==
|
||||||
|
|
||||||
nice-try@^1.0.4:
|
nice-try@^1.0.4:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
|
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
|
||||||
@@ -9895,6 +10178,15 @@ pako@~1.0.5:
|
|||||||
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
|
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
|
||||||
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
|
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
|
||||||
|
|
||||||
|
panzoom@^9.4.3:
|
||||||
|
version "9.4.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/panzoom/-/panzoom-9.4.3.tgz#195c4031bb643f2e6c42f1de0ca87cc10e224042"
|
||||||
|
integrity sha512-xaxCpElcRbQsUtIdwlrZA90P90+BHip4Vda2BC8MEb4tkI05PmR6cKECdqUCZ85ZvBHjpI9htJrZBxV5Gp/q/w==
|
||||||
|
dependencies:
|
||||||
|
amator "^1.1.0"
|
||||||
|
ngraph.events "^1.2.2"
|
||||||
|
wheel "^1.0.0"
|
||||||
|
|
||||||
parallel-transform@^1.1.0:
|
parallel-transform@^1.1.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc"
|
resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc"
|
||||||
@@ -11364,6 +11656,11 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
|
|||||||
hash-base "^3.0.0"
|
hash-base "^3.0.0"
|
||||||
inherits "^2.0.1"
|
inherits "^2.0.1"
|
||||||
|
|
||||||
|
robust-predicates@^3.0.0:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.1.tgz#ecde075044f7f30118682bd9fb3f123109577f9a"
|
||||||
|
integrity sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==
|
||||||
|
|
||||||
rsvp@^4.8.4:
|
rsvp@^4.8.4:
|
||||||
version "4.8.5"
|
version "4.8.5"
|
||||||
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
|
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
|
||||||
@@ -11388,6 +11685,11 @@ run-queue@^1.0.0, run-queue@^1.0.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
aproba "^1.1.1"
|
aproba "^1.1.1"
|
||||||
|
|
||||||
|
rw@1:
|
||||||
|
version "1.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4"
|
||||||
|
integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==
|
||||||
|
|
||||||
rxjs@^6.6.0:
|
rxjs@^6.6.0:
|
||||||
version "6.6.7"
|
version "6.6.7"
|
||||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9"
|
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9"
|
||||||
@@ -13540,6 +13842,11 @@ whatwg-url@^7.0.0:
|
|||||||
tr46 "^1.0.1"
|
tr46 "^1.0.1"
|
||||||
webidl-conversions "^4.0.2"
|
webidl-conversions "^4.0.2"
|
||||||
|
|
||||||
|
wheel@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/wheel/-/wheel-1.0.0.tgz#6cf46e06a854181adb8649228077f8b0d5c574ce"
|
||||||
|
integrity sha512-XiCMHibOiqalCQ+BaNSwRoZ9FDTAvOsXxGHXChBugewDj7HC8VBIER71dEOiRH1fSdLbRCQzngKTSiZ06ZQzeA==
|
||||||
|
|
||||||
which-boxed-primitive@^1.0.2:
|
which-boxed-primitive@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
|
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
|
||||||
|
|||||||
Reference in New Issue
Block a user