Skip to content

Commit

Permalink
Add support for component properties
Browse files Browse the repository at this point in the history
Based on DependencyTrack/dependency-track#3499

Signed-off-by: nscuro <nscuro@protonmail.com>
  • Loading branch information
nscuro committed Apr 14, 2024
1 parent e426fb1 commit 658b4c9
Show file tree
Hide file tree
Showing 5 changed files with 285 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@
"component_namespace_group_vendor": "Namespace / group / vendor",
"component_operating_system": "Operating system",
"component_package_url_desc": "A Valid Package URL is required for libraries and frameworks. PURL syntax: pkg:type/namespace/name@version?qualifiers#subpath",
"component_properties": "Component Properties",
"component_search": "Component Search",
"component_spdx_license_desc": "Specifies the SPDX license ID of the component",
"component_supplier_name_desc": "The organization that supplied the component",
Expand All @@ -381,6 +382,7 @@
"cpe": "CPE",
"cpe_full": "Common Platform Enumeration (CPE)",
"create": "Create",
"create_component_property": "Create Component Property",
"create_license_group": "Create License Group",
"create_policy": "Create Policy",
"create_project": "Create Project",
Expand Down
6 changes: 6 additions & 0 deletions src/views/portfolio/projects/Component.vue
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@
:component="cloneDeep(component)"
v-on:componentUpdated="syncComponentFields"
/>
<component-properties-modal :uuid="this.uuid" />
<component-create-property-modal :uuid="this.uuid" />
</div>
</template>

