diff --git a/package-lock.json b/package-lock.json index 591ea08..86d4322 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "bulma": "^0.9.3", "bulma-switch": "^2.0.4", "bulma-toast": "^2.4.3", + "cron-validate": "^1.4.5", "d3-force": "^3.0.0", "echarts": "^5.3.1", "floating-vue": "^2.0.0-beta.20", @@ -1712,7 +1713,6 @@ "version": "7.18.3", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.3.tgz", "integrity": "sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug==", - "dev": true, "dependencies": { "regenerator-runtime": "^0.13.4" }, @@ -2475,6 +2475,11 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/lodash": { + "version": "4.17.6", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.6.tgz", + "integrity": "sha512-OpXEVoCKSS3lQqjx9GGGOapBeuW5eUboYHRlHP9urXPX25IKZ6AnP5ZRxtVf63iieUbsHxLn8NQ5Nlftc6yzAA==" + }, "node_modules/@types/node": { "version": "17.0.35", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.35.tgz", @@ -3572,6 +3577,14 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/cron-validate": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/cron-validate/-/cron-validate-1.4.5.tgz", + "integrity": "sha512-nKlOJEnYKudMn/aNyNH8xxWczlfpaazfWV32Pcx/2St51r2bxWbGhZD7uwzMcRhunA/ZNL+Htm/i0792Z59UMQ==", + "dependencies": { + "yup": "0.32.9" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -7182,8 +7195,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash-es": { "version": "4.17.21", @@ -7363,6 +7375,11 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, + "node_modules/nanoclone": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz", + "integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==" + }, "node_modules/nanoid": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", @@ -7945,6 +7962,11 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, + "node_modules/property-expr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", + "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==" + }, "node_modules/psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", @@ -8099,8 +8121,7 @@ "node_modules/regenerator-runtime": { "version": "0.13.9", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", - "dev": true + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" }, "node_modules/regenerator-transform": { "version": "0.15.0", @@ -8806,6 +8827,11 @@ "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", "integrity": "sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ=" }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" + }, "node_modules/tough-cookie": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", @@ -9462,6 +9488,23 @@ "node": ">=12" } }, + "node_modules/yup": { + "version": "0.32.9", + "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.9.tgz", + "integrity": "sha512-Ci1qN+i2H0XpY7syDQ0k5zKQ/DoxO0LzPg8PAR/X4Mpj6DqaeCoIYEEjDJwhArh3Fa7GWbQQVDZKeXYlSH4JMg==", + "dependencies": { + "@babel/runtime": "^7.10.5", + "@types/lodash": "^4.14.165", + "lodash": "^4.17.20", + "lodash-es": "^4.17.15", + "nanoclone": "^0.2.1", + "property-expr": "^2.0.4", + "toposort": "^2.0.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/zrender": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.3.1.tgz", diff --git a/package.json b/package.json index a731c2a..c2a9ec6 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "bulma": "^0.9.3", "bulma-switch": "^2.0.4", "bulma-toast": "^2.4.3", + "cron-validate": "^1.4.5", "d3-force": "^3.0.0", "echarts": "^5.3.1", "floating-vue": "^2.0.0-beta.20", diff --git a/src/components/core/Navigation.vue b/src/components/core/Navigation.vue index 5aaf1c6..130daa5 100644 --- a/src/components/core/Navigation.vue +++ b/src/components/core/Navigation.vue @@ -66,6 +66,8 @@ function promptToEnablePlugin(pluginName) { router-link.menu-item(to="/adversaries") adversaries li router-link.menu-item(to="/operations") operations + li + router-link.menu-item(to="/schedules") schedules p.menu-label font-awesome-icon(icon="fas fa-puzzle-piece").pr-2 | Plugins @@ -124,6 +126,7 @@ function promptToEnablePlugin(pluginName) { router-link.dropdown-item(to="/abilities") abilities router-link.dropdown-item(to="/adversaries") adversaries router-link.dropdown-item(to="/operations") operations + router-link.dropdown-item(to="/schedules") schedules .dropdown.is-hoverable.mb-2 .dropdown-trigger button.button(aria-haspopup="true" aria-controls="dropdown-menu") diff --git a/src/components/schedules/CreateScheduleModal.vue b/src/components/schedules/CreateScheduleModal.vue new file mode 100644 index 0000000..ffd87af --- /dev/null +++ b/src/components/schedules/CreateScheduleModal.vue @@ -0,0 +1,359 @@ + + + + + diff --git a/src/components/schedules/DeleteScheduleModal.vue b/src/components/schedules/DeleteScheduleModal.vue new file mode 100644 index 0000000..89ac4b4 --- /dev/null +++ b/src/components/schedules/DeleteScheduleModal.vue @@ -0,0 +1,42 @@ + + + + + diff --git a/src/router.js b/src/router.js index 603d160..b974b3b 100644 --- a/src/router.js +++ b/src/router.js @@ -9,6 +9,7 @@ import PayloadsView from "./views/PayloadsView.vue"; import AbilitiesView from "./views/AbilitiesView.vue"; import AdversariesView from "./views/AdversariesView.vue"; import OperationsView from "./views/OperationsView.vue"; +import SchedulesView from "@/views/SchedulesView.vue"; import FactSourcesView from "./views/FactSourcesView.vue"; import ObjectivesView from "./views/ObjectivesView.vue"; import PlannersView from "./views/PlannersView.vue"; @@ -63,6 +64,11 @@ const router = createRouter({ name: "operations", component: OperationsView, }, + { + path: "/schedules", + name: "schedules", + component: SchedulesView, + }, { path: "/factsources", name: "fact sources", diff --git a/src/stores/coreDisplayStore.js b/src/stores/coreDisplayStore.js index 565f25e..eea7ba0 100644 --- a/src/stores/coreDisplayStore.js +++ b/src/stores/coreDisplayStore.js @@ -34,6 +34,10 @@ export const useCoreDisplayStore = defineStore("coreDisplayStore", { showFilters: false, showOutput: false, }, + schedules: { + showCreate: false, + showDelete: false, + }, }, }; }, diff --git a/src/stores/schedulesStore.js b/src/stores/schedulesStore.js new file mode 100644 index 0000000..ca27bf6 --- /dev/null +++ b/src/stores/schedulesStore.js @@ -0,0 +1,56 @@ +import { defineStore } from "pinia"; + +export const useScheduleStore = defineStore("scheduleStore", { + state: () => { + return { + schedules: {}, + selectedScheduleID: "", + }; + }, + getters: { + currentSchedule(state) { + return state.schedules[state.selectedScheduleID]; + }, + }, + actions: { + async getSchedules($api) { + try { + const response = await $api.get("/api/v2/schedules"); + // TODO: Sort schedules + for (const schedule of response.data) { + this.schedules[schedule.id] = schedule; + } + } catch (error) { + console.error("Error fetching schedules", error); + } + }, + async createSchedule($api, schedule) { + try { + const response = await $api.post("/api/v2/schedules", schedule); + this.schedules[response.data.id] = response.data; + this.selectedScheduleID = response.data.id; + } catch (error) { + console.error("Error creating schedule", error); + throw error; + } + }, + async deleteSchedule($api, scheduleID) { + try { + await $api.delete(`/api/v2/schedules/${scheduleID}`); + delete this.schedules[scheduleID]; + this.selectedScheduleID = ""; + this.getSchedules($api); + } catch (error) { + console.error("Error deleting schedule", error); + } + }, + async updateSchedule($api, newSchedule) { + try { + await $api.put(`/api/v2/schedules/${this.selectedScheduleID}`, newSchedule); + this.getSchedules($api); + } catch (error) { + console.error("Error updating schedule", error); + } + }, + }, +}); diff --git a/src/views/SchedulesView.vue b/src/views/SchedulesView.vue new file mode 100644 index 0000000..4363906 --- /dev/null +++ b/src/views/SchedulesView.vue @@ -0,0 +1,77 @@ + + + + +