diff --git a/app/assets/stylesheets/graylog2.less b/app/assets/stylesheets/graylog2.less index 87190317453d..5ee3b924035c 100644 --- a/app/assets/stylesheets/graylog2.less +++ b/app/assets/stylesheets/graylog2.less @@ -273,8 +273,7 @@ label { } #sidebar { - padding-left: 10px; - padding-right: 10px; + padding-left: 0; } #sidebar .affix { @@ -284,13 +283,19 @@ label { #main-content-sidebar { padding-left: 0; - padding-right: 10px; + padding-right: 0; } #main-content { padding: 15px 25px; } +// This is rendered inside a #main-content, and we need to compensate the margin-top to make it look nicer +#main-content-search { + margin-bottom: 0; + margin-top: -15px; +} + #universalsearch { padding: 5px; margin-bottom: 0px; diff --git a/app/controllers/SavedSearchesController.java b/app/controllers/SavedSearchesController.java index 75a4490ecff7..a2f91068f943 100644 --- a/app/controllers/SavedSearchesController.java +++ b/app/controllers/SavedSearchesController.java @@ -113,7 +113,7 @@ private Call callFromSavedSearch(SavedSearch search, String streamId, boolean in } if (streamId == null || streamId.isEmpty()) { - return routes.SearchControllerV2.index( + return routes.SearchController.index( (String) searchQuery.get("query"), (String) searchQuery.get("rangeType"), relative, diff --git a/app/controllers/SearchController.java b/app/controllers/SearchController.java index 5a07e6fbd5ec..98ba818dcecc 100755 --- a/app/controllers/SearchController.java +++ b/app/controllers/SearchController.java @@ -18,8 +18,15 @@ */ package controllers; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.base.Function; +import com.google.common.base.Predicate; import com.google.common.base.Splitter; +import com.google.common.base.Strings; +import com.google.common.collect.Collections2; +import com.google.common.collect.HashMultimap; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -35,28 +42,41 @@ import org.graylog2.restclient.lib.timeranges.InvalidRangeParametersException; import org.graylog2.restclient.lib.timeranges.RelativeRange; import org.graylog2.restclient.lib.timeranges.TimeRange; +import org.graylog2.restclient.models.ClusterEntity; +import org.graylog2.restclient.models.Input; import org.graylog2.restclient.models.MessagesService; +import org.graylog2.restclient.models.Node; +import org.graylog2.restclient.models.NodeService; +import org.graylog2.restclient.models.Radio; import org.graylog2.restclient.models.SavedSearch; import org.graylog2.restclient.models.SavedSearchService; import org.graylog2.restclient.models.SearchSort; import org.graylog2.restclient.models.Stream; +import org.graylog2.restclient.models.StreamService; import org.graylog2.restclient.models.UniversalSearch; import org.graylog2.restclient.models.api.responses.QueryParseError; import org.graylog2.restclient.models.api.results.DateHistogramResult; +import org.graylog2.restclient.models.api.results.MessageResult; import org.graylog2.restclient.models.api.results.SearchResult; +import org.joda.time.DateTime; import org.joda.time.Minutes; +import play.Logger; import play.libs.Json; import play.mvc.Result; -import views.helpers.Permissions; +import javax.annotation.Nullable; import javax.inject.Inject; import java.io.IOException; +import java.io.InputStream; import java.util.Collections; import java.util.Comparator; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import static views.helpers.Permissions.isPermitted; + public class SearchController extends AuthenticatedController { // guess high, so we never have a bad resolution private static final int DEFAULT_ASSUMED_GRAPH_RESOLUTION = 4000; @@ -70,19 +90,12 @@ public class SearchController extends AuthenticatedController { @Inject private ServerNodes serverNodes; @Inject + private StreamService streamService; + @Inject + private NodeService nodeService; + @Inject private ObjectMapper objectMapper; - public Result globalSearch() { - // User would not be allowed to do any global searches anyway, so we can redirect him to the streams page to avoid confusion. - if (Permissions.isPermitted(RestPermissions.SEARCHES_ABSOLUTE) - || Permissions.isPermitted(RestPermissions.SEARCHES_RELATIVE) - || Permissions.isPermitted(RestPermissions.SEARCHES_KEYWORD)) { - return ok(views.html.search.global.render(currentUser())); - } else { - return redirect(routes.StreamsController.index()); - } - } - public Result index(String q, String rangeType, int relative, @@ -94,12 +107,44 @@ public Result index(String q, String sortField, String sortOrder, String fields, int displayWidth) { - SearchSort sort = buildSearchSort(sortField, sortOrder); - - return renderSearch(q, rangeType, relative, from, to, keyword, interval, page, savedSearchId, fields, displayWidth, sort, null, null); + if (isPermitted(RestPermissions.SEARCHES_ABSOLUTE) + || isPermitted(RestPermissions.SEARCHES_RELATIVE) + || isPermitted(RestPermissions.SEARCHES_KEYWORD)) { + SearchSort sort = buildSearchSort(sortField, sortOrder); + + return renderSearch(q, + Strings.isNullOrEmpty(rangeType) ? "relative" : rangeType, // stupid + relative, + from, + to, + keyword, + interval, + page, + savedSearchId, + fields, + displayWidth, + sort, + null, + null); + } else { + return redirect(routes.StreamsController.index()); + } } - protected Result renderSearch(String q, String rangeType, int relative, String from, String to, String keyword, String interval, int page, String savedSearchId, String fields, int displayWidth, SearchSort sort, Stream stream, String filter) { + protected Result renderSearch(String q, + String rangeType, + int relative, + String from, + String to, + String keyword, + String interval, + int page, + String savedSearchId, + String fields, + int displayWidth, + SearchSort sort, + Stream stream, + String filter) { UniversalSearch search; try { search = getSearch(q, filter, rangeType, relative, from, to, keyword, page, sort); @@ -109,18 +154,101 @@ protected Result renderSearch(String q, String rangeType, int relative, String f return status(400, views.html.errors.error.render("Invalid range type provided.", e1, request())); } + SearchResult searchResult; DateHistogramResult histogramResult; SavedSearch savedSearch = null; Set selectedFields = getSelectedFields(fields); String formattedHistogramResults; - + Set streams; + Set inputs = Sets.newHashSet(); + Map nodes = Maps.newHashMap(); + + nodes.putAll(Maps.transformEntries(serverNodes.asMap(), + new Maps.EntryTransformer() { + @Override + public NodeDescription transformEntry( + @Nullable String key, + @Nullable Node value) { + return new NodeDescription(value); + } + })); try { if (savedSearchId != null && !savedSearchId.isEmpty()) { savedSearch = savedSearchService.get(savedSearchId); } + searchResult = search.search(); + // TODO create a bulk call to get stream and input details (and restrict the fields that come back) + final Set streamIds = Sets.newHashSet(); + final HashMultimap usedInputIds = HashMultimap.create(); + final HashMultimap usedRadioIds = HashMultimap.create(); + + for (MessageResult messageResult : searchResult.getMessages()) { + streamIds.addAll(messageResult.getStreamIds()); + usedInputIds.put(messageResult.getSourceNodeId(), messageResult.getSourceInputId()); + if (messageResult.getSourceRadioId() != null) { + usedRadioIds.put(messageResult.getSourceRadioId(), messageResult.getSourceRadioInputId()); + } + } + // resolve all stream information in the result set + final HashSet allStreams = Sets.newHashSet(streamService.all().iterator()); + streams = Sets.newHashSet(Collections2.transform(Sets.filter(allStreams, new Predicate() { + @Override + public boolean apply(Stream input) { + return streamIds.contains(input.getId()); + } + }), new Function() { + @Nullable + @Override + public StreamDescription apply(@Nullable Stream stream) { + return new StreamDescription(stream); + } + })); + + // resolve all used inputs and nodes from the result set + final Map nodeMap = serverNodes.asMap(); + for (final String nodeId : usedInputIds.keySet()) { + final HashSet allInputs = Sets.newHashSet(nodeMap.get(nodeId).getInputs()); + inputs = Sets.newHashSet(Collections2.transform(Sets.filter(allInputs, new Predicate() { + @Override + public boolean apply(Input input) { + return usedInputIds.get(nodeId).contains(input.getId()); + } + }), new Function() { + @Nullable + @Override + public InputDescription apply(Input input) { + return new InputDescription(input); + } + })); + } + + // resolve radio inputs + for (final String radioId : usedRadioIds.keySet()) { + try { + final Radio radio = nodeService.loadRadio(radioId); + nodes.put(radio.getId(), new NodeDescription(radio)); + final HashSet allRadioInputs = Sets.newHashSet(radio.getInputs()); + inputs.addAll(Collections2.transform(Sets.filter(allRadioInputs, new Predicate() { + @Override + public boolean apply(Input input) { + return usedRadioIds.get(radioId).contains(input.getId()); + } + }), new Function() { + @Override + public InputDescription apply(Input input) { + return new InputDescription(input); + } + })); + + } catch (NodeService.NodeNotFoundException e) { + Logger.warn("Could not load radio node " + radioId, e); + } + } + + searchResult.setAllFields(getAllFields()); // histogram resolution (strangely aka interval) @@ -145,11 +273,32 @@ protected Result renderSearch(String q, String rangeType, int relative, String f return status(504, views.html.errors.error.render(message, e, request())); } - if (searchResult.getTotalResultCount() > 0) { - return ok(views.html.search.results.render(currentUser(), search, searchResult, histogramResult, formattedHistogramResults, q, page, savedSearch, selectedFields, serverNodes.asMap(), stream)); - } else { - return ok(views.html.search.noresults.render(currentUser(), q, searchResult, savedSearch, selectedFields, stream)); - } + return ok(views.html.search.index.render(currentUser(), + q, + search, + page, + savedSearch, + selectedFields, + searchResult, + histogramResult, + formattedHistogramResults, + nodes, + Maps.uniqueIndex(streams, new Function() { + @Nullable + @Override + public String apply(@Nullable StreamDescription stream) { + return stream == null ? null : stream.getId(); + } + }), + Maps.uniqueIndex(inputs, new Function() { + @Nullable + @Override + public String apply(@Nullable InputDescription input) { + return input == null ? null : input.getId(); + } + }), + stream)); + } protected String determineHistogramResolution(final SearchResult searchResult) { @@ -177,9 +326,11 @@ public int compare(IndexRangeSummary o1, IndexRangeSummary o2) { } }); IndexRangeSummary oldestIndex = usedIndices.get(0); - queryRangeInMinutes = Minutes.minutesBetween(oldestIndex.start(), searchResult.getToDateTime()).getMinutes(); + queryRangeInMinutes = Minutes.minutesBetween(oldestIndex.start(), + searchResult.getToDateTime()).getMinutes(); } else { - queryRangeInMinutes = Minutes.minutesBetween(searchResult.getFromDateTime(), searchResult.getToDateTime()).getMinutes(); + queryRangeInMinutes = Minutes.minutesBetween(searchResult.getFromDateTime(), + searchResult.getToDateTime()).getMinutes(); } if (queryRangeInMinutes < DAY / 2) { @@ -242,31 +393,45 @@ protected Set getSelectedFields(String fields) { return selectedFields; } - public Result exportAsCsv(String q, String filter, String rangeType, int relative, String from, String to, String keyword, String fields) { + public Result exportAsCsv(String q, + String filter, + String rangeType, + int relative, + String from, + String to, + String keyword, + String fields) { UniversalSearch search; try { - search = getSearch(q, filter.isEmpty() ? null : filter, rangeType, relative, from, to, keyword, 0, UniversalSearch.DEFAULT_SORT); + search = getSearch(q, + filter.isEmpty() ? null : filter, + rangeType, + relative, + from, + to, + keyword, + 0, + UniversalSearch.DEFAULT_SORT); } catch (InvalidRangeParametersException e2) { return status(400, views.html.errors.error.render("Invalid range parameters provided.", e2, request())); } catch (IllegalArgumentException e1) { return status(400, views.html.errors.error.render("Invalid range type provided.", e1, request())); } - final String s = "obsolete"; -// try { + final InputStream stream; + try { Set selectedFields = getSelectedFields(fields); - //s = search.searchAsCsv(selectedFields); -// } catch (IOException e) { -// return status(504, views.html.errors.error.render(ApiClient.ERROR_MSG_IO, e, request())); -// } catch (APIException e) { -// String message = "There was a problem with your search. We expected HTTP 200, but got a HTTP " + e.getHttpCode() + "."; -// return status(504, views.html.errors.error.render(message, e, request())); -// } - - // TODO streaming the result + stream = search.searchAsCsv(selectedFields); + } catch (IOException e) { + return status(504, views.html.errors.error.render(ApiClient.ERROR_MSG_IO, e, request())); + } catch (APIException e) { + String message = "There was a problem with your search. We expected HTTP 200, but got a HTTP " + e.getHttpCode() + "."; + return status(504, views.html.errors.error.render(message, e, request())); + } + response().setContentType(MediaType.CSV_UTF_8.toString()); response().setHeader("Content-Disposition", "attachment; filename=graylog-searchresult.csv"); - return ok(s); + return ok(stream); } protected List getAllFields() { @@ -277,7 +442,15 @@ protected List getAllFields() { return allFields; } - protected UniversalSearch getSearch(String q, String filter, String rangeType, int relative, String from, String to, String keyword, int page, SearchSort order) + protected UniversalSearch getSearch(String q, + String filter, + String rangeType, + int relative, + String from, + String to, + String keyword, + int page, + SearchSort order) throws InvalidRangeParametersException, IllegalArgumentException { if (q == null || q.trim().isEmpty()) { q = "*"; @@ -307,4 +480,282 @@ protected SearchSort buildSearchSort(String sortField, String sortOrder) { return UniversalSearch.DEFAULT_SORT; } } + + + public static class InputDescription { + @JsonIgnore + private final Input input; + + public InputDescription(Input input) { + this.input = input; + } + + @JsonProperty + public String getId() { + return input.getId(); + } + + @JsonProperty + public String getName() { + return input.getName(); + } + + @JsonProperty + public String getTitle() { + return input.getTitle(); + } + + @JsonProperty + public String getCreatorUser() { + return input.getCreatorUser().getName(); + } + + @JsonProperty + public Boolean getGlobal() { + return input.getGlobal(); + } + + @JsonProperty + public DateTime getCreatedAt() { + return input.getCreatedAt(); + } + + @JsonProperty + public String getType() { + return input.getType(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + InputDescription that = (InputDescription) o; + + return input.equals(that.input); + + } + + @Override + public int hashCode() { + return input.hashCode(); + } + } + + public static class StreamDescription { + @JsonIgnore + private final Stream stream; + + public StreamDescription(Stream stream) { + this.stream = stream; + } + + public static StreamDescription of(Stream stream) { + if (stream == null) { + return null; + } + return new StreamDescription(stream); + } + + @JsonProperty + public String getDescription() { + return stream.getDescription(); + } + + @JsonProperty + public String getTitle() { + return stream.getTitle(); + } + + @JsonProperty + public String getCreatorUser() { + return stream.getCreatorUser().getName(); + } + + @JsonProperty + public DateTime getCreatedAt() { + return stream.getCreatedAt(); + } + + @JsonProperty + public String getId() { + return stream.getId(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + StreamDescription that = (StreamDescription) o; + + return stream.equals(that.stream); + + } + + @Override + public int hashCode() { + return stream.hashCode(); + } + } + + public static class NodeDescription { + @JsonIgnore + private final ClusterEntity entity; + + public NodeDescription(ClusterEntity cluster) { + this.entity = cluster; + } + + @JsonProperty + public String getNodeId() { + return entity.getNodeId(); + } + + @JsonProperty + public String getShortNodeId() { + return entity.getShortNodeId(); + } + + @JsonProperty + public String getHostname() { + return entity.getHostname(); + } + + @JsonProperty + public boolean isMaster() { + return entity instanceof Node && ((Node) entity).isMaster(); + } + + @JsonProperty + public boolean isRadio() { + return entity instanceof Radio; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + NodeDescription that = (NodeDescription) o; + + return entity.equals(that.entity); + + } + + @Override + public int hashCode() { + return entity.hashCode(); + } + } + + public Result showMessage(String index, String id) { + final Set inputs = Sets.newHashSet(); + final Set streams = Sets.newHashSet(); + final Set nodes = Sets.newHashSet(); + + try { + final MessageResult message = messagesService.getMessage(index, id); + final Node sourceNode = getSourceNode(message); + final Radio sourceRadio = getSourceRadio(message); + final Input sourceInput = getSourceInput(sourceNode, message); + final Input sourceRadioInput = getSourceInput(sourceRadio, message); + + nodes.add(new NodeDescription(sourceNode)); + if (sourceRadio != null) { + nodes.add(new NodeDescription(sourceRadio)); + } + inputs.add(new InputDescription(sourceInput)); + if (sourceRadioInput != null) { + inputs.add(new InputDescription(sourceRadioInput)); + } + + for (String streamId : message.getStreamIds()) { + if (isPermitted(RestPermissions.STREAMS_READ, streamId)) { + try { + final Stream stream = streamService.get(streamId); + streams.add(new StreamDescription(stream)); + } catch (APIException e) { + // We get a 404 if the stream no longer exists. + Logger.debug("Skipping stream of message", e); + } + } + } + + return ok(views.html.search.show_message.render(currentUser(), + message, + Maps.uniqueIndex(nodes, new Function() { + @Nullable + @Override + public String apply(@Nullable NodeDescription node) { + return node == null ? null : node.getNodeId(); + } + }), + Maps.uniqueIndex(streams, new Function() { + @Nullable + @Override + public String apply(@Nullable StreamDescription stream) { + return stream == null ? null : stream.getId(); + } + }), + Maps.uniqueIndex(inputs, new Function() { + @Nullable + @Override + public String apply(@Nullable InputDescription input) { + return input == null ? null : input.getId(); + } + }))); + } catch (IOException e) { + return status(500, views.html.errors.error.render(ApiClient.ERROR_MSG_IO, e, request())); + } catch (APIException e) { + String message = "Could not get message. We expected HTTP 200, but got a HTTP " + e.getHttpCode() + "."; + return status(500, views.html.errors.error.render(message, e, request())); + } + } + + private Node getSourceNode(MessageResult m) { + try { + return nodeService.loadNode(m.getSourceNodeId()); + } catch (Exception e) { + Logger.warn("Could not derive source node from message <" + m.getId() + ">.", e); + } + + return null; + } + + private Radio getSourceRadio(MessageResult m) { + if (m.viaRadio()) { + try { + return nodeService.loadRadio(m.getSourceRadioId()); + } catch (Exception e) { + Logger.warn("Could not derive source radio from message <" + m.getId() + ">.", e); + } + } + + return null; + } + + private static Input getSourceInput(Node node, MessageResult m) { + if (node != null && isPermitted(RestPermissions.INPUTS_READ, m.getSourceInputId())) { + try { + return node.getInput(m.getSourceInputId()); + } catch (Exception e) { + Logger.warn("Could not derive source input from message <" + m.getId() + ">.", e); + } + } + + return null; + } + + private static Input getSourceInput(Radio radio, MessageResult m) { + if (radio != null) { + try { + return radio.getInput(m.getSourceRadioInputId()); + } catch (Exception e) { + Logger.warn("Could not derive source radio input from message <" + m.getId() + ">.", e); + } + } + + return null; + } } diff --git a/app/controllers/SearchControllerV2.java b/app/controllers/SearchControllerV2.java deleted file mode 100755 index 8e83bc007ac7..000000000000 --- a/app/controllers/SearchControllerV2.java +++ /dev/null @@ -1,762 +0,0 @@ -/* - * Copyright 2012-2015 TORCH GmbH, 2015 Graylog, Inc. - * - * This file is part of Graylog. - * - * Graylog is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Graylog is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Graylog. If not, see . - */ -package controllers; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.base.Function; -import com.google.common.base.Predicate; -import com.google.common.base.Splitter; -import com.google.common.base.Strings; -import com.google.common.collect.Collections2; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import com.google.common.net.MediaType; -import lib.SearchTools; -import lib.security.RestPermissions; -import org.graylog2.rest.models.system.indexer.responses.IndexRangeSummary; -import org.graylog2.restclient.lib.APIException; -import org.graylog2.restclient.lib.ApiClient; -import org.graylog2.restclient.lib.Field; -import org.graylog2.restclient.lib.ServerNodes; -import org.graylog2.restclient.lib.timeranges.InvalidRangeParametersException; -import org.graylog2.restclient.lib.timeranges.RelativeRange; -import org.graylog2.restclient.lib.timeranges.TimeRange; -import org.graylog2.restclient.models.ClusterEntity; -import org.graylog2.restclient.models.Input; -import org.graylog2.restclient.models.MessagesService; -import org.graylog2.restclient.models.Node; -import org.graylog2.restclient.models.NodeService; -import org.graylog2.restclient.models.Radio; -import org.graylog2.restclient.models.SavedSearch; -import org.graylog2.restclient.models.SavedSearchService; -import org.graylog2.restclient.models.SearchSort; -import org.graylog2.restclient.models.Stream; -import org.graylog2.restclient.models.StreamService; -import org.graylog2.restclient.models.UniversalSearch; -import org.graylog2.restclient.models.api.responses.QueryParseError; -import org.graylog2.restclient.models.api.results.DateHistogramResult; -import org.graylog2.restclient.models.api.results.MessageResult; -import org.graylog2.restclient.models.api.results.SearchResult; -import org.joda.time.DateTime; -import org.joda.time.Minutes; -import play.Logger; -import play.libs.Json; -import play.mvc.Result; - -import javax.annotation.Nullable; -import javax.inject.Inject; -import java.io.IOException; -import java.io.InputStream; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import static views.helpers.Permissions.isPermitted; - -// TODO none of this makes sense, it's just a start. -public class SearchControllerV2 extends AuthenticatedController { - // guess high, so we never have a bad resolution - private static final int DEFAULT_ASSUMED_GRAPH_RESOLUTION = 4000; - - @Inject - protected UniversalSearch.Factory searchFactory; - @Inject - protected MessagesService messagesService; - @Inject - protected SavedSearchService savedSearchService; - @Inject - private ServerNodes serverNodes; - @Inject - private StreamService streamService; - @Inject - private NodeService nodeService; - @Inject - private ObjectMapper objectMapper; - - public Result index(String q, - String rangeType, - int relative, - String from, String to, - String keyword, - String interval, - int page, - String savedSearchId, - String sortField, String sortOrder, - String fields, - int displayWidth) { - if (isPermitted(RestPermissions.SEARCHES_ABSOLUTE) - || isPermitted(RestPermissions.SEARCHES_RELATIVE) - || isPermitted(RestPermissions.SEARCHES_KEYWORD)) { - SearchSort sort = buildSearchSort(sortField, sortOrder); - - return renderSearch(q, - Strings.isNullOrEmpty(rangeType) ? "relative" : rangeType, // stupid - relative, - from, - to, - keyword, - interval, - page, - savedSearchId, - fields, - displayWidth, - sort, - null, - null); - } else { - return redirect(routes.StreamsController.index()); - } - } - - protected Result renderSearch(String q, - String rangeType, - int relative, - String from, - String to, - String keyword, - String interval, - int page, - String savedSearchId, - String fields, - int displayWidth, - SearchSort sort, - Stream stream, - String filter) { - UniversalSearch search; - try { - search = getSearch(q, filter, rangeType, relative, from, to, keyword, page, sort); - } catch (InvalidRangeParametersException e2) { - return status(400, views.html.errors.error.render("Invalid range parameters provided.", e2, request())); - } catch (IllegalArgumentException e1) { - return status(400, views.html.errors.error.render("Invalid range type provided.", e1, request())); - } - - - SearchResult searchResult; - DateHistogramResult histogramResult; - SavedSearch savedSearch = null; - Set selectedFields = getSelectedFields(fields); - String formattedHistogramResults; - Set streams; - Set inputs = Sets.newHashSet(); - Map nodes = Maps.newHashMap(); - - nodes.putAll(Maps.transformEntries(serverNodes.asMap(), - new Maps.EntryTransformer() { - @Override - public NodeDescription transformEntry( - @Nullable String key, - @Nullable Node value) { - return new NodeDescription(value); - } - })); - try { - if (savedSearchId != null && !savedSearchId.isEmpty()) { - savedSearch = savedSearchService.get(savedSearchId); - } - - - searchResult = search.search(); - // TODO create a bulk call to get stream and input details (and restrict the fields that come back) - final Set streamIds = Sets.newHashSet(); - final HashMultimap usedInputIds = HashMultimap.create(); - final HashMultimap usedRadioIds = HashMultimap.create(); - - for (MessageResult messageResult : searchResult.getMessages()) { - streamIds.addAll(messageResult.getStreamIds()); - usedInputIds.put(messageResult.getSourceNodeId(), messageResult.getSourceInputId()); - if (messageResult.getSourceRadioId() != null) { - usedRadioIds.put(messageResult.getSourceRadioId(), messageResult.getSourceRadioInputId()); - } - } - // resolve all stream information in the result set - final HashSet allStreams = Sets.newHashSet(streamService.all().iterator()); - streams = Sets.newHashSet(Collections2.transform(Sets.filter(allStreams, new Predicate() { - @Override - public boolean apply(Stream input) { - return streamIds.contains(input.getId()); - } - }), new Function() { - @Nullable - @Override - public StreamDescription apply(@Nullable Stream stream) { - return new StreamDescription(stream); - } - })); - - // resolve all used inputs and nodes from the result set - final Map nodeMap = serverNodes.asMap(); - for (final String nodeId : usedInputIds.keySet()) { - final HashSet allInputs = Sets.newHashSet(nodeMap.get(nodeId).getInputs()); - inputs = Sets.newHashSet(Collections2.transform(Sets.filter(allInputs, new Predicate() { - @Override - public boolean apply(Input input) { - return usedInputIds.get(nodeId).contains(input.getId()); - } - }), new Function() { - @Nullable - @Override - public InputDescription apply(Input input) { - return new InputDescription(input); - } - })); - } - - // resolve radio inputs - for (final String radioId : usedRadioIds.keySet()) { - try { - final Radio radio = nodeService.loadRadio(radioId); - nodes.put(radio.getId(), new NodeDescription(radio)); - final HashSet allRadioInputs = Sets.newHashSet(radio.getInputs()); - inputs.addAll(Collections2.transform(Sets.filter(allRadioInputs, new Predicate() { - @Override - public boolean apply(Input input) { - return usedRadioIds.get(radioId).contains(input.getId()); - } - }), new Function() { - @Override - public InputDescription apply(Input input) { - return new InputDescription(input); - } - })); - - } catch (NodeService.NodeNotFoundException e) { - Logger.warn("Could not load radio node " + radioId, e); - } - } - - - searchResult.setAllFields(getAllFields()); - - // histogram resolution (strangely aka interval) - if (interval == null || interval.isEmpty() || !SearchTools.isAllowedDateHistogramInterval(interval)) { - interval = determineHistogramResolution(searchResult); - } - histogramResult = search.dateHistogram(interval); - formattedHistogramResults = formatHistogramResults(histogramResult.getResults(), displayWidth); - } catch (IOException e) { - return status(504, views.html.errors.error.render(ApiClient.ERROR_MSG_IO, e, request())); - } catch (APIException e) { - if (e.getHttpCode() == 400) { - try { - QueryParseError qpe = objectMapper.readValue(e.getResponseBody(), QueryParseError.class); - return ok(views.html.search.queryerror.render(currentUser(), q, qpe, savedSearch, fields, stream)); - } catch (IOException ioe) { - // Ignore - } - } - - String message = "There was a problem with your search. We expected HTTP 200, but got a HTTP " + e.getHttpCode() + "."; - return status(504, views.html.errors.error.render(message, e, request())); - } - - return ok(views.html.searchv2.index.render(currentUser(), - q, - search, - page, - savedSearch, - selectedFields, - searchResult, - histogramResult, - formattedHistogramResults, - nodes, - Maps.uniqueIndex(streams, new Function() { - @Nullable - @Override - public String apply(@Nullable StreamDescription stream) { - return stream == null ? null : stream.getId(); - } - }), - Maps.uniqueIndex(inputs, new Function() { - @Nullable - @Override - public String apply(@Nullable InputDescription input) { - return input == null ? null : input.getId(); - } - }), - stream)); - - } - - protected String determineHistogramResolution(final SearchResult searchResult) { - final String interval; - final int HOUR = 60; - final int DAY = HOUR * 24; - final int WEEK = DAY * 7; - final int MONTH = HOUR * 24 * 30; - final int YEAR = MONTH * 12; - - // Return minute as default resolution if search from and to DateTimes are not available - if (searchResult.getFromDateTime() == null && searchResult.getToDateTime() == null) { - return "minute"; - } - - int queryRangeInMinutes; - - // We don't want to use fromDateTime coming from the search query if the user asked for all messages - if (isEmptyRelativeRange(searchResult.getTimeRange())) { - List usedIndices = searchResult.getUsedIndices(); - Collections.sort(usedIndices, new Comparator() { - @Override - public int compare(IndexRangeSummary o1, IndexRangeSummary o2) { - return o1.start().compareTo(o2.start()); - } - }); - IndexRangeSummary oldestIndex = usedIndices.get(0); - queryRangeInMinutes = Minutes.minutesBetween(oldestIndex.start(), - searchResult.getToDateTime()).getMinutes(); - } else { - queryRangeInMinutes = Minutes.minutesBetween(searchResult.getFromDateTime(), - searchResult.getToDateTime()).getMinutes(); - } - - if (queryRangeInMinutes < DAY / 2) { - interval = "minute"; - } else if (queryRangeInMinutes < DAY * 2) { - interval = "hour"; - } else if (queryRangeInMinutes < MONTH) { - interval = "day"; - } else if (queryRangeInMinutes < MONTH * 6) { - interval = "week"; - } else if (queryRangeInMinutes < YEAR * 2) { - interval = "month"; - } else if (queryRangeInMinutes < YEAR * 10) { - interval = "quarter"; - } else { - interval = "year"; - } - return interval; - } - - private boolean isEmptyRelativeRange(TimeRange timeRange) { - return (timeRange.getType() == TimeRange.Type.RELATIVE) && (((RelativeRange) timeRange).isEmptyRange()); - } - - /** - * [{ x: -1893456000, y: 92228531 }, { x: -1577923200, y: 106021568 }] - * - * @return A JSON string representation of the result, suitable for Rickshaw data graphing. - */ - protected String formatHistogramResults(Map histogramResults, int displayWidth) { - final int saneDisplayWidth = (displayWidth == -1 || displayWidth < 100 || displayWidth > DEFAULT_ASSUMED_GRAPH_RESOLUTION) ? DEFAULT_ASSUMED_GRAPH_RESOLUTION : displayWidth; - final List> points = Lists.newArrayList(); - - // using the absolute value guarantees, that there will always be enough values for the given resolution - final int factor = (saneDisplayWidth != -1 && histogramResults.size() > saneDisplayWidth) ? histogramResults.size() / saneDisplayWidth : 1; - - int index = 0; - for (Map.Entry result : histogramResults.entrySet()) { - // TODO: instead of sampling we might consider interpolation (compare DashboardsApiController) - if (index % factor == 0) { - Map point = Maps.newHashMap(); - point.put("x", Long.parseLong(result.getKey())); - point.put("y", result.getValue()); - - points.add(point); - } - index++; - } - - return Json.stringify(Json.toJson(points)); - } - - protected Set getSelectedFields(String fields) { - Set selectedFields = Sets.newLinkedHashSet(); - if (fields != null && !fields.isEmpty()) { - Iterables.addAll(selectedFields, Splitter.on(',').split(fields)); - } else { - selectedFields.addAll(Field.STANDARD_SELECTED_FIELDS); - } - return selectedFields; - } - - public Result exportAsCsv(String q, - String filter, - String rangeType, - int relative, - String from, - String to, - String keyword, - String fields) { - UniversalSearch search; - try { - search = getSearch(q, - filter.isEmpty() ? null : filter, - rangeType, - relative, - from, - to, - keyword, - 0, - UniversalSearch.DEFAULT_SORT); - } catch (InvalidRangeParametersException e2) { - return status(400, views.html.errors.error.render("Invalid range parameters provided.", e2, request())); - } catch (IllegalArgumentException e1) { - return status(400, views.html.errors.error.render("Invalid range type provided.", e1, request())); - } - - final InputStream stream; - try { - Set selectedFields = getSelectedFields(fields); - stream = search.searchAsCsv(selectedFields); - } catch (IOException e) { - return status(504, views.html.errors.error.render(ApiClient.ERROR_MSG_IO, e, request())); - } catch (APIException e) { - String message = "There was a problem with your search. We expected HTTP 200, but got a HTTP " + e.getHttpCode() + "."; - return status(504, views.html.errors.error.render(message, e, request())); - } - - response().setContentType(MediaType.CSV_UTF_8.toString()); - response().setHeader("Content-Disposition", "attachment; filename=graylog-searchresult.csv"); - return ok(stream); - } - - protected List getAllFields() { - List allFields = Lists.newArrayList(); - for (String f : messagesService.getMessageFields()) { - allFields.add(new Field(f)); - } - return allFields; - } - - protected UniversalSearch getSearch(String q, - String filter, - String rangeType, - int relative, - String from, - String to, - String keyword, - int page, - SearchSort order) - throws InvalidRangeParametersException, IllegalArgumentException { - if (q == null || q.trim().isEmpty()) { - q = "*"; - } - - // Determine timerange type. - TimeRange timerange = TimeRange.factory(rangeType, relative, from, to, keyword); - - UniversalSearch search; - if (filter == null) { - search = searchFactory.queryWithRangePageAndOrder(q, timerange, page, order); - } else { - search = searchFactory.queryWithFilterRangePageAndOrder(q, filter, timerange, page, order); - } - - return search; - } - - protected SearchSort buildSearchSort(String sortField, String sortOrder) { - if (sortField == null || sortOrder == null || sortField.isEmpty() || sortOrder.isEmpty()) { - return UniversalSearch.DEFAULT_SORT; - } - - try { - return new SearchSort(sortField, SearchSort.Direction.valueOf(sortOrder.toUpperCase())); - } catch (IllegalArgumentException e) { - return UniversalSearch.DEFAULT_SORT; - } - } - - - public static class InputDescription { - @JsonIgnore - private final Input input; - - public InputDescription(Input input) { - this.input = input; - } - - @JsonProperty - public String getId() { - return input.getId(); - } - - @JsonProperty - public String getName() { - return input.getName(); - } - - @JsonProperty - public String getTitle() { - return input.getTitle(); - } - - @JsonProperty - public String getCreatorUser() { - return input.getCreatorUser().getName(); - } - - @JsonProperty - public Boolean getGlobal() { - return input.getGlobal(); - } - - @JsonProperty - public DateTime getCreatedAt() { - return input.getCreatedAt(); - } - - @JsonProperty - public String getType() { - return input.getType(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - InputDescription that = (InputDescription) o; - - return input.equals(that.input); - - } - - @Override - public int hashCode() { - return input.hashCode(); - } - } - - public static class StreamDescription { - @JsonIgnore - private final Stream stream; - - public StreamDescription(Stream stream) { - this.stream = stream; - } - - public static StreamDescription of(Stream stream) { - if (stream == null) { - return null; - } - return new StreamDescription(stream); - } - - @JsonProperty - public String getDescription() { - return stream.getDescription(); - } - - @JsonProperty - public String getTitle() { - return stream.getTitle(); - } - - @JsonProperty - public String getCreatorUser() { - return stream.getCreatorUser().getName(); - } - - @JsonProperty - public DateTime getCreatedAt() { - return stream.getCreatedAt(); - } - - @JsonProperty - public String getId() { - return stream.getId(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - StreamDescription that = (StreamDescription) o; - - return stream.equals(that.stream); - - } - - @Override - public int hashCode() { - return stream.hashCode(); - } - } - - public static class NodeDescription { - @JsonIgnore - private final ClusterEntity entity; - - public NodeDescription(ClusterEntity cluster) { - this.entity = cluster; - } - - @JsonProperty - public String getNodeId() { - return entity.getNodeId(); - } - - @JsonProperty - public String getShortNodeId() { - return entity.getShortNodeId(); - } - - @JsonProperty - public String getHostname() { - return entity.getHostname(); - } - - @JsonProperty - public boolean isMaster() { - return entity instanceof Node && ((Node) entity).isMaster(); - } - - @JsonProperty - public boolean isRadio() { - return entity instanceof Radio; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - NodeDescription that = (NodeDescription) o; - - return entity.equals(that.entity); - - } - - @Override - public int hashCode() { - return entity.hashCode(); - } - } - - public Result showMessage(String index, String id) { - final Set inputs = Sets.newHashSet(); - final Set streams = Sets.newHashSet(); - final Set nodes = Sets.newHashSet(); - - try { - final MessageResult message = messagesService.getMessage(index, id); - final Node sourceNode = getSourceNode(message); - final Radio sourceRadio = getSourceRadio(message); - final Input sourceInput = getSourceInput(sourceNode, message); - final Input sourceRadioInput = getSourceInput(sourceRadio, message); - - nodes.add(new NodeDescription(sourceNode)); - if (sourceRadio != null) { - nodes.add(new NodeDescription(sourceRadio)); - } - inputs.add(new InputDescription(sourceInput)); - if (sourceRadioInput != null) { - inputs.add(new InputDescription(sourceRadioInput)); - } - - for (String streamId : message.getStreamIds()) { - if (isPermitted(RestPermissions.STREAMS_READ, streamId)) { - try { - final Stream stream = streamService.get(streamId); - streams.add(new StreamDescription(stream)); - } catch (APIException e) { - // We get a 404 if the stream no longer exists. - Logger.debug("Skipping stream of message", e); - } - } - } - - return ok(views.html.searchv2.show_message.render(currentUser(), - message, - Maps.uniqueIndex(nodes, new Function() { - @Nullable - @Override - public String apply(@Nullable NodeDescription node) { - return node == null ? null : node.getNodeId(); - } - }), - Maps.uniqueIndex(streams, new Function() { - @Nullable - @Override - public String apply(@Nullable StreamDescription stream) { - return stream == null ? null : stream.getId(); - } - }), - Maps.uniqueIndex(inputs, new Function() { - @Nullable - @Override - public String apply(@Nullable InputDescription input) { - return input == null ? null : input.getId(); - } - }))); - } catch (IOException e) { - return status(500, views.html.errors.error.render(ApiClient.ERROR_MSG_IO, e, request())); - } catch (APIException e) { - String message = "Could not get message. We expected HTTP 200, but got a HTTP " + e.getHttpCode() + "."; - return status(500, views.html.errors.error.render(message, e, request())); - } - } - - private Node getSourceNode(MessageResult m) { - try { - return nodeService.loadNode(m.getSourceNodeId()); - } catch (Exception e) { - Logger.warn("Could not derive source node from message <" + m.getId() + ">.", e); - } - - return null; - } - - private Radio getSourceRadio(MessageResult m) { - if (m.viaRadio()) { - try { - return nodeService.loadRadio(m.getSourceRadioId()); - } catch (Exception e) { - Logger.warn("Could not derive source radio from message <" + m.getId() + ">.", e); - } - } - - return null; - } - - private static Input getSourceInput(Node node, MessageResult m) { - if (node != null && isPermitted(RestPermissions.INPUTS_READ, m.getSourceInputId())) { - try { - return node.getInput(m.getSourceInputId()); - } catch (Exception e) { - Logger.warn("Could not derive source input from message <" + m.getId() + ">.", e); - } - } - - return null; - } - - private static Input getSourceInput(Radio radio, MessageResult m) { - if (radio != null) { - try { - return radio.getInput(m.getSourceRadioInputId()); - } catch (Exception e) { - Logger.warn("Could not derive source radio input from message <" + m.getId() + ">.", e); - } - } - - return null; - } -} diff --git a/app/controllers/StreamSearchController.java b/app/controllers/StreamSearchController.java index 19607e71aeb4..9b6a7642b972 100644 --- a/app/controllers/StreamSearchController.java +++ b/app/controllers/StreamSearchController.java @@ -11,7 +11,7 @@ import java.io.IOException; -public class StreamSearchController extends SearchControllerV2 { +public class StreamSearchController extends SearchController { @Inject private StreamService streamService; diff --git a/app/controllers/api/StreamsApiController.java b/app/controllers/api/StreamsApiController.java index f5229c46b57b..aa443b090729 100644 --- a/app/controllers/api/StreamsApiController.java +++ b/app/controllers/api/StreamsApiController.java @@ -2,7 +2,7 @@ import com.google.common.collect.Lists; import controllers.AuthenticatedController; -import controllers.SearchControllerV2; +import controllers.SearchController; import org.graylog2.restclient.lib.APIException; import org.graylog2.restclient.models.Stream; import org.graylog2.restclient.models.StreamService; @@ -42,11 +42,11 @@ public Result testMatch(String stream_id) { } public Result listStreams() { - List streamDescriptions = Lists.newArrayList(); + List streamDescriptions = Lists.newArrayList(); try { final List streams = streamService.all(); for (Stream stream : streams) { - streamDescriptions.add(new SearchControllerV2.StreamDescription(stream)); + streamDescriptions.add(new SearchController.StreamDescription(stream)); } } catch (IOException e) { return status(500, "Could not load streams"); diff --git a/app/views/alerts/manage.scala.html b/app/views/alerts/manage.scala.html index 0a1fd83c4c7e..cb6c2093d523 100644 --- a/app/views/alerts/manage.scala.html +++ b/app/views/alerts/manage.scala.html @@ -11,7 +11,7 @@ @import views.helpers.Permissions._ @import views.helpers.Permissions -@main("Alerts", null, "", currentUser, false, false) { +@main("Alerts", null, "", currentUser, false) {

