Fix xss vulnerability (#924)

* Fix XSS vulnerability for Distribution Set and Software Module field
* Fix XSS vulnerability for Artifact Details of header in Upload view
* Fix XSS vulnerability in Distribution View Software Module box show artifact details window and fix SonarQube issue
* Fix XSS vulnerability in Upload View Software Module field manage metadata
* Fix XSS vulnerability for Notifications when creating or deleting new or existing Distributions or Software Modules plus adapting error notifications when trying to duplicate
* Fix XSS vulnerability for Distributions View when assigning sm to dist confirmation popup text
* Fix XSS vulnerability for Distributions View modules tab of distribution value of SoftwareModule
* Fix XSS vulnerability for Deployment View assigned tab of target which has risky distribution assigned
* Fix XSS vulnerability in Deployment view action history (of) field and eliminate bugs
* Fix XSS vulnerability bug in Deployment View Action history of field
* Fix XSS vulnerability for Distributions View Module tab as it rendered tool tip
* Fix XSS vulnerability formatting
* Invented some IDs to ease testing regarding XSS vulnerability
* Fix XSS peer review findings
* Fix XSS vulnerability for Distribution Set and Software Module field
* Resolve merge conflicts

Signed-off-by: Ammar Bikic <ammar.bikic@bosch-si.com>
This commit is contained in:
amic
2020-01-27 14:41:07 +01:00
committed by Stefan Behl
parent 62c876a435
commit 6c162b2e93
19 changed files with 336 additions and 176 deletions

View File

@@ -24,7 +24,6 @@ import org.eclipse.hawkbit.ui.artifacts.event.SoftwareModuleEvent;
import org.eclipse.hawkbit.ui.artifacts.event.SoftwareModuleEvent.SoftwareModuleEventType;
import org.eclipse.hawkbit.ui.artifacts.state.ArtifactUploadState;
import org.eclipse.hawkbit.ui.common.ConfirmationDialog;
import org.eclipse.hawkbit.ui.common.builder.LabelBuilder;
import org.eclipse.hawkbit.ui.common.table.BaseEntityEventType;
import org.eclipse.hawkbit.ui.components.SPUIButton;
import org.eclipse.hawkbit.ui.components.SPUIComponentProvider;
@@ -48,7 +47,6 @@ import org.vaadin.spring.events.annotation.EventBusListenerMethod;
import com.google.common.collect.Maps;
import com.vaadin.data.Container;
import com.vaadin.server.FontAwesome;
import com.vaadin.shared.ui.label.ContentMode;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.HorizontalLayout;
@@ -91,8 +89,12 @@ public class ArtifactDetailsLayout extends VerticalLayout {
private final UINotification uINotification;
private Label prefixTitleOfArtifactDetails;
private Label titleOfArtifactDetails;
private HorizontalLayout headerCaptionLayout;
private SPUIButton maxMinButton;
private Table artifactDetailsTable;
@@ -140,7 +142,7 @@ public class ArtifactDetailsLayout extends VerticalLayout {
labelSoftwareModule = HawkbitCommonUtil.getFormattedNameVersion(selectedSoftwareModule.get().getName(),
selectedSoftwareModule.get().getVersion());
}
createComponents(labelSoftwareModule);
createComponents();
buildLayout();
eventBus.subscribe(this);
@@ -162,12 +164,36 @@ public class ArtifactDetailsLayout extends VerticalLayout {
return Optional.empty();
}
private void createComponents(final String labelSoftwareModule) {
titleOfArtifactDetails = new LabelBuilder().id(UIComponentIdProvider.ARTIFACT_DETAILS_HEADER_LABEL_ID)
.name(HawkbitCommonUtil.getArtifactoryDetailsLabelId(labelSoftwareModule, i18n)).buildCaptionLabel();
titleOfArtifactDetails.setContentMode(ContentMode.HTML);
private void createComponents() {
prefixTitleOfArtifactDetails = new Label();
prefixTitleOfArtifactDetails.addStyleName(ValoTheme.LABEL_SMALL);
prefixTitleOfArtifactDetails.addStyleName(ValoTheme.LABEL_BOLD);
prefixTitleOfArtifactDetails.setSizeUndefined();
titleOfArtifactDetails = new Label();
titleOfArtifactDetails.setId(UIComponentIdProvider.ARTIFACT_DETAILS_HEADER_LABEL_ID);
titleOfArtifactDetails.setSizeFull();
titleOfArtifactDetails.setImmediate(true);
titleOfArtifactDetails.setWidth("100%");
titleOfArtifactDetails.addStyleName(ValoTheme.LABEL_SMALL);
titleOfArtifactDetails.addStyleName("text-bold");
titleOfArtifactDetails.addStyleName("text-cut");
titleOfArtifactDetails.addStyleName("header-caption-right");
headerCaptionLayout = new HorizontalLayout();
headerCaptionLayout.setMargin(false);
headerCaptionLayout.setSpacing(true);
headerCaptionLayout.setSizeFull();
headerCaptionLayout.addStyleName("header-caption");
headerCaptionLayout.addComponent(prefixTitleOfArtifactDetails);
headerCaptionLayout.setComponentAlignment(prefixTitleOfArtifactDetails, Alignment.TOP_LEFT);
headerCaptionLayout.setExpandRatio(prefixTitleOfArtifactDetails, 0.0F);
headerCaptionLayout.addComponent(titleOfArtifactDetails);
headerCaptionLayout.setComponentAlignment(titleOfArtifactDetails, Alignment.TOP_LEFT);
headerCaptionLayout.setExpandRatio(titleOfArtifactDetails, 1.0F);
maxMinButton = createMaxMinButton();
artifactDetailsTable = createArtifactDetailsTable();
@@ -199,10 +225,10 @@ public class ArtifactDetailsLayout extends VerticalLayout {
header.setSizeFull();
header.setHeightUndefined();
header.setImmediate(true);
header.addComponents(titleOfArtifactDetails, maxMinButton);
header.setComponentAlignment(titleOfArtifactDetails, Alignment.TOP_LEFT);
header.addComponents(headerCaptionLayout, maxMinButton);
header.setComponentAlignment(headerCaptionLayout, Alignment.TOP_LEFT);
header.setComponentAlignment(maxMinButton, Alignment.TOP_RIGHT);
header.setExpandRatio(titleOfArtifactDetails, 1.0F);
header.setExpandRatio(headerCaptionLayout, 1.0F);
setSizeFull();
setImmediate(true);
@@ -426,12 +452,14 @@ public class ArtifactDetailsLayout extends VerticalLayout {
private void populateArtifactDetails(final Long baseSwModuleId, final String swModuleName) {
if (!readOnly) {
if (StringUtils.isEmpty(swModuleName)) {
setTitleOfLayoutHeader();
if (StringUtils.hasText(swModuleName)) {
prefixTitleOfArtifactDetails.setValue(i18n.getMessage(UIMessageIdProvider.CAPTION_ARTIFACT_DETAILS_OF));
titleOfArtifactDetails.setValue(swModuleName);
} else {
titleOfArtifactDetails.setValue(HawkbitCommonUtil.getArtifactoryDetailsLabelId(swModuleName, i18n));
titleOfArtifactDetails.setContentMode(ContentMode.HTML);
setTitleOfLayoutHeader();
}
}
final Map<String, Object> queryConfiguration;
if (baseSwModuleId != null) {
@@ -452,8 +480,8 @@ public class ArtifactDetailsLayout extends VerticalLayout {
* Set title of artifact details header layout.
*/
private void setTitleOfLayoutHeader() {
titleOfArtifactDetails.setValue(HawkbitCommonUtil.getArtifactoryDetailsLabelId("", i18n));
titleOfArtifactDetails.setContentMode(ContentMode.HTML);
prefixTitleOfArtifactDetails.setValue(i18n.getMessage(UIMessageIdProvider.CAPTION_ARTIFACT_DETAILS));
titleOfArtifactDetails.setValue("");
}
@EventBusListenerMethod(scope = EventScope.UI)

View File

@@ -22,7 +22,6 @@ import org.eclipse.hawkbit.ui.common.builder.WindowBuilder;
import org.eclipse.hawkbit.ui.components.SPUIComponentProvider;
import org.eclipse.hawkbit.ui.customrenderers.renderers.HtmlButtonRenderer;
import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleNoBorder;
import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil;
import org.eclipse.hawkbit.ui.utils.SPUIDefinitions;
import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions;
import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider;
@@ -131,10 +130,15 @@ public abstract class AbstractMetadataPopupLayout<E extends NamedEntity, M exten
public CommonDialogWindow getWindow(final E entity, final String metaDatakey) {
selectedEntity = entity;
metadataWindow = new WindowBuilder(SPUIDefinitions.CREATE_UPDATE_WINDOW).caption(getMetadataCaption())
.content(this).cancelButtonClickListener(event -> onCancel())
.id(UIComponentIdProvider.METADATA_POPUP_ID).layout(mainLayout).i18n(i18n)
.saveDialogCloseListener(new SaveOnDialogCloseListener()).buildCommonDialogWindow();
metadataWindow = new WindowBuilder(SPUIDefinitions.CREATE_UPDATE_WINDOW).content(this)
.cancelButtonClickListener(event -> onCancel()).id(UIComponentIdProvider.METADATA_POPUP_ID)
.layout(mainLayout).i18n(i18n).saveDialogCloseListener(new SaveOnDialogCloseListener())
.buildCommonDialogWindow();
metadataWindow.setCaptionAsHtml(false);
metadataWindow.setAssistivePrefix(i18n.getMessage("caption.metadata.popup") + " " + "<b>");
metadataWindow.setCaption(getElementTitle());
metadataWindow.setAssistivePostfix("</b>");
metadataWindow.setHeight(550, Unit.PIXELS);
metadataWindow.setWidth(800, Unit.PIXELS);
@@ -431,14 +435,6 @@ public abstract class AbstractMetadataPopupLayout<E extends NamedEntity, M exten
return true;
}
private String getMetadataCaption() {
final StringBuilder caption = new StringBuilder();
caption.append(HawkbitCommonUtil.DIV_DESCRIPTION_START + i18n.getMessage("caption.metadata.popup") + " "
+ HawkbitCommonUtil.getBoldHTMLText(getElementTitle()));
caption.append(HawkbitCommonUtil.DIV_DESCRIPTION_END);
return caption.toString();
}
protected String getElementTitle() {
return getSelectedEntity().getName();
}

View File

@@ -58,8 +58,8 @@ public class ConfirmationDialog implements Button.ClickListener {
* @param callback
* the callback.
* @param tab
* ConfirmationTab which contains more information about the
* action which has to be confirmed, e.g. maintenance window
* ConfirmationTab which contains more information about the action
* which has to be confirmed, e.g. maintenance window
* @param id
* the id of the confirmation window
*/
@@ -71,7 +71,7 @@ public class ConfirmationDialog implements Button.ClickListener {
/**
* Constructor for configuring confirmation dialog.
*
*
* @param caption
* the dialog caption.
* @param question
@@ -90,7 +90,7 @@ public class ConfirmationDialog implements Button.ClickListener {
/**
* Constructor for configuring confirmation dialog.
*
*
* @param caption
* the dialog caption.
* @param question
@@ -111,7 +111,7 @@ public class ConfirmationDialog implements Button.ClickListener {
/**
* Constructor for configuring confirmation dialog.
*
*
* @param caption
* the dialog caption.
* @param question
@@ -125,8 +125,8 @@ public class ConfirmationDialog implements Button.ClickListener {
* @param id
* the id of the confirmation dialog
* @param mapCloseToCancel
* Flag indicating whether or not the close event on the
* enclosing window should be mapped to a cancel event.
* Flag indicating whether or not the close event on the enclosing
* window should be mapped to a cancel event.
*/
public ConfirmationDialog(final String caption, final String question, final String okLabel,
final String cancelLabel, final ConfirmationDialogCallback callback, final String id,
@@ -136,7 +136,7 @@ public class ConfirmationDialog implements Button.ClickListener {
/**
* Constructor for configuring confirmation dialog.
*
*
* @param caption
* the dialog caption.
* @param question
@@ -157,7 +157,7 @@ public class ConfirmationDialog implements Button.ClickListener {
/**
* Constructor for configuring confirmation dialog.
*
*
* @param caption
* the dialog caption.
* @param question
@@ -173,8 +173,8 @@ public class ConfirmationDialog implements Button.ClickListener {
* @param id
* the id of the confirmation dialog
* @param tab
* ConfirmationTab which contains more information about the
* action which has to be confirmed, e.g. maintenance window
* ConfirmationTab which contains more information about the action
* which has to be confirmed, e.g. maintenance window
*/
public ConfirmationDialog(final String caption, final String question, final String okLabel,
final String cancelLabel, final ConfirmationDialogCallback callback, final Resource icon, final String id,
@@ -185,7 +185,7 @@ public class ConfirmationDialog implements Button.ClickListener {
/**
* Constructor for configuring confirmation dialog.
*
*
* @param caption
* the dialog caption.
* @param question
@@ -201,11 +201,11 @@ public class ConfirmationDialog implements Button.ClickListener {
* @param id
* the id of the confirmation dialog
* @param tab
* ConfirmationTab which contains more information about the
* action which has to be confirmed, e.g. maintenance window
* ConfirmationTab which contains more information about the action
* which has to be confirmed, e.g. maintenance window
* @param mapCloseToCancel
* Flag indicating whether or not the close event on the
* enclosing window should be mapped to a cancel event.
* Flag indicating whether or not the close event on the enclosing
* window should be mapped to a cancel event.
*/
public ConfirmationDialog(final String caption, final String question, final String okLabel,
final String cancelLabel, final ConfirmationDialogCallback callback, final Resource icon, final String id,
@@ -253,9 +253,9 @@ public class ConfirmationDialog implements Button.ClickListener {
}
private static Label createConfirmationQuestion(final String question) {
final Label questionLbl = new Label(String.format("<p>%s</p>", question.replaceAll("\n", "<br/>")),
ContentMode.HTML);
final Label questionLbl = new Label(question, ContentMode.TEXT);
questionLbl.addStyleName(SPUIStyleDefinitions.CONFIRMBOX_QUESTION_LABEL);
return questionLbl;
}

View File

@@ -10,7 +10,6 @@ package org.eclipse.hawkbit.ui.common.detailslayout;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.ui.SpPermissionChecker;
import org.eclipse.hawkbit.ui.common.builder.LabelBuilder;
import org.eclipse.hawkbit.ui.common.table.BaseEntityEventType;
import org.eclipse.hawkbit.ui.common.table.BaseUIEntityEvent;
import org.eclipse.hawkbit.ui.common.tagdetails.AbstractTagToken;
@@ -26,7 +25,6 @@ import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
import org.vaadin.spring.events.EventBus.UIEventBus;
import com.vaadin.server.FontAwesome;
import com.vaadin.shared.ui.label.ContentMode;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.HorizontalLayout;
@@ -34,6 +32,7 @@ import com.vaadin.ui.Label;
import com.vaadin.ui.TabSheet;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.themes.ValoTheme;
/**
* Abstract Layout to show the entity details.
@@ -50,7 +49,9 @@ public abstract class AbstractTableDetailsLayout<T extends NamedEntity> extends
private T selectedBaseEntity;
private Label caption;
private Label captionPrefix;
private Label captionNameVersion;
private Button editButton;
@@ -91,8 +92,8 @@ public abstract class AbstractTableDetailsLayout<T extends NamedEntity> extends
/**
* Subscribes the view to the eventBus. Method has to be overriden (return
* false) if the view does not contain any listener to avoid Vaadin blowing
* up our logs with warnings.
* false) if the view does not contain any listener to avoid Vaadin blowing up
* our logs with warnings.
*/
protected boolean doSubscribeToEventBus() {
return true;
@@ -122,7 +123,7 @@ public abstract class AbstractTableDetailsLayout<T extends NamedEntity> extends
/**
* Default implementation to handle an entity event.
*
*
* @param baseEntityEvent
* the event
*/
@@ -139,7 +140,8 @@ public abstract class AbstractTableDetailsLayout<T extends NamedEntity> extends
}
protected void setName(final String headerCaption, final String value) {
caption.setValue(HawkbitCommonUtil.getSoftwareModuleName(headerCaption, value));
captionPrefix.setValue(headerCaption + " : ");
captionNameVersion.setValue(HawkbitCommonUtil.getFormattedName(value));
}
/**
@@ -176,8 +178,8 @@ public abstract class AbstractTableDetailsLayout<T extends NamedEntity> extends
descriptionLayout.removeAllComponents();
final Label descLabel = SPUIComponentProvider.createNameValueLabel("", description == null ? "" : description);
/**
* By default text will be truncated based on layout width. So removing
* it as we need full description.
* By default text will be truncated based on layout width. So removing it as we
* need full description.
*/
descLabel.removeStyleName("label-style");
descLabel.setId(UIComponentIdProvider.DETAILS_DESCRIPTION_LABEL_ID);
@@ -213,10 +215,20 @@ public abstract class AbstractTableDetailsLayout<T extends NamedEntity> extends
}
protected void createComponents() {
caption = createHeaderCaption();
caption.setImmediate(true);
caption.setContentMode(ContentMode.HTML);
caption.setId(getDetailsHeaderCaptionId());
captionPrefix = new Label(getDefaultCaption());
captionPrefix.setImmediate(true);
captionPrefix.addStyleName(ValoTheme.LABEL_SMALL);
captionPrefix.addStyleName(ValoTheme.LABEL_BOLD);
captionPrefix.setSizeUndefined();
captionNameVersion = new Label();
captionNameVersion.setImmediate(true);
captionNameVersion.setId(getDetailsHeaderCaptionId());
captionNameVersion.setWidth("100%");
captionNameVersion.addStyleName(ValoTheme.LABEL_SMALL);
captionNameVersion.addStyleName("text-bold");
captionNameVersion.addStyleName("text-cut");
captionNameVersion.addStyleName("header-caption-right");
editButton = SPUIComponentProvider.getButton("", "", i18n.getMessage(UIMessageIdProvider.TOOLTIP_UPDATE), null,
false, FontAwesome.PENCIL_SQUARE_O, SPUIButtonStyleNoBorder.class);
@@ -242,16 +254,31 @@ public abstract class AbstractTableDetailsLayout<T extends NamedEntity> extends
protected void buildLayout() {
nameEditLayout = new HorizontalLayout();
final HorizontalLayout headerCaptionLayout = new HorizontalLayout();
headerCaptionLayout.setMargin(false);
headerCaptionLayout.setSpacing(true);
headerCaptionLayout.setSizeFull();
headerCaptionLayout.addStyleName("header-caption");
headerCaptionLayout.addComponent(captionPrefix);
headerCaptionLayout.setComponentAlignment(captionPrefix, Alignment.TOP_LEFT);
headerCaptionLayout.setExpandRatio(captionPrefix, 0.0F);
headerCaptionLayout.addComponent(captionNameVersion);
headerCaptionLayout.setComponentAlignment(captionNameVersion, Alignment.TOP_LEFT);
headerCaptionLayout.setExpandRatio(captionNameVersion, 1.0F);
nameEditLayout.setWidth(100.0F, Unit.PERCENTAGE);
nameEditLayout.addComponent(caption);
nameEditLayout.setComponentAlignment(caption, Alignment.TOP_LEFT);
nameEditLayout.addComponents(headerCaptionLayout);
nameEditLayout.setComponentAlignment(headerCaptionLayout, Alignment.TOP_LEFT);
if (hasEditPermission()) {
nameEditLayout.addComponent(editButton);
nameEditLayout.setComponentAlignment(editButton, Alignment.TOP_RIGHT);
nameEditLayout.addComponent(manageMetadataBtn);
nameEditLayout.setComponentAlignment(manageMetadataBtn, Alignment.TOP_RIGHT);
}
nameEditLayout.setExpandRatio(caption, 1.0F);
nameEditLayout.setExpandRatio(headerCaptionLayout, 1.0F);
nameEditLayout.addStyleName(SPUIStyleDefinitions.WIDGET_TITLE);
addComponent(nameEditLayout);
@@ -264,13 +291,9 @@ public abstract class AbstractTableDetailsLayout<T extends NamedEntity> extends
addStyleName(SPUIStyleDefinitions.WIDGET_STYLE);
}
private Label createHeaderCaption() {
return new LabelBuilder().name(getDefaultCaption()).buildCaptionLabel();
}
/**
* If there is no data in table (i.e. no row selected), then disable the
* edit button. If row is selected, enable edit button.
* If there is no data in table (i.e. no row selected), then disable the edit
* button. If row is selected, enable edit button.
*/
protected void populateData(final T selectedBaseEntity) {
this.selectedBaseEntity = selectedBaseEntity;
@@ -307,7 +330,7 @@ public abstract class AbstractTableDetailsLayout<T extends NamedEntity> extends
protected abstract void populateMetadataDetails();
/**
* Default caption of header to be displayed when no data row selected in
* Default captionPrefix of header to be displayed when no data row selected in
* table.
*
* @return String

View File

@@ -79,8 +79,8 @@ public class SoftwareModuleDetailsTable extends Table {
* @param i18n
* I18N
* @param isUnassignSoftModAllowed
* boolean flag to check for unassign functionality allowed for
* the view.
* boolean flag to check for unassign functionality allowed for the
* view.
* @param distributionSetManagement
* DistributionSetManagement
* @param permissionChecker

View File

@@ -8,7 +8,6 @@
*/
package org.eclipse.hawkbit.ui.common.grid;
import org.eclipse.hawkbit.ui.common.builder.LabelBuilder;
import org.eclipse.hawkbit.ui.components.SPUIButton;
import org.eclipse.hawkbit.ui.components.SPUIComponentProvider;
import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleNoBorder;
@@ -16,13 +15,14 @@ import org.eclipse.hawkbit.ui.management.state.ManagementUIState;
import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions;
import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider;
import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
import org.springframework.util.StringUtils;
import com.vaadin.server.FontAwesome;
import com.vaadin.shared.ui.label.ContentMode;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.themes.ValoTheme;
/**
* Abstract grid header placed on top of a grid.
@@ -34,6 +34,8 @@ public class DefaultGridHeader extends VerticalLayout {
private final String titleText;
private Label title;
private Label prefixText;
private HorizontalLayout prefixWithTitle;
private HorizontalLayout titleLayout;
private transient AbstractHeaderMaximizeSupport maximizeSupport;
@@ -67,24 +69,49 @@ public class DefaultGridHeader extends VerticalLayout {
* @return this DefaultGridHeader in order to allow method chaining
*/
public DefaultGridHeader init() {
buildTitleLabel();
buildTitleHorizontalLayout();
buildTitleLayout();
buildComponent();
return this;
}
/**
* Builds the title label.
* Builds the title HorizontalLayout containing two labels.
*
* @return title-label
* @return title as HorizontalLayout
*/
protected Label buildTitleLabel() {
// create default title - even shown when no data is available
title = new LabelBuilder().name(titleText).buildCaptionLabel();
title.setImmediate(true);
title.setContentMode(ContentMode.HTML);
protected HorizontalLayout buildTitleHorizontalLayout() {
return title;
prefixText = new Label();
prefixText.setValue(titleText);
prefixText.addStyleName(ValoTheme.LABEL_SMALL);
prefixText.addStyleName(ValoTheme.LABEL_BOLD);
prefixText.setSizeUndefined();
title = new Label();
title.setSizeFull();
title.setImmediate(true);
title.setWidth("100%");
title.addStyleName(ValoTheme.LABEL_SMALL);
title.addStyleName("text-bold");
title.addStyleName("text-cut");
title.addStyleName("header-caption-right");
prefixWithTitle = new HorizontalLayout();
prefixWithTitle.setMargin(false);
prefixWithTitle.setSpacing(true);
prefixWithTitle.setSizeFull();
prefixWithTitle.addStyleName("header-caption");
prefixWithTitle.addComponent(prefixText);
prefixWithTitle.setComponentAlignment(prefixText, Alignment.TOP_LEFT);
prefixWithTitle.setExpandRatio(prefixText, 0.0F);
prefixWithTitle.addComponent(title);
prefixWithTitle.setComponentAlignment(title, Alignment.TOP_LEFT);
prefixWithTitle.setExpandRatio(title, 1.0F);
return prefixWithTitle;
}
/**
@@ -98,9 +125,9 @@ public class DefaultGridHeader extends VerticalLayout {
titleLayout.setSpacing(false);
titleLayout.setMargin(false);
titleLayout.setSizeFull();
titleLayout.addComponent(title);
titleLayout.setComponentAlignment(title, Alignment.TOP_LEFT);
titleLayout.setExpandRatio(title, 0.8F);
titleLayout.addComponent(prefixWithTitle);
titleLayout.setComponentAlignment(prefixWithTitle, Alignment.TOP_LEFT);
titleLayout.setExpandRatio(prefixWithTitle, 0.8F);
if (hasHeaderMaximizeSupport()) {
titleLayout.addComponents(getHeaderMaximizeSupport().maxMinButton);
@@ -125,12 +152,12 @@ public class DefaultGridHeader extends VerticalLayout {
}
/**
* Enables maximize-support for the header by setting a
* HeaderMaximizeSupport implementation.
* Enables maximize-support for the header by setting a HeaderMaximizeSupport
* implementation.
*
* @param maximizeSupport
* encapsulates layout of min-max-button and behavior for
* minimize and maximize.
* encapsulates layout of min-max-button and behavior for minimize
* and maximize.
*/
public void setHeaderMaximizeSupport(final AbstractHeaderMaximizeSupport maximizeSupport) {
this.maximizeSupport = maximizeSupport;
@@ -140,8 +167,7 @@ public class DefaultGridHeader extends VerticalLayout {
* Gets the HeaderMaximizeSupport implementation describing behavior for
* minimize and maximize.
*
* @return maximizeSupport that encapsulates behavior for minimize and
* maximize.
* @return maximizeSupport that encapsulates behavior for minimize and maximize.
*/
public AbstractHeaderMaximizeSupport getHeaderMaximizeSupport() {
return maximizeSupport;
@@ -162,8 +188,15 @@ public class DefaultGridHeader extends VerticalLayout {
*
* @param newTitle
*/
public void updateTitle(final String newTitle) {
title.setValue(newTitle);
public void updateTitle(final String newTitle, final String hasTextCasePrefixText, final String hasNoTextCasePrefixText) {
if (StringUtils.hasText(newTitle)) {
prefixText.setValue(i18n.getMessage(hasTextCasePrefixText));
title.setValue(newTitle);
} else {
prefixText.setValue(i18n.getMessage(hasNoTextCasePrefixText));
title.setValue("");
}
}
/**
@@ -204,14 +237,12 @@ public class DefaultGridHeader extends VerticalLayout {
}
/**
* Additional actions for maximize operation might be performed by this
* method.
* Additional actions for maximize operation might be performed by this method.
*/
protected abstract void maximize();
/**
* Additional actions for minimize operation might be performed by this
* method.
* Additional actions for minimize operation might be performed by this method.
*/
protected abstract void minimize();

View File

@@ -25,9 +25,11 @@ import com.vaadin.server.ExternalResource;
import com.vaadin.server.FontAwesome;
import com.vaadin.server.Resource;
import com.vaadin.shared.ui.label.ContentMode;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.CheckBox;
import com.vaadin.ui.ComboBox;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.Link;
import com.vaadin.ui.Panel;
@@ -44,6 +46,8 @@ import com.vaadin.ui.themes.ValoTheme;
public final class SPUIComponentProvider {
private static final Logger LOG = LoggerFactory.getLogger(SPUIComponentProvider.class);
private static final String LABEL_STYLE = "label-style";
/**
* Prevent Instance creation as utility class.
*/
@@ -202,10 +206,50 @@ public final class SPUIComponentProvider {
final Label nameValueLabel = new Label(getBoldHTMLText(label) + valueStr, ContentMode.HTML);
nameValueLabel.setSizeFull();
nameValueLabel.addStyleName(SPUIDefinitions.TEXT_STYLE);
nameValueLabel.addStyleName("label-style");
nameValueLabel.addStyleName(LABEL_STYLE);
return nameValueLabel;
}
/**
* Method to CreateName value labels.
*
* @param label
* as string
* @param values
* as string
* @return HorizontalLayout
*/
public static HorizontalLayout createNameValueLayout(final String label, final String... values) {
final String valueStr = StringUtils.arrayToDelimitedString(values, " ");
final Label nameValueLabel = new Label( label );
nameValueLabel.setContentMode(ContentMode.TEXT);
nameValueLabel.addStyleName(SPUIDefinitions.TEXT_STYLE);
nameValueLabel.addStyleName("text-bold");
nameValueLabel.setSizeUndefined();
final Label valueStrLabel = new Label(valueStr);
valueStrLabel.setWidth("100%");
valueStrLabel.addStyleName("text-cut");
valueStrLabel.addStyleName(SPUIDefinitions.TEXT_STYLE);
valueStrLabel.addStyleName(LABEL_STYLE);
final HorizontalLayout nameValueLayout = new HorizontalLayout();
nameValueLayout.setMargin(false);
nameValueLayout.setSpacing(true);
nameValueLayout.setSizeFull();
nameValueLayout.addComponent(nameValueLabel);
nameValueLayout.setComponentAlignment(nameValueLabel, Alignment.TOP_LEFT);
nameValueLayout.setExpandRatio(nameValueLabel, 0.0F);
nameValueLayout.addComponent(valueStrLabel);
nameValueLayout.setComponentAlignment(valueStrLabel, Alignment.TOP_LEFT);
nameValueLayout.setExpandRatio(valueStrLabel, 1.0F);
return nameValueLayout;
}
private static Label createUsernameLabel(final String label, final String username) {
String loadAndFormatUsername = "";
if (!StringUtils.isEmpty(username)) {
@@ -215,14 +259,14 @@ public final class SPUIComponentProvider {
final Label nameValueLabel = new Label(getBoldHTMLText(label) + loadAndFormatUsername, ContentMode.HTML);
nameValueLabel.setSizeFull();
nameValueLabel.addStyleName(SPUIDefinitions.TEXT_STYLE);
nameValueLabel.addStyleName("label-style");
nameValueLabel.addStyleName(LABEL_STYLE);
nameValueLabel.setDescription(loadAndFormatUsername);
return nameValueLabel;
}
/**
* Create label which represents the {@link BaseEntity#getCreatedBy()} by
* user name
* Create label which represents the {@link BaseEntity#getCreatedBy()} by user
* name
*
* @param i18n
* the i18n
@@ -236,8 +280,8 @@ public final class SPUIComponentProvider {
}
/**
* Create label which represents the {@link BaseEntity#getLastModifiedBy()}
* by user name
* Create label which represents the {@link BaseEntity#getLastModifiedBy()} by
* user name
*
* @param i18n
* the i18n
@@ -306,11 +350,10 @@ public final class SPUIComponentProvider {
* @param icon
* of the link
* @param targetOpen
* specify how the link should be open (f. e. new windows =
* _blank)
* specify how the link should be open (f. e. new windows = _blank)
* @param style
* chosen style of the link. Might be {@code null} if no style
* should be used
* chosen style of the link. Might be {@code null} if no style should
* be used
* @return a link UI component
*/
public static Link getLink(final String id, final String name, final String resource, final FontAwesome icon,

View File

@@ -87,7 +87,7 @@ public class SwModuleDetails extends AbstractSoftwareModuleDetails {
}
private Button createShowArtifactDetailsButton() {
artifactDetailsButton = SPUIComponentProvider.getButton("", "", "", null, false, FontAwesome.FILE_O,
artifactDetailsButton = SPUIComponentProvider.getButton(UIComponentIdProvider.UPLOAD_SW_MODULE_SHOW_ARTIFACT_DETAILS_BUTTON, "", "", null, false, FontAwesome.FILE_O,
SPUIButtonStyleNoBorder.class);
artifactDetailsButton.setDescription(getI18n().getMessage(UIMessageIdProvider.TOOLTIP_ARTIFACT_ICON));
artifactDetailsButton.addClickListener(event -> showArtifactDetailsWindow(getSelectedBaseEntity()));
@@ -96,9 +96,11 @@ public class SwModuleDetails extends AbstractSoftwareModuleDetails {
private void showArtifactDetailsWindow(final SoftwareModule softwareModule) {
final Window artifactDtlsWindow = new Window();
artifactDtlsWindow.setCaption(HawkbitCommonUtil
.getArtifactoryDetailsLabelId(softwareModule.getName() + "." + softwareModule.getVersion(), getI18n()));
artifactDtlsWindow.setCaptionAsHtml(true);
artifactDtlsWindow.setId(UIComponentIdProvider.SHOW_ARTIFACT_DETAILS_POPUP_ID);
artifactDtlsWindow.setAssistivePrefix(HawkbitCommonUtil
.getArtifactoryDetailsLabelId(softwareModule.getName(), getI18n()) + " " + "<b>");
artifactDtlsWindow.setCaption(softwareModule.getName() + ":" + softwareModule.getVersion());
artifactDtlsWindow.setAssistivePostfix("</b>");
artifactDtlsWindow.setClosable(true);
artifactDtlsWindow.setResizable(true);
artifactDtlsWindow.setImmediate(true);

View File

@@ -21,7 +21,6 @@ import org.eclipse.hawkbit.ui.common.table.BaseEntityEventType;
import org.eclipse.hawkbit.ui.management.event.ManagementUIEvent;
import org.eclipse.hawkbit.ui.management.event.TargetTableEvent;
import org.eclipse.hawkbit.ui.management.state.ManagementUIState;
import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil;
import org.eclipse.hawkbit.ui.utils.SPUIDefinitions;
import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider;
import org.eclipse.hawkbit.ui.utils.UINotification;
@@ -79,13 +78,12 @@ public class ActionHistoryLayout extends AbstractGridComponentLayout {
private String getActionHistoryCaption(final String targetName) {
final String caption;
if (StringUtils.hasText(targetName)) {
caption = getI18n().getMessage(UIMessageIdProvider.CAPTION_ACTION_HISTORY_FOR,
HawkbitCommonUtil.getBoldHTMLText(targetName));
caption = getI18n().getMessage(UIMessageIdProvider.CAPTION_ACTION_HISTORY_FOR, targetName);
} else {
caption = getI18n().getMessage(UIMessageIdProvider.CAPTION_ACTION_HISTORY);
}
return HawkbitCommonUtil.getCaptionText(caption);
return caption;
}
@Override
@@ -113,12 +111,12 @@ public class ActionHistoryLayout extends AbstractGridComponentLayout {
}
/**
* Override default registration for selection propagation in order to
* interrupt update cascade in minimized state to prevent updates on
* invisible action-status-grid and message-grid.
* Override default registration for selection propagation in order to interrupt
* update cascade in minimized state to prevent updates on invisible
* action-status-grid and message-grid.
* <p>
* The master selection is stored and propagation is performed as soon as
* the state changes to maximize and hence the dependent grids are updated.
* The master selection is stored and propagation is performed as soon as the
* state changes to maximize and hence the dependent grids are updated.
*/
@Override
public void registerDetails(final AbstractGrid<?>.DetailsSupport details) {
@@ -188,7 +186,8 @@ public class ActionHistoryLayout extends AbstractGridComponentLayout {
* name of the target
*/
public void updateActionHistoryHeader(final String targetName) {
updateTitle(getActionHistoryCaption(targetName));
updateTitle(targetName, UIMessageIdProvider.CAPTION_ACTION_HISTORY_FOR,
UIMessageIdProvider.CAPTION_ACTION_HISTORY);
}
/**

View File

@@ -350,7 +350,7 @@ public class BulkUploadHandler extends CustomComponent
errorMessage.append(dsAssignmentFailedMsg);
}
if (errorMessage.length() > 0) {
errorMessage.append("<br>");
errorMessage.append("\n");
}
if (tagAssignmentFailedMsg != null) {
errorMessage.append(tagAssignmentFailedMsg);

View File

@@ -225,18 +225,18 @@ public class TargetDetails extends AbstractTableDetailsLayout<Target> {
private void populateDistributionDtls(final VerticalLayout layout, final DistributionSet distributionSet) {
layout.removeAllComponents();
layout.addComponent(SPUIComponentProvider.createNameValueLabel(getI18n().getMessage("label.dist.details.name"),
layout.addComponent(SPUIComponentProvider.createNameValueLayout(getI18n().getMessage("label.dist.details.name"),
distributionSet == null ? "" : distributionSet.getName()));
layout.addComponent(
SPUIComponentProvider.createNameValueLabel(getI18n().getMessage("label.dist.details.version"),
SPUIComponentProvider.createNameValueLayout(getI18n().getMessage("label.dist.details.version"),
distributionSet == null ? "" : distributionSet.getVersion()));
if (distributionSet == null) {
return;
}
distributionSet.getModules()
.forEach(module -> layout.addComponent(getSWModlabel(module.getType().getName(), module)));
.forEach(module -> layout.addComponent(getSWModLayout(module.getType().getName(), module)));
}
private void updateAttributesLayout(final String controllerId) {
@@ -264,11 +264,11 @@ public class TargetDetails extends AbstractTableDetailsLayout<Target> {
final TreeMap<String, String> sortedAttributes = new TreeMap<>((key1, key2) -> key1.compareToIgnoreCase(key2));
sortedAttributes.putAll(attributes);
sortedAttributes.forEach((key, value) -> {
final Label conAttributeLabel = SPUIComponentProvider.createNameValueLabel(key.concat(" : "),
final HorizontalLayout conAttributeLayout = SPUIComponentProvider.createNameValueLayout(key.concat(" : "),
value == null ? "" : value);
conAttributeLabel.setDescription(key.concat(" : ") + value);
conAttributeLabel.addStyleName("label-style");
attributesLayout.addComponent(conAttributeLabel);
conAttributeLayout.setDescription(key.concat(" : ") + value);
conAttributeLayout.addStyleName("label-style");
attributesLayout.addComponent(conAttributeLayout);
});
}
@@ -322,8 +322,9 @@ public class TargetDetails extends AbstractTableDetailsLayout<Target> {
* as Module (JVM|OS|AH)
* @return Label as UI
*/
private static Label getSWModlabel(final String labelName, final SoftwareModule swModule) {
return SPUIComponentProvider.createNameValueLabel(labelName + " : ", swModule.getName(), swModule.getVersion());
private static HorizontalLayout getSWModLayout(final String labelName, final SoftwareModule swModule) {
return SPUIComponentProvider.createNameValueLayout(labelName + " : ", swModule.getName(),
swModule.getVersion());
}
@Override

View File

@@ -84,8 +84,8 @@ public final class HawkbitCommonUtil {
*
* @param text
* text to be trimmed
* @return null if the text is null or if the text is blank, text.trim() if
* the text is not empty.
* @return null if the text is null or if the text is blank, text.trim() if the
* text is not empty.
*/
public static String trimAndNullIfEmpty(final String text) {
if (text != null && !text.trim().isEmpty()) {
@@ -95,18 +95,16 @@ public final class HawkbitCommonUtil {
}
/**
* Concatenate the given text all the string arguments with the given
* delimiter.
* Concatenate the given text all the string arguments with the given delimiter.
*
* @param delimiter
* the delimiter text to be used while concatenation.
* @param texts
* all these string values will be concatenated with the given
* delimiter.
* @return null in case no text arguments to be compared. just concatenation
* of all texts arguments if "delimiter" is null or empty.
* concatenation of all texts arguments with "delimiter" if it not
* null.
* @return null in case no text arguments to be compared. just concatenation of
* all texts arguments if "delimiter" is null or empty. concatenation of
* all texts arguments with "delimiter" if it not null.
*/
public static String concatStrings(final String delimiter, final String... texts) {
final String delim = delimiter == null ? "" : delimiter;
@@ -140,21 +138,6 @@ public final class HawkbitCommonUtil {
return boldStr;
}
/**
* Get Label for Artifact Details.
*
* @param caption
* as caption of the details
* @param name
* as name
* @return SoftwareModuleName
*/
public static String getSoftwareModuleName(final String caption, final String name) {
return new StringBuilder()
.append(DIV_DESCRIPTION_START + caption + " : " + getBoldHTMLText(getFormattedName(name)))
.append(DIV_DESCRIPTION_END).toString();
}
/**
* Get tool tip for Poll status.
*
@@ -181,7 +164,7 @@ public final class HawkbitCommonUtil {
* @return String formatted text
*/
public static String getFormattedName(final String orgText) {
return trimAndNullIfEmpty(orgText) == null ? SPUIDefinitions.SPACE : orgText;
return trimAndNullIfEmpty(orgText) == null ? "" : orgText;
}
private static float findRequiredSwModuleExtraWidth(final float newBrowserWidth) {
@@ -246,10 +229,11 @@ public final class HawkbitCommonUtil {
* @return Label
*/
public static Label getFormatedLabel(final String labelContent) {
final Label labelValue = new Label(labelContent, ContentMode.HTML);
final Label labelValue = new Label(labelContent, ContentMode.TEXT);
labelValue.setSizeFull();
labelValue.addStyleName(SPUIDefinitions.TEXT_STYLE);
labelValue.addStyleName("label-style");
labelValue.addStyleName("avoid-tooltip");
return labelValue;
}
@@ -301,20 +285,20 @@ public final class HawkbitCommonUtil {
final int unassignedCount = result.getUnassigned();
if (assignedCount == 1) {
formMsg.append(i18n.getMessage("message.target.assigned.one", result.getAssignedEntity().get(0).getName(),
tagName)).append("<br>");
tagName)).append("\n");
} else if (assignedCount > 1) {
formMsg.append(i18n.getMessage("message.target.assigned.many", assignedCount, tagName)).append("<br>");
formMsg.append(i18n.getMessage("message.target.assigned.many", assignedCount, tagName)).append("\n");
if (alreadyAssignedCount > 0) {
final String alreadyAssigned = i18n.getMessage("message.target.alreadyAssigned", alreadyAssignedCount);
formMsg.append(alreadyAssigned).append("<br>");
formMsg.append(alreadyAssigned).append("\n");
}
}
if (unassignedCount == 1) {
formMsg.append(i18n.getMessage("message.target.unassigned.one",
result.getUnassignedEntity().get(0).getName(), tagName)).append("<br>");
result.getUnassignedEntity().get(0).getName(), tagName)).append("\n");
} else if (unassignedCount > 1) {
formMsg.append(i18n.getMessage("message.target.unassigned.many", unassignedCount, tagName)).append("<br>");
formMsg.append(i18n.getMessage("message.target.unassigned.many", unassignedCount, tagName)).append("\n");
}
return formMsg.toString();
}
@@ -552,14 +536,13 @@ public final class HawkbitCommonUtil {
* @return complete caption text of the table
*/
public static String getArtifactoryDetailsLabelId(final String name, final VaadinMessageSource i18n) {
String caption;
final String caption;
if (StringUtils.hasText(name)) {
caption = i18n.getMessage(UIMessageIdProvider.CAPTION_ARTIFACT_DETAILS_OF,
HawkbitCommonUtil.getBoldHTMLText(name));
caption = i18n.getMessage(UIMessageIdProvider.CAPTION_ARTIFACT_DETAILS_OF);
} else {
caption = i18n.getMessage(UIMessageIdProvider.CAPTION_ARTIFACT_DETAILS);
}
return getCaptionText(caption);
return caption;
}
/**

View File

@@ -65,7 +65,7 @@ public class NotificationMessage extends Notification {
setCaption(caption);
setDescription(description);
setStyleName(styleName);
setHtmlContentAllowed(true);
setHtmlContentAllowed(false);
setPosition(Position.BOTTOM_RIGHT);
if (autoClose) {
setDelayMsec(SPUILabelDefinitions.SP_DELAY);

View File

@@ -252,6 +252,16 @@ public final class SPUIStyleDefinitions {
*/
public static final String COLOR_LABEL_STYLE = "color-label-style";
/**
* Label bold style.
*/
public static final String BOLD_LABEL_STYLE = "label-bold-style";
/**
* Label right padding style
*/
public static final String RIGHT_PADDING_LABEL_STYLE = "header-caption-right";
/**
* Style - Message.
*/

View File

@@ -445,6 +445,11 @@ public final class UIComponentIdProvider {
*/
public static final String UPLOAD_SW_MODULE_EDIT_BUTTON = "swmodule.edit.button";
/**
* Artifact upload - sw module show artifact details button id.
*/
public static final String UPLOAD_SW_MODULE_SHOW_ARTIFACT_DETAILS_BUTTON = "swmodule.show.artifacts.details.button";
/**
* Artifact upload - sw module metadata button id.
*/
@@ -1167,6 +1172,11 @@ public final class UIComponentIdProvider {
*/
public static final String METADATA_POPUP_ID = "metadata.popup.id";
/**
* Show artifact details popup id.
*/
public static final String SHOW_ARTIFACT_DETAILS_POPUP_ID = "show.artifact.details.popup.id";
/**
* Rollout popup id.
*/

View File

@@ -39,6 +39,7 @@ public class UINotification implements Serializable {
* is the message to displayed as success.
*/
public void displaySuccess(final String message) {
notificationMessage.setIcon(null);
notificationMessage.showNotification(SPUIStyleDefinitions.SP_NOTIFICATION_SUCCESS_MESSAGE_STYLE, null, message,
true);
}
@@ -50,6 +51,7 @@ public class UINotification implements Serializable {
* is the message to displayed as warning.
*/
public void displayWarning(final String message) {
notificationMessage.setIcon(null);
notificationMessage.showNotification(SPUIStyleDefinitions.SP_NOTIFICATION_WARNING_MESSAGE_STYLE, null, message,
true);
}
@@ -61,11 +63,10 @@ public class UINotification implements Serializable {
* as message.
*/
public void displayValidationError(final String message) {
final StringBuilder updatedMsg = new StringBuilder(FontAwesome.EXCLAMATION_TRIANGLE.getHtml());
updatedMsg.append(' ');
updatedMsg.append(message);
notificationMessage.showNotification(SPUIStyleDefinitions.SP_NOTIFICATION_ERROR_MESSAGE_STYLE, null,
updatedMsg.toString(), true);
final StringBuilder updatedMsg = new StringBuilder(message);
notificationMessage.setIcon(FontAwesome.EXCLAMATION_TRIANGLE);
notificationMessage.showNotification(SPUIStyleDefinitions.SP_NOTIFICATION_ERROR_MESSAGE_STYLE, updatedMsg.toString(), null
, true);
}
}

View File

@@ -38,7 +38,7 @@ $generic-text-font-size: $generic-text-font-scale * $v-font-size;
//Report widget max icon style
.icon-only {
@include sp-icon-style($sp-button-size : $icon-font-size);
@include sp-icon-style($sp-button-size: $icon-font-size);
margin-bottom: 12px !important;
margin-right: 6px !important;
}
@@ -91,6 +91,39 @@ $generic-text-font-size: $generic-text-font-scale * $v-font-size;
-webkit-text-overflow: ellipsis;
}
.text-bold {
font-weight: bold !important;
}
.v-Notification-error .v-icon {
color: white;
}
.header-caption-right {
padding-right: 0.5em;
}
.avoid-tooltip {
pointer-events: none !important;
}
//This was needed to make the split of prefix "Artifact details of"
//and the actual value "smName:smVersion" in context of the XSS vulnerabilities
//possible after changing ContentMode
//from HTML to TEXT. As no Labels allowed at the windows caption field of the
//artifact details window , decision was made to use the methods
//Window.setAssistivePrefix() and Window.setAssistivePostfix() to solve the XSS-problem.
//Both, assistive postfix and prefix are usually not visible on the page and only for
//assistive purpose, but with overwriting this third party html class it's a possible
//workaround for the XSS vulnerability. I added a comment in the generic-styles.scss.
.v-assistive-device-only {
position: static;
top: auto;
left: auto;
width: auto;
overflow: visible;
}
.v-panel,
.v-textfield {
background: $widget-bg;

View File

@@ -169,7 +169,7 @@
//confimation dialogue popup - Content margin adjustment
.confirmbox-question-label {
margin: 0 8px 8px;
margin: 15px 8px 18px 8px;
}
.bulk-upload-button{

View File

@@ -98,9 +98,9 @@ distribution.set.tag.updated.event.container.notifcation.message=distribution se
caption.filter.by.type = Filter by type
caption.bulk.upload = Bulk Upload
caption.action.history = Action history
caption.action.history.for = Action history for {0}
caption.action.history.for = Action history for
caption.artifact.details = Artifact Details
caption.artifact.details.of = Artifact Details of {0}
caption.artifact.details.of = Artifact Details of
caption.action.states= Action States
caption.action.messages = Messages
caption.error = Error