Expand All @@ -178,10 +180,14 @@ import EventBus from '../../../shared/eventbus';
import permissionsMixin from '../../../mixins/permissionsMixin';
import ComponentDetailsModal from './ComponentDetailsModal';
import ExternalReferencesDropdown from '../../components/ExternalReferencesDropdown.vue';
import ComponentCreatePropertyModal from './ComponentCreatePropertyModal.vue';
import ComponentPropertiesModal from './ComponentPropertiesModal.vue';
export default {
mixins: [permissionsMixin],
components: {
ComponentCreatePropertyModal,
ComponentPropertiesModal,
SeverityBarChart,
ComponentDashboard,
ComponentVulnerabilities,
Expand Down
123 changes: 123 additions & 0 deletions src/views/portfolio/projects/ComponentCreatePropertyModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<template>
<b-modal
id="componentCreatePropertyModal"
@hide="resetValues()"
size="md"
hide-header-close
no-stacking
:title="$t('message.create_component_property')"
>
<b-form-group
id="fieldset-1"
:label="this.$t('message.group_name')"
label-for="input-1"
>
<b-form-input id="input-1" v-model="groupName" trim />
</b-form-group>
<b-form-group
id="fieldset-2"
:label="this.$t('message.property_name')"
label-for="input-2"
label-class="required"
>
<b-form-input id="input-2" v-model="propertyName" class="required" trim />
</b-form-group>
<b-form-group
id="fieldset-3"
:label="this.$t('message.property_value')"
label-for="input-3"
label-class="required"
>
<b-form-textarea
id="input-3"
v-model="propertyValue"
class="required"
trim
/>
</b-form-group>
<b-form-group
id="fieldset-4"
:label="this.$t('message.property_type')"
label-for="input-4"
label-class="required"
>
<b-form-select
id="input-4"
v-model="propertyType"
class="required"
:options="options"
/>
</b-form-group>
<b-form-group
id="fieldset-5"
:label="this.$t('message.description')"
label-for="input-5"
>
<b-form-textarea id="input-5" v-model="description" trim />
</b-form-group>
<template v-slot:modal-footer="{ cancel }">
<b-button size="md" variant="secondary" @click="cancel()">{{
$t('message.close')
}}</b-button>
<b-button size="md" variant="primary" @click="createProperty()">{{
$t('message.create')
}}</b-button>
</template>
</b-modal>
</template>

<script>
export default {
name: 'ComponentCreatePropertyModal',
props: {
uuid: String,
},
data() {
return {
groupName: null,
propertyName: null,
propertyValue: null,
propertyType: null,
description: null,
options: [
{ value: 'BOOLEAN', text: 'BOOLEAN' },
{ value: 'INTEGER', text: 'INTEGER' },
{ value: 'NUMBER', text: 'NUMBER' },
{ value: 'STRING', text: 'STRING' },
{ value: 'TIMESTAMP', text: 'TIMESTAMP' },
{ value: 'URL', text: 'URL' },
{ value: 'UUID', text: 'UUID' },
],
};
},
methods: {
createProperty: function () {
let url = `${this.$api.BASE_URL}/${this.$api.URL_COMPONENT}/${this.uuid}/property`;
this.axios
.put(url, {
groupName: this.groupName,
propertyName: this.propertyName,
propertyValue: this.propertyValue,
propertyType: this.propertyType,
description: this.description,
})
.then(() => {
this.$root.$emit('bv::hide::modal', 'componentCreatePropertyModal');
this.$root.$emit('bv::show::modal', 'componentPropertiesModal');
this.$toastr.s(this.$t('message.property_created'));
});
},
resetValues: function () {
this.groupName = null;
this.propertyName = null;
this.propertyValue = null;
this.propertyType = null;
this.description = null;
},
},
};
</script>

<style lang="scss">
@import '../../../assets/scss/vendors/vue-tags-input/vue-tags-input';
</style>
9 changes: 9 additions & 0 deletions src/views/portfolio/projects/ComponentDetailsModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,13 @@
v-permission="PERMISSIONS.PORTFOLIO_MANAGEMENT"
>{{ $t('message.delete') }}</b-button
>
<b-button
size="md"
variant="outline-primary"
v-b-modal.componentPropertiesModal
v-permission="PERMISSIONS.PORTFOLIO_MANAGEMENT"
>{{ $t('message.properties') }}</b-button
>
<b-button size="md" variant="secondary" @click="cancel()">{{
$t('message.close')
}}</b-button>
Expand All @@ -380,6 +387,7 @@
<script>
import BInputGroupFormInput from '../../../forms/BInputGroupFormInput';
import BInputGroupFormSelect from '../../../forms/BInputGroupFormSelect';
import ComponentPropertiesModal from './ComponentPropertiesModal.vue';
import permissionsMixin from '../../../mixins/permissionsMixin';
import xssFilters from 'xss-filters';
import common from '@/shared/common';
Expand All @@ -390,6 +398,7 @@ export default {
components: {
BInputGroupFormInput,
BInputGroupFormSelect,
ComponentPropertiesModal,
},
props: {
component: Object,
Expand Down
145 changes: 145 additions & 0 deletions src/views/portfolio/projects/ComponentPropertiesModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<template>
<b-modal
id="componentPropertiesModal"
size="lg"
hide-header-close
no-stacking
:title="$t('message.component_properties')"
>
<bootstrap-table
ref="table"
:columns="columns"
:data="data"
:options="options"
@on-check="onRowSelectionChange"
@on-uncheck="onRowSelectionChange"
>
</bootstrap-table>
<template v-slot:modal-footer="{ cancel }">
<b-button
size="md"
variant="outline-danger"
@click="deleteProperty"
:disabled="!hasRowsSelected"
>{{ $t('message.delete') }}</b-button
>
<b-button size="md" variant="secondary" @click="cancel()">{{
$t('message.close')
}}</b-button>
<b-button
size="md"
variant="primary"
v-b-modal.componentCreatePropertyModal
>{{ $t('message.create_property') }}</b-button
>
</template>
</b-modal>
</template>

<script>
import common from '../../../shared/common';
import xssFilters from 'xss-filters';
export default {
name: 'ComponentPropertiesModal',
props: {
uuid: String,
},
data() {
return {
columns: [
{
field: 'state',
checkbox: true,
},
{
title: this.$t('message.group'),
field: 'groupName',
sortable: false,
formatter(value) {
return xssFilters.inHTMLData(common.valueWithDefault(value, ''));
},
},
{
title: this.$t('message.name'),
field: 'propertyName',
sortable: false,
formatter(value) {
return xssFilters.inHTMLData(common.valueWithDefault(value, ''));
},
},
{
title: this.$t('message.value'),
field: 'propertyValue',
sortable: false,
editable: true,
formatter(value) {
return xssFilters.inHTMLData(common.valueWithDefault(value, ''));
},
},
{
title: this.$t('message.type'),
field: 'propertyType',
sortable: false,
formatter(value) {
return xssFilters.inHTMLData(common.valueWithDefault(value, ''));
},
},
{
title: this.$t('message.description'),
field: 'description',
sortable: false,
visible: false,
formatter(value) {
return xssFilters.inHTMLData(common.valueWithDefault(value, ''));
},
},
],
data: [],
options: {
search: true,
showColumns: true,
showRefresh: true,
pagination: true,
silentSort: false,
sidePagination: 'client',
queryParamsType: 'pageSize',
pageList: '[10, 25]',
pageSize: 10,
icons: {
refresh: 'fa-refresh',
},
responseHandler: function (res, xhr) {
res.total = xhr.getResponseHeader('X-Total-Count');
return res;
},
url: this.apiUrl(),
},
hasRowsSelected: false,
};
},
methods: {
apiUrl: function () {
return `${this.$api.BASE_URL}/${this.$api.URL_COMPONENT}/${this.uuid}/property`;
},
onRowSelectionChange: function () {
this.hasRowsSelected = this.$refs.table.getSelections().length > 0;
},
deleteProperty: function () {
let selections = this.$refs.table.getSelections();
for (let i = 0; i < selections.length; i++) {
this.axios.delete(`${this.apiUrl()}/${selections[i].uuid}`).then(() => {
this.$refs.table.refresh({ silent: true });
this.$toastr.s(this.$t('message.property_deleted'));
});
}
this.$refs.table.uncheckAll();
this.hasRowsSelected = false;
},
},
};
</script>

<style lang="scss">
@import '../../../assets/scss/vendors/vue-tags-input/vue-tags-input';
</style>

0 comments on commit 658b4c9

Please sign in to comment.