diff --git a/docs/user_guide.md b/docs/user_guide.md
index c4850d3f7a..9e6a27e784 100644
--- a/docs/user_guide.md
+++ b/docs/user_guide.md
@@ -1505,8 +1505,9 @@ optional arguments:
-i COMPONENT_FILE, --import COMPONENT_FILE
Path to the source component file which contains
multiple file paths. Each file path start with a '+'
- (path should be filtered) or '-' (path should not be
- filtered) sign. E.g.:
+ (results from this path should be listed) or '-'
+ (results from this path should not be listed) sign.
+ E.g.:
+/a/b/x.cpp
-/a/b/
Please see the User guide for more information.
diff --git a/libcodechecker/libhandlers/cmd.py b/libcodechecker/libhandlers/cmd.py
index 2eacba2006..bb31e90a5f 100644
--- a/libcodechecker/libhandlers/cmd.py
+++ b/libcodechecker/libhandlers/cmd.py
@@ -742,9 +742,10 @@ def __register_add(parser):
required=True,
help="Path to the source component file which "
"contains multiple file paths. Each file "
- "path start with a '+' (path should be "
- "filtered) or '-' (path should not be "
- "filtered) sign. E.g.: \n"
+ "path start with a '+' (results from this "
+ "path should be listed) or '-' (results from "
+ "this path should not be listed) sign. "
+ "E.g.: \n"
" +/a/b/x.cpp\n"
" -/a/b/\n"
"Please see the User guide for more "
diff --git a/www/scripts/codecheckerviewer/SourceComponentManager.js b/www/scripts/codecheckerviewer/SourceComponentManager.js
new file mode 100644
index 0000000000..05fa24d4f6
--- /dev/null
+++ b/www/scripts/codecheckerviewer/SourceComponentManager.js
@@ -0,0 +1,365 @@
+// -------------------------------------------------------------------------
+// The CodeChecker Infrastructure
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+// -------------------------------------------------------------------------
+
+define([
+ 'dojo/dom-construct',
+ 'dojo/dom-attr',
+ 'dojo/dom-class',
+ 'dojo/_base/declare',
+ 'dojo/data/ItemFileWriteStore',
+ 'dojox/grid/DataGrid',
+ 'dijit/ConfirmDialog',
+ 'dijit/form/Button',
+ 'dijit/form/SimpleTextarea',
+ 'dijit/form/TextBox',
+ 'dijit/layout/BorderContainer',
+ 'dijit/layout/ContentPane'],
+function (dom, domAttr, domClass, declare, ItemFileWriteStore, DataGrid,
+ ConfirmDialog, Button, SimpleTextarea, TextBox, BorderContainer,
+ ContentPane) {
+
+ var DeleteComponentDialog = declare(ConfirmDialog, {
+ constructor : function () {
+ this._confirmLabel = new ContentPane({
+ class : 'delete-confirm-text',
+ innerHTML : ''
+ });
+ },
+
+ postCreate : function () {
+ this.inherited(arguments);
+ this.addChild(this._confirmLabel);
+ },
+
+ onExecute : function () {
+ CC_SERVICE.removeSourceComponent(this.componentName);
+ this.listSourceComponent.refreshGrid();
+ }
+ });
+
+ var EditSourceComponent = declare(ContentPane, {
+ postCreate : function () {
+ var that = this;
+
+ this._errorMessage = dom.create('div', {class : 'mbox mbox-error hide'});
+ dom.place(this._errorMessage, this.domNode);
+
+ this._componentName = new TextBox({
+ class : 'component-name',
+ placeHolder : 'Name of the source component...'
+ });
+ this._placeFormElement(this._componentName, 'name', 'Name*');
+
+ this._componentValue = new SimpleTextarea({
+ class : 'component-value',
+ rows : 10,
+ placeholder : 'Value of the source component. Each line must start '
+ + 'with a "+" (results from this path should be listed) or '
+ + '"-" (results from this path should not be listed) sign. '
+ + 'E.g.: +/a/b/x.cpp or -/a/b/'
+ });
+ this._placeFormElement(this._componentValue, 'value', 'Value*');
+
+ this._componentDescription = new TextBox({
+ class : 'component-description',
+ placeHolder : 'Description of the source component...'
+ });
+ this._placeFormElement(this._componentDescription, 'description',
+ 'Description');
+
+ this._btnCreate = new Button({
+ class : 'btn-save',
+ label : 'Save',
+ onClick : function () {
+ var componentName = that._componentName.get('value');
+ if (!componentName) {
+ return that.showError('Component name can not be empty!');
+ }
+
+ var componentValue = that._componentValue.get('value');
+ if (!componentValue) {
+ return that.showError('Component value can not be empty!');
+ }
+
+ if (!that.isValidComponentValue(componentValue)) {
+ return that.showError('Component value format is invalid! Every'
+ + 'line should start with + or - sign followed by one or more '
+ + 'character.');
+ }
+
+ var componentDescription = that._componentDescription.get('value');
+
+ // Remove the original component because the user would like to change
+ // the name.
+ var origComponentName = that._origComponent
+ ? that._origComponent.name[0]
+ : null;
+
+ if (origComponentName && origComponentName !== componentName) {
+ CC_SERVICE.removeSourceComponent(origComponentName);
+ }
+
+ CC_SERVICE.addSourceComponent(componentName, componentValue,
+ componentDescription, function (success) {
+ if (success) {
+ that.showSuccess('The component has been successfully ' +
+ 'created/edited!');
+ that.sourceComponentManager.updateNeeded = true;
+
+ // Reset items of the filter tooltip.
+ that.sourceComponentFilter._filterTooltip.reset();
+ } else {
+ that.showError('Failed to create/edit component!');
+ }
+ }).fail(function (jsReq, status, exc) {
+ if (status === 'parsererror') {
+ that.showError(exc.message);
+ }
+ });
+ }
+ });
+ this.addChild(this._btnCreate);
+ },
+
+ isValidComponentValue : function (value) {
+ var lines = value.split(/\r|\n/);
+ for (var i = 0; i < lines.length; ++i) {
+ if (!lines[i].startsWith('+') && !lines[i].startsWith('-') ||
+ lines[i].trim().length < 2
+ ) {
+ return false;
+ }
+ }
+ return true;
+ },
+
+ hideError : function () {
+ domClass.add(this._errorMessage, 'hide');
+ },
+
+ showSuccess : function (msg) {
+ this._errorMessage.innerHTML = msg;
+ domClass.remove(this._errorMessage, 'hide');
+ domClass.remove(this._errorMessage, 'mbox-error');
+ domClass.add(this._errorMessage, 'mbox-success');
+ },
+
+ showError : function (msg) {
+ this._errorMessage.innerHTML = msg;
+ domClass.remove(this._errorMessage, 'hide');
+ domClass.remove(this._errorMessage, 'mbox-success');
+ domClass.add(this._errorMessage, 'mbox-error');
+ },
+
+ _placeFormElement : function (element, key, label) {
+ this.addChild(element);
+
+ var container = dom.create('div', {
+ class : 'formElement'
+ }, this.containerNode);
+
+ if (label) {
+ var labelNode = dom.create('label', {
+ class : 'formLabel bold',
+ innerHTML : label + ': '
+ }, container);
+
+ if (key) {
+ domAttr.set(labelNode, 'for', key);
+ }
+ }
+
+ dom.place(element.domNode, container);
+ },
+
+ init : function (component) {
+ if (component) {
+ this._componentName.set('value', component.name[0]);
+ this._componentValue.set('value', component.value[0]);
+ this._componentDescription.set('value', component.description[0]);
+ this._origComponent = component;
+ } else {
+ this._componentName.set('value', null);
+ this._componentValue.set('value', null);
+ this._componentDescription.set('value', null);
+ this._origComponent = null;
+ }
+
+ this.hideError();
+ }
+ });
+
+ function componentValueFormatter(value) {
+ return value.split(/\r|\n/).map(function (line) {
+ if (line.startsWith('+')) {
+ return '' + line + '';
+ } else {
+ return '' + line + '';
+ }
+ }).join('
');
+ }
+
+ var ListSourceComponent = declare(DataGrid, {
+ constructor : function () {
+ this.store = new ItemFileWriteStore({
+ data : { identifier : 'id', items : [] }
+ });
+
+ this.structure = [
+ { name : 'Name', field : 'name', styles : 'text-align: left;', width : '100%' },
+ { name : 'Value', field : 'value', styles : 'text-align: left;', width : '100%', formatter: componentValueFormatter },
+ { name : 'Description', field : 'description', styles : 'text-align: left;', width : '100%' },
+ { name : ' ', field : 'editIcon', cellClasses : 'status', width : '20px', noresize : true },
+ { name : ' ', field : 'deleteIcon', cellClasses : 'status', width : '20px', noresize : true }
+ ];
+
+ this.focused = true;
+ this.selectable = true;
+ this.keepSelection = true;
+ this.escapeHTMLInData = false;
+ this.autoHeight = false;
+ },
+
+ postCreate : function () {
+ this.inherited(arguments);
+
+ this._confirmDeleteDialog = new DeleteComponentDialog({
+ title : 'Confirm deletion of component',
+ listSourceComponent : this
+ });
+ },
+
+ onRowClick : function (evt) {
+ var item = this.getItem(evt.rowIndex);
+ switch (evt.cell.field) {
+ case 'editIcon':
+ this.editComponent(item);
+ break;
+ case 'deleteIcon':
+ this.removeComponent(item.name[0]);
+ break;
+ }
+ },
+
+ removeComponent : function (name) {
+ this._confirmDeleteDialog.componentName = name;
+ this._confirmDeleteDialog.show();
+ },
+
+ editComponent : function (component) {
+ this.sourceComponentManager.showNewComponentPage(component);
+ },
+
+ refreshGrid : function () {
+ var that = this;
+
+ this.store.fetch({
+ onComplete : function (sourceComponents) {
+ sourceComponents.forEach(function (component) {
+ that.store.deleteItem(component);
+ });
+ that.store.save();
+ }
+ });
+
+ CC_SERVICE.getSourceComponents(null, function (sourceComponents) {
+ sourceComponents.forEach(function (item) {
+ that._addSourceComponent(item);
+ });
+ });
+ },
+
+ _addSourceComponent : function (component) {
+ this.store.newItem({
+ id : component.name,
+ name : component.name,
+ value : component.value,
+ description : component.description,
+ editIcon : '',
+ deleteIcon : ''
+ });
+ },
+
+ onShow : function () {
+ if (this.sourceComponentManager.updateNeeded) {
+ this.refreshGrid();
+ this.sourceComponentManager.updateNeeded = false;
+ }
+ }
+ });
+
+ return declare(ContentPane, {
+ updateNeeded : true,
+
+ postCreate : function () {
+ var that = this;
+
+ this._wrapper = new BorderContainer({
+ class : 'component-manager-wrapper'
+ });
+
+ this.addChild(this._wrapper);
+
+ this._btnNew = new Button({
+ class : 'btn-new',
+ region : 'top',
+ label : 'New',
+ onClick : function () {
+ that.showNewComponentPage();
+ }
+ });
+
+ this._btnBack = new Button({
+ class : 'btn-back',
+ region : 'top',
+ label : 'Back',
+ onClick : function () {
+ that.showListOfComponentPage();
+ }
+ });
+
+ this._editSourceComponent = new EditSourceComponent({
+ region : 'center',
+ sourceComponentManager : this,
+ sourceComponentFilter : this.sourceComponentFilter
+ });
+
+ this._listSourceComponent = new ListSourceComponent({
+ region : 'center',
+ editSourceComponent : this._editSourceComponent,
+ sourceComponentManager : this
+ });
+ },
+
+ showNewComponentPage : function (component) {
+ this._clearDom();
+
+ this._editSourceComponent.init(component);
+
+ this._wrapper.addChild(this._btnBack);
+ this._wrapper.addChild(this._editSourceComponent);
+ },
+
+ showListOfComponentPage : function () {
+ this._clearDom();
+
+ this._wrapper.addChild(this._btnNew);
+ this._wrapper.addChild(this._listSourceComponent);
+ this._listSourceComponent.onShow();
+ },
+
+ refreshGrid : function () {
+ this._listSourceComponent.refreshGrid();
+ },
+
+ _clearDom : function () {
+ this._wrapper.getChildren().forEach(function (child) {
+ this.removeChild(child);
+ }, this);
+ }
+ });
+});
diff --git a/www/scripts/codecheckerviewer/codecheckerviewer.js b/www/scripts/codecheckerviewer/codecheckerviewer.js
index 67ad1edf4f..2d37185b18 100644
--- a/www/scripts/codecheckerviewer/codecheckerviewer.js
+++ b/www/scripts/codecheckerviewer/codecheckerviewer.js
@@ -106,6 +106,8 @@ function (declare, cookie, topic, Lightbox, Dialog, Button, BorderContainer,
var currentProductName = util.atou(CURRENT_PRODUCT.displayedName_b64);
document.title = currentProductName + ' - CodeChecker';
+ IS_ADMIN_OF_ANY_PRODUCT = CC_PROD_SERVICE.isAdministratorOfAnyProduct();
+
//--- Back button to product list ---//
var productListButton = new Button({
diff --git a/www/scripts/codecheckerviewer/filter/SourceComponentFilter.js b/www/scripts/codecheckerviewer/filter/SourceComponentFilter.js
index 9809dc22f8..ee8a1615b6 100644
--- a/www/scripts/codecheckerviewer/filter/SourceComponentFilter.js
+++ b/www/scripts/codecheckerviewer/filter/SourceComponentFilter.js
@@ -5,17 +5,52 @@
// -------------------------------------------------------------------------
define([
- 'dojo/_base/declare',
'dojo/dom-construct',
+ 'dojo/_base/declare',
'dojo/Deferred',
- 'codechecker/filter/SelectFilter'],
-function (declare, dom, Deferred, SelectFilter) {
+ 'dijit/Dialog',
+ 'codechecker/filter/SelectFilter',
+ 'codechecker/SourceComponentManager'],
+function (dom, declare, Deferred, Dialog, SelectFilter,
+ SourceComponentManager) {
+
return declare(SelectFilter, {
search : {
enable : true,
placeHolder : 'Search for source components...'
},
+ postCreate : function () {
+ this.inherited(arguments);
+ var that = this;
+
+ // Source components can be managed only by administrators.
+ if (IS_ADMIN_OF_ANY_PRODUCT) {
+ this._edit = dom.create('i', {
+ class : 'customIcon edit',
+ onclick : function () {
+ that._manageDialog.show();
+ }
+ }, this._clean, 'before');
+
+ // Source component manager.
+ this._manageDialog = new Dialog({
+ title : 'Manage source components',
+ style : 'width: 50%;',
+ onShow : function () {
+ that._sourceComponentManager.showListOfComponentPage();
+ }
+ });
+
+ this._sourceComponentManager = new SourceComponentManager({
+ class : 'source-component-manager',
+ style : 'height: 450px;',
+ sourceComponentFilter : this
+ });
+ this._manageDialog.addChild(this._sourceComponentManager);
+ }
+ },
+
formatDescription : function (value) {
var list = dom.create('ul', { class : 'component-description'});
value.split('\n').forEach(function (item) {
diff --git a/www/style/bugfilter.css b/www/style/bugfilter.css
index cb20e91cbb..3057814670 100644
--- a/www/style/bugfilter.css
+++ b/www/style/bugfilter.css
@@ -115,8 +115,10 @@
}
.bug-filters .clean,
-.bug-filters .cogwheel {
+.bug-filters .cogwheel,
+.bug-filters .edit {
cursor: pointer;
+ color: #878d96;
}
.bug-filters .clean::before {
@@ -322,3 +324,7 @@
width: 100%;
margin: 5px 0px;
}
+
+.component-manager-wrapper .dijitBorderContainer-child {
+ border: 0px;
+}
diff --git a/www/style/codecheckerviewer.css b/www/style/codecheckerviewer.css
index 9dbc402ce5..d8f7ffb122 100644
--- a/www/style/codecheckerviewer.css
+++ b/www/style/codecheckerviewer.css
@@ -418,6 +418,7 @@ html, body {
.mbox.mbox-error {
border-color: #e92625;
+ padding: 5px;
}
.mbox.mbox-error .heading {
@@ -658,3 +659,45 @@ html, body {
.tab-new-features-label {
font-weight: bold;
}
+
+/* Form labels */
+
+.formElement {
+ display: block;
+ margin: 2px;
+ padding: 4px;
+}
+
+.formElement .formLabel {
+ display: inline-block;
+ margin-left: 5px;
+ padding-right: 6px;
+ width: 200px;
+}
+
+.formElement .formLabel.bold {
+ font-weight: bold;
+}
+
+/* Source Component Manager */
+.source-component-manager .component-name,
+.source-component-manager .component-value,
+.source-component-manager .component-description {
+ width: 50%;
+}
+
+.component-dialog .formLabel {
+ width: 100px;
+}
+
+.source-component-manager .edit,
+.source-component-manager .delete {
+ cursor: pointer;
+}
+
+.source-component-manager .edit {
+ color: #5b6169;
+}
+.source-component-manager .delete {
+ color: #d63434;
+}
diff --git a/www/style/productlist.css b/www/style/productlist.css
index 26069e5126..486e0324fa 100644
--- a/www/style/productlist.css
+++ b/www/style/productlist.css
@@ -116,23 +116,6 @@ span.product-avatar {
/*** Product configuration dialog ***/
-.formElement {
- display: block;
- margin: 2px;
- padding: 4px;
-}
-
-.formElement .formLabel {
- display: inline-block;
- margin-left: 5px;
- padding-right: 6px;
- width: 200px;
-}
-
-.formElement .formLabel.bold {
- font-weight: bold;
-}
-
.formElement .form-input {
width: 350px;
}