diff --git a/app/views/main.scala.html b/app/views/main.scala.html index 6e29353c3cee..99803961a1e7 100644 --- a/app/views/main.scala.html +++ b/app/views/main.scala.html @@ -1,4 +1,4 @@ -@(title: String, sidebarContent: Html, searchQuery: String = "", currentUser: org.graylog2.restclient.models.User, hasSearch: Boolean = true, reactSearch: Boolean = false)(content: Html)(implicit stream: org.graylog2.restclient.models.Stream = null) +@(title: String, sidebarContent: Html, searchQuery: String = "", currentUser: org.graylog2.restclient.models.User, reactSearch: Boolean = true)(content: Html)(implicit stream: org.graylog2.restclient.models.Stream = null) @import play.libs.Json @import views.helpers.Permissions._ @import lib.security.RestPermissions._ @@ -25,10 +25,7 @@
- @if(hasSearch && (stream != null || (isPermitted(SEARCHES_ABSOLUTE) || isPermitted(SEARCHES_RELATIVE) || isPermitted(SEARCHES_KEYWORD)))) { - @partials.universalsearch(searchQuery) - } - @if(reactSearch) { + @if(reactSearch && (stream != null || (isPermitted(SEARCHES_ABSOLUTE) || isPermitted(SEARCHES_RELATIVE) || isPermitted(SEARCHES_KEYWORD)))) { } @@ -51,27 +48,9 @@

- @if(reactSearch) { - @content - } else { - @if(sidebarContent != null) { - - } - - @if(sidebarContent != null) { -
- @content -
- } else { -
- @content -
- } - } +
+ @content +
diff --git a/app/views/messages/show_as_partial.scala.html b/app/views/messages/show_as_partial.scala.html index f79fc2c2064f..b51fa615a0d5 100644 --- a/app/views/messages/show_as_partial.scala.html +++ b/app/views/messages/show_as_partial.scala.html @@ -10,7 +10,7 @@

- + @message.getId

@@ -82,7 +82,7 @@

  • Show message terms
  • -
  • Permalink
  • +
  • Permalink
  • diff --git a/app/views/partials/navbar.scala.html b/app/views/partials/navbar.scala.html index 1ca0b33801fc..b8dd1dab9754 100644 --- a/app/views/partials/navbar.scala.html +++ b/app/views/partials/navbar.scala.html @@ -33,10 +33,9 @@ routes.javascript.OutputsController.index, routes.javascript.SavedSearchesController.execute, routes.javascript.SavedSearchesController.delete, - routes.javascript.SearchController.globalSearch, - routes.javascript.SearchControllerV2.index, - routes.javascript.SearchControllerV2.showMessage, - routes.javascript.SearchControllerV2.exportAsCsv, + routes.javascript.SearchController.index, + routes.javascript.SearchController.showMessage, + routes.javascript.SearchController.exportAsCsv, routes.javascript.SessionsController.destroy, routes.javascript.StreamsController.index, routes.javascript.StreamSearchController.index, diff --git a/app/views/partials/universalsearch.scala.html b/app/views/partials/universalsearch.scala.html deleted file mode 100644 index 4e1271b55eaf..000000000000 --- a/app/views/partials/universalsearch.scala.html +++ /dev/null @@ -1,88 +0,0 @@ -@(searchQuery: String)(implicit stream: org.graylog2.restclient.models.Stream = null) - -@import views.helpers.Permissions.isPermitted -@import lib.security.RestPermissions.SAVEDSEARCHES_READ - -
    -
    -
    -
    -
    - - - - - @*** Those are never changed by JS and can be read by methods that need the *original* information. ***@ - @if(request.getQueryString("rangetype") != null && !request.getQueryString("rangetype").isEmpty) { - - } - - - - - @******************************************************************************************************@ - - @*************************************************************** - * Parameter selection here sucks and I promise to fix it soon. * - * * - * Lennart, September 25th 2013 (never forget) * - ***************************************************************@ - -
    - - -
    - @partials.timerangeselectors.relative(request) -
    - -
    - @partials.timerangeselectors.absolute() -
    - -
    - @partials.timerangeselectors.keyword() -
    -
    - - @if(isPermitted(SAVEDSEARCHES_READ)) { -
    - -
    - } -
    - -
    -
    - @views.html.partials.support.bubble.render("general/queries") -
    - -
    - -
    -
    - -
    -
    -
    -
    -
    diff --git a/app/views/search/global.scala.html b/app/views/search/global.scala.html deleted file mode 100644 index 40874826e2bd..000000000000 --- a/app/views/search/global.scala.html +++ /dev/null @@ -1,17 +0,0 @@ -@(currentUser: org.graylog2.restclient.models.User) - -@main("Search", null, "", currentUser) { - - -
    -
    - Need help with the search syntax? Take a look at our @views.html.partials.search_query_documentation(). -
    - Use our getting started guides to take the first steps. -
    -
    - - - - -} diff --git a/app/views/searchv2/index.scala.html b/app/views/search/index.scala.html similarity index 79% rename from app/views/searchv2/index.scala.html rename to app/views/search/index.scala.html index 0b9796c5de2b..1152473359ab 100644 --- a/app/views/searchv2/index.scala.html +++ b/app/views/search/index.scala.html @@ -8,13 +8,13 @@ searchResult: org.graylog2.restclient.models.api.results.SearchResult, histogram: org.graylog2.restclient.models.api.results.DateHistogramResult, formattedHistogramResults: String, - nodes: Map[String, controllers.SearchControllerV2.NodeDescription], - streams: Map[String, controllers.SearchControllerV2.StreamDescription], - inputs: Map[String, controllers.SearchControllerV2.InputDescription] + nodes: Map[String, controllers.SearchController.NodeDescription], + streams: Map[String, controllers.SearchController.StreamDescription], + inputs: Map[String, controllers.SearchController.InputDescription] )(implicit stream: org.graylog2.restclient.models.Stream) @import play.libs.Json; -@import controllers.SearchControllerV2.StreamDescription +@import controllers.SearchController.StreamDescription @streamId = @{ if (stream == null) { @@ -24,7 +24,7 @@ } } -@main("Search V2", null, query, currentUser, false, true) { +@main("Search", null, query, currentUser, true) {
    Delete saved search - } - -

    - @if(stream == null) { - Nothing found - } else { - No results in stream «@stream.getTitle» - } -

    -

    - Your search - @if(stream != null) { - in stream @stream.getTitle - }returned no results. Take a look at the @views.html.partials.search_query_documentation() if you need help with the search syntax. - -

    - -

    -

    - 0 messages found. - - @if(stream == null) { - @views.html.partials.dashboards.add_to_dashboard.render("search_result_count", "Add to dashboard", null) - } else { - @views.html.partials.dashboards.add_to_dashboard.render("stream_search_result_count", "Add to dashboard", Array("data-stream-id='" + stream.getId + "'")) - } -

    -

    - - @views.html.partials.support_sources() - @views.html.search.partials.show_query_modal(searchResult.getBuiltQuery) - -
    -
    -} diff --git a/app/views/search/partials/column_sorter.scala.html b/app/views/search/partials/column_sorter.scala.html deleted file mode 100644 index 2eb1d804da46..000000000000 --- a/app/views/search/partials/column_sorter.scala.html +++ /dev/null @@ -1,34 +0,0 @@ -@(field: String, search: org.graylog2.restclient.models.UniversalSearch, page: Integer) -@import views.helpers.SearchRouteHelper - -@if(field.equals(search.getOrder.getField)) { - @if(search.getOrder.isAscending) { - - } - - @if(search.getOrder.isDescending) { - - } - - @*** This strange if() construct is necessary for display order reason lol ***@ - - @if(!search.getOrder.isAscending) { - - - - } - - @if(!search.getOrder.isDescending) { - - - - } -} else { - - - - - - - -} \ No newline at end of file diff --git a/app/views/search/partials/show_query_modal.scala.html b/app/views/search/partials/show_query_modal.scala.html deleted file mode 100644 index a7efeb4211ab..000000000000 --- a/app/views/search/partials/show_query_modal.scala.html +++ /dev/null @@ -1,24 +0,0 @@ -@(builtQuery: String) - - diff --git a/app/views/search/results.scala.html b/app/views/search/results.scala.html deleted file mode 100755 index fe0ae223ce54..000000000000 --- a/app/views/search/results.scala.html +++ /dev/null @@ -1,496 +0,0 @@ - -@(currentUser: org.graylog2.restclient.models.User, - search: org.graylog2.restclient.models.UniversalSearch, - searchResult: org.graylog2.restclient.models.api.results.SearchResult, - histogram: org.graylog2.restclient.models.api.results.DateHistogramResult, - formattedHistogramResults: String, - query: String, - page: Integer, - savedSearch: org.graylog2.restclient.models.SavedSearch, - selectedFields: java.util.Set[String], - nodes: Map[String, org.graylog2.restclient.models.Node])(implicit stream: org.graylog2.restclient.models.Stream) - -@import lib.security.RestPermissions -@import views.helpers.DateHistogramResolutionSelector -@import views.helpers.Permissions -@import views.helpers.DateHelper -@import controllers.routes; -@import views.helpers.SearchRouteHelper -@import org.graylog2.restclient.models.UniversalSearch -@import org.graylog2.restclient.models.api.results.MessageResult -@import org.graylog2.restclient.lib.timeranges.TimeRange -@import org.graylog2.restclient.lib.timeranges.RelativeRange -@import org.graylog2.restclient.lib.Tools - - -@paginatorLength() = @{10} - -@lowestPage() = @{ - ((page.toDouble / paginatorLength()).floor * paginatorLength()).toInt -} - -@highestPage() = @{ - if ((lowestPage() + paginatorLength()) * UniversalSearch.PER_PAGE >= searchResult.getTotalResultCount) - (searchResult.getTotalResultCount / UniversalSearch.PER_PAGE + 1).toInt - else - lowestPage() + paginatorLength() -} - -@route(page: Integer) = @{ - /* This lousy hack is necessary, because there are no proper - classes in play to create or modify Call objects in a - programmatic manner to add or replace query parameters. - */ - val s = request.uri() - val Matcher = """([?&])page=[0-9]+""".r - Matcher.findFirstIn(s) match { - case Some(_) => Matcher.replaceAllIn(s, "$1page="+page) - case None => { - val FirstParam = """\?""".r - FirstParam.findFirstIn(s) match { - case Some(_) => s + "&page="+page - case None => s + "?page="+page - } - } - } -} - -@streamId = @{ - if (stream == null) { - null - } else { - stream.getId - } -} - -@renderMessageResultField(fieldName: String, fields: Map[String, Object], r: MessageResult) = { - @if(r.hasHighlightedField(fieldName)) { - @* Need to use map{} here because a for() loop would produce unwanted newlines and thus extra spaces in the message. *@ - @{r.getHighlightedField(fieldName).getChunks(fields).map {x => if(x.avoidHtmlEscape) { Html(x.getString) } else { x.getString }}} - } else { - @fields.get(fieldName) - } -} - -@dataAttributes() = @{ - var boundaries = histogram.getHistogramBoundaries - var string = "" - if(histogram.hasFixedTimeAxis()) { - string = string + " data-from='" + boundaries.getFrom + "'" - } - string = string + " data-to='" + boundaries.getTo + "'" - string -} - -@main("Search results", sidebars.searchresults(searchResult, streamId, selectedFields), query, currentUser) { - - - - - - -
    -
    - @if(stream == null) { - @views.html.partials.dashboards.add_to_dashboard.render("search_result_chart", "Add to dashboard", Array("data-interval='" + histogram.getInterval() + "'")) - } else { - @views.html.partials.dashboards.add_to_dashboard.render("search_result_chart", "Add to dashboard", Array("data-stream-id='" + streamId + "'", "data-interval='" + histogram.getInterval() + "'")) - } -
    - -

    Histogram

    - -
    - - @Html(DateHistogramResolutionSelector.getOptions(histogram.getInterval(), request())) -
    - -
    -
    -
    -
    -
    - - - -
    - - - - - @if(stream != null) { - @views.html.partials.streamrules.stream_rule_form.render(streamId, null) - } - - @views.html.search.partials.show_query_modal(searchResult.getBuiltQuery) - -
    - - - -
    -
    - -} diff --git a/app/views/searchv2/show_message.scala.html b/app/views/search/show_message.scala.html similarity index 55% rename from app/views/searchv2/show_message.scala.html rename to app/views/search/show_message.scala.html index 96a73f0ce41d..f73249ed7a15 100644 --- a/app/views/searchv2/show_message.scala.html +++ b/app/views/search/show_message.scala.html @@ -1,13 +1,13 @@ @(currentUser: org.graylog2.restclient.models.User, message: org.graylog2.restclient.models.api.results.MessageResult, - nodes: Map[String, controllers.SearchControllerV2.NodeDescription], - streams: Map[String, controllers.SearchControllerV2.StreamDescription], - inputs: Map[String, controllers.SearchControllerV2.InputDescription] + nodes: Map[String, controllers.SearchController.NodeDescription], + streams: Map[String, controllers.SearchController.StreamDescription], + inputs: Map[String, controllers.SearchController.InputDescription] ) @import play.libs.Json; -@main("Message " + message.getId, null, "", currentUser, false, true) { +@main("Message " + message.getId, null, "", currentUser, true) {
    diff --git a/app/views/system/bundles/index.scala.html b/app/views/system/bundles/index.scala.html index 0dc2f4899b24..8af7f59b9833 100644 --- a/app/views/system/bundles/index.scala.html +++ b/app/views/system/bundles/index.scala.html @@ -1,7 +1,7 @@ @(currentUser: org.graylog2.restclient.models.User, breadcrumbs: lib.BreadcrumbList) -@main("Content packs", null, "", currentUser) { +@main("Content packs", null, "", currentUser, false) { @views.html.partials.breadcrumbs(breadcrumbs)
    diff --git a/app/views/system/inputs/extractors/edit_extractor.scala.html b/app/views/system/inputs/extractors/edit_extractor.scala.html index 33bfa8f0a2e8..498ac503c8e4 100644 --- a/app/views/system/inputs/extractors/edit_extractor.scala.html +++ b/app/views/system/inputs/extractors/edit_extractor.scala.html @@ -5,7 +5,7 @@ extractor: org.graylog2.restclient.models.Extractor, example: String) -@main("Edit Extractor", null, "", currentUser, false, false) { +@main("Edit Extractor", null, "", currentUser, false) { @views.html.partials.breadcrumbs(breadcrumbs)
    diff --git a/app/views/system/inputs/extractors/export.scala.html b/app/views/system/inputs/extractors/export.scala.html index ffa155c67293..8240c3dfdec9 100644 --- a/app/views/system/inputs/extractors/export.scala.html +++ b/app/views/system/inputs/extractors/export.scala.html @@ -1,6 +1,6 @@ @(currentUser: org.graylog2.restclient.models.User, breadcrumbs: lib.BreadcrumbList, node: org.graylog2.restclient.models.Node, input: org.graylog2.restclient.models.Input, extractorExport: String) -@main("Export extractors", null, "", currentUser, false, false) { +@main("Export extractors", null, "", currentUser, false) { @views.html.partials.breadcrumbs(breadcrumbs) diff --git a/app/views/system/inputs/extractors/importPage.scala.html b/app/views/system/inputs/extractors/importPage.scala.html index 2975dccdb386..ced013e6426a 100644 --- a/app/views/system/inputs/extractors/importPage.scala.html +++ b/app/views/system/inputs/extractors/importPage.scala.html @@ -3,7 +3,7 @@ node: org.graylog2.restclient.models.Node, input: org.graylog2.restclient.models.Input) -@main("Import extractors", null, "", currentUser, false, false) { +@main("Import extractors", null, "", currentUser, false) { @views.html.partials.breadcrumbs(breadcrumbs) diff --git a/app/views/system/inputs/extractors/manage.scala.html b/app/views/system/inputs/extractors/manage.scala.html index 8abdf648b576..bc4cc967e143 100644 --- a/app/views/system/inputs/extractors/manage.scala.html +++ b/app/views/system/inputs/extractors/manage.scala.html @@ -1,6 +1,6 @@ @(currentUser: org.graylog2.restclient.models.User, breadcrumbs: lib.BreadcrumbList, node: org.graylog2.restclient.models.Node, input: org.graylog2.restclient.models.Input, extractors: List[org.graylog2.restclient.models.Extractor]) @import org.graylog2.restclient.models.Extractor -@main("Extractors", null, "", currentUser, false, false) { +@main("Extractors", null, "", currentUser, false) { @views.html.partials.breadcrumbs(breadcrumbs) diff --git a/app/views/system/inputs/extractors/new_extractor.scala.html b/app/views/system/inputs/extractors/new_extractor.scala.html index 10fc0d04e059..4196e6b618a3 100644 --- a/app/views/system/inputs/extractors/new_extractor.scala.html +++ b/app/views/system/inputs/extractors/new_extractor.scala.html @@ -6,7 +6,7 @@ field: String, example: String) -@main("New Extractor", null, "", currentUser, false, false) { +@main("New Extractor", null, "", currentUser, false) { @views.html.partials.breadcrumbs(breadcrumbs) diff --git a/app/views/system/outputs/index.scala.html b/app/views/system/outputs/index.scala.html index 6e8cabe4b521..f981c888ce60 100644 --- a/app/views/system/outputs/index.scala.html +++ b/app/views/system/outputs/index.scala.html @@ -4,7 +4,7 @@ @import lib.security.RestPermissions._; @import views.helpers.Permissions -@main("Outputs in cluster", null, "", currentUser) { +@main("Outputs in cluster", null, "", currentUser, false) { @views.html.partials.breadcrumbs(breadcrumbs)
    diff --git a/conf/routes b/conf/routes index 885ffd93a218..6531c4fb680c 100755 --- a/conf/routes +++ b/conf/routes @@ -11,18 +11,17 @@ GET /logout GET /disconnected @controllers.LonesomeInterfaceController.index() # Home page -GET / @controllers.SearchController.globalSearch() +GET / @controllers.SearchController.index(q ?= "", rangetype ?= "relative", relative:Integer ?= 600, from ?= "", to ?= "", keyword ?= "", interval ?= "", page:Integer ?= 1, saved:String ?= "", sortField:String ?= "", sortOrder:String ?= "", fields:String ?= "", width:Integer ?= -1) # Redirect for personal startpage. GET /startpage @controllers.StartpageController.redirect() # Search -GET /search @controllers.SearchController.index(q ?= "", rangetype ?= "", relative:Integer ?= -1, from ?= "", to ?= "", keyword ?= "", interval ?= "", page:Integer ?= 1, saved:String ?= "", sortField:String ?= "", sortOrder:String ?= "", fields:String ?= "", width:Integer ?= -1) -GET /searchv2 @controllers.SearchControllerV2.index(q ?= "", rangetype ?= "relative", relative:Integer ?= 600, from ?= "", to ?= "", keyword ?= "", interval ?= "", page:Integer ?= 1, saved:String ?= "", sortField:String ?= "", sortOrder:String ?= "", fields:String ?= "", width:Integer ?= -1) -GET /searchv2/csv @controllers.SearchControllerV2.exportAsCsv(q ?= "", filter ?= "", rangetype ?= "", relative:Integer ?= -1, from ?= "", to ?= "", keyword ?= "", fields:String ?= "") +GET /search @controllers.SearchController.index(q ?= "", rangetype ?= "relative", relative:Integer ?= 600, from ?= "", to ?= "", keyword ?= "", interval ?= "", page:Integer ?= 1, saved:String ?= "", sortField:String ?= "", sortOrder:String ?= "", fields:String ?= "", width:Integer ?= -1) +GET /search/csv @controllers.SearchController.exportAsCsv(q ?= "", filter ?= "", rangetype ?= "", relative:Integer ?= -1, from ?= "", to ?= "", keyword ?= "", fields:String ?= "") # Messages -GET /messages/:index/:id @controllers.SearchControllerV2.showMessage(index: String, id: String) +GET /messages/:index/:id @controllers.SearchController.showMessage(index: String, id: String) GET /messages/:index/:id/partial @controllers.MessagesController.partial(index: String, id: String) # Streams @@ -44,7 +43,7 @@ POST /streams/:stream_id/clone GET /streams/:stream_id/csv @controllers.StreamSearchController.exportAsCsv(stream_id: String, q ?= "", rangetype ?= "", relative:Integer ?= -1, from ?= "", to ?= "", keyword ?= "", fields:String ?="") # Stream Outputs -GET /streams/:stream_id/outputs @controllers.StreamOutputsController.index(stream_id: String) +GET /streams/:stream_id/outputs @controllers.StreamOutputsController.index(stream_id: String) # Alerts GET /streams/:stream_id/alerts @controllers.AlertsController.index(stream_id: String) diff --git a/javascript/src/components/navigation/Navigation.jsx b/javascript/src/components/navigation/Navigation.jsx index 682ed8347b45..070df3f0436e 100644 --- a/javascript/src/components/navigation/Navigation.jsx +++ b/javascript/src/components/navigation/Navigation.jsx @@ -26,7 +26,7 @@ var Navigation = React.createClass({ render() { var logoUrl = jsRoutes.controllers.Assets.at("images/toplogo.png").url; - var homeUrl = jsRoutes.controllers.SearchController.globalSearch().url; + var homeUrl = jsRoutes.controllers.SearchController.index().url; var brand = ( ); @@ -35,13 +35,9 @@ var Navigation = React.createClass({