mirror of
https://github.com/elisspace/autopsy.git
synced 2026-09-03 14:30:01 +00:00
Merge pull request #4078 from wschaeferB/4112-AddCrStatusColumn
4112 add cr status column
This commit is contained in:
@@ -83,8 +83,8 @@ public class CaseDBCommonAttributeInstanceNode extends FileNode {
|
||||
final String NO_DESCR = Bundle.CommonFilesSearchResultsViewerTable_noDescText();
|
||||
|
||||
sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), NO_DESCR, this.getContent().getName()));
|
||||
this.addScoreProperty(sheetSet, tags);
|
||||
this.addCommentProperty(sheetSet, tags);
|
||||
|
||||
sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_pathColLbl(), Bundle.CommonFilesSearchResultsViewerTable_pathColLbl(), NO_DESCR, this.getContent().getParentPath()));
|
||||
sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_hashsetHitsColLbl(), Bundle.CommonFilesSearchResultsViewerTable_hashsetHitsColLbl(), NO_DESCR, getHashSetHitsCsvList(this.getContent())));
|
||||
sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl(), Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl(), NO_DESCR, this.getDataSource()));
|
||||
|
||||
@@ -67,8 +67,8 @@ final class RelationshipNode extends BlackboardArtifactNode {
|
||||
}
|
||||
|
||||
sheetSet.put(new NodeProperty<>("Type", "Type", "Type", getDisplayName()));
|
||||
addScoreProperty(sheetSet, tags);
|
||||
addCommentProperty(sheetSet, tags);
|
||||
|
||||
final BlackboardArtifact artifact = getArtifact();
|
||||
BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(getArtifact().getArtifactTypeID());
|
||||
if (null != fromID) {
|
||||
|
||||
@@ -728,6 +728,7 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont
|
||||
|
||||
AbstractFile file = getContent();
|
||||
sheetSet.put(new NodeProperty<>("Name", "Name", "Name", file.getName()));
|
||||
addScoreProperty(sheetSet, tags);
|
||||
addCommentProperty(sheetSet, tags);
|
||||
sheetSet.put(new NodeProperty<>("Size", "Size", "Size", file.getSize()));
|
||||
sheetSet.put(new NodeProperty<>("Mime Type", "Mime Type", "Mime Type", StringUtils.defaultString(file.getMIMEType())));
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.awt.Graphics;
|
||||
import java.awt.dnd.DnDConstants;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.beans.FeatureDescriptor;
|
||||
import java.beans.PropertyVetoException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
@@ -86,8 +87,13 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Logger LOGGER = Logger.getLogger(DataResultViewerTable.class.getName());
|
||||
|
||||
private static final String NOTEPAD_ICON_PATH = "org/sleuthkit/autopsy/images/notepad16.png";
|
||||
private static final String RED_CIRCLE_ICON_PATH = "org/sleuthkit/autopsy/images/red-circle-exclamation.png";
|
||||
private static final String YELLOW_CIRCLE_ICON_PATH = "org/sleuthkit/autopsy/images/yellow-circle-yield.png";
|
||||
private static final ImageIcon COMMENT_ICON = new ImageIcon(ImageUtilities.loadImage(NOTEPAD_ICON_PATH, false));
|
||||
private static final ImageIcon INTERESTING_SCORE_ICON = new ImageIcon(ImageUtilities.loadImage(YELLOW_CIRCLE_ICON_PATH, false));
|
||||
private static final ImageIcon NOTABLE_ICON_SCORE = new ImageIcon(ImageUtilities.loadImage(RED_CIRCLE_ICON_PATH, false));
|
||||
@NbBundle.Messages("DataResultViewerTable.firstColLbl=Name")
|
||||
static private final String FIRST_COLUMN_LABEL = Bundle.DataResultViewerTable_firstColLbl();
|
||||
static private final Color TAGGED_ROW_COLOR = new Color(255, 255, 195);
|
||||
@@ -109,7 +115,6 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
public DataResultViewerTable() {
|
||||
this(null, Bundle.DataResultViewerTable_title());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a tabular result viewer that displays the children of a given
|
||||
@@ -658,35 +663,43 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
*/
|
||||
private class IconRendererTableListener implements TableColumnModelListener {
|
||||
|
||||
@NbBundle.Messages({"DataResultViewerTable.commentRender.name=C"})
|
||||
@NbBundle.Messages({"DataResultViewerTable.commentRender.name=C",
|
||||
"DataResultViewerTable.scoreRender.name=S"})
|
||||
@Override
|
||||
public void columnAdded(TableColumnModelEvent e) {
|
||||
if (e.getSource() instanceof ETableColumnModel) {
|
||||
TableColumn column = ((TableColumnModel) e.getSource()).getColumn(e.getToIndex());
|
||||
if (e.getSource() instanceof ETableColumnModel) {
|
||||
TableColumn column = ((TableColumnModel) e.getSource()).getColumn(e.getToIndex());
|
||||
if (column.getHeaderValue().toString().equals(Bundle.DataResultViewerTable_commentRender_name())) {
|
||||
//if the current column is a comment column set the cell renderer to be the HasCommentCellRenderer
|
||||
if (column.getHeaderValue().toString().equals(Bundle.DataResultViewerTable_commentRender_name())) {
|
||||
column.setCellRenderer(new HasCommentCellRenderer());
|
||||
}
|
||||
column.setCellRenderer(new HasCommentCellRenderer());
|
||||
} else if (column.getHeaderValue().toString().equals(Bundle.DataResultViewerTable_scoreRender_name())) {
|
||||
//if the current column is a score column set the cell renderer to be the ScoreCellRenderer
|
||||
column.setCellRenderer(new ScoreCellRenderer());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void columnRemoved(TableColumnModelEvent e) {
|
||||
public void columnRemoved(TableColumnModelEvent e
|
||||
) {
|
||||
//Don't do anything when column removed
|
||||
}
|
||||
|
||||
@Override
|
||||
public void columnMoved(TableColumnModelEvent e) {
|
||||
public void columnMoved(TableColumnModelEvent e
|
||||
) {
|
||||
//Don't do anything when column moved
|
||||
}
|
||||
|
||||
@Override
|
||||
public void columnMarginChanged(ChangeEvent e) {
|
||||
public void columnMarginChanged(ChangeEvent e
|
||||
) {
|
||||
//Don't do anything when column margin changed
|
||||
}
|
||||
|
||||
@Override
|
||||
public void columnSelectionChanged(ListSelectionEvent e) {
|
||||
public void columnSelectionChanged(ListSelectionEvent e
|
||||
) {
|
||||
//Don't do anything when column selection changed
|
||||
}
|
||||
|
||||
@@ -929,18 +942,76 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* A renderer which based on the contents of the cell will display an icon
|
||||
* to indicate the score associated with the item.
|
||||
*/
|
||||
private final class ScoreCellRenderer extends ColorTagCustomRenderer {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
||||
Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
setBackground(component.getBackground()); //inherit highlighting
|
||||
setHorizontalAlignment(CENTER);
|
||||
Object switchValue = null;
|
||||
if ((value instanceof NodeProperty)) {
|
||||
//The Outline view has properties in the cell, the value contained in the property is what we want
|
||||
try {
|
||||
switchValue = ((Node.Property) value).getValue();
|
||||
setToolTipText(((FeatureDescriptor) value).getShortDescription());
|
||||
} catch (IllegalAccessException | InvocationTargetException ex) {
|
||||
//Unable to get the value from the NodeProperty no Icon will be displayed
|
||||
}
|
||||
|
||||
} else {
|
||||
//JTables contain the value we want directly in the cell
|
||||
switchValue = value;
|
||||
}
|
||||
setText("");
|
||||
if ((switchValue instanceof Score)) {
|
||||
|
||||
switch ((Score) switchValue) {
|
||||
case INTERESTING_SCORE:
|
||||
setIcon(INTERESTING_SCORE_ICON);
|
||||
break;
|
||||
case NOTABLE_SCORE:
|
||||
setIcon(NOTABLE_ICON_SCORE);
|
||||
break;
|
||||
case NO_SCORE:
|
||||
default:
|
||||
setIcon(null);
|
||||
}
|
||||
} else {
|
||||
setIcon(null);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum to denote the presence of a comment associated with the content or
|
||||
* artifacts generated from it.
|
||||
*/
|
||||
public enum HasCommentStatus {
|
||||
NO_COMMENT,
|
||||
TAG_NO_COMMENT,
|
||||
TAG_NO_COMMENT,
|
||||
CR_COMMENT,
|
||||
TAG_COMMENT,
|
||||
CR_AND_TAG_COMMENTS
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum to denote the score given to an item to draw the users attention
|
||||
*/
|
||||
public enum Score {
|
||||
NO_SCORE,
|
||||
INTERESTING_SCORE,
|
||||
NOTABLE_SCORE
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called from within the constructor to initialize the form.
|
||||
* WARNING: Do NOT modify this code. The content of this method is always
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil;
|
||||
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.Score;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.AbstractFilePropertyType.*;
|
||||
import static org.sleuthkit.autopsy.datamodel.Bundle.*;
|
||||
@@ -47,9 +48,11 @@ import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.HasCommentStat
|
||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||
import org.sleuthkit.autopsy.ingest.ModuleContentEvent;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.ContentTag;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
|
||||
/**
|
||||
* An abstract node that encapsulates AbstractFile data
|
||||
@@ -313,6 +316,49 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
|
||||
status));
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by subclasses of AbstractAbstractFileNode to add the Score property
|
||||
* to their sheets.
|
||||
*
|
||||
* @param sheetSet the modifiable Sheet.Set returned by
|
||||
* Sheet.get(Sheet.PROPERTIES)
|
||||
* @param tags the list of tags associated with the file
|
||||
*/
|
||||
@NbBundle.Messages({"AbstractAbstractFileNode.createSheet.score.name=S",
|
||||
"AbstractAbstractFileNode.createSheet.score.displayName=S",
|
||||
"AbstractAbstractFileNode.createSheet.notableFile.description=File recognized as notable.",
|
||||
"AbstractAbstractFileNode.createSheet.interestingResult.description=File has interesting result associated with it.",
|
||||
"AbstractAbstractFileNode.createSheet.taggedFile.description=File has been tagged.",
|
||||
"AbstractAbstractFileNode.createSheet.notableTaggedFile.description=File tagged with notable tag."})
|
||||
protected void addScoreProperty(Sheet.Set sheetSet, List<ContentTag> tags) {
|
||||
Score score = Score.NO_SCORE;
|
||||
String description = NO_DESCR;
|
||||
if (content.getKnown() == TskData.FileKnown.BAD) {
|
||||
score = Score.NOTABLE_SCORE;
|
||||
description = Bundle.AbstractAbstractFileNode_createSheet_notableFile_description();
|
||||
}
|
||||
try {
|
||||
if (score == Score.NO_SCORE && !content.getArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT).isEmpty()) {
|
||||
score = Score.INTERESTING_SCORE;
|
||||
description = Bundle.AbstractAbstractFileNode_createSheet_interestingResult_description();
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.WARNING, "Error getting artifacts for file: " + content.getName(), ex);
|
||||
}
|
||||
if (tags.size() > 0 && (score == Score.NO_SCORE || score == Score.INTERESTING_SCORE)) {
|
||||
score = Score.INTERESTING_SCORE;
|
||||
description = Bundle.AbstractAbstractFileNode_createSheet_taggedFile_description();
|
||||
for (ContentTag tag : tags) {
|
||||
if (tag.getName().getKnownStatus() == TskData.FileKnown.BAD) {
|
||||
score = Score.NOTABLE_SCORE;
|
||||
description = Bundle.AbstractAbstractFileNode_createSheet_notableTaggedFile_description();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
sheetSet.put(new NodeProperty<>(Bundle.AbstractAbstractFileNode_createSheet_score_name(), Bundle.AbstractAbstractFileNode_createSheet_score_displayName(), description, score));
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by subclasses of AbstractAbstractFileNode to add the tags property
|
||||
* to their sheets.
|
||||
|
||||
@@ -81,9 +81,10 @@ public abstract class AbstractFsContentNode<T extends AbstractFile> extends Abst
|
||||
AbstractFilePropertyType.NAME.toString(),
|
||||
NO_DESCR,
|
||||
getName()));
|
||||
//add the cr status property before the propertyMap to ensure it is early in column order
|
||||
addScoreProperty(sheetSet, tags);
|
||||
//add the comment property before the propertyMap to ensure it is early in column order
|
||||
addCommentProperty(sheetSet, tags);
|
||||
|
||||
for (AbstractFilePropertyType propType : AbstractFilePropertyType.values()) {
|
||||
final String propString = propType.toString();
|
||||
sheetSet.put(new NodeProperty<>(propString, propString, NO_DESCR, map.get(propString)));
|
||||
|
||||
@@ -52,6 +52,7 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil;
|
||||
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil;
|
||||
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.Score;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import static org.sleuthkit.autopsy.datamodel.DisplayableItemNode.findLinked;
|
||||
@@ -66,6 +67,7 @@ import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.Tag;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
|
||||
/**
|
||||
* Node wrapping a blackboard artifact object. This is generated from several
|
||||
@@ -345,6 +347,7 @@ public class BlackboardArtifactNode extends AbstractContentNode<BlackboardArtifa
|
||||
NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.srcFile.displayName"),
|
||||
NO_DESCR,
|
||||
this.getSourceName()));
|
||||
addScoreProperty(sheetSet, tags);
|
||||
addCommentProperty(sheetSet, tags);
|
||||
if (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID()) {
|
||||
try {
|
||||
@@ -589,6 +592,51 @@ public class BlackboardArtifactNode extends AbstractContentNode<BlackboardArtifa
|
||||
status));
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by (subclasses of) BlackboardArtifactNode to add the Score property
|
||||
* to their sheets.
|
||||
*
|
||||
* @param sheetSet the modifiable Sheet.Set returned by
|
||||
* Sheet.get(Sheet.PROPERTIES)
|
||||
* @param tags the list of tags associated with the file
|
||||
*/
|
||||
@NbBundle.Messages({"BlackboardArtifactNode.createSheet.score.name=S",
|
||||
"BlackboardArtifactNode.createSheet.score.displayName=S",
|
||||
"BlackboardArtifactNode.createSheet.notableFile.description=Associated file recognized as notable.",
|
||||
"BlackboardArtifactNode.createSheet.interestingResult.description=Result has an interesting result associated with it.",
|
||||
"BlackboardArtifactNode.createSheet.taggedItem.description=Result or associated file has been tagged.",
|
||||
"BlackboardArtifactNode.createSheet.notableTaggedItem.description=Result or associated file tagged with notable tag."})
|
||||
protected void addScoreProperty(Sheet.Set sheetSet, List<Tag> tags) {
|
||||
Score score = Score.NO_SCORE;
|
||||
String description = "";
|
||||
if (associated instanceof AbstractFile) {
|
||||
if (((AbstractFile) associated).getKnown() == TskData.FileKnown.BAD) {
|
||||
score = Score.NOTABLE_SCORE;
|
||||
description = Bundle.BlackboardArtifactNode_createSheet_notableFile_description();
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (score == Score.NO_SCORE && !content.getArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT).isEmpty()) {
|
||||
score = Score.INTERESTING_SCORE;
|
||||
description = Bundle.BlackboardArtifactNode_createSheet_interestingResult_description();
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.WARNING, "Error getting artifacts for artifact: " + content.getName(), ex);
|
||||
}
|
||||
if (tags.size() > 0 && (score == Score.NO_SCORE || score == Score.INTERESTING_SCORE)) {
|
||||
score = Score.INTERESTING_SCORE;
|
||||
description = Bundle.BlackboardArtifactNode_createSheet_taggedItem_description();
|
||||
for (Tag tag : tags) {
|
||||
if (tag.getName().getKnownStatus() == TskData.FileKnown.BAD) {
|
||||
score = Score.NOTABLE_SCORE;
|
||||
description = Bundle.BlackboardArtifactNode_createSheet_notableTaggedItem_description();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_score_name(), Bundle.BlackboardArtifactNode_createSheet_score_displayName(), description, score));
|
||||
}
|
||||
|
||||
private void updateSheet() {
|
||||
this.setSheet(createSheet());
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ public class LayoutFileNode extends AbstractAbstractFileNode<LayoutFile> {
|
||||
NbBundle.getMessage(this.getClass(), "LayoutFileNode.createSheet.name.displayName"),
|
||||
NbBundle.getMessage(this.getClass(), "LayoutFileNode.createSheet.name.desc"),
|
||||
getName()));
|
||||
addScoreProperty(sheetSet, tags);
|
||||
addCommentProperty(sheetSet, tags);
|
||||
final String NO_DESCR = NbBundle.getMessage(this.getClass(), "LayoutFileNode.createSheet.noDescr.text");
|
||||
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
||||
|
||||
@@ -62,6 +62,7 @@ public class LocalDirectoryNode extends SpecialDirectoryNode {
|
||||
Bundle.LocalDirectoryNode_createSheet_name_displayName(),
|
||||
Bundle.LocalDirectoryNode_createSheet_name_desc(),
|
||||
getName()));
|
||||
addScoreProperty(sheetSet, tags);
|
||||
addCommentProperty(sheetSet, tags);
|
||||
// At present, a LocalDirectory will never be a datasource - the top level of a logical
|
||||
// file set is a VirtualDirectory
|
||||
|
||||
@@ -81,8 +81,8 @@ public class LocalFileNode extends AbstractAbstractFileNode<AbstractFile> {
|
||||
NbBundle.getMessage(this.getClass(), "LocalFileNode.createSheet.name.displayName"),
|
||||
NbBundle.getMessage(this.getClass(), "LocalFileNode.createSheet.name.desc"),
|
||||
getName()));
|
||||
addScoreProperty(sheetSet, tags);
|
||||
addCommentProperty(sheetSet, tags);
|
||||
|
||||
final String NO_DESCR = NbBundle.getMessage(this.getClass(), "LocalFileNode.createSheet.noDescr.text");
|
||||
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
||||
sheetSet.put(new NodeProperty<>(entry.getKey(), entry.getKey(), NO_DESCR, entry.getValue()));
|
||||
|
||||
@@ -89,6 +89,7 @@ public class VirtualDirectoryNode extends SpecialDirectoryNode {
|
||||
NbBundle.getMessage(this.getClass(), "VirtualDirectoryNode.createSheet.name.desc"),
|
||||
getName()));
|
||||
if (!this.content.isDataSource()) {
|
||||
addScoreProperty(sheetSet, tags);
|
||||
addCommentProperty(sheetSet, tags);
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
fillPropertyMap(map, getContent());
|
||||
|
||||
BIN
Core/src/org/sleuthkit/autopsy/images/red-circle-exclamation.png
Normal file
BIN
Core/src/org/sleuthkit/autopsy/images/red-circle-exclamation.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 314 B |
BIN
Core/src/org/sleuthkit/autopsy/images/yellow-circle-yield.png
Normal file
BIN
Core/src/org/sleuthkit/autopsy/images/yellow-circle-yield.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 578 B |
Reference in New Issue
Block a user