diff --git a/package-lock.json b/package-lock.json index 97a3f82..911aafb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,8 @@ "vue-eslint-parser": "^10.2.0", "vue-prism-editor": "^2.0.0-alpha.2", "vue-router": "^4.0.12", - "vue-scrollto": "^2.20.0" + "vue-scrollto": "^2.20.0", + "yaml": "^2.8.3" }, "devDependencies": { "@babel/core": "^7.17.12", @@ -74,6 +75,7 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -2150,6 +2152,7 @@ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz", "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==", "license": "MIT", + "peer": true, "dependencies": { "@fortawesome/fontawesome-common-types": "6.7.2" }, @@ -2741,17 +2744,6 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "license": "MIT" }, - "node_modules/@types/node": { - "version": "24.6.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.6.2.tgz", - "integrity": "sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "undici-types": "~7.13.0" - } - }, "node_modules/@vitejs/plugin-vue": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.1.tgz", @@ -2915,6 +2907,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3109,6 +3102,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", @@ -3323,6 +3317,7 @@ "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", "license": "ISC", + "peer": true, "dependencies": { "d3-dispatch": "1 - 3", "d3-quadtree": "1 - 3", @@ -3614,6 +3609,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz", "integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==", "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -5291,6 +5287,7 @@ "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz", "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==", "license": "MIT", + "peer": true, "dependencies": { "@vue/devtools-api": "^6.6.3", "vue-demi": "^0.14.10" @@ -5844,6 +5841,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -5881,14 +5879,6 @@ "node": ">= 0.8.0" } }, - "node_modules/undici-types": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.13.0.tgz", - "integrity": "sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ==", - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", @@ -6014,6 +6004,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.9.tgz", "integrity": "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -6105,6 +6096,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -6126,6 +6118,7 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.22.tgz", "integrity": "sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==", "license": "MIT", + "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.22", "@vue/compiler-sfc": "3.5.22", @@ -6378,6 +6371,22 @@ "dev": true, "license": "ISC" }, + "node_modules/yaml": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "license": "ISC", + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 9b9dd44..82b4a95 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,8 @@ "vue-eslint-parser": "^10.2.0", "vue-prism-editor": "^2.0.0-alpha.2", "vue-router": "^4.0.12", - "vue-scrollto": "^2.20.0" + "vue-scrollto": "^2.20.0", + "yaml": "^2.8.3" }, "devDependencies": { "@babel/core": "^7.17.12", diff --git a/src/components/abilities/ImportAbilityModal.vue b/src/components/abilities/ImportAbilityModal.vue new file mode 100644 index 0000000..f9badb4 --- /dev/null +++ b/src/components/abilities/ImportAbilityModal.vue @@ -0,0 +1,137 @@ + + + + + diff --git a/src/stores/abilityStore.js b/src/stores/abilityStore.js index f75d32e..b2140a3 100644 --- a/src/stores/abilityStore.js +++ b/src/stores/abilityStore.js @@ -68,6 +68,16 @@ export const useAbilityStore = defineStore("abilityStore", { console.error("Error fetching abilities", error); } }, + async importAbility($api, ability) { + try { + const response = await $api.post("/api/v2/abilities", ability); + await this.getAbilities($api); + return response.data; + } catch(error) { + console.error("Error importing ability.", error); + throw error; + } + }, async getPayloads($api, sort=false, excludePlugins=false, addPath=false) { try { const response = await $api.get("/api/v2/payloads", {params: {sort: sort, exclude_plugins: excludePlugins, add_path: addPath}}); diff --git a/src/stores/coreDisplayStore.js b/src/stores/coreDisplayStore.js index eea7ba0..5b284e9 100644 --- a/src/stores/coreDisplayStore.js +++ b/src/stores/coreDisplayStore.js @@ -14,6 +14,9 @@ export const useCoreDisplayStore = defineStore("coreDisplayStore", { payloads: { showUpload: false, }, + abilities: { + showImport: false, + }, adversaries: { showFactBreakdown: false, showImport: false, diff --git a/src/views/AbilitiesView.vue b/src/views/AbilitiesView.vue index 8bae5c7..e15abf4 100644 --- a/src/views/AbilitiesView.vue +++ b/src/views/AbilitiesView.vue @@ -2,13 +2,16 @@ import { storeToRefs } from "pinia"; import { reactive, ref, inject, onMounted, computed } from "vue"; import { useRoute } from "vue-router"; +import { useCoreDisplayStore } from "@/stores/coreDisplayStore"; import { useAbilityStore } from "@/stores/abilityStore"; import { getAbilityPlatforms } from "@/utils/abilityUtil.js"; import CreateEditAbility from "@/components/abilities/CreateEditAbility.vue"; +import ImportAbilityModal from "@/components/abilities/ImportAbilityModal.vue"; const $api = inject("$api"); const route = useRoute(); +const coreDisplayStore = useCoreDisplayStore(); const abilityStore = useAbilityStore(); const { abilities, tactics, techniques, plugins, platforms } = storeToRefs(abilityStore); @@ -64,6 +67,10 @@ hr span.icon font-awesome-icon(icon="fas fa-plus") span Create an Ability + button.button.is-fullwidth.mb-4(@click="coreDisplayStore.modals.abilities.showImport = true") + span.icon + font-awesome-icon(icon="fas fa-file-import") + span Import form .field .control.has-icons-left @@ -113,6 +120,7 @@ hr //- Modals CreateEditAbility(:ability="selectedAbility" :active="showAbilityModal" :creating="isCreatingAbility" @close="showAbilityModal = false") +ImportAbilityModal