diff options
author | 2020-06-03 10:06:52 +0200 | |
---|---|---|
committer | 2020-06-03 10:06:52 +0200 | |
commit | 7bb53c64da2dcf88894bfd31503accdd81498f3d (patch) | |
tree | 4310e12366818af27947b5e2c80cb162da93a4b5 /dashboard/src | |
parent | cbea4e360e9bfaa9698cf7c61c83c96a1ba89b8c (diff) |
Update to new version 5.4HEADstable/jermamaster
Signed-off-by: Thomas Duval <thomas.duval@orange.com>
Change-Id: Idcd868133d75928a1ffd74d749ce98503e0555ea
Diffstat (limited to 'dashboard/src')
45 files changed, 4390 insertions, 0 deletions
diff --git a/dashboard/src/App.vue b/dashboard/src/App.vue new file mode 100644 index 00000000..99c5cd0d --- /dev/null +++ b/dashboard/src/App.vue @@ -0,0 +1,41 @@ +<template> + <div id="app"> + + <nav role="navigation" class="navbar navbar-expand-sm" v-if="$route.name != 'auth' && $route.name != 'error'"> + <ul class="navbar-nav" > + <li class="nav-item"> + <router-link to="/models" class="nav-link" active-class="o-active">Models</router-link> + </li> + <li class="nav-item"> + <router-link to="/rules" class="nav-link" active-class="o-active">Rules</router-link> + </li> + <li class="nav-item"> + <router-link to="/assignments" class="nav-link" active-class="o-active">Assignments</router-link> + </li> + <li class="nav-item"> + <router-link to="/pdp" class="nav-link" active-class="o-active">PDP</router-link> + </li> + <li class="nav-item"> + <router-link to="/admin" class="nav-link" active-class="o-active">Admin</router-link> + </li> + </ul> + <a href="#" class="btn btn-primary btn-sm active ml-auto" role="button" aria-pressed="true" @click="logout()">logout</a> + </nav> + <br> + + <router-view/> + </div> +</template> + + +<script> +export default { + methods: { + logout: function() { + localStorage.setItem("auth-key", null); + this.$router.push('auth'); + } + } +} +</script> + diff --git a/dashboard/src/assets/logo.png b/dashboard/src/assets/logo.png Binary files differnew file mode 100644 index 00000000..f3d2503f --- /dev/null +++ b/dashboard/src/assets/logo.png diff --git a/dashboard/src/components/FormHeader.vue b/dashboard/src/components/FormHeader.vue new file mode 100644 index 00000000..05419179 --- /dev/null +++ b/dashboard/src/components/FormHeader.vue @@ -0,0 +1,27 @@ +<template> + <div class="form-inline row"> + <input + type="search" + class="form-control filter col-9 mr-auto" + :placeholder="placeholder" + v-bind:value="value" + v-on:input="$emit('input', $event.target.value)" + > + <div class="col"></div> + <button v-if="needButton" type="button" class="btn btn-primary col-auto " @click="$emit('click')"> + <span class="fa fa-plus"></span> + {{ buttonText }} + </button> + </div> +</template> + +<script> +export default { + props: { + value: String, + needButton: Boolean, + buttonText: String, + placeholder: String, + } +} +</script>
\ No newline at end of file diff --git a/dashboard/src/components/assignment/Category.vue b/dashboard/src/components/assignment/Category.vue new file mode 100644 index 00000000..0f2f921b --- /dev/null +++ b/dashboard/src/components/assignment/Category.vue @@ -0,0 +1,103 @@ +<template> + <details class="list-group" > + <summary @click="populatePolicy()"> + <h4 class="inline-block width-200"> + {{name}} + <i class="fa fa-question-circle" style="margin-left: 2%" v-if="categoryHelpStrings[name.toLowerCase()]" data-toggle="tooltip" :title="categoryHelpStrings[name.toLowerCase()]"></i> + </h4> + </summary> + <div class="assignments-details"> + <FormHeader + :placeholder="placeholder" + v-model="filter" + needButton + :buttonText="buttonText" + @click="creatingData" + ></FormHeader> + <create-data + class="m-3" + @close="creatingDataOpen = false" + :policy="policy" + :type="name.toLowerCase()" + :categoryId="categoryId" + v-if="creatingDataOpen" + ></create-data> + <br/> + <details class="list-group" v-for="item in filteredData" :key="item.id"> + <summary @click="assignData(name.toLowerCase(), item)"> + <h4 class="inline-block width-200">{{item.name}}</h4> + </summary> + <AssignPerimeter :policy="policy" :dataToAssign="dataToAssign"></AssignPerimeter> + </details> + </div> + </details> +</template> + +<script> + import AssignPerimeter from "../policy/AssignPerimeter"; + import FormHeader from "../FormHeader"; + import CreateData from "./CreateData"; + import util from "../../services/Util.service"; + import PolicyService from "../../services/Policy.service"; + import helpstrings from "../../helpstrings"; + + export default { + props:{ + policy: Object, + data: Array, + name: String + }, + name: "Assignment", + components: { + AssignPerimeter, + FormHeader, + CreateData + }, + data() { + return{ + placeholder: "", + buttonText: "", + creatingDataOpen: false, + filter: "", + dataToAssign: {}, + categoryId: "", + categoryHelpStrings: {} + } + }, + created() { + this.categoryHelpStrings = helpstrings.metarule; + this.placeholder = "Filter by " + this.name; + this.buttonText = "Create " + this.name; + if (this.policy.model.meta_rules.length){ + let category = this.name.toLowerCase()+ "_categories"; + let metaRule = this.policy.model.meta_rules[0]; + this.categoryId = metaRule[category][0].id; + } + + }, + methods: { + populatePolicy() { + PolicyService.populatePolicy(this.policy); + }, + assignData(type, data) { + this.dataToAssign = { + selectedData: data, + selectedDataType: type, + }; + }, + creatingData(){ + this.creatingDataOpen = true; + + } + }, + computed: { + filteredData() { + return util.filterAndSortByName(this.data, this.filter); + } + } + } +</script> + +<style scoped> + +</style>
\ No newline at end of file diff --git a/dashboard/src/components/assignment/CreateData.vue b/dashboard/src/components/assignment/CreateData.vue new file mode 100644 index 00000000..63c69650 --- /dev/null +++ b/dashboard/src/components/assignment/CreateData.vue @@ -0,0 +1,75 @@ +<template> + <div class="list-group-item row"> + <form> + <div class="form-group"> + <label for="dataName">Name</label> + <input + type="text" + name="name" + v-model="dataCreate.name" + v-validate.initial="'alpha_dash|required|min:3'" + class="form-control" + id="dataName" + /> + </div> + <div class="form-group"> + <label for="dataDescription">Description</label> + <textarea + name="description" + v-model="dataCreate.description" + v-validate.initial="'required|min:3'" + class="form-control" + ></textarea> + </div> + <ul> + <li v-for="error in errors.all()" :key="error.id">{{ error }}</li> + </ul> + <button type="button" class="btn btn-secondary" @click="close()">Cancel</button> + <span> </span> + <button + type="button" + :disabled="errors.any()" + class="btn btn-primary" + @click="createData()" + >Create</button> + </form> + </div> +</template> + +<script> +import PolicyService from "./../../services/Policy.service.js"; + +export default { + name: "createData", + props: { + policy: Object, + type: String, + categoryId: String, + }, + data: function() { + return { + dataCreate: { + name: "", + description: "" + } + }; + }, + methods: { + createData() { + var self = this; + PolicyService.createData( + this.type, + this.policy, + this.categoryId, + this.dataCreate + ).then(function(datas) { + self.$emit("dataCreated", datas[0]); + self.close(); + }); + }, + close() { + this.$emit("close"); + } + } +}; +</script> diff --git a/dashboard/src/components/assignment/Policy.vue b/dashboard/src/components/assignment/Policy.vue new file mode 100644 index 00000000..6fd4afb4 --- /dev/null +++ b/dashboard/src/components/assignment/Policy.vue @@ -0,0 +1,123 @@ +<template> + <div> + <template v-if="edit"> + <form> + <div class="form-group" > + <label for="policyName">Name</label> + <input + type="text" + name="name" + v-model="policyEdit.name" + v-validate="'alpha_dash|required|min:3'" + class="form-control" + id="policyName" + /> + </div> + <div class="form-group"> + <label for="policyDescription">Description</label> + <textarea + name="description" + v-model="policyEdit.description" + v-validate="'required|min:3'" + class="form-control" + ></textarea> + </div> + <div class="form-group"> + <label for="policyGenre">Genre</label> + <select + v-model="policyEdit.genre" + class="form-control" + id="policyGenre" + v-validate.initial="'required'" + name="genre" + > + <option>admin</option> + <option>authz</option> + </select> + </div> + <ul> + <li v-for="error in errors.all()" :key="error.id">{{ error }}</li> + </ul> + <button type="button" class="btn btn-secondary" @click="edit = false">Cancel</button> + <span> </span> + <button + type="button" + :disabled="errors.any()" + class="btn btn-primary" + @click="updatePolicy()" + >Update</button> + </form> + </template> + <template v-else> + <h3 class="list-group-item-heading inline " data-toggle="tooltip" data-placement="top" title="Tooltip on top">{{ policy.name }}</h3> + <div class="pull-right"> + <button + type="button" + class="fa fa-trash btn-dark btn-sm" + title="Remove Policy" + @click="removePolicy()" + ></button> + <button + type="button" + class="fa fa-edit btn-dark btn-sm" + title="Edit Policy" + @click="updatingPolicy()" + ></button> + </div> + <p class="list-group-item-text">{{ policy.description }}</p> + + <Category :policy="policy" :data="policy.subjectData" name="Subject" ></Category> + <Category :policy="policy" :data="policy.objectData" name="Object" ></Category> + <Category :policy="policy" :data="policy.actionData" name="Action" ></Category> + </template> + <hr /> + </div> +</template> + +<script> +import PolicyService from "./../../services/Policy.service.js"; +import util from "./../../services/Util.service.js"; +import Category from "./Category"; + +export default { + props: { + policy: Object + }, + data() { + return { + filter: "", + edit: false, + policyEdit: {}, + assignments: [] + }; + }, + computed: { + + }, + components: { + Category + }, + methods: { + + removePolicy() { + if ( + confirm( + "Are you sure to delete this Policy? (Associated perimeter, data an PDP will be deleted too)" + ) + ) + PolicyService.removePolicy(this.policy); + }, + updatingPolicy() { + this.policyEdit = util.clone(this.policy); + this.edit = true; + }, + updatePolicy() { + this.edit = false; + PolicyService.updatePolicy(this.policyEdit); + }, + showAssignments(data){ + this.assignments = this.policy[data]; + } + } +}; +</script>
\ No newline at end of file diff --git a/dashboard/src/components/model/AddCategory.vue b/dashboard/src/components/model/AddCategory.vue new file mode 100644 index 00000000..c22ab333 --- /dev/null +++ b/dashboard/src/components/model/AddCategory.vue @@ -0,0 +1,126 @@ +<template> + <div> + <template v-if="categories.length > 0"> + <h4>Select category:</h4> + <form data-vv-scope="select"> + <div class="form-group"> + <select v-model="selectedCategoryId" v-validate.initial="'required'"> + <option disabled value>Please select one</option> + <option + v-for="category in categories" + :value="category.id" + :key="category.id" + >{{category.name}}</option> + </select> + </div> + <button type="button" class="btn btn-secondary" @click="close()">Cancel</button> + <span> </span> + <button + type="button" + :disabled="errors.any('select')" + class="btn btn-primary" + @click="addCategory()" + >Add</button> + </form> + <br> + <br> + <h4>Or create a new one:</h4> + </template> + <h4 v-else>Create a category:</h4> + <form data-vv-scope="create"> + <div class="form-group"> + <label for="categoryName">Name</label> + <input + type="text" + name="name" + v-model="categoryCreate.name" + v-validate.initial="'alpha_dash|required|min:3'" + class="form-control" + id="categoryName" + > + </div> + <div class="form-group"> + <label for="modelDescription">Description</label> + <textarea + name="description" + v-model="categoryCreate.description" + v-validate="'required|min:3'" + class="form-control" + ></textarea> + </div> + <ul> + <li v-for="error in errors.all('create')" :key="error.id">{{ error }}</li> + </ul> + <button type="button" class="btn btn-secondary" @click="close()">Cancel</button> + <span> </span> + <button + type="button" + :disabled="errors.any('create')" + class="btn btn-primary" + @click="createCategory()" + >Create</button> + </form> + </div> +</template> + +<script> +import ModelService from "./../../services/Model.service.js"; +import util from "./../../services/Util.service.js"; + +var categoryMap = { + subject: { + listName: "subject_categories", + serviceListName: "subjectCategories" + }, + object: { + listName: "object_categories", + serviceListName: "objectCategories" + }, + action: { + listName: "action_categories", + serviceListName: "actionCategories" + } +}; + +export default { + name: "addCategory", + data: function() { + return { + selectedCategoryId: null, + categoryCreate: { + name: "", + description: "" + } + }; + }, + props: { + metarule: Object, + type: String + }, + methods: { + createCategory() { + ModelService.createCategory(this.type, this.categoryCreate).then(category => { + this.selectedCategoryId = category.id; + this.addCategory(); + }); + }, + addCategory() { + var category = ModelService.getCategory(this.type, this.selectedCategoryId); + var metaRuleCopy = util.clone(this.metarule); + metaRuleCopy[categoryMap[this.type].listName].push(category); + ModelService.updateMetaRule(metaRuleCopy); + this.close(); + }, + close() { + this.$emit("close"); + } + }, + computed: { + categories() { + return ModelService[categoryMap[this.type].serviceListName].filter( + el => !this.metarule[categoryMap[this.type].listName].includes(el) + ); + } + } +}; +</script> diff --git a/dashboard/src/components/model/AddMetarule.vue b/dashboard/src/components/model/AddMetarule.vue new file mode 100644 index 00000000..ef6e8503 --- /dev/null +++ b/dashboard/src/components/model/AddMetarule.vue @@ -0,0 +1,111 @@ +<template> + <div> + <hr> + <template v-if="metarules.length > 0"> + <h4>Select metarule:</h4> + <form data-vv-scope="select"> + <div class="form-group"> + <select v-model="selectedMetaruleId" v-validate.initial="'required'"> + <option disabled value>Please select one</option> + <option + v-for="metarule in metarules" + :value="metarule.id" + :key="metarule.id" + >{{metarule.name}}</option> + </select> + </div> + <button type="button" class="btn btn-secondary" @click="close()">Cancel</button> + <span> </span> + <button + type="button" + :disabled="errors.any('select')" + class="btn btn-primary" + @click="addMetarule()" + >Add</button> + </form> + <br> + <br> + <h4>Or create a new one:</h4> + </template> + <h4 v-else>Create a metarule:</h4> + <form data-vv-scope="create"> + <div class="form-group"> + <label for="metaruleName">Name</label> + <input + type="text" + name="name" + v-model="metaruleCreate.name" + v-validate.initial="'alpha_dash|required|min:3'" + class="form-control" + id="metaruleName" + > + </div> + <div class="form-group"> + <label for="modelDescription">Description</label> + <textarea + name="description" + v-model="metaruleCreate.description" + v-validate="'required|min:3'" + class="form-control" + ></textarea> + </div> + <ul> + <li v-for="error in errors.all('create')" :key="error.id">{{ error }}</li> + </ul> + <button type="button" class="btn btn-secondary" @click="close()">Cancel</button> + <span> </span> + <button + type="button" + :disabled="errors.any('create')" + class="btn btn-primary" + @click="createMetarule()" + >Create and add</button> + </form> + </div> +</template> + +<script> +import ModelService from "./../../services/Model.service.js"; +import util from "./../../services/Util.service.js"; + +export default { + name: "addMetarule", + data: function() { + return { + selectedMetaruleId: null, + metaruleCreate: { + name: "", + description: "" + } + }; + }, + props: { + model: Object + }, + methods: { + createMetarule() { + ModelService.createMetaRule(this.metaruleCreate).then(metarule => { + this.selectedMetaruleId = metarule.id; + this.addMetarule(); + }); + }, + addMetarule() { + var metaRule = ModelService.getMetaRule(this.selectedMetaruleId); + var modelCopy = util.clone(this.model); + modelCopy.meta_rules.push(metaRule); + ModelService.updateModel(modelCopy); + this.close(); + }, + close() { + this.$emit("close"); + } + }, + computed: { + metarules() { + return ModelService.metaRules.filter( + el => !this.model.meta_rules.includes(el) + ); + } + } +}; +</script> diff --git a/dashboard/src/components/model/Category.vue b/dashboard/src/components/model/Category.vue new file mode 100644 index 00000000..81efb1ed --- /dev/null +++ b/dashboard/src/components/model/Category.vue @@ -0,0 +1,64 @@ +<template> + <div> + <span :title="category.description">{{ category.name }}</span> + <button type="button" class="fa fa-trash pull-right btn btn-dark btn-sm" @click="removeCategory()" title="Remove"></button> + <div v-for="attribute in attributes" :key="attribute.id"> + <b>attributes: </b> {{attribute.id}} + </div> + </div> +</template> + +<script> +import ModelService from "./../../services/Model.service.js"; +import util from "./../../services/Util.service.js"; + +var categoryMap = { + subject: { + addTitle: "Add Subject Category", + removeTitleFromMetaRule: + "Are you sure to remove from meta rule this Subject Category?", + removeTitle: "Are you sure to remove this Subject Category?", + listName: "subject_categories", + serviceListName: "subjectCategories" + }, + object: { + addTitle: "Add Object Category", + removeTitleFromMetaRule: + "Are you sure to remove from meta rule this Object Category?", + removeTitle: "Are you sure to remove this Object Category?", + listName: "object_categories", + serviceListName: "objectCategories" + }, + action: { + addTitle: "Add Action Category", + removeTitleFromMetaRule: + "Are you sure to remove from meta rule this Action Category?", + removeTitle: "Are you sure to remove this Action Category?", + listName: "action_categories", + serviceListName: "actionCategories" + } +}; + +export default { + name: "category", + props: { + metarule: Object, + category: Object, + attributes: Array, + type: String + }, + methods: { + removeCategory() { + var typeValue = categoryMap[this.type]; + if (confirm(typeValue.removeTitleFromMetaRule)) { + var metaruleCopy = util.clone(this.metarule); + metaruleCopy[typeValue.listName].splice( + metaruleCopy[typeValue.listName].indexOf(this.category), + 1 + ); + ModelService.updateMetaRule(metaruleCopy); + } + } + } +}; +</script> diff --git a/dashboard/src/components/model/CreateModel.vue b/dashboard/src/components/model/CreateModel.vue new file mode 100644 index 00000000..d5403e7b --- /dev/null +++ b/dashboard/src/components/model/CreateModel.vue @@ -0,0 +1,57 @@ +<template> + <div class="list-group-item row"> + <form> + <div class="form-group"> + <label for="modelName">Name</label> + <input + type="text" + name="name" + v-model="modelCreate.name" + v-validate.initial="'alpha_dash|required|min:3'" + class="form-control" + id="modelName" + > + </div> + <div class="form-group"> + <label for="modelDescription">Description</label> + <textarea + name="description" + v-model="modelCreate.description" + v-validate.initial="'required|min:3'" + class="form-control" + ></textarea> + </div> + <ul> + <li v-for="error in errors.all()" :key="error.id">{{ error }}</li> + </ul> + <button type="button" class="btn btn-secondary" @click="close()">Cancel</button> + <span> </span> + <button type="button" :disabled="errors.any()" class="btn btn-primary" @click="createModel()">Create</button> + </form> + </div> +</template> + +<script> +import ModelService from "./../../services/Model.service.js"; + +export default { + name: "createModel", + data: function() { + return { + modelCreate: { + name: "", + description: "" + } + }; + }, + methods: { + createModel() { + ModelService.createModel(this.modelCreate); + this.close(); + }, + close() { + this.$emit("close") + } + } +}; +</script> diff --git a/dashboard/src/components/model/Metarule.vue b/dashboard/src/components/model/Metarule.vue new file mode 100644 index 00000000..1cb266bd --- /dev/null +++ b/dashboard/src/components/model/Metarule.vue @@ -0,0 +1,155 @@ +<template> + <div class=""> + <template v-if="edit"> + <form> + <div class="form-group"> + <label for="metaruleName">Name</label> + <input + type="text" + name="name" + v-model="metaruleEdit.name" + v-validate="'alpha_dash|required|min:3'" + class="form-control" + id="metaruleName" + > + </div> + <div class="form-group"> + <label for="modelDescription">Description</label> + <textarea + name="description" + v-model="metaruleEdit.description" + v-validate="'required|min:3'" + class="form-control" + ></textarea> + </div> + <ul> + <li v-for="error in errors.all()" :key="error.id">{{ error }}</li> + </ul> + <button type="button" class="btn btn-secondary" @click="edit = false">Cancel</button> + <span> </span> + <button type="button" :disabled="errors.any()" class="btn btn-primary" @click="updateMetarule()">Update</button> + </form> + </template> + <template v-else> + <h3 class="list-group-item-heading inline">{{ metarule.name }}</h3> + <div class="pull-right"> + <button + type="button" + class="fa fa-trash btn btn-dark btn-sm" + @click="removeMetarule()" + title="Remove Meta Rule" + ></button> + <button + type="button" + class="fa fa-edit btn btn-dark btn-sm" + @click="updatingMetarule()" + title="Edit Meta Rule" + ></button> + </div> + <p class="list-group-item-text">{{ metarule.description }}</p> + <p class="list-group-item-text"></p> + <table class="table categories"> + <thead> + <tr> + <th> + <span>Subjects</span> + <i class="fa fa-question-circle" style="margin-left: 2%" v-if="metaruleHelpStrings.subject" data-toggle="tooltip" :title="metaruleHelpStrings.subject"></i> + <button + type="button" + class="fa fa-plus pull-right btn btn-dark btn-sm" + @click="addSubjectCategory = true" + title="Add Subject" + ></button> + </th> + <th> + <span>Objects</span> + <i class="fa fa-question-circle" style="margin-left: 2%" v-if="metaruleHelpStrings.object" data-toggle="tooltip" :title="metaruleHelpStrings.object"></i> + <button + type="button" + class="fa fa-plus pull-right btn btn-dark btn-sm" + @click="addObjectCategory = true" + title="Add Object" + ></button> + </th> + <th> + <span>Actions</span> + <i class="fa fa-question-circle" style="margin-left: 2%" v-if="metaruleHelpStrings.action" data-toggle="tooltip" :title="metaruleHelpStrings.action"></i> + <button + type="button" + class="fa fa-plus pull-right btn btn-dark btn-sm" + @click="addActionCategory = true" + title="Add Action" + ></button> + </th> + </tr> + </thead> + <tbody> + <tr> + <td> + <AddCategory v-if="addSubjectCategory" :metarule="metarule" type="subject" @close="addSubjectCategory = false"></AddCategory> + <Category v-else v-for="category in metarule.subject_categories" :key="category.id" :category="category" :metarule="metarule" :attributes="metarule.subjectAttributes" type="subject"></Category> + </td> + <td> + <AddCategory v-if="addObjectCategory" :metarule="metarule" type="object" @close="addObjectCategory = false"></AddCategory> + <Category v-else v-for="category in metarule.object_categories" :key="category.id" :category="category" :metarule="metarule" :attributes="metarule.objectAttributes" type="object"></Category> + </td> + <td> + <AddCategory v-if="addActionCategory" :metarule="metarule" type="action" @close="addActionCategory = false"></AddCategory> + <Category v-else v-for="category in metarule.action_categories" :key="category.id" :category="category" :metarule="metarule" :attributes="metarule.actionAttributes" type="action"></Category> + </td> + </tr> + </tbody> + </table> + </template> + </div> +</template> + +<script> +import Category from './Category.vue' +import AddCategory from './AddCategory.vue' +import ModelService from "./../../services/Model.service.js"; +import util from "./../../services/Util.service.js"; +import helpstrings from "../../helpstrings"; + +export default { + name: "metarule", + data: function() { + return { + edit: false, + metaruleEdit: {}, + addSubjectCategory: false, + addObjectCategory: false, + addActionCategory: false, + metaruleHelpStrings: {} + }; + }, + components: { + Category, + AddCategory + }, + props: { + metarule: Object, + model: Object, + }, + mounted() { + this.metaruleHelpStrings = helpstrings.metarule; + }, + methods: { + updatingMetarule() { + this.metaruleEdit = util.clone(this.metarule); + this.edit = true; + }, + updateMetarule() { + this.edit = false; + ModelService.updateMetaRule(this.metaruleEdit); + }, + removeMetarule() { + if (confirm('Are you sure to remove this Meta Rule from model?')) { + var modelCopy = util.clone(this.model); + modelCopy.meta_rules.splice(modelCopy.meta_rules.indexOf(this.metarule), 1); + ModelService.updateModel(modelCopy); + } + } + } +}; +</script> diff --git a/dashboard/src/components/model/Model.vue b/dashboard/src/components/model/Model.vue new file mode 100644 index 00000000..82ad992d --- /dev/null +++ b/dashboard/src/components/model/Model.vue @@ -0,0 +1,112 @@ +<template> + <div> + <template v-if="edit"> + <form> + <div class="form-group"> + <label for="modelName">Name</label> + <input + type="text" + name="name" + v-model="modelEdit.name" + v-validate="'alpha_dash|required|min:3'" + class="form-control" + id="modelName" + > + </div> + <div class="form-group"> + <label for="modelDescription">Description</label> + <textarea + name="description" + v-model="modelEdit.description" + v-validate="'required|min:3'" + class="form-control" + ></textarea> + </div> + <ul> + <li v-for="error in errors.all()" :key="error.id">{{ error }}</li> + </ul> + <button type="button" class="btn btn-secondary" @click="edit = false">Cancel</button> + <span> </span> + <button type="button" :disabled="errors.any()" class="btn btn-primary" @click="updateModel()">Update</button> + </form> + </template> + <template v-else> + <h3 class="list-group-item-heading inline">{{ model.name }}</h3> + <div class="pull-right"> + <button type="button" class="fa fa-trash btn btn-dark btn-sm" @click="removeModel()" title="Remove Model"></button> + <button type="button" class="fa fa-edit btn btn-dark btn-sm" @click="updatingModel()" title="Edit Model"></button> + </div> + <p class="list-group-item-text">{{ model.description }}</p> + + <AddMetarule v-if="addMetarule" :model="model" @close="addMetarule = false"></AddMetarule> + <details class="list-group-item-text" v-else> + <summary> + <h4 class="inline-block width-200"> + {{ model.meta_rules.length + ' meta rule' + (model.meta_rules.length > 1 ? 's' : ' ') }} + <i class="fa fa-question-circle" v-if="modelHelpStrings.metarule" data-toggle="tooltip" :title="modelHelpStrings.metarule"></i> + </h4> + <button + type="button" + class="fa fa-plus btn btn-dark btn-sm" + @click="addMetarule = true" + title="Add Meta Rule" + ></button> + </summary> + <div class="list-group"> + <Metarule + v-for="metarule in model.meta_rules" + :key="metarule.id" + :metarule="metarule" + :model="model" + ></Metarule> + </div> + </details> + </template> + <hr> + </div> +</template> + +<script> +import Metarule from "./Metarule.vue"; +import ModelService from "./../../services/Model.service.js"; +import AddMetarule from "./AddMetarule.vue"; +import util from "./../../services/Util.service.js"; +import helpstrings from "../../helpstrings"; + +export default { + name: "model", + data: function() { + return { + edit: false, + addMetarule: false, + modelEdit: {}, + modelHelpStrings: {} + }; + }, + components: { + Metarule, + AddMetarule + }, + props: { + model: Object + }, + mounted() { + this.modelHelpStrings = helpstrings.model; + }, + methods: { + updatingModel() { + this.modelEdit = util.clone(this.model); + this.edit = true; + }, + updateModel() { + this.edit = false; + ModelService.updateModel(this.modelEdit); + }, + removeModel() { + if (confirm('Are you sure to delete this Model?')) { + ModelService.removeModel(this.model); + } + } + } +}; +</script> diff --git a/dashboard/src/components/model/OrphanCategory.vue b/dashboard/src/components/model/OrphanCategory.vue new file mode 100644 index 00000000..316b8095 --- /dev/null +++ b/dashboard/src/components/model/OrphanCategory.vue @@ -0,0 +1,65 @@ +<template> + <div class="list-group col-lg-3"> + <h3 class="list-group-item active">{{title}}</h3> + <div v-for="category in categories" class="list-group-item" :key="category.id"> + <h4 class="list-group-item-heading inline">{{ category.name }}</h4> + <button + type="button" + class="fa fa-trash pull-right btn btn-dark btn-sm" + @click="removeCategory(category)" + :title="buttonTitle" + ></button> + <p class="list-group-item-text">{{ category.description }}</p> + </div> + </div> +</template> + +<script> +import ModelService from "./../../services/Model.service.js"; + +var categoryMap = { + subject: { + title: "Orphan Subject categories", + removeButtonTitle: "Remove Subject category", + removeTitle: "Are you sure to remove this Subject Category?", + listName: "subject_categories", + serviceListName: "subjectCategories" + }, + object: { + title: "Orphan Object categories", + removeButtonTitle: "Remove Object category", + removeTitle: "Are you sure to remove this Object Category?", + listName: "object_categories", + serviceListName: "objectCategories" + }, + action: { + title: "Orphan Action categories", + removeButtonTitle: "Remove Action category", + removeTitle: "Are you sure to remove this Action Category?", + listName: "action_categories", + serviceListName: "actionCategories" + } +}; + +export default { + props: { + categories: Array, + type: String + }, + computed: { + title() { + return categoryMap[this.type].title; + }, + buttonTitle() { + return categoryMap[this.type].removeButtonTitle; + } + }, + methods: { + removeCategory(category) { + if (confirm(categoryMap[this.type].removeTitle)) { + ModelService.removeCategory(this.type, category); + } + } + } +}; +</script> diff --git a/dashboard/src/components/model/Orphans.vue b/dashboard/src/components/model/Orphans.vue new file mode 100644 index 00000000..b3c1c524 --- /dev/null +++ b/dashboard/src/components/model/Orphans.vue @@ -0,0 +1,79 @@ +<template> + <div> + <div class="alert alert-dismissable alert-warning"> + <button type="button" class="close" data-dismiss="alert" @click="showOrphan = false; $emit('close')">×</button> + <h4>Warning!</h4> + <p> + Some metarules or categories are orphan, please check them and delete them if necessary. + <a + href + @click.prevent="showOrphan = true" + v-show="!showOrphan" + >Show orphans</a> + <a href @click.prevent="showOrphan = false" v-show="showOrphan">Hide orphans</a> + </p> + </div> + + <div class="row" v-show="showOrphan"> + <div class="list-group col-lg-3" v-if="orphanMetaRules.length"> + <h3 class="list-group-item active">Orphan Meta rules</h3> + <div v-for="metaRule in orphanMetaRules" class="list-group-item" :key="metaRule.id"> + <h4 class="list-group-item-heading inline">{{ metaRule.name }}</h4> + <button + type="button" + class="fa fa-trash pull-right btn btn-dark btn-sm" + @click="removeMetarule(metaRule)" + title="Remove Meta rule" + ></button> + <p class="list-group-item-text">{{ metaRule.description }}</p> + </div> + </div> + + <OrphanCategory + v-if="orphanSubjectCategories.length" + type="subject" + :categories="orphanSubjectCategories" + ></OrphanCategory> + <OrphanCategory + v-if="orphanObjectCategories.length" + type="object" + :categories="orphanObjectCategories" + ></OrphanCategory> + <OrphanCategory + v-if="orphanActionCategories.length" + type="action" + :categories="orphanActionCategories" + ></OrphanCategory> + </div> + </div> +</template> + +<script> +import ModelService from "./../../services/Model.service.js"; +import OrphanCategory from "./OrphanCategory.vue"; + +export default { + props: { + orphanMetaRules: Array, + orphanSubjectCategories: Array, + orphanObjectCategories: Array, + orphanActionCategories: Array + }, + components: { + OrphanCategory, + }, + data() { + return { + showOrphan: false, + allowAlert: true + }; + }, + methods: { + removeMetarule(metarule) { + if (confirm("Are you sure to remove this Meta Rule?")) { + ModelService.removeMetaRule(metarule); + } + } + } +}; +</script>
\ No newline at end of file diff --git a/dashboard/src/components/pdp/AddPolicy.vue b/dashboard/src/components/pdp/AddPolicy.vue new file mode 100644 index 00000000..82ad07e2 --- /dev/null +++ b/dashboard/src/components/pdp/AddPolicy.vue @@ -0,0 +1,64 @@ +<template> + <div> + <hr> + <h4>Select policy:</h4> + <form data-vv-scope="select"> + <div class="form-group"> + <select v-model="selectedPolicyId" v-validate.initial="'required'"> + <option disabled value>Please select one</option> + <option + v-for="policy in policies" + :value="policy.id" + :key="policy.id" + >{{policy.name}}</option> + </select> + </div> + <button type="button" class="btn btn-secondary" @click="close()">Cancel</button> + <span> </span> + <button + type="button" + :disabled="errors.any('select')" + class="btn btn-primary" + @click="addPolicy()" + >Add</button> + </form> + <br> + <br> + </div> +</template> + +<script> +import PdpService from "./../../services/Pdp.service.js"; +import util from "./../../services/Util.service.js"; + +export default { + name: "addPolicy", + data: function() { + return { + selectedPolicyId: null, + }; + }, + props: { + pdp: Object + }, + methods: { + addPolicy() { + var policy = PdpService.getPolicy(this.selectedPolicyId); + var pdpCopy = util.clone(this.pdp); + pdpCopy.security_pipeline.push(policy); + PdpService.updatePdp(pdpCopy); + this.close(); + }, + close() { + this.$emit("close"); + } + }, + computed: { + policies() { + return PdpService.policies.filter( + el => !this.pdp.security_pipeline.includes(el) + ); + } + } +}; +</script> diff --git a/dashboard/src/components/pdp/CreatePdp.vue b/dashboard/src/components/pdp/CreatePdp.vue new file mode 100644 index 00000000..aca46413 --- /dev/null +++ b/dashboard/src/components/pdp/CreatePdp.vue @@ -0,0 +1,83 @@ +<template> + <div class="list-group-item row"> + <form> + <div class="form-group"> + <label for="modelName">Name</label> + <input + type="text" + name="name" + v-model="pdpCreate.name" + v-validate.initial="'alpha_dash|required|min:3'" + class="form-control" + id="modelName" + > + </div> + <div class="form-group"> + <label for="modelDescription">Description</label> + <textarea + name="description" + v-model="pdpCreate.description" + v-validate.initial="'required|min:3'" + class="form-control" + ></textarea> + </div> + <div class="form-group"> + <label for="pdpVpi">Vim project id</label> + <input + type="text" + name="vim_project_id" + v-model="pdpCreate.vim_project_id" + class="form-control" + id="pdpVpi" + > + </div> + <div class="form-group"> + <label for="pdpPolicy">Policy</label> + <select v-model="selectedPolicy" class="form-control" id="pdpPolicy" v-validate.initial="'required'" name="policy"> + <option v-for="policy in policies" :key="policy.id" :value="policy.id">{{ policy.name }}</option> + </select> + </div> + <ul> + <li v-for="error in errors.all()" :key="error.id">{{ error }}</li> + </ul> + <button type="button" class="btn btn-secondary" @click="close()">Cancel</button> + <span> </span> + <button type="button" :disabled="errors.any()" class="btn btn-primary" @click="createModel()">Create</button> + </form> + </div> +</template> + +<script> +import PdpService from "./../../services/Pdp.service.js"; +import PolicyService from "../../services/Policy.service"; +import util from "../../services/Util.service"; + +export default { + data: function() { + return { + selectedPolicy: null, + pdpCreate: { + name: "", + description: "", + security_pipeline: [], + vim_project_id: "" + } + }; + }, + computed:{ + policies() { + return util.sortByName(PolicyService.policies); + } + }, + methods: { + createModel() { + this.pdpCreate.security_pipeline.push(this.selectedPolicy); + PdpService.createPdp(this.pdpCreate); + this.close(); + }, + close() { + this.$emit("close") + } + } +}; +</script> diff --git a/dashboard/src/components/pdp/Pdp.vue b/dashboard/src/components/pdp/Pdp.vue new file mode 100644 index 00000000..3aba3fed --- /dev/null +++ b/dashboard/src/components/pdp/Pdp.vue @@ -0,0 +1,173 @@ +<template> + <div class=""> + <template v-if="edit"> + <form> + <div class="form-group"> + <label for="pdpName">Name</label> + <input + type="text" + name="name" + v-model="pdpEdit.name" + v-validate="'alpha_dash|required|min:3'" + class="form-control" + id="pdpName" + > + </div> + <div class="form-group"> + <label for="pdpDescription">Description</label> + <textarea + name="description" + v-model="pdpEdit.description" + v-validate="'required|min:3'" + class="form-control" + id="pdpDescription" + ></textarea> + </div> + <ul> + <li v-for="error in errors.all()" :key="error.id">{{ error }}</li> + </ul> + <button type="button" class="btn btn-secondary" @click="edit = false">Cancel</button> + <span> </span> + <button type="button" :disabled="errors.any()" class="btn btn-primary" @click="updatePdp()">Update</button> + </form> + </template> + <template v-else> + <div> + <h3 class="list-group-item-heading inline">{{ pdp.name }}</h3> + <div class="pull-right"> + <button + type="button" + class="fa fa-trash btn btn-dark btn-sm" + @click="removePdp(pdp)" + title="Remove PDP" + ></button> + <button + type="button" + class="fa fa-edit btn btn-dark btn-sm" + @click="updatingPdp(pdp)" + title="Edit PDP" + ></button> + </div> + <p class="list-group-item-text">{{ pdp.description }}</p> + <h4 class="list-group-item-text"> + <div v-if="!changeProject"> + Project: {{ pdp.project ? pdp.project : 'none' }} + <button + type="button" + class="fa fa-edit btn btn-dark btn-sm" + @click="changingProject()" + title="Change project" + ></button> + </div> + <form class="form-inline" v-else> + <label for="projectId">Project ID: </label> + + <input + type="text" + name="id" + v-model="project" + class="form-control" + id="projectId" + > + + <button type="button" class="btn btn-secondary" @click="changeProject = false">Cancel</button> + <span> </span> + <button + type="button" + class="btn btn-primary" + @click="setProject()" + >OK</button> + </form> + </h4> + + <UpdatePolicy v-if="updatePolicy" :pdp="pdp" @close="updatePolicy = false"></UpdatePolicy> + <details class="list-group-item-text" v-else> + <summary> + <h4 class="inline"> + {{ pdp.security_pipeline.length }} {{ (pdp.security_pipeline.length > 1) ? "policies" : "policy"}} + </h4> + <button + type="button" + class="fa fa-edit btn btn-dark btn-sm" + @click="updatePolicy = true" + title="Change Policy" + ></button> + </summary> + <div class="list-group"> + <div + v-for="policy in pdp.security_pipeline" :key="policy.id" + > + <h3 class="list-group-item-heading inline">{{ policy.name }}</h3> + <!--<button + type="button" + class="fa fa-trash pull-right btn btn-dark btn-sm" + @click="removePolicyFromPdp(policy)" + title="Remove Policy" + ></button>--> + <p class="list-group-item-text">{{ policy.description }}</p> + </div> + </div> + </details> + </div> + </template> + <hr> + </div> +</template> + +<script> +import PdpService from './../../services/Pdp.service.js'; +//import AddPolicy from "./AddPolicy.vue"; +import UpdatePolicy from "./UpdatePolicy"; +import util from "./../../services/Util.service.js"; + +export default { + name: "pdp", + data: function() { + return { + edit: false, + updatePolicy: false, + changeProject: false, + project: "", + pdpEdit: {} + }; + }, + props: { + pdp: Object + }, + components: { + //AddPolicy + UpdatePolicy + }, + methods: { + changingProject() { + this.project = this.pdp.project; + this.changeProject = true; + }, + removePdp() { + if (confirm('Are you sure to delete this PDP?')) + PdpService.removePdp(this.pdp); + }, + updatingPdp() { + this.pdpEdit = util.clone(this.pdp); + this.edit = true; + }, + updatePdp() { + this.edit = false; + PdpService.updatePdp(this.pdpEdit); + }, + removePolicyFromPdp(policy) { + if (confirm('Are you sure to remove this Policy from PDP?')) { + //var pdpCopy = util.clone(this.pdp); + this.pdp.security_pipeline.splice(this.pdp.security_pipeline.indexOf(policy), 1); + PdpService.updatePdp(this.pdp); + } + }, + setProject() { + var pdpCopy = util.clone(this.pdp); + pdpCopy.project = this.project; + PdpService.updatePdp(pdpCopy); + this.changeProject = false; + } + } +}; +</script> diff --git a/dashboard/src/components/pdp/UpdatePolicy.vue b/dashboard/src/components/pdp/UpdatePolicy.vue new file mode 100644 index 00000000..f84cf37e --- /dev/null +++ b/dashboard/src/components/pdp/UpdatePolicy.vue @@ -0,0 +1,64 @@ +<template> + <div> + <hr> + <h4>Select policy:</h4> + <form data-vv-scope="select"> + <div class="form-group"> + <select v-model="selectedPolicyId" v-validate.initial="'required'"> + <option disabled value>Please select one</option> + <option + v-for="policy in policies" + :value="policy.id" + :key="policy.id" + >{{policy.name}}</option> + </select> + </div> + <button type="button" class="btn btn-secondary" @click="close()">Cancel</button> + <span> </span> + <button + type="button" + :disabled="errors.any('select')" + class="btn btn-primary" + @click="updatePolicy()" + >Update</button> + </form> + <br> + <br> + </div> +</template> + +<script> +import PdpService from "./../../services/Pdp.service.js"; +import util from "./../../services/Util.service.js"; + +export default { + name: "updatePolicy", + data: function() { + return { + selectedPolicyId: null, + }; + }, + props: { + pdp: Object + }, + methods: { + updatePolicy() { + var policy = PdpService.getPolicy(this.selectedPolicyId); + var pdpCopy = util.clone(this.pdp); + pdpCopy.security_pipeline = [policy]; + PdpService.updatePdp(pdpCopy); + this.close(); + }, + close() { + this.$emit("close"); + } + }, + computed: { + policies() { + return PdpService.policies.filter( + el => !this.pdp.security_pipeline.includes(el) + ); + } + } +}; +</script> diff --git a/dashboard/src/components/policy/AssignPerimeter.vue b/dashboard/src/components/policy/AssignPerimeter.vue new file mode 100644 index 00000000..07814075 --- /dev/null +++ b/dashboard/src/components/policy/AssignPerimeter.vue @@ -0,0 +1,244 @@ +<template> + <div > + <div v-if="selectedData && loading" class="row padding-10"> + <h4>Loading...</h4> + </div> + <div v-if="selectedData && !loading"> + <div class="p-2"> + <h3>Assign perimeters to {{ selectedData.name }}</h3> + <form-header + placeholder="Filter" + buttonText="Create Perimeter" + @click="creatingPerimeter = true" + v-model="filterPerimeter" + need-button + ></form-header> + </div> + <create-perimeter + v-if="creatingPerimeter" + :policy="policy" + :type="selectedDataType" + @close="creatingPerimeter = false" + @perimeterCreated="createPerimeter" + ></create-perimeter> + <div class="row mt-3" v-else> + <div class="col-sm"> + <h4> + All perimeters + <i class="fa fa-question-circle" style="margin-left: 2%" v-if="assignPerimeterHelpStrings.allPerimeters" data-toggle="tooltip" :title="assignPerimeterHelpStrings.allPerimeters"></i> + </h4> + <div class="w-100 height-200 scroll list-group border"> + <button + class="list-group-item" + v-for="perimeter in allPerimetersFiltered" + :title="perimeter.description" + :key="perimeter.id" + @click="addPerimeter(perimeter)" + >{{ perimeter.name }}</button> + </div> + <p>Click to add</p> + </div> + <div class="col-sm"> + <h4> + Policy perimeters + <i class="fa fa-question-circle" style="margin-left: 2%" v-if="assignPerimeterHelpStrings.policyPerimeters" data-toggle="tooltip" :title="assignPerimeterHelpStrings.policyPerimeters"></i> + </h4> + <div class="w-100 height-200 scroll list-group list-group-flush border"> + <div + @click="assign(perimeter)" + class="list-group-item" + :key="perimeter.id" + v-for="perimeter in perimetersFiltered" + > + <span :title="perimeter.description">{{ perimeter.name }}</span> + <button + type="button" + class="fa fa-trash pull-right btn-dark btn-sm" + @click.stop="removePerimeterFromPolicy(perimeter)" + title="Remove Perimeter" + ></button> + </div> + </div> + <p>Click to assign</p> + </div> + <div class="col-sm"> + <h4> + Assigned perimeters + <i class="fa fa-question-circle" style="margin-left: 2%" v-if="assignPerimeterHelpStrings.assignedPerimeters" data-toggle="tooltip" :title="assignPerimeterHelpStrings.assignedPerimeters"></i> + </h4> + <div class="w-100 list-group border height-200 scroll"> + <button + class="list-group-item" + :key="perimeter.id" + v-for="perimeter in assignmentsFiltered" + :title="perimeter.description" + @click="unassign(perimeter)" + >{{ perimeter.name }}</button> + </div> + <p>Click to unassign</p> + </div> + </div> + </div> + </div> +</template> + +<script> +import PolicyService from "./../../services/Policy.service.js"; +import util from "./../../services/Util.service.js"; +import FormHeader from "./../FormHeader.vue"; +import CreatePerimeter from "./CreatePerimeter.vue"; +import helpstrings from "../../helpstrings"; + +var categoryMap = { + subject: { + perimeterId: "subject_id" + }, + object: { + perimeterId: "object_id" + }, + action: { + perimeterId: "action_id" + } +}; + +export default { + props: { + dataToAssign: Object, + policy: Object + }, + components: { + FormHeader, + CreatePerimeter + }, + data() { + return { + selectedData: null, + selectedDataType: "", + loading: false, + perimeters: [], + allPerimeters: [], + assignments: [], + filterPerimeter: "", + creatingPerimeter: false, + assignPerimeterHelpStrings: {} + }; + }, + mounted() { + this.assignPerimeterHelpStrings = helpstrings.assignPerimeter; + }, + computed: { + allPerimetersFiltered() { + return util.filterAndSortByName(this.allPerimeters, this.filterPerimeter); + }, + perimetersFiltered() { + return util.filterAndSortByName(this.perimeters, this.filterPerimeter); + }, + assignmentsFiltered() { + return util.filterAndSortByName(this.assignments, this.filterPerimeter); + } + }, + watch: { + dataToAssign() { + this.selectedData = this.dataToAssign.selectedData; + this.selectedDataType = this.dataToAssign.selectedDataType; + if (this.selectedData) { + this.loadPerimeter(); + } + } + }, + methods: { + createPerimeter(perimeters) { + util.pushAll(this.perimeters, perimeters); + }, + addPerimeter(perimeter) { + var self = this; + PolicyService.addPerimeterToPolicy( + self.selectedDataType, + self.policy, + perimeter + ).then(function() { + self.allPerimeters.splice(self.allPerimeters.indexOf(perimeter), 1); + self.perimeters.push(perimeter); + }); + }, + assign(perimeter) { + var self = this; + PolicyService.createAssignment( + self.selectedDataType, + self.policy, + perimeter, + self.selectedData + ).then(function() { + self.assignments.push(perimeter); + self.perimeters.splice(self.perimeters.indexOf(perimeter), 1); + }); + }, + unassign(perimeter) { + var self = this; + PolicyService.removeAssignment( + self.selectedDataType, + self.policy, + perimeter, + self.selectedData + ).then(function() { + self.perimeters.push(perimeter); + self.assignments.splice(self.assignments.indexOf(perimeter), 1); + }); + }, + removePerimeterFromPolicy(perimeter) { + if ( + confirm( + "Are you sure to delete this Perimeter? (Associated assignments will be deleted too)" + ) + ) { + var self = this; + PolicyService.removePerimeterFromPolicy( + self.selectedDataType, + self.policy, + perimeter + ).then(function() { + self.perimeters.splice(self.perimeters.indexOf(perimeter), 1); + perimeter.policy_list.splice( + perimeter.policy_list.indexOf(self.policy.id), + 1 + ); + if (perimeter.policy_list.length > 0) { + self.allPerimeters.push(perimeter); + } + }); + } + }, + loadPerimeter() { + var self = this; + self.loading = true; + self.perimeters = []; + self.allPerimeters = []; + self.assignments = []; + + PolicyService.loadPerimetersAndAssignments( + self.selectedDataType, + self.policy + ).then(function(values) { + var category = categoryMap[self.selectedDataType]; + self.loading = false; + self.perimeters = values.perimeters; + var index, perimeter; + for (index = 0; index < values.allPerimeters.length; index++) { + perimeter = values.allPerimeters[index]; + if (perimeter.policy_list.indexOf(self.policy.id) < 0) { + self.allPerimeters.push(perimeter); + } + } + for (index = 0; index < values.assignments.length; index++) { + var assignment = values.assignments[index]; + if (assignment.assignments.indexOf(self.selectedData.id) >= 0) { + perimeter = values.perimetersMap[assignment[category.perimeterId]]; + self.assignments.push(perimeter); + self.perimeters.splice(self.perimeters.indexOf(perimeter), 1); + } + } + }); + } + } +}; +</script>
\ No newline at end of file diff --git a/dashboard/src/components/policy/CreateData.vue b/dashboard/src/components/policy/CreateData.vue new file mode 100644 index 00000000..ca3c5183 --- /dev/null +++ b/dashboard/src/components/policy/CreateData.vue @@ -0,0 +1,75 @@ +<template> + <div class="list-group-item row"> + <form> + <div class="form-group"> + <label for="dataName">Name</label> + <input + type="text" + name="name" + v-model="dataCreate.name" + v-validate.initial="'alpha_dash|required|min:3'" + class="form-control" + id="dataName" + /> + </div> + <div class="form-group"> + <label for="dataDescription">Description</label> + <textarea + name="description" + v-model="dataCreate.description" + v-validate.initial="'required|min:3'" + class="form-control" + ></textarea> + </div> + <ul> + <li v-for="error in errors.all()" :key="error.id">{{ error }}</li> + </ul> + <button type="button" class="btn btn-secondary" @click="close()">Cancel</button> + <span> </span> + <button + type="button" + :disabled="errors.any()" + class="btn btn-primary" + @click="createData()" + >Create</button> + </form> + </div> +</template> + +<script> +import PolicyService from "./../../services/Policy.service.js"; + +export default { + name: "createData", + props: { + policy: Object, + type: String, + category: Object, + }, + data: function() { + return { + dataCreate: { + name: "", + description: "" + } + }; + }, + methods: { + createData() { + var self = this; + PolicyService.createData( + this.type, + this.policy, + this.category.id, + this.dataCreate + ).then(function(datas) { + self.$emit("dataCreated", datas[0]); + self.close(); + }); + }, + close() { + this.$emit("close"); + } + } +}; +</script> diff --git a/dashboard/src/components/policy/CreatePerimeter.vue b/dashboard/src/components/policy/CreatePerimeter.vue new file mode 100644 index 00000000..b8a9d532 --- /dev/null +++ b/dashboard/src/components/policy/CreatePerimeter.vue @@ -0,0 +1,73 @@ +<template> + <div class="list-group-item row"> + <form> + <div class="form-group"> + <label for="perimeterName">Name</label> + <input + type="text" + name="name" + v-model="perimeterCreate.name" + v-validate.initial="'alpha_dash|required|min:3'" + class="form-control" + id="perimeterName" + /> + </div> + <div class="form-group"> + <label for="perimeterDescription">Description</label> + <textarea + name="description" + v-model="perimeterCreate.description" + v-validate.initial="'required|min:3'" + class="form-control" + ></textarea> + </div> + <ul> + <li v-for="error in errors.all()" :key="error.id">{{ error }}</li> + </ul> + <button type="button" class="btn btn-secondary" @click="close()">Cancel</button> + <span> </span> + <button + type="button" + :disabled="errors.any()" + class="btn btn-primary" + @click="createPerimeter()" + >Create</button> + </form> + </div> +</template> + +<script> +import PolicyService from "./../../services/Policy.service.js"; + +export default { + name: "createPerimeter", + props: { + policy: Object, + type: String + }, + data: function() { + return { + perimeterCreate: { + name: "", + description: "" + } + }; + }, + methods: { + createPerimeter() { + var self = this; + PolicyService.createPerimeter( + this.type, + this.policy, + this.perimeterCreate + ).then(function(perimeters) { + self.$emit("perimeterCreated", perimeters); + self.close(); + }); + }, + close() { + this.$emit("close"); + } + } +}; +</script> diff --git a/dashboard/src/components/policy/CreatePolicy.vue b/dashboard/src/components/policy/CreatePolicy.vue new file mode 100644 index 00000000..b3e90c2d --- /dev/null +++ b/dashboard/src/components/policy/CreatePolicy.vue @@ -0,0 +1,84 @@ +<template> + <div class="list-group-item row"> + <form> + <div class="form-group"> + <label for="policyName">Name</label> + <input + type="text" + name="name" + v-model="policyCreate.name" + v-validate.initial="'alpha_dash|required|min:3'" + class="form-control" + id="policyName" + /> + </div> + <div class="form-group"> + <label for="policyDescription">Description</label> + <textarea + name="description" + v-model="policyCreate.description" + v-validate.initial="'required|min:3'" + class="form-control" + ></textarea> + </div> + <div class="form-group"> + <label for="policyGenre">Genre</label> + <select v-model="policyCreate.genre" class="form-control" id="policyGenre" v-validate.initial="'required'" name="genre"> + <option>admin</option> + <option>authz</option> + </select> + </div> + <div class="form-group"> + <label for="policyModel">Model</label> + <select v-model="policyCreate.model_id" class="form-control" id="policyModel" v-validate.initial="'required'" name="model"> + <option v-for="model in models" :key="model.id" :value="model.id">{{ model.name }}</option> + </select> + </div> + <ul> + <li v-for="error in errors.all()" :key="error.id">{{ error }}</li> + </ul> + <button type="button" class="btn btn-secondary" @click="close()">Cancel</button> + <span> </span> + <button + type="button" + :disabled="errors.any()" + class="btn btn-primary" + @click="createPolicy()" + >Create</button> + </form> + </div> +</template> + +<script> +import PolicyService from "./../../services/Policy.service.js"; +import ModelService from "./../../services/Model.service.js"; +import util from "./../../services/Util.service.js"; + +export default { + name: "createPolicy", + data: function() { + return { + policyCreate: { + name: "", + description: "", + genre: "", + model_id: "" + } + }; + }, + computed: { + models() { + return util.sortByName(ModelService.models); + } + }, + methods: { + createPolicy() { + PolicyService.createPolicy(this.policyCreate); + this.close(); + }, + close() { + this.$emit("close"); + } + } +}; +</script> diff --git a/dashboard/src/components/policy/CreateRule.vue b/dashboard/src/components/policy/CreateRule.vue new file mode 100644 index 00000000..656fa39a --- /dev/null +++ b/dashboard/src/components/policy/CreateRule.vue @@ -0,0 +1,213 @@ +<template> + <div class="list-group-item row"> + <form v-if="!metaruleId"> + <div class="form-group"> + <label for="metarule">Select a Metarule:</label> + <select v-model="metaruleId" class="form-control" id="metarule" name="metarule"> + <option + v-for="metarule in policy.model.meta_rules" + :key="metarule.id" + :value="metarule.id" + >{{ metarule.name }}</option> + </select> + </div> + <button type="button" class="btn btn-secondary" @click="close()">Cancel</button> + </form> + <form v-else> + <div + class="form-group" + v-for="(categoryWrapper, index) in ruleData" + :key="categoryWrapper.id" + > + <label + :for="categoryWrapper.category.name" + >{{ 'Select ' + categoryWrapper.type + ' data of ' + categoryWrapper.category.name + ' category' }}</label> + <select + v-model="ruleCreate.rule[index]" + class="form-control" + :id="categoryWrapper.category.name" + :name="categoryWrapper.category.name" + v-validate.initial="'required'" + > + <option + v-for="data in categoryWrapper.data" + :key="data.id" + :value="data.id" + >{{ data.name }}</option> + </select> + <create-data + class="m-3" + @close="creatingDataCategory = null" + @dataCreated="dataCreated(categoryWrapper, $event, index)" + :policy="policy" + :type="categoryWrapper.type" + :category="categoryWrapper.category" + v-if="creatingDataCategory == categoryWrapper.category" + ></create-data> + <button + v-else + type="button" + class="btn btn-primary mt-3" + @click="creatingDataCategory = categoryWrapper.category" + >Or Create one</button> + </div> + <div v-for='(attribute, index) in policy.attributes' :key="attribute.id" class="form-group"> + <label for="ruleAttributes">Attribute {{attribute.id}}</label> + <select + v-model="attributes[index]" + class="form-control" + v-validate.initial="'required'" + > + <option + v-for="value in attribute.values" + :key="value" + :value="value" + >{{ value }}</option> + </select> + </div> + <div class="form-group"> + <label for="ruleInstructions">Instructions</label> + <select + v-model="ruleCreate.instructions" + class="form-control" + v-validate.initial="'required'" + > + <option + v-for="data in instructions" + :key="data" + :value="data" + >{{ data }}</option> + </select> + </div> + <ul> + <li v-for="error in errors.all()" :key="error.id">{{ error }}</li> + </ul> + <button type="button" class="btn btn-secondary" @click="close()">Cancel</button> + <span> </span> + <button + type="button" + :disabled="errors.any()" + class="btn btn-primary" + @click="createRule()" + >Create</button> + </form> + </div> +</template> + +<script> +import PolicyService from "./../../services/Policy.service.js"; +import ModelService from "./../../services/Model.service.js"; +import CreateData from "./CreateData.vue"; + +function addCategories(type, categories, data, mapArray, initArray) { + var dataFiltered = []; + + for (var i = 0; i < categories.length; i++) { + var category = categories[i]; + for (var j = 0; j < data.length; j++) { + var element = data[j]; + if (element.category_id == category.id) { + dataFiltered.push(element); + } + } + + mapArray.push({ + id: category.id, + category: category, + data: dataFiltered, + type: type + }); + initArray.push(dataFiltered.length > 0 ? dataFiltered[0].id : null); + } +} + + +export default { + name: "createRule", + props: { + policy: Object + }, + components: { + CreateData + }, + data: function() { + return { + metarule: null, + metaruleId: null, + creatingDataCategory: null, + ruleData: [], + ruleCreate: { + instructions: 'grant', + rule: [], + }, + instructions: ['grant', 'deny'], + attributes: [], + attribute: '', + }; + }, + watch: { + policy() { + if (this.policy) { + this.metaruleId = null; + if (this.policy.model.meta_rules.length == 1) { + this.metaruleId = this.policy.model.meta_rules[0].id; + } + } + }, + metaruleId() { + if (this.metaruleId) { + this.metarule = ModelService.getMetaRule(this.metaruleId); + if (this.metarule){ + for (let i = 0; i < this.policy.attributes.length; i++){ + let attrs = this.policy.attributes[i].default; + this.attributes.push(attrs); + } + } + addCategories( + "subject", + this.metarule.subject_categories, + this.policy.subjectData, + this.ruleData, + this.ruleCreate.rule + ); + addCategories( + "object", + this.metarule.object_categories, + this.policy.objectData, + this.ruleData, + this.ruleCreate.rule + ); + addCategories( + "action", + this.metarule.action_categories, + this.policy.actionData, + this.ruleData, + this.ruleCreate.rule + ); + } + } + }, + methods: { + createRule() { + this.ruleCreate.enabled = true; + const instruction = "[{\n\t\"decision\": " + "\"" + this.ruleCreate.instructions + "\"" + "\n}]"; + this.ruleCreate.instructions = JSON.parse(instruction); + this.ruleCreate.meta_rule_id = this.metarule.id; + this.ruleCreate.policy_id = this.policy.id; + for (let i = 0; i < this.attributes.length; i++) { + this.ruleCreate.rule.push("attributes:" + this.attributes[i]); + } + + PolicyService.addRuleToPolicy(this.policy, this.ruleCreate); + this.close(); + }, + dataCreated(wrapper, data, index) { + wrapper.data.push(data); + this.$set(this.ruleCreate.rule, index, data.id); + }, + close() { + this.$emit("close"); + } + } +}; +</script> diff --git a/dashboard/src/components/policy/DataList.vue b/dashboard/src/components/policy/DataList.vue new file mode 100644 index 00000000..40f384f3 --- /dev/null +++ b/dashboard/src/components/policy/DataList.vue @@ -0,0 +1,16 @@ +<template> + <span> + <span v-for="(data, index) in list" :key="data.id"> + <span v-if="index > 0">,</span> + <span>{{ data.name }}</span> + </span> + </span> +</template> + +<script> +export default { + props: { + list: Array + } +} +</script>
\ No newline at end of file diff --git a/dashboard/src/components/policy/FilterRules.vue b/dashboard/src/components/policy/FilterRules.vue new file mode 100644 index 00000000..5d685952 --- /dev/null +++ b/dashboard/src/components/policy/FilterRules.vue @@ -0,0 +1,24 @@ +<template> + <div class="form-inline"> + <input + type="search" + class="form-control filter col mr-auto" + placeholder="Filter rules" + v-bind:value="value" + v-on:input="$emit('input', $event.target.value)" + > + </div> +</template> + +<script> + export default { + name: "FilterRules", + props: { + value: String + } + } +</script> + +<style scoped> + +</style>
\ No newline at end of file diff --git a/dashboard/src/components/policy/Policy.vue b/dashboard/src/components/policy/Policy.vue new file mode 100644 index 00000000..415f6b02 --- /dev/null +++ b/dashboard/src/components/policy/Policy.vue @@ -0,0 +1,186 @@ +<template> + <div> + <template v-if="edit"> + <form> + <div class="form-group" > + <label for="policyName">Name</label> + <input + type="text" + name="name" + v-model="policyEdit.name" + v-validate="'alpha_dash|required|min:3'" + class="form-control" + id="policyName" + /> + </div> + <div class="form-group"> + <label for="policyDescription">Description</label> + <textarea + name="description" + v-model="policyEdit.description" + v-validate="'required|min:3'" + class="form-control" + ></textarea> + </div> + <div class="form-group"> + <label for="policyGenre">Genre</label> + <select + v-model="policyEdit.genre" + class="form-control" + id="policyGenre" + v-validate.initial="'required'" + name="genre" + > + <option>admin</option> + <option>authz</option> + </select> + </div> + <ul> + <li v-for="error in errors.all()" :key="error.id">{{ error }}</li> + </ul> + <button type="button" class="btn btn-secondary" @click="edit = false">Cancel</button> + <span> </span> + <button + type="button" + :disabled="errors.any()" + class="btn btn-primary" + @click="updatePolicy()" + >Update</button> + </form> + </template> + <template v-else> + <h3 class="list-group-item-heading inline " >{{ policy.name }}</h3> + <div class="pull-right"> + <button + type="button" + class="fa fa-trash btn-dark btn-sm" + title="Remove Policy" + @click="removePolicy()" + ></button> + <button + type="button" + class="fa fa-edit btn-dark btn-sm" + title="Edit Policy" + @click="updatingPolicy()" + ></button> + </div> + <p class="list-group-item-text">{{ policy.description }}</p> + + <unused-data :policy="policy" v-if="showAlert" @close="allowAlert = false"></unused-data> + + <details class="list-group-item-text"> + <summary @click="populatePolicy()"> + <h4 class="inline-block width-200"> + Rules + <i class="fa fa-question-circle" style="margin-left: 2%" v-if="policyHelpStrings.rules" data-toggle="tooltip" :title="policyHelpStrings.rules"></i> + </h4> + <button + type="button" + class="fa fa-plus btn-dark btn-sm" + @click="creatingRule = true" + title="Add Rule" + ></button> + </summary> + <create-rule v-if="creatingRule" @close="creatingRule = false" :policy="policy"></create-rule> + <div class="list-group" v-else> + <filter-rules v-model="filter" ></filter-rules> + <br/> + <p v-if="!policy.rulesPopulated" class="list-group-item-text">Loading rules...</p> + <div v-else> + <rule + v-for="rule in filteredRules" + :key="rule.id" + :rule="rule" + :selected="selectedRule == rule" + :policy="policy" + @show="selectRule($event)" + ></rule> + </div> + </div> + </details> + </template> + <hr /> + </div> +</template> + +<script> +import UnusedData from "./UnusedData.vue"; +import PolicyService from "./../../services/Policy.service.js"; +import util from "./../../services/Util.service.js"; +import Rule from "./Rule.vue"; +import CreateRule from "./CreateRule.vue"; +import FilterRules from "./FilterRules.vue"; + +import Vue from "vue"; +import helpstrings from "../../helpstrings"; + +var selectedRule = new Vue({data: {rule: null}}); + +export default { + props: { + policy: Object + }, + data() { + return { + filter: "", + edit: false, + creatingRule: false, + allowAlert: true, + policyEdit: {}, + policyHelpStrings: {} + }; + }, + mounted() { + this.policyHelpStrings = helpstrings.policy; + }, + computed: { + filteredRules() { + let filteredRules = PolicyService.filterByRules(this.policy.rules, this.filter); + + + return filteredRules; + }, + selectedRule() { + return selectedRule.rule; + }, + showAlert() { + return ( + this.allowAlert && + (this.policy.unusedSubjectData.length || + this.policy.unusedObjectData.length || + this.policy.unusedActionData.length) + ); + }, + }, + components: { + UnusedData, + Rule, + CreateRule, + FilterRules + }, + methods: { + populatePolicy() { + PolicyService.populatePolicy(this.policy); + }, + removePolicy() { + if ( + confirm( + "Are you sure to delete this Policy? (Associated perimeter, data an PDP will be deleted too)" + ) + ) + PolicyService.removePolicy(this.policy); + }, + updatingPolicy() { + this.policyEdit = util.clone(this.policy); + this.edit = true; + }, + updatePolicy() { + this.edit = false; + PolicyService.updatePolicy(this.policyEdit); + }, + selectRule(rule) { + selectedRule.rule = rule; + } + } +}; +</script>
\ No newline at end of file diff --git a/dashboard/src/components/policy/Rule.vue b/dashboard/src/components/policy/Rule.vue new file mode 100644 index 00000000..c8889b86 --- /dev/null +++ b/dashboard/src/components/policy/Rule.vue @@ -0,0 +1,231 @@ +<template> + <div class="list-group-item"> + <details > + <summary class="list-group-item-heading" :style="ruleIsGrant ? 'background-color: var(--green)' : 'background-color: Tomato'" > + + <b>Rule: </b> + <data-list :list="rule.subjectData"></data-list> | + <data-list :list="rule.actionData"></data-list> | + <data-list :list="rule.objectData"></data-list> + <span v-if="rule.attributeData.length"> | <data-list :list="rule.attributeData"></data-list></span> + <div class="pull-right" style="background-color: white"> + <button + type="button" + class="fa fa-trash pull-right btn-dark btn-sm" + @click="removeRuleFromPolicy()" + title="Remove Rule" + ></button> + <button type="button" + :class="ruleIsGrant ? buttonRuleIsGrant : buttonRuleIsDeny" + @click="changeRuleDecision()" + title="Change Decision" + ></button> + </div> + </summary> + <div > + <p class="list-group-item-text"></p> + <table class="table"> + <thead> + <tr> + <th> + <span> + Subjects data + <i class="fa fa-question-circle" v-if="ruleHelpStrings.subjectsData" data-toggle="tooltip" :title="ruleHelpStrings.subjectsData"></i> + </span> + </th> + <th> + <span> + Objects data + <i class="fa fa-question-circle" v-if="ruleHelpStrings.objectsData" data-toggle="tooltip" :title="ruleHelpStrings.objectsData"></i> + </span> + </th> + <th> + <span> + Actions data + <i class="fa fa-question-circle" v-if="ruleHelpStrings.actionsData" data-toggle="tooltip" :title="ruleHelpStrings.actionsData"></i> + </span> + </th> + <th v-if="rule.attributeData.length"> + <span> + Attributes + <i class="fa fa-question-circle" v-if="ruleHelpStrings.attributes" data-toggle="tooltip" :title="ruleHelpStrings.attributes"></i> + </span> + </th> + <th> + <span> + Instructions + <i class="fa fa-question-circle" v-if="ruleHelpStrings.instructions" data-toggle="tooltip" :title="ruleHelpStrings.instructions"></i> + </span> + </th> + </tr> + </thead> + <tbody> + <tr> + <td> + <p + v-for="data in rule.subjectData" + :key="data.id" + :class="{'selected-data': selectedData == data}" + > + <span :title="data.description">{{ data.name }}</span> + <button + v-if="selectedData != data" + type="button" + class="fa fa-exchange pull-right btn-dark btn-sm" + @click="assignData('subject', data)" + title="Assign to perimeters" + ></button> + <button + v-if="selectedData == data" + type="button" + class="fa fa-times pull-right btn-dark btn-sm" + @click="unassignData()" + title="Close" + ></button> + </p> + </td> + <td> + <p + v-for="data in rule.objectData" + :key="data.id" + :class="{'selected-data': selectedData == data}" + > + <span :title="data.description">{{ data.name }}</span> + <button + v-if="selectedData != data" + type="button" + class="fa fa-exchange pull-right btn-dark btn-sm" + @click="assignData('object', data)" + title="Assign to perimeters" + ></button> + <button + v-if="selectedData == data" + type="button" + class="fa fa-times pull-right btn-dark btn-sm" + @click="unassignData()" + title="Close" + ></button> + </p> + </td> + <td> + <p + v-for="data in rule.actionData" + :key="data.id" + :class="{'selected-data': selectedData == data}" + > + <span :title="data.description">{{ data.name }}</span> + <button + v-if="selectedData != data" + type="button" + class="fa fa-exchange pull-right btn-dark btn-sm" + @click="assignData('action', data)" + title="Assign to perimeters" + ></button> + <button + v-if="selectedData == data" + type="button" + class="fa fa-times pull-right btn-dark btn-sm" + @click="unassignData()" + title="Close" + ></button> + </p> + </td> + <td v-if="rule.attributeData.length"> + <p + v-for="data in rule.attributeData" + :key="data.id" + :class="{'selected-data': selectedData == data}" + > + <span :title="data.description">{{data.id}} : {{ data.name }}</span> + </p> + </td> + <td> + <pre><code>{{rule.instructions}}</code></pre> + </td> + </tr> + </tbody> + </table> + <assign-perimeter :policy="policy" :dataToAssign="dataToAssign"></assign-perimeter> + + </div> + </details> + </div> +</template> + +<script> + import AttributeService from "./../../services/Attribute.service" + import PolicyService from "./../../services/Policy.service.js"; + import DataList from "./DataList.vue"; + import AssignPerimeter from "./AssignPerimeter.vue"; + import helpstrings from "../../helpstrings"; + + export default { + props: { + rule: Object, + selected: Boolean, + policy: Object + }, + data() { + return { + dataToAssign: { + selectedData: null, + selectedDataType: "", + }, + selectedData: null, + ruleIsGrant: false, + buttonRuleIsGrant: 'fa fa-toggle-on pull-right btn-dark btn-sm', + buttonRuleIsDeny: 'fa fa-toggle-off pull-right btn-dark btn-sm', + ruleHelpStrings: {} + + } + }, + components: { + DataList, + AssignPerimeter, + }, + created() { + AttributeService.initialize(); + }, + mounted() { + this.ruleIsGrant = ("grant".localeCompare(this.rule.instructions[0].decision) == 0); + this.ruleHelpStrings = helpstrings.rule; + }, + watch: { + selected() { + if (!this.selected) + this.unassignData(); + } + }, + methods: { + changeRuleDecision() { + var decision = ("grant".localeCompare(this.rule.instructions[0].decision) == 0) ? "deny" : "grant"; + PolicyService.updateRule(this.policy, this.rule, decision).then(res => { + this.ruleIsGrant = !this.ruleIsGrant; + this.rule.instructions = res; + }); + + }, + showRule() { + this.$emit("show", this.selected ? null : this.rule); + }, + removeRuleFromPolicy() { + if (confirm("Are you sure to delete this Rule?")) + PolicyService.removeRuleFromPolicy(this.policy, this.rule); + }, + assignData(type, data) { + this.dataToAssign = { + selectedData: data, + selectedDataType: type, + }; + this.selectedData = data; + }, + unassignData() { + this.assignData("", null); + } + } + }; +</script> + +<style scoped> + +</style>
\ No newline at end of file diff --git a/dashboard/src/components/policy/UnusedData.vue b/dashboard/src/components/policy/UnusedData.vue new file mode 100644 index 00000000..48b8b642 --- /dev/null +++ b/dashboard/src/components/policy/UnusedData.vue @@ -0,0 +1,89 @@ +<template> + <div> + <div + v-if="policy.unusedSubjectData.length + || policy.unusedObjectData.length + || policy.unusedActionData.length" + class="alert alert-dismissable alert-warning" + > + <button type="button" class="close" data-dismiss="alert" @click="showUnused = false; $emit('close')">×</button> + <h4>Warning!</h4> + <p> + Some data are unused, please check them and delete them if necessary. + <a + href + @click.prevent="showUnused = true" + v-show="!showUnused" + >Show unused data</a> + <a href @click.prevent="showUnused = false" v-show="showUnused">Hide unused data</a> + </p> + </div> + + <div v-if="showUnused" class="row overflow-hidden mb-3"> + <div class="list-group col" v-if="policy.unusedSubjectData.length"> + <h3 class="list-group-item active">Unused Subject data</h3> + <div v-for="subject in policy.unusedSubjectData" :key="subject.id" class="list-group-item"> + <h4 class="list-group-item-heading inline" :title="subject.description">{{ subject.name }}</h4> + <button + type="button" + class="fa fa-trash pull-right btn-dark btn-sm" + @click="removeData('subject', policy, subject)" + title="Remove Subject data" + ></button> + </div> + </div> + + <div class="list-group col" v-if="policy.unusedObjectData.length"> + <h3 class="list-group-item active">Unused Object data</h3> + <div v-for="object in policy.unusedObjectData" :key="object.id" class="list-group-item"> + <h4 class="list-group-item-heading inline" :title="object.description">{{ object.name }}</h4> + <button + type="button" + class="fa fa-trash pull-right btn-dark btn-sm" + @click="removeData('object', policy, object)" + title="Remove Object data" + ></button> + </div> + </div> + + <div class="list-group col" v-if="policy.unusedActionData.length"> + <h3 class="list-group-item active">Unused Action data</h3> + <div v-for="action in policy.unusedActionData" :key="action.id" class="list-group-item"> + <h4 class="list-group-item-heading inline" :title="action.description">{{ action.name }}</h4> + <button + type="button" + class="fa fa-trash pull-right btn-dark btn-sm" + @click="removeData('action', policy, action)" + title="Remove Action data" + ></button> + </div> + </div> + </div> + </div> +</template> + +<script> +import PolicyService from "./../../services/Policy.service.js"; + + +export default { + props: { + policy: Object + }, + data() { + return { + showUnused: false + }; + }, + methods: { + removeData(type, policy, data) { + if ( + confirm( + "Are you sure to delete this Data? (Associated assignments and rules will be deleted too)" + ) + ) + PolicyService.removeData(type, policy, data); + } + } +}; +</script>
\ No newline at end of file diff --git a/dashboard/src/config.js b/dashboard/src/config.js new file mode 100644 index 00000000..2849ec7d --- /dev/null +++ b/dashboard/src/config.js @@ -0,0 +1,4 @@ +export default { + // Moon Manager URL + host: 'http://127.0.0.1:8000', +}
\ No newline at end of file diff --git a/dashboard/src/helpstrings.js b/dashboard/src/helpstrings.js new file mode 100644 index 00000000..63751e78 --- /dev/null +++ b/dashboard/src/helpstrings.js @@ -0,0 +1,25 @@ +export default { + model: { + metarule: 'model.metarule' + }, + metarule: { + subject: 'metarule.subject', + object: 'meterule.object', + action: 'meterule.action' + }, + policy:{ + rules: 'policy.rules' + }, + assignPerimeter:{ + allPerimeters: 'assignPerimeter.allPerimeters', + policyPerimeters: 'assignPerimeter.policyPerimeters', + assignedPerimeters: 'assignPerimeter.assignedPerimeters' + }, + rule:{ + subjectsData: 'rule.subjectsData', + objectsData: 'rule.objectsData', + actionsData: 'rule.actionsData', + attributes: 'rule.attributes', + instructions: 'rule.instructions', + } +}
\ No newline at end of file diff --git a/dashboard/src/main.js b/dashboard/src/main.js new file mode 100644 index 00000000..e18e3ebf --- /dev/null +++ b/dashboard/src/main.js @@ -0,0 +1,58 @@ +import Vue from 'vue' +import App from './App.vue' +import router from './router' +import VueResource from 'vue-resource' +import VeeValidate from 'vee-validate' +import Toasted from 'vue-toasted' + +Vue.config.productionTip = false + +Vue.use(VueResource) +Vue.use(VeeValidate) +Vue.use(Toasted) + +Vue.http.interceptors.push(function () { + return function (response) { + if (response.status == 401) { + router.push('auth'); + } else if (response.status == 0) { + router.push('error'); + } + } +}); + +var authKey = localStorage.getItem("auth-key") +if (authKey) { + Vue.http.headers.common['x-api-key'] = authKey; +} else { + router.push('auth'); +} + +Vue.toasted.register('toast', + (payload) => { + return ` + <div class="toast show" role="alert" aria-live="assertive" aria-atomic="true"> + <div class="toast-header"> + <span class="badge badge-${payload.type}"> </span> + <strong class="ml-2">${payload.title}</strong> + </div> + <div class="toast-body"> + ${payload.message} + </div> + </div> + `; + }, + + { + className: "toast-background", + position: 'top-center', + duration: 3000 + }) + + +new Vue({ + router, + render: h => h(App) +}).$mount('#app') + + diff --git a/dashboard/src/router.js b/dashboard/src/router.js new file mode 100644 index 00000000..f05d9515 --- /dev/null +++ b/dashboard/src/router.js @@ -0,0 +1,56 @@ +import Vue from 'vue' +import Router from 'vue-router' +import Models from './views/Models.vue' +import Auth from './views/Auth.vue' +import Rules from './views/Rules.vue' +import PDP from './views/Pdps.vue' +import Admin from './views/Admin.vue' +import ErrorPage from './views/Error.vue' +import Assignments from "./views/Assignments"; + +Vue.use(Router) + +export default new Router({ + routes: [ + { + path: '/auth', + name: 'auth', + component: Auth + }, + { + path: '/', + redirect: { name: 'models' } + }, + { + path: '/models', + name: 'models', + component: Models + }, + { + path: '/rules', + name: 'rules', + component: Rules + }, + { + path: '/assignments', + name: 'assignments', + component: Assignments + }, + { + path: '/pdp', + name: 'pdp', + component: PDP + }, + { + path: '/admin', + name: 'admin', + component: Admin + }, + { + path: '/error', + name: 'error', + component: ErrorPage + }, + + ] +}) diff --git a/dashboard/src/services/Attribute.service.js b/dashboard/src/services/Attribute.service.js new file mode 100644 index 00000000..a00da8f1 --- /dev/null +++ b/dashboard/src/services/Attribute.service.js @@ -0,0 +1,46 @@ +import Vue from 'vue' +import config from '../config.js' +import util from './Util.service' + +var host = config.host; + +var attributeResource; + +var attributesMap = {}; +var attributes = []; + +function loadAttributes(){ + attributeResource = Vue.resource(host + '/attributes{/id}', {}); + + attributeResource.query().then(res => { + createAttributes(res.body); + }, util.displayErrorFunction('Unable to load attributes')); +} + +function createAttributes(attributesData){ + attributes.splice(0, attributes.length); + util.cleanObject(attributesMap); + util.createInternal(attributesData.attributes, attributes, attributesMap); +} + +function getAttribute(id){ + return attributesMap[id]; +} + +function getAttributeId(name){ + for (let i = 0; i < attributes.length; i++){ + let attr = attributes[i]; + for (let j = 0; j < attr.values.length; j++){ + let value = attr.values[j]; + if (value === name){ + return attr.id; + } + } + } +} + +export default { + initialize: loadAttributes, + getAttribute: getAttribute, + getAttributeId: getAttributeId +}
\ No newline at end of file diff --git a/dashboard/src/services/Import.service.js b/dashboard/src/services/Import.service.js new file mode 100644 index 00000000..f2cc77ff --- /dev/null +++ b/dashboard/src/services/Import.service.js @@ -0,0 +1,16 @@ +import Vue from 'vue' +import util from './Util.service.js' +import config from '../config.js' + +var host = config.host; + +export default { + importData: async function importData(data) { + var importResource = Vue.resource(host + '/import/', {}); + + return importResource.save(null, data).then(success, util.displayErrorFunction('Unable to import data')); + function success() { + util.displaySuccess('Data imported'); + } + } +}
\ No newline at end of file diff --git a/dashboard/src/services/Model.service.js b/dashboard/src/services/Model.service.js new file mode 100644 index 00000000..b9b96743 --- /dev/null +++ b/dashboard/src/services/Model.service.js @@ -0,0 +1,351 @@ +import Vue from 'vue' +import util from './Util.service.js' +import config from '../config.js' + +var host = config.host; + +var modelResource; +var metaRuleResource; +var subjectCategoryResource; +var objectCategoryResource; +var actionCategoryResource; +var attributesResource; + +var modelsMap = {}; +var metaRulesMap = {}; +var subjectCategoriesMap = {}; +var objectCategoriesMap = {}; +var actionCategoriesMap = {}; +var attributesMap = {}; +var models = []; +var metaRules = []; +var orphanMetaRules = []; +var subjectCategories = []; +var objectCategories = []; +var actionCategories = []; +var attributes = []; +var orphanSubjectCategories = []; +var orphanObjectCategories = []; +var orphanActionCategories = []; + +var categoryMap = { + 'subject': { + resource: subjectCategoryResource, + map: subjectCategoriesMap, + list: subjectCategories, + listName: 'subject_categories' + }, + 'object': { + resource: objectCategoryResource, + map: objectCategoriesMap, + list: objectCategories, + listName: 'object_categories' + }, + 'action': { + resource: actionCategoryResource, + map: actionCategoriesMap, + list: actionCategories, + listName: 'action_categories' + }, + 'attribute' : { + resource: attributesResource, + map: attributesMap, + list: attributes, + listName: 'attributes' + } +} + + +function loadModels() { + modelResource = Vue.resource(host + '/models{/id}', {}, {patch: {method: 'PATCH'}}); + metaRuleResource = Vue.resource(host + '/meta_rules{/id}', {}, {patch: {method: 'PATCH'}}); + categoryMap['subject'].resource = subjectCategoryResource = Vue.resource(host + '/subject_categories{/id}'); + categoryMap['object'].resource = objectCategoryResource = Vue.resource(host + '/object_categories{/id}'); + categoryMap['action'].resource = actionCategoryResource = Vue.resource(host + '/action_categories{/id}'); + categoryMap['attribute'].resource = attributesResource = Vue.resource(host + '/attributes{/id}'); + var queries = [ + modelResource.query(), + metaRuleResource.query(), + subjectCategoryResource.query(), + objectCategoryResource.query(), + actionCategoryResource.query(), + attributesResource.query() + ] + + var result = Promise.all(queries).then(function (result) { + createModels(result[0].body, result[1].body, result[2].body, result[3].body, result[4].body, result[5].body) + }) + + return result; +} + +function createModels(modelsData, metarulesData, subjectCategoriesData, objectCategoriesData, actionCategoriesData, attributesData) { + util.cleanObject(modelsMap); + util.cleanObject(metaRulesMap); + util.cleanObject(subjectCategoriesMap); + util.cleanObject(objectCategoriesMap); + util.cleanObject(actionCategoriesMap); + util.cleanObject(attributesMap); + models.splice(0, models.length); + metaRules.splice(0, metaRules.length); + subjectCategories.splice(0, subjectCategories.length); + objectCategories.splice(0, objectCategories.length); + actionCategories.splice(0, actionCategories.length); + attributes.splice(0, attributes.length); + if (subjectCategoriesData.subject_categories) createCategoryInternal('subject', subjectCategoriesData.subject_categories); + if (objectCategoriesData.object_categories) createCategoryInternal('object', objectCategoriesData.object_categories); + if (actionCategoriesData.action_categories) createCategoryInternal('action', actionCategoriesData.action_categories); + if (attributesData.attributes) createCategoryInternal('attribute', attributesData.attributes); + if (metarulesData.meta_rules) createMetaRuleInternal(metarulesData.meta_rules); + if (modelsData.models) createModelInternal(modelsData.models); + + updateOrphan(); +} + +function mapModel(model) { + util.mapIdToItem(model.meta_rules, metaRulesMap); +} + +function createModelInternal(data) { + return util.createInternal(data, models, modelsMap, mapModel); +} + +function updateModelInternal(data) { + return util.updateInternal(data, modelsMap, mapModel); +} + +function removeModelInternal(id) { + return util.removeInternal(id, models, modelsMap); +} + +function mapIdToItemWithAttributes(categories, categoriesMap, attributes, attributesMap) { + let categoriesToRemove = [] + if (categories) { + var index2 = 0; + for (var index = 0; index < categories.length; index++) { + var id = categories[index]; + if (categoriesMap[id]) + categories[index] = categoriesMap[id]; + else { + if(id.includes('attributes:')){ + const newId = id.split(':')[1]; + if (attributesMap[newId]){ + attributes[index2++] = attributesMap[newId]; + } + categoriesToRemove.push(index) + } + } + } + for (let i = categoriesToRemove.length - 1; i >= 0; i--){ + categories.splice(categoriesToRemove[i], 1); + } + } +} + +function cleanCategoryByRemovingAttributes(categories, attributesMap){ + let categoriesToRemove = [] + if (categories) { + for (let index = 0; index < categories.length; index++){ + var category = categories[index]; + if (attributesMap[category.name]){ + categoriesToRemove.push(index) + } + } + for (let i = categoriesToRemove.length - 1; i >= 0; i--){ + categories.splice(categoriesToRemove[i], 1); + } + } +} + +function mapMetaRule(metaRule) { + metaRule.subjectAttributes = []; + metaRule.objectAttributes = []; + metaRule.actionAttributes = []; + mapIdToItemWithAttributes(metaRule.subject_categories, subjectCategoriesMap, metaRule.subjectAttributes, attributesMap); + mapIdToItemWithAttributes(metaRule.object_categories, objectCategoriesMap, metaRule.objectAttributes,attributesMap); + mapIdToItemWithAttributes(metaRule.action_categories, actionCategoriesMap, metaRule.actionAttributes, attributesMap); + cleanCategoryByRemovingAttributes(subjectCategories, attributesMap); + cleanCategoryByRemovingAttributes(objectCategories, attributesMap); + cleanCategoryByRemovingAttributes(actionCategories, attributesMap); +} + +function createMetaRuleInternal(data) { + return util.createInternal(data, metaRules, metaRulesMap, mapMetaRule); +} + +function updateMetaRuleInternal(data) { + return util.updateInternal(data, metaRulesMap, mapMetaRule); +} + +function removeMetaRuleInternal(id) { + return util.removeInternal(id, metaRules, metaRulesMap); +} + +function createCategoryInternal(type, data) { + var categoryValue = categoryMap[type]; + + return util.createInternal(data, categoryValue.list, categoryValue.map) +} + +function removeCategoryInternal(type, id) { + var categoryValue = categoryMap[type]; + return util.removeInternal(id, categoryValue.list, categoryValue.map); +} + +function updateOrphan() { + updateOrphanInternal(metaRules, orphanMetaRules, models, "meta_rules"); + updateOrphanInternal(subjectCategories, orphanSubjectCategories, metaRules, "subject_categories"); + updateOrphanInternal(objectCategories, orphanObjectCategories, metaRules, "object_categories"); + updateOrphanInternal(actionCategories, orphanActionCategories, metaRules, "action_categories"); +} + +function updateOrphanInternal(list, orphanList, parentList, childListName) { + orphanList.splice(0, orphanList.length); + util.pushAll(orphanList, list); + for (var i = 0; i < parentList.length; i++) { + var parent = parentList[i]; + var children = parent[childListName]; + if (children) { + for (var j = 0; j < children.length; j++) { + var child = children[j]; + if (child){ + var notOrphanIndex = util.indexOf(orphanList, "id", child.id); + if (notOrphanIndex >= 0) { + orphanList.splice(notOrphanIndex, 1); + } + else{ + for (var k = 0; k < attributes.length; k++){ + const attr = attributes[k]; + notOrphanIndex = util.indexOf(orphanList, "name", attr.id); + if (notOrphanIndex >= 0) { + orphanList.splice(notOrphanIndex, 1); + } + } + } + + } + } + } + } +} + +function getAttributesForModelId(modelId) { + let model = modelsMap[modelId]; + let attrs = [] + + for (let i = 0; i < model.meta_rules.length; i++){ + let metaRule = model.meta_rules[i]; + let j; + for (j = 0; j < metaRule.subjectAttributes.length; j++){ + attrs.push(metaRule.subjectAttributes[j]) + } + for (j = 0; j < metaRule.objectAttributes.length; j++){ + attrs.push(metaRule.objectAttributes[j]) + } + for (j = 0; j < metaRule.actionAttributes.length; j++){ + attrs.push(metaRule.actionAttributes[j]) + } + } + return attrs; +} + +export default { + initialize: loadModels, + createModels: createModels, + models: models, + metaRules: metaRules, + orphanMetaRules: orphanMetaRules, + orphanSubjectCategories: orphanSubjectCategories, + orphanObjectCategories: orphanObjectCategories, + orphanActionCategories: orphanActionCategories, + subjectCategories: subjectCategories, + objectCategories: objectCategories, + actionCategories: actionCategories, + getModel: function getModel(id) { + return modelsMap[id]; + }, + getAttributesForModelId: getAttributesForModelId, + createModel: function createModel(model) { + model.meta_rules = []; + modelResource.save(null, model).then(success, util.displayErrorFunction('Unable to create model')); + + function success(data) { + createModelInternal(data.body.models); + util.displaySuccess('Model created'); + } + }, + removeModel: function removeModel(model) { + modelResource.remove({ id: model.id }).then(success, util.displayErrorFunction('Unable to remove model')); + + function success() { + removeModelInternal(model.id); + updateOrphan(); + util.displaySuccess('Model removed'); + } + }, + updateModel: function updateModel(model) { + util.mapItemToId(model.meta_rules) + modelResource.patch({ id: model.id }, model).then(success, util.displayErrorFunction('Unable to update model')); + + function success(data) { + updateModelInternal(data.body.models); + + updateOrphan(); + util.displaySuccess('Model updated'); + } + }, + getMetaRule: function getMetaRule(id) { + return metaRulesMap[id]; + }, + createMetaRule: function createMetaRule(metaRule) { + metaRule.subject_categories = []; + metaRule.object_categories = []; + metaRule.action_categories = []; + + return metaRuleResource.save(null, metaRule).then(function (data) { + util.displaySuccess('Meta Rule created'); + return createMetaRuleInternal(data.body.meta_rules)[0]; + }, util.displayErrorFunction('Unable to create meta rule')) + }, + updateMetaRule: function updateMetaRule(metaRule) { + util.mapItemToId(metaRule.subject_categories); + util.mapItemToId(metaRule.object_categories); + util.mapItemToId(metaRule.action_categories); + metaRuleResource.patch({ id: metaRule.id }, metaRule).then(success, util.displayErrorFunction('Unable to update meta rule')); + + function success(data) { + updateMetaRuleInternal(data.body.meta_rules); + updateOrphan(); + util.displaySuccess('Meta Rule updated'); + } + }, + removeMetaRule: function removeMetaRule(metaRule) { + metaRuleResource.remove({ id: metaRule.id }).then(success, util.displayErrorFunction('Unable to remove meta rule')); + + function success() { + removeMetaRuleInternal(metaRule.id); + updateOrphan(); + util.displaySuccess('Meta Rule removed'); + } + }, + getCategory: function getCategory(type, id) { + return categoryMap[type].map[id]; + }, + createCategory: function createCategory(type, category) { + var categoryValue = categoryMap[type]; + return categoryValue.resource.save(null, category).then(function (data) { + util.displaySuccess('Category created'); + return createCategoryInternal(type, data.body[categoryValue.listName])[0]; + }, util.displayErrorFunction('Unable to create category')) + }, + removeCategory: function removeCategory(type, category) { + var categoryValue = categoryMap[type]; + categoryValue.resource.remove({ id: category.id }).then(success, util.displayErrorFunction('Unable to remove category')); + + function success() { + removeCategoryInternal(type, category.id); + updateOrphan(); + util.displaySuccess('Category removed'); + } + }, +}
\ No newline at end of file diff --git a/dashboard/src/services/Pdp.service.js b/dashboard/src/services/Pdp.service.js new file mode 100644 index 00000000..e0c286a0 --- /dev/null +++ b/dashboard/src/services/Pdp.service.js @@ -0,0 +1,92 @@ +import Vue from 'vue' +import util from './Util.service.js' +import config from '../config.js' + +var host = config.host; + +var pdpResource; +var policyResource; + +var pdpsMap = {}; +var pdps = []; +var policiesMap = {}; +var policies = []; + +function loadPdps() { + pdpResource = Vue.resource(host + '/pdp{/id}', {}, {patch: {method: 'PATCH'}}); + policyResource = Vue.resource(host + '/policies{/id}', {}); + + var queries = [ + pdpResource.query(), + policyResource.query(), + ] + Promise.all(queries).then(function (result) { + createPdps(result[0].body, result[1].body) + }) + +} + +function createPdps(pdpsData, policiesData) { + pdps.splice(0, pdps.length); + policies.splice(0, policies.length); + util.cleanObject(pdpsMap); + util.cleanObject(policiesMap); + + util.createInternal(policiesData.policies, policies, policiesMap); + createPdpInternal(pdpsData.pdps); +} + +function mapPdp(pdp) { + util.mapIdToItem(pdp.security_pipeline, policiesMap); + pdp.project = pdp.vim_project_id; +} + +function createPdpInternal(data) { + return util.createInternal(data, pdps, pdpsMap, mapPdp); +} + +function updatePdpInternal(data) { + return util.updateInternal(data, pdpsMap, mapPdp); +} + +function removePdpInternal(id) { + return util.removeInternal(id, pdps, pdpsMap); +} + +export default { + initialize: loadPdps, + createPdps: createPdps, + pdps: pdps, + policies: policies, + createPdp: function createPdp(pdp) { + pdpResource.save(null, pdp).then(success, util.displayErrorFunction('Unable to create PDP')); + + function success(data) { + createPdpInternal(data.body.pdps); + util.displaySuccess('PDP created'); + } + }, + removePdp: function removePdp(pdp) { + pdpResource.remove({ id: pdp.id }).then(success, util.displayErrorFunction('Unable to remove PDP')); + + function success() { + removePdpInternal(pdp.id); + util.displaySuccess('PDP removed'); + } + }, + updatePdp: function updatePdp(pdp) { + util.mapItemToId(pdp.security_pipeline); + pdp.vim_project_id = pdp.project; + pdpResource.patch({ id: pdp.id }, pdp).then(success, util.displayErrorFunction('Unable to update PDP')); + + function success(data) { + updatePdpInternal(data.body.pdps) + util.displaySuccess('PDP updated'); + } + }, + getPolicy: function getPolicy(id) { + return policiesMap[id]; + }, +} + + diff --git a/dashboard/src/services/Policy.service.js b/dashboard/src/services/Policy.service.js new file mode 100644 index 00000000..433680e9 --- /dev/null +++ b/dashboard/src/services/Policy.service.js @@ -0,0 +1,470 @@ +import Vue from 'vue' +import util from './Util.service.js' +import ModelService from './Model.service.js' +import AttributeService from './Attribute.service' +import config from '../config.js' + +var host = config.host; + +var policyResource; +var policyRulesResource; + +var categoryMap = { + 'subject': { + arrayName: "subjectData", + mapName: "subjectDataMap", + responseName: "subject_data", + perimeterResponseName: "subjects", + assignmentResponseName: "subject_assignments", + unusedArrayName: "unusedSubjectData", + }, + 'object': { + arrayName: "objectData", + mapName: "objectDataMap", + responseName: "object_data", + perimeterResponseName: "objects", + assignmentResponseName: "object_assignments", + unusedArrayName: "unusedObjectData", + }, + 'action': { + arrayName: "actionData", + mapName: "actionDataMap", + responseName: "action_data", + perimeterResponseName: "actions", + assignmentResponseName: "action_assignments", + unusedArrayName: "unusedActionData", + } +} + +var policiesMap = {}; +var policies = []; + +function loadPolicies() { + policyResource = Vue.resource(host + '/policies{/id}', {}, {patch: {method: 'PATCH'}}); + categoryMap['subject'].policyPerimeterResource = Vue.resource(host + '/policies{/policy_id}/subjects{/perimeter_id}', {}); + categoryMap['object'].policyPerimeterResource = Vue.resource(host + '/policies{/policy_id}/objects{/perimeter_id}', {}, ); + categoryMap['action'].policyPerimeterResource = Vue.resource(host + '/policies{/policy_id}/actions{/perimeter_id}', {}, ); + categoryMap['subject'].perimeterResource = Vue.resource(host + '/subjects{/perimeter_id}', {}, {patch: {method: 'PATCH'}}); + categoryMap['object'].perimeterResource = Vue.resource(host + '/objects{/perimeter_id}', {}, {patch: {method: 'PATCH'}}); + categoryMap['action'].perimeterResource = Vue.resource(host + '/actions{/perimeter_id}', {}, {patch: {method: 'PATCH'}}); + categoryMap['subject'].assignmentResource = Vue.resource(host + '/policies{/policy_id}/subject_assignments{/perimeter_id}{/category_id}{/data_id}', {}, ); + categoryMap['object'].assignmentResource = Vue.resource(host + '/policies{/policy_id}/object_assignments{/perimeter_id}{/category_id}{/data_id}', {}, ); + categoryMap['action'].assignmentResource = Vue.resource(host + '/policies{/policy_id}/action_assignments{/perimeter_id}{/category_id}{/data_id}', {}, ); + var queries = [ + policyResource.query(), + ModelService.initialize(), + AttributeService.initialize() + ] + + Promise.all(queries).then(function (result) { + createPolicies(result[0].body); + }) +} + +function createPolicies(policiesData) { + policies.splice(0, policies.length); + util.cleanObject(policiesMap); + createPolicyInternal(policiesData.policies); +} + +function mapPolicy(policy) { + policy.rulesPopulated = false; + policy.rules = []; + policy.subjectData = []; + policy.objectData = []; + policy.actionData = []; + policy.unusedSubjectData = []; + policy.unusedObjectData = []; + policy.unusedActionData = []; + policy.attributes = []; + if (policy.model_id) { + policy.model = ModelService.getModel(policy.model_id); + policy.attributes = ModelService.getAttributesForModelId(policy.model_id); + } +} + +function createPolicyInternal(data) { + return util.createInternal(data, policies, policiesMap, mapPolicy); +} + +function removePolicyInternal(id) { + return util.removeInternal(id, policies, policiesMap); +} + +function updatePolicyInternal(data) { + return util.updateInternal(data, policiesMap, mapPolicy); +} + +function removeRuleInternal(policy, rule) { + policy.rules.splice(policy.rules.indexOf(rule), 1); + updateUnusedData(policy); +} + +function loadPolicyRule(policy) { + if (!policy.rulesPopulated) { + policyRulesResource = Vue.resource(host + '/policies{/policy_id}/rules{/rule_id}', {}, {patch: {method: 'PATCH'}}); + categoryMap['subject'].resource = Vue.resource(host + '/policies{/policy_id}/subject_data{/category_id}{/data_id}', {}); + categoryMap['object'].resource = Vue.resource(host + '/policies{/policy_id}/object_data{/category_id}{/data_id}', {}); + categoryMap['action'].resource = Vue.resource(host + '/policies{/policy_id}/action_data{/category_id}{/data_id}', {}); + var queries = [ + policyRulesResource.query({ policy_id: policy.id }), + categoryMap['subject'].resource.query({ policy_id: policy.id }), + categoryMap['object'].resource.query({ policy_id: policy.id }), + categoryMap['action'].resource.query({ policy_id: policy.id }) + ] + + Promise.all(queries).then(function (result) { + createRules(policy, result[0].body, result[1].body, result[2].body, result[3].body); + updateUnusedData(policy); + }, util.displayErrorFunction('Unable to load rules')) + } +} + +function updateUnusedData(policy) { + policy.unusedSubjectData.splice(0, policy.unusedSubjectData.length); + util.pushAll(policy.unusedSubjectData, policy.subjectData); + + policy.unusedObjectData.splice(0, policy.unusedObjectData.length); + util.pushAll(policy.unusedObjectData, policy.objectData); + + policy.unusedActionData.splice(0, policy.unusedActionData.length); + util.pushAll(policy.unusedActionData, policy.actionData); + + for (var i = 0; i < policy.rules.length; i++) { + var rule = policy.rules[i]; + removeUsedData(rule.subjectData, policy.unusedSubjectData); + removeUsedData(rule.objectData, policy.unusedObjectData); + removeUsedData(rule.actionData, policy.unusedActionData); + } +} + +function removeUsedData(list, orphanList) { + for (var j = 0; j < list.length; j++) { + var data = list[j]; + if (data) { + var notOrphanIndex = util.indexOf(orphanList, "id", data.id); + if (notOrphanIndex >= 0) { + orphanList.splice(notOrphanIndex, 1); + } + } + } +} + +function transformData(list) { + var result = {}; + for (var index = 0; index < list.length; index++) { + var data = list[index].data; + for (var key in data) { + if (data.hasOwnProperty(key)) { + result[key] = data[key]; + } + } + } + return result; +} + +function createRules(policy, rulesData, subjectsData, objectsData, actionsData) { + policy.rules = rulesData ? rulesData.rules.rules : []; + policy.subjectDataMap = transformData(subjectsData.subject_data); + policy.subjectData = util.mapToArray(policy.subjectDataMap); + policy.objectDataMap = transformData(objectsData.object_data); + policy.objectData = util.mapToArray(policy.objectDataMap); + policy.actionDataMap = transformData(actionsData.action_data); + policy.actionData = util.mapToArray(policy.actionDataMap); + for (var i = 0; i < policy.rules.length; i++) { + var rule = policy.rules[i]; + populateRule(policy, rule); + } + policy.rulesPopulated = true; +} + +function populateRule(policy, rule) { + if (rule.meta_rule_id) { + rule.metaRule = ModelService.getMetaRule(rule.meta_rule_id); + } + if (rule.metaRule) { + var j = 0; + var k, id; + rule.subjectData = []; + rule.objectData = []; + rule.actionData = []; + rule.attributeData = []; + + for (k = 0; k < rule.metaRule.subject_categories.length; k++) { + id = rule.rule[j + k]; + if (policy.subjectDataMap[id]) + rule.subjectData.push(policy.subjectDataMap[id]); + } + j += k; + for (k = 0; k < rule.metaRule.object_categories.length; k++) { + id = rule.rule[j + k]; + if (policy.objectDataMap[id]) + rule.objectData.push(policy.objectDataMap[id]); + } + j += k; + for (k = 0; k < rule.metaRule.action_categories.length; k++) { + id = rule.rule[j + k]; + if (policy.actionDataMap[id]) { + rule.actionData.push(policy.actionDataMap[id]); + } + } + + for (const value of rule.rule.values()){ + if (value.includes("attributes:")){ + let attrName = value.split(':')[1]; + let attrId = AttributeService.getAttributeId(attrName); + rule.attributeData.push({id: attrId, name: attrName}); + + } + } + } + return rule; +} + +function updateRule(policy, rule, decision){ + return new Promise(resolveUpdateRule => { + var body = { + "instructions":[ + {"decision": decision} + ] + }; + + policyRulesResource.patch({policy_id: policy.id, rule_id: rule.id}, body).then(success, util.displayErrorFunction('Unable to update Rule')); + + function success(data){ + resolveUpdateRule(data.body.rules[rule.id].instructions); + } + }); +} + +function filterRuleBySpecificItem(filteredRules, rule, items, filter){ + items.forEach(item => { + if (item){ + if (filter == null || item.name.indexOf(filter) >= 0){ + if (!(filteredRules.includes(rule))) + filteredRules.push(rule) + } + } + + }); +} + +function interArray(array1, array2){ + let inter = []; + + + for (let i = 0; i < array1.length; i++){ + for (let j = 0; j < array2.length; j++){ + if (array1[i] === array2[j]) + inter.push(array2[j]); + } + } + return inter; +} + + +function filterByRules(rules, filters){ + let filteredRules = []; + let filteredByWords = []; + + + if (filters === ""){ + filteredRules = rules; + } else { + filters = filters.split(' '); + filters.forEach( filter => { + if (filter !== "") { + let tmp = []; + rules.forEach((rule) => { + + filterRuleBySpecificItem(tmp, rule, rule.subjectData, filter); + filterRuleBySpecificItem(tmp, rule, rule.objectData, filter); + filterRuleBySpecificItem(tmp, rule, rule.actionData, filter); + filterRuleBySpecificItem(tmp, rule, rule.attributeData, filter); + + filteredByWords.push(tmp); + }); + + } + }); + + filteredRules = filteredByWords[0]; + for (let i = 1; i < filteredByWords.length; i++){ + filteredRules = interArray(filteredRules, filteredByWords[i]); + } + } + + return filteredRules; +} + +function policyRuleWithAttributes(policy){ + const meta_rules = policy.model.meta_rules; + + for (let i = 0; i < meta_rules.length; i++){ + const meta_rule = meta_rules[i]; + if (meta_rule.actionAttributes.length || meta_rule.subjectAttributes.length || meta_rule.objectAttributes.length) + return true; + } + return false; +} + +export default { + policyRuleWithAttributes: policyRuleWithAttributes, + filterByRules: filterByRules, + initialize: loadPolicies, + createPolicies: createPolicies, + policies: policies, + getPolicy: function getPolicy(id) { + return policiesMap[id]; + }, + createPolicy: function createPolicy(policy) { + policyResource.save(null, policy).then(success, util.displayErrorFunction('Unable to create Policy')); + + function success(data) { + createPolicyInternal(data.body.policies); + util.displaySuccess('Policy created'); + } + }, + removePolicy: function removePolicy(policy) { + policyResource.remove({ id: policy.id }, null).then(success, util.displayErrorFunction('Unable to remove Policy')); + + function success() { + removePolicyInternal(policy.id); + util.displaySuccess('Policy removed'); + } + }, + updatePolicy: function updatePolicy(policy) { + policyResource.patch({ id: policy.id }, policy).then(success, util.displayErrorFunction('Unable to update Policy')); + + function success(data) { + updatePolicyInternal(data.body.policies) + util.displaySuccess('Policy updated'); + } + }, + populatePolicy: loadPolicyRule, + createRules: createRules, + updateRule: updateRule, + addRuleToPolicy: function addRuleToPolicy(policy, rule) { + policyRulesResource.save({ policy_id: policy.id }, rule).then(success, util.displayErrorFunction('Unable to create Rule')); + + function success(data) { + var rules = util.mapToArray(data.body.rules); + for (var i = 0; i < rules.length; i++) { + var rule = rules[i]; + policy.rules.push(populateRule(policy, rule)) + } + util.displaySuccess('Rule created'); + updateUnusedData(policy); + } + }, + removeRuleFromPolicy: function removeRuleFromPolicy(policy, rule) { + policyRulesResource.remove({ policy_id: policy.id, rule_id: rule.id }, null).then(success, util.displayErrorFunction('Unable to remove Rule')); + + function success() { + removeRuleInternal(policy, rule); + util.displaySuccess('Rule removed'); + } + }, + createData: function createData(type, policy, categoryId, dataCategory) { + var categoryValue = categoryMap[type]; + return categoryValue.resource.save({ policy_id: policy.id, category_id: categoryId }, dataCategory).then( + function (data) { + var result = util.createInternal(data.body[categoryValue.responseName].data, policy[categoryValue.arrayName], policy[categoryValue.mapName]); + util.displaySuccess('Data created'); + util.pushAll(policy[categoryValue.unusedArrayName], result); + return result; + }, + util.displayErrorFunction('Unable to create Data') + ); + }, + removeData: function removeData(type, policy, data) { + var categoryValue = categoryMap[type]; + return categoryValue.resource.remove({ policy_id: policy.id, category_id: data.category_id, data_id: data.id }).then( + function () { + policy[categoryValue.arrayName].splice(policy.subjectData.indexOf(data), 1); + policy[categoryValue.unusedArrayName].splice(policy.unusedSubjectData.indexOf(data), 1); + delete policy[categoryValue.mapName][data.id]; + util.displaySuccess('Data removed'); + }, + util.displayErrorFunction('Unable to remove Data') + ); + }, + createPerimeter: function createPerimeter(type, policy, perimeter) { + var categoryValue = categoryMap[type]; + return categoryValue.policyPerimeterResource.save({ policy_id: policy.id }, perimeter).then( + function (data) { + util.displaySuccess('Perimeter created'); + return util.mapToArray(data.body[categoryValue.perimeterResponseName]); + }, + util.displayErrorFunction('Unable to create Perimeter') + ); + }, + removePerimeterFromPolicy: function removePerimeterFromPolicy(type, policy, perimeter) { + var categoryValue = categoryMap[type]; + + return categoryValue.policyPerimeterResource.remove({ policy_id: policy.id, perimeter_id: perimeter.id }, null).then( + function () { + util.displaySuccess('Perimeter removed'); + return perimeter; + }, + util.displayErrorFunction('Unable to remove Perimeter') + ) + }, + addPerimeterToPolicy: function addPerimeterToPolicy(type, policy, perimeter) { + var categoryValue = categoryMap[type]; + perimeter.policy_list.push(policy.id); + var perimeterClone = util.clone(perimeter); + delete perimeterClone.policy_list; + return categoryValue.policyPerimeterResource.save({ policy_id: policy.id }, perimeterClone).then( + function () { + util.displaySuccess('Perimeter added'); + }, + util.displayErrorFunction('Unable to add Perimeter') + ) + }, + loadPerimetersAndAssignments: function loadPerimetersAndAssignments(type, policy) { + var categoryValue = categoryMap[type]; + var queries = [ + categoryValue.perimeterResource.query(), + categoryValue.policyPerimeterResource.query({ policy_id: policy.id }), + categoryValue.assignmentResource.query({ policy_id: policy.id }), + ] + + return Promise.all(queries).then(function (data) { + var result = {}; + result.assignments = util.mapToArray(data[2].body[categoryValue.assignmentResponseName]); + result.perimetersMap = data[1].body[categoryValue.perimeterResponseName]; + result.perimeters = util.mapToArray(result.perimetersMap); + result.allPerimeters = util.mapToArray(data[0].body[categoryValue.perimeterResponseName]); + return result; + }, util.displayErrorFunction('Unable to load Perimeters')) + + }, + createAssignment: function createAssignment(type, policy, perimeter, data) { + var categoryValue = categoryMap[type]; + var assignment = { + "id": perimeter.id, + "category_id": data.category_id, + "data_id": data.id, + "policy_id": policy.id + } + return categoryValue.assignmentResource.save({ policy_id: policy.id }, assignment).then( + function (data) { + util.displaySuccess('Assignment created'); + return util.mapToArray(data.body[categoryValue.assignmentResponseName]); + }, + util.displayErrorFunction('Unable to create Assignment') + ) + }, + removeAssignment: function removeAssignment(type, policy, perimeter, data) { + var categoryValue = categoryMap[type]; + + return categoryValue.assignmentResource.remove({ policy_id: policy.id, perimeter_id: perimeter.id, category_id: data.category_id, data_id: data.id }, null).then( + function () { + util.displaySuccess('Assignment removed'); + }, + util.displayErrorFunction('Unable to remove Assignment') + ) + }, +} + diff --git a/dashboard/src/services/Util.service.js b/dashboard/src/services/Util.service.js new file mode 100644 index 00000000..a6b53d6f --- /dev/null +++ b/dashboard/src/services/Util.service.js @@ -0,0 +1,153 @@ +import Vue from 'vue' + +export default { + sortByName(items) { + return items + .sort((item1, item2) => { + return item1.name.localeCompare(item2.name); + }); + }, + filterAndSortByName(items, filter) { + return items + .filter(item => { + return filter == null || item.name.indexOf(filter) >= 0; + }) + .sort((item1, item2) => { + return item1.name.localeCompare(item2.name); + }); + }, + clone: function clone(src) { + return JSON.parse(JSON.stringify(src)); + }, + + mapToArray: function mapToArray(map, action) { + var result = [] + for (var key in map) { + if (map.hasOwnProperty(key)) { + var item = map[key]; + item.id = key; + if (action != null) { + action(item); + } + result.push(item); + } + } + return result; + }, + + + mapIdToItem: function mapIdToItem(array, map) { + if (array) { + for (var index = 0; index < array.length; index++) { + var id = array[index]; + if (map[id]) + array[index] = map[id]; + } + } + }, + + mapItemToId: function mapItemToId(array) { + if (array) { + for (var index = 0; index < array.length; index++) { + var item = array[index]; + array[index] = item.id; + } + } + }, + + addToMap: function addToMap(array, map) { + if (array) { + for (var index = 0; index < array.length; index++) { + var item = array[index]; + map[item.id] = item; + } + } + }, + + updateObject: function updateObject(object, newObject) { + for (var key in newObject) { + if (newObject.hasOwnProperty(key)) { + object[key] = newObject[key]; + } + } + }, + + cleanObject: function cleanObject(object) { + for (var key in object) { + if (object.hasOwnProperty(key)) { + delete object[key]; + } + } + }, + + pushAll: function pushAll(array, arrayToPush) { + for (var i = 0; i < arrayToPush.length; i += 1) { + array.push(arrayToPush[i]); + } + }, + + indexOf: function indexOf(array, property, value) { + for (var i = 0; i < array.length; i += 1) { + if (array[i][property] === value) { + return i; + } + } + return -1; + }, + + createInternal: function createInternal(data, array, map, action) { + var added = this.mapToArray(data, action) + this.addToMap(added, map); + this.pushAll(array, added); + + return added; + }, + + updateInternal: function updateInternal(data, map, action) { + var updated = this.mapToArray(data, action) + var result = [] + for (var index = 0; index < updated.length; index++) { + var item = updated[index]; + this.updateObject(map[item.id], item) + result.push(map[item.id]) + } + return result; + }, + + removeInternal: function removeInternal(id, array, map) { + var old = map[id]; + delete map[old.id]; + array.splice(array.indexOf(old), 1); + return old; + }, + + arrayToTitleMap: function arrayToTitleMap(array) { + return array.map(function (item) { + return { value: item.id, name: item.name } + }).sort(function (itemA, itemB) { + return itemA.name.localeCompare(itemB.name); + }) + }, + + displayErrorFunction: function displayErrorFunction(message) { + return function (response) { + var text = message; + if (response && response.data && response.data.message) { + text += ' (' + response.data.message + ')' + } + console.log(text); + Vue.toasted.global.toast({message: text, type: 'danger', title: 'Error'}); + } + }, + + displaySuccess: function displaySuccess(message) { + Vue.toasted.global.toast({message: message, type: 'success', title: 'Success'}); + }, + + displayError: function displayError(message) { + Vue.toasted.global.toast({message: message, type: 'danger', title: 'Error'}); + }, + + + +}
\ No newline at end of file diff --git a/dashboard/src/views/Admin.vue b/dashboard/src/views/Admin.vue new file mode 100644 index 00000000..414e68a0 --- /dev/null +++ b/dashboard/src/views/Admin.vue @@ -0,0 +1,52 @@ +<template> + <div> + <div v-if="isLoading" > + <div class="d-flex justify-content-center"> + <div class="spinner-border" role="status"> + <span class="sr-only">Loading...</span> + </div> + </div> + </div> + <div v-else class="row justify-content-center"> + <label for="file" class="label-file btn btn-primary"> + <span class="fa fa-upload"></span> + Import + </label> + <input + id="file" + class="input-file" + type="file" + @change="readFile" + accept="application/json, .json" + /> + + + </div> + </div> +</template> + +<script> +import ImportService from "./../services/Import.service.js"; + +export default { + data(){ + return { + isLoading: false + } + }, + methods: { + readFile(event) { + var reader = new FileReader(); + reader.onload = async function() { + this.isLoading = true; + var fileContents = reader.result; + await ImportService.importData(JSON.parse(fileContents)); + this.isLoading = false; + }.bind(this); + reader.readAsText(event.target.files[0]); + }, + } +}; +</script> + + diff --git a/dashboard/src/views/Assignments.vue b/dashboard/src/views/Assignments.vue new file mode 100644 index 00000000..d0e500e3 --- /dev/null +++ b/dashboard/src/views/Assignments.vue @@ -0,0 +1,54 @@ +<template> + <div> + <form-header + placeholder="Filter by Policy" + needButton + buttonText="Create Policy" + @click="creatingPolicy = true" + v-model="filter" + ></form-header> + <br/> + + <CreatePolicy v-if="creatingPolicy" @close="creatingPolicy = false"></CreatePolicy> + <div class="list-group row" v-else> + <policy v-for="policy in filteredPolicies" :key="policy.id" :policy="policy"></policy> + </div> + </div> +</template> + +<script> + import PolicyService from './../services/Policy.service.js' + //import util from './../services/Util.service.js' + import FormHeader from "./../components/FormHeader.vue" + import Policy from "./../components/assignment/Policy.vue" + import CreatePolicy from "./../components/policy/CreatePolicy.vue" + import util from "../services/Util.service"; + + export default { + data() { + return { + filter: "", + creatingPolicy: false, + policies: [] + }; + }, + mounted() { + PolicyService.initialize(); + this.policies = PolicyService.policies; + }, + components: { + FormHeader, + Policy, + CreatePolicy, + }, + computed: { + filteredPolicies() { + return util.filterAndSortByName(this.policies, this.filter); + } + } + } +</script> + +<style scoped> + +</style>
\ No newline at end of file diff --git a/dashboard/src/views/Auth.vue b/dashboard/src/views/Auth.vue new file mode 100644 index 00000000..c057e284 --- /dev/null +++ b/dashboard/src/views/Auth.vue @@ -0,0 +1,65 @@ +<template> + <div class="row justify-content-center"> + <form> + <div class="form-group"> + <label for="login">Login</label> + <input + type="text" + name="login" + v-model="name" + v-validate.initial="'required'" + class="form-control" + id="login" + /> + </div> + <div class="form-group"> + <label for="password">Password</label> + <input + type="password" + name="password" + v-model="password" + v-validate.initial="'required'" + class="form-control" + id="password" + /> + </div> + <button type="button" class="btn btn-primary btn-xlg col-auto" @click="login()">Login</button> + </form> + + </div> +</template> + +<script> +import Vue from "vue"; +import util from "./../services/Util.service.js"; +import config from '../config.js' + +var host = config.host; + +export default { + name: "auth", + data() { + return { + name: "", + password: "" + }; + }, + methods: { + login() { + Vue.http.headers.common["Authorization"] = + "Basic " + btoa(this.name + ":" + this.password); + Vue.http.get(host + "/auth").then( + response => { + Vue.http.headers.common["Authorization"] = "Basic "; + Vue.http.headers.common["x-api-key"] = response.data; + localStorage.setItem("auth-key", response.data); + this.$router.push("models"); + }, + response => { + util.displayError("Unable to log in " + response); + } + ); + } + } +}; +</script> diff --git a/dashboard/src/views/Error.vue b/dashboard/src/views/Error.vue new file mode 100644 index 00000000..528fb8aa --- /dev/null +++ b/dashboard/src/views/Error.vue @@ -0,0 +1,8 @@ +<template> + <div class="row justify-content-center"> + /!\ Error please configure CORS on server + </div> +</template> + + + diff --git a/dashboard/src/views/Models.vue b/dashboard/src/views/Models.vue new file mode 100644 index 00000000..bdd46483 --- /dev/null +++ b/dashboard/src/views/Models.vue @@ -0,0 +1,80 @@ +<template> + <div> + <orphans + @close="allowAlert = false" + v-if="showAlert" + :orphanMetaRules="orphanMetaRules" + :orphanSubjectCategories="orphanSubjectCategories" + :orphanObjectCategories="orphanObjectCategories" + :orphanActionCategories="orphanActionCategories" + ></orphans> + + <br /> + <form-header + placeholder="Filter" + buttonText="Create Model" + @click="creatingModel = true" + v-model="filter" + need-button + ></form-header> + <br /> + + <CreateModel v-if="creatingModel" @close="creatingModel = false"></CreateModel> + <div class="list-group row" v-else> + <Model v-for="model in filteredModels" :key="model.id" :model="model"></Model> + </div> + </div> +</template> + +<script> +import Model from "./../components/model/Model.vue"; +import CreateModel from "./../components/model/CreateModel.vue"; +import ModelService from "./../services/Model.service.js"; +import util from "./../services/Util.service.js"; +import Orphans from "./../components/model/Orphans.vue"; +import FormHeader from "./../components/FormHeader.vue"; + +export default { + name: "models", + components: { + Model, + CreateModel, + FormHeader, + Orphans + }, + mounted() { + ModelService.initialize(); + this.models = ModelService.models; + this.orphanMetaRules = ModelService.orphanMetaRules; + this.orphanSubjectCategories = ModelService.orphanSubjectCategories; + this.orphanObjectCategories = ModelService.orphanObjectCategories; + this.orphanActionCategories = ModelService.orphanActionCategories; + }, + data() { + return { + filter: "", + creatingModel: false, + allowAlert: true, + orphanMetaRules: [], + orphanSubjectCategories: [], + orphanActionCategories: [], + orphanObjectCategories: [], + models: [] + }; + }, + computed: { + showAlert() { + return ( + this.allowAlert && + (this.orphanMetaRules.length || + this.orphanSubjectCategories.length || + this.orphanActionCategories.length || + this.orphanObjectCategories.length) + ); + }, + filteredModels() { + return util.filterAndSortByName(this.models, this.filter); + } + } +}; +</script> diff --git a/dashboard/src/views/Pdps.vue b/dashboard/src/views/Pdps.vue new file mode 100644 index 00000000..778f23fe --- /dev/null +++ b/dashboard/src/views/Pdps.vue @@ -0,0 +1,53 @@ +<template> + <div> + <form-header + placeholder="Filter" + buttonText="Create PDP" + @click="creatingPdp = true" + v-model="filter" + need-button + ></form-header> + <br /> + + <CreatePdp v-if="creatingPdp" @close="creatingPdp = false"></CreatePdp> + <div class="list-group row" v-else> + <pdp v-for="pdp in filteredPdps" :key="pdp.id" :pdp="pdp"></pdp> + </div> + </div> +</template> + +<script> +import PdpService from "./../services/Pdp.service.js"; +import util from "./../services/Util.service.js"; +import Pdp from "./../components/pdp/Pdp.vue"; +import CreatePdp from "./../components/pdp/CreatePdp.vue"; +import FormHeader from "./../components/FormHeader.vue"; +import PolicyService from "../services/Policy.service"; + +export default { + data() { + return { + filter: "", + creatingPdp: false, + pdps: [] + }; + }, + mounted() { + PdpService.initialize(); + PolicyService.initialize(); + this.pdps = PdpService.pdps; + }, + components: { + Pdp, + CreatePdp, + FormHeader + }, + computed: { + filteredPdps() { + return util.filterAndSortByName(this.pdps, this.filter); + } + } +}; +</script> + + diff --git a/dashboard/src/views/Rules.vue b/dashboard/src/views/Rules.vue new file mode 100644 index 00000000..042b00c0 --- /dev/null +++ b/dashboard/src/views/Rules.vue @@ -0,0 +1,50 @@ +<template> + <div> + <form-header + placeholder="Filter by Policy" + needButton + buttonText="Create Policy" + @click="creatingPolicy = true" + v-model="filter" + ></form-header> + <br/> + + <CreatePolicy v-if="creatingPolicy" @close="creatingPolicy = false"></CreatePolicy> + <div class="list-group row" v-else> + <policy v-for="policy in filteredPolicies" :key="policy.id" :policy="policy"></policy> + </div> + </div> +</template> + +<script> +import PolicyService from './../services/Policy.service.js' +//import util from './../services/Util.service.js' +import FormHeader from "./../components/FormHeader.vue" +import Policy from "./../components/policy/Policy.vue" +import CreatePolicy from "./../components/policy/CreatePolicy.vue" +import util from "../services/Util.service"; + +export default { + data() { + return { + filter: "", + creatingPolicy: false, + policies: [] + }; + }, + mounted() { + PolicyService.initialize(); + this.policies = PolicyService.policies; + }, + components: { + FormHeader, + Policy, + CreatePolicy, + }, + computed: { + filteredPolicies() { + return util.filterAndSortByName(this.policies, this.filter); + } + } +} +</script> |