diff --git a/README.md b/README.md
index 6fa4be97c..0832b36d2 100644
--- a/README.md
+++ b/README.md
@@ -191,6 +191,62 @@ This option can also be set using `-alertmanager.uris` flag. Example:
This variable is required and there is no default value.
+#### ANNOTATIONS_DEFAULT_HIDDEN
+
+Enabling this option will hide all annotations in the UI, except for those
+that are listed in the `ANNOTATIONS_VISIBLE` option.
+
+Examples:
+
+ ANNOTATIONS_DEFAULT_HIDDEN=true
+ ANNOTATIONS_DEFAULT_HIDDEN=false
+
+This option can also be set using `-annotations.default.hidden` flag. Example:
+
+ $ unsee -annotations.default.hidden
+
+Default is `false`, which means that all annotations are visible.
+
+#### ANNOTATIONS_HIDDEN
+
+List of annotation names that should be hidden in the UI. Hidden annotations
+can still be accessed if needed by clicking on a zoom button that will appear
+if there are any hidden annotations.
+
+Examples:
+
+ ANNOTATIONS_HIDDEN=summary
+ ANNOTATIONS_HIDDEN="summary owner"
+
+This option can also be set using `-annotations.hidden` flag. Example:
+
+ $ unsee -annotations.hidden "summary owner"
+
+This variable is optional and default is not set (all annotations are visible),
+unless user enables `ANNOTATIONS_DEFAULT_HIDDEN` option.
+
+#### ANNOTATIONS_VISIBLE
+
+List of annotation names that should be visible in the UI. This option is only
+useful when `ANNOTATIONS_DEFAULT_HIDDEN` is set.
+With `ANNOTATIONS_DEFAULT_HIDDEN` all annotations are hidden by default unless
+they are present in the `ANNOTATIONS_VISIBLE` option.
+If `ANNOTATIONS_DEFAULT_HIDDEN` is not enabled this option is no-op.
+
+Examples:
+
+ ANNOTATIONS_VISIBLE=summary
+ ANNOTATIONS_VISIBLE="summary owner"
+
+This option can also be set using `-annotations.visible` flag. Example:
+
+ $ unsee -annotations.visible "summary owner"
+
+This variable is optional and default is not set.
+If `ANNOTATIONS_HIDDEN` is enabled then all annotations are hidden by default.
+If `ANNOTATIONS_HIDDEN` is not enabled then all annotations are visible by
+default.
+
#### DEBUG
Will enable [gin](https://github.com/gin-gonic/gin) debug mode. This will
diff --git a/api_test.go b/api_test.go
index 8cf3cd316..dd7b0455a 100644
--- a/api_test.go
+++ b/api_test.go
@@ -29,8 +29,9 @@ var groupTests = []groupTest{
},
alerts: []models.Alert{
models.Alert{
- Annotations: map[string]string{
- "alert": "Memory usage exceeding threshold",
+ Annotations: models.Annotations{
+ models.Annotation{Visible: true, Name: "alert", Value: "Memory usage exceeding threshold"},
+ models.Annotation{Visible: true, Name: "dashboard", Value: "http://localhost/dashboard.html", IsLink: true},
},
Labels: map[string]string{
"alertname": "Memory_Usage_Too_High",
@@ -48,9 +49,6 @@ var groupTests = []groupTest{
},
},
Receiver: "by-name",
- Links: map[string]string{
- "dashboard": "http://localhost/dashboard.html",
- },
},
},
id: "099c5ca6d1c92f615b13056b935d0c8dee70f18c",
@@ -69,8 +67,9 @@ var groupTests = []groupTest{
},
alerts: []models.Alert{
models.Alert{
- Annotations: map[string]string{
- "alert": "Memory usage exceeding threshold",
+ Annotations: models.Annotations{
+ models.Annotation{Visible: true, Name: "alert", Value: "Memory usage exceeding threshold"},
+ models.Annotation{Visible: true, Name: "dashboard", Value: "http://localhost/dashboard.html", IsLink: true},
},
Alertmanager: []models.AlertmanagerInstance{
models.AlertmanagerInstance{
@@ -86,9 +85,6 @@ var groupTests = []groupTest{
"instance": "server2",
"job": "node_exporter",
},
- Links: map[string]string{
- "dashboard": "http://localhost/dashboard.html",
- },
State: models.AlertStateActive,
Receiver: "by-cluster-service",
},
@@ -109,8 +105,8 @@ var groupTests = []groupTest{
},
alerts: []models.Alert{
models.Alert{
- Annotations: map[string]string{
- "summary": "Example summary",
+ Annotations: models.Annotations{
+ models.Annotation{Visible: true, Name: "summary", Value: "Example summary"},
},
Alertmanager: []models.AlertmanagerInstance{
models.AlertmanagerInstance{
@@ -126,13 +122,12 @@ var groupTests = []groupTest{
"instance": "server3",
"job": "node_ping",
},
- Links: map[string]string{},
State: models.AlertStateActive,
Receiver: "by-cluster-service",
},
models.Alert{
- Annotations: map[string]string{
- "summary": "Example summary",
+ Annotations: models.Annotations{
+ models.Annotation{Visible: true, Name: "summary", Value: "Example summary"},
},
Alertmanager: []models.AlertmanagerInstance{
models.AlertmanagerInstance{
@@ -148,13 +143,12 @@ var groupTests = []groupTest{
"instance": "server4",
"job": "node_ping",
},
- Links: map[string]string{},
State: models.AlertStateActive,
Receiver: "by-cluster-service",
},
models.Alert{
- Annotations: map[string]string{
- "summary": "Example summary",
+ Annotations: models.Annotations{
+ models.Annotation{Visible: true, Name: "summary", Value: "Example summary"},
},
Alertmanager: []models.AlertmanagerInstance{
models.AlertmanagerInstance{
@@ -170,7 +164,6 @@ var groupTests = []groupTest{
"instance": "server5",
"job": "node_ping",
},
- Links: map[string]string{},
State: models.AlertStateActive,
Receiver: "by-cluster-service",
},
@@ -191,8 +184,8 @@ var groupTests = []groupTest{
},
alerts: []models.Alert{
models.Alert{
- Annotations: map[string]string{
- "summary": "Example summary",
+ Annotations: models.Annotations{
+ models.Annotation{Visible: true, Name: "summary", Value: "Example summary"},
},
Alertmanager: []models.AlertmanagerInstance{
models.AlertmanagerInstance{
@@ -214,13 +207,12 @@ var groupTests = []groupTest{
"instance": "server6",
"job": "node_ping",
},
- Links: map[string]string{},
State: models.AlertStateSuppressed,
Receiver: "by-cluster-service",
},
models.Alert{
- Annotations: map[string]string{
- "summary": "Example summary",
+ Annotations: models.Annotations{
+ models.Annotation{Visible: true, Name: "summary", Value: "Example summary"},
},
Alertmanager: []models.AlertmanagerInstance{
models.AlertmanagerInstance{
@@ -245,13 +237,12 @@ var groupTests = []groupTest{
"instance": "server7",
"job": "node_ping",
},
- Links: map[string]string{},
State: models.AlertStateSuppressed,
Receiver: "by-cluster-service",
},
models.Alert{
- Annotations: map[string]string{
- "summary": "Example summary",
+ Annotations: models.Annotations{
+ models.Annotation{Visible: true, Name: "summary", Value: "Example summary"},
},
Alertmanager: []models.AlertmanagerInstance{
models.AlertmanagerInstance{
@@ -272,7 +263,6 @@ var groupTests = []groupTest{
"instance": "server8",
"job": "node_ping",
},
- Links: map[string]string{},
State: models.AlertStateSuppressed,
Receiver: "by-cluster-service",
},
@@ -292,8 +282,9 @@ var groupTests = []groupTest{
},
alerts: []models.Alert{
models.Alert{
- Annotations: map[string]string{
- "summary": "Example summary",
+ Annotations: models.Annotations{
+ models.Annotation{Visible: true, Name: "summary", Value: "Example summary"},
+ models.Annotation{Visible: true, Name: "url", Value: "http://localhost/example.html", IsLink: true},
},
Labels: map[string]string{
"alertname": "Host_Down",
@@ -311,13 +302,10 @@ var groupTests = []groupTest{
},
},
Receiver: "by-name",
- Links: map[string]string{
- "url": "http://localhost/example.html",
- },
},
models.Alert{
- Annotations: map[string]string{
- "summary": "Example summary",
+ Annotations: models.Annotations{
+ models.Annotation{Visible: true, Name: "summary", Value: "Example summary"},
},
Labels: map[string]string{
"alertname": "Host_Down",
@@ -335,11 +323,10 @@ var groupTests = []groupTest{
},
},
Receiver: "by-name",
- Links: map[string]string{},
},
models.Alert{
- Annotations: map[string]string{
- "summary": "Example summary",
+ Annotations: models.Annotations{
+ models.Annotation{Visible: true, Name: "summary", Value: "Example summary"},
},
Labels: map[string]string{
"alertname": "Host_Down",
@@ -357,11 +344,10 @@ var groupTests = []groupTest{
},
},
Receiver: "by-name",
- Links: map[string]string{},
},
models.Alert{
- Annotations: map[string]string{
- "summary": "Example summary",
+ Annotations: models.Annotations{
+ models.Annotation{Visible: true, Name: "summary", Value: "Example summary"},
},
Labels: map[string]string{
"alertname": "Host_Down",
@@ -379,11 +365,10 @@ var groupTests = []groupTest{
},
},
Receiver: "by-name",
- Links: map[string]string{},
},
models.Alert{
- Annotations: map[string]string{
- "summary": "Example summary",
+ Annotations: models.Annotations{
+ models.Annotation{Visible: true, Name: "summary", Value: "Example summary"},
},
Labels: map[string]string{
"alertname": "Host_Down",
@@ -401,11 +386,10 @@ var groupTests = []groupTest{
},
},
Receiver: "by-name",
- Links: map[string]string{},
},
models.Alert{
- Annotations: map[string]string{
- "summary": "Example summary",
+ Annotations: models.Annotations{
+ models.Annotation{Visible: true, Name: "summary", Value: "Example summary"},
},
Labels: map[string]string{
"alertname": "Host_Down",
@@ -428,11 +412,10 @@ var groupTests = []groupTest{
},
},
Receiver: "by-name",
- Links: map[string]string{},
},
models.Alert{
- Annotations: map[string]string{
- "summary": "Example summary",
+ Annotations: models.Annotations{
+ models.Annotation{Visible: true, Name: "summary", Value: "Example summary"},
},
Labels: map[string]string{
"alertname": "Host_Down",
@@ -459,11 +442,10 @@ var groupTests = []groupTest{
},
},
Receiver: "by-name",
- Links: map[string]string{},
},
models.Alert{
- Annotations: map[string]string{
- "summary": "Example summary",
+ Annotations: models.Annotations{
+ models.Annotation{Visible: true, Name: "summary", Value: "Example summary"},
},
Labels: map[string]string{
"alertname": "Host_Down",
@@ -486,7 +468,6 @@ var groupTests = []groupTest{
},
},
Receiver: "by-name",
- Links: map[string]string{},
},
},
id: "58c6a3467cebc53abe68ecbe8643ce478c5a1573",
@@ -505,8 +486,9 @@ var groupTests = []groupTest{
},
alerts: []models.Alert{
models.Alert{
- Annotations: map[string]string{
- "alert": "Less than 10% disk space is free",
+ Annotations: models.Annotations{
+ models.Annotation{Visible: true, Name: "alert", Value: "Less than 10% disk space is free"},
+ models.Annotation{Visible: true, Name: "dashboard", Value: "http://localhost/dashboard.html", IsLink: true},
},
Alertmanager: []models.AlertmanagerInstance{
models.AlertmanagerInstance{
@@ -522,9 +504,6 @@ var groupTests = []groupTest{
"instance": "server5",
"job": "node_exporter",
},
- Links: map[string]string{
- "dashboard": "http://localhost/dashboard.html",
- },
State: models.AlertStateActive,
Receiver: "by-cluster-service",
},
@@ -545,8 +524,9 @@ var groupTests = []groupTest{
},
alerts: []models.Alert{
models.Alert{
- Annotations: map[string]string{
- "summary": "Example summary",
+ Annotations: models.Annotations{
+ models.Annotation{Visible: true, Name: "summary", Value: "Example summary"},
+ models.Annotation{Visible: true, Name: "url", Value: "http://localhost/example.html", IsLink: true},
},
Alertmanager: []models.AlertmanagerInstance{
models.AlertmanagerInstance{
@@ -562,15 +542,12 @@ var groupTests = []groupTest{
"instance": "server1",
"job": "node_ping",
},
- Links: map[string]string{
- "url": "http://localhost/example.html",
- },
State: models.AlertStateActive,
Receiver: "by-cluster-service",
},
models.Alert{
- Annotations: map[string]string{
- "summary": "Example summary",
+ Annotations: models.Annotations{
+ models.Annotation{Visible: true, Name: "summary", Value: "Example summary"},
},
Alertmanager: []models.AlertmanagerInstance{
models.AlertmanagerInstance{
@@ -586,7 +563,6 @@ var groupTests = []groupTest{
"instance": "server2",
"job": "node_ping",
},
- Links: map[string]string{},
State: models.AlertStateActive,
Receiver: "by-cluster-service",
},
@@ -606,9 +582,10 @@ var groupTests = []groupTest{
},
alerts: []models.Alert{
models.Alert{
- Annotations: map[string]string{
- "summary": "Example summary",
- "help": "Example help annotation",
+ Annotations: models.Annotations{
+ models.Annotation{Visible: true, Name: "help", Value: "Example help annotation"},
+ models.Annotation{Visible: true, Name: "summary", Value: "Example summary"},
+ models.Annotation{Visible: true, Name: "url", Value: "http://localhost/example.html", IsLink: true},
},
Alertmanager: []models.AlertmanagerInstance{
models.AlertmanagerInstance{
@@ -629,15 +606,12 @@ var groupTests = []groupTest{
"instance": "web1",
"job": "node_exporter",
},
- Links: map[string]string{
- "url": "http://localhost/example.html",
- },
State: models.AlertStateSuppressed,
Receiver: "by-name",
},
models.Alert{
- Annotations: map[string]string{
- "summary": "Example summary",
+ Annotations: models.Annotations{
+ models.Annotation{Visible: true, Name: "summary", Value: "Example summary"},
},
Alertmanager: []models.AlertmanagerInstance{
models.AlertmanagerInstance{
@@ -653,7 +627,6 @@ var groupTests = []groupTest{
"instance": "web2",
"job": "node_exporter",
},
- Links: map[string]string{},
State: models.AlertStateActive,
Receiver: "by-name",
},
@@ -673,8 +646,9 @@ var groupTests = []groupTest{
},
alerts: []models.Alert{
models.Alert{
- Annotations: map[string]string{
- "alert": "Less than 10% disk space is free",
+ Annotations: models.Annotations{
+ models.Annotation{Visible: true, Name: "alert", Value: "Less than 10% disk space is free"},
+ models.Annotation{Visible: true, Name: "dashboard", Value: "http://localhost/dashboard.html", IsLink: true},
},
Alertmanager: []models.AlertmanagerInstance{
models.AlertmanagerInstance{
@@ -690,9 +664,6 @@ var groupTests = []groupTest{
"instance": "server5",
"job": "node_exporter",
},
- Links: map[string]string{
- "dashboard": "http://localhost/dashboard.html",
- },
State: models.AlertStateActive,
Receiver: "by-name",
},
@@ -713,9 +684,10 @@ var groupTests = []groupTest{
},
alerts: []models.Alert{
models.Alert{
- Annotations: map[string]string{
- "help": "Example help annotation",
- "summary": "Example summary",
+ Annotations: models.Annotations{
+ models.Annotation{Visible: true, Name: "help", Value: "Example help annotation"},
+ models.Annotation{Visible: true, Name: "summary", Value: "Example summary"},
+ models.Annotation{Visible: true, Name: "url", Value: "http://localhost/example.html", IsLink: true},
},
Alertmanager: []models.AlertmanagerInstance{
models.AlertmanagerInstance{
@@ -736,15 +708,12 @@ var groupTests = []groupTest{
"instance": "web1",
"job": "node_exporter",
},
- Links: map[string]string{
- "url": "http://localhost/example.html",
- },
State: models.AlertStateSuppressed,
Receiver: "by-cluster-service",
},
models.Alert{
- Annotations: map[string]string{
- "summary": "Example summary",
+ Annotations: models.Annotations{
+ models.Annotation{Visible: true, Name: "summary", Value: "Example summary"},
},
Alertmanager: []models.AlertmanagerInstance{
models.AlertmanagerInstance{
@@ -760,7 +729,6 @@ var groupTests = []groupTest{
"instance": "web2",
"job": "node_exporter",
},
- Links: map[string]string{},
State: models.AlertStateActive,
Receiver: "by-cluster-service",
},
@@ -875,10 +843,6 @@ func testAlert(version string, t *testing.T, expectedAlert, gotAlert models.Aler
t.Errorf("[%s] Labels mismatch on alert receiver='%s', expected labels=%v but got %v",
version, expectedAlert.Receiver, expectedAlert.Labels, gotAlert.Labels)
}
- if !reflect.DeepEqual(gotAlert.Links, expectedAlert.Links) {
- t.Errorf("[%s] Links mismatch on alert receiver='%s' labels=%v expected %v but got %v",
- version, expectedAlert.Receiver, expectedAlert.Labels, expectedAlert.Links, gotAlert.Links)
- }
if len(gotAlert.Alertmanager) != len(expectedAlert.Alertmanager) {
t.Errorf("[%s] Expected %d alertmanager instances but got %d on alert receiver='%s' labels=%v",
version, len(expectedAlert.Alertmanager), len(gotAlert.Alertmanager), gotAlert.Receiver, expectedAlert.Labels)
diff --git a/assets/static/alerts.js b/assets/static/alerts.js
index 830afe7e2..1ed950ea9 100644
--- a/assets/static/alerts.js
+++ b/assets/static/alerts.js
@@ -33,6 +33,7 @@ AlertGroup.prototype.Added = function() {
var elem = $("#" + this.id);
ui.setupGroupTooltips(elem);
ui.setupGroupLinkHover(elem);
+ ui.setupGroupAnnotationToggles(elem);
};
AlertGroup.prototype.Update = function() {
diff --git a/assets/static/templates.js b/assets/static/templates.js
index ddaedaab3..9012b87d9 100644
--- a/assets/static/templates.js
+++ b/assets/static/templates.js
@@ -36,8 +36,9 @@ var templates = {},
silenceFormFatal: "#silence-form-fatal",
silenceFormLoading: "#silence-form-loading",
- // label button
+ // alert partials
buttonLabel: "#label-button-filter",
+ alertAnnotation: "#alert-annotation",
// alert group
alertGroup: "#alert-group",
diff --git a/assets/static/ui.js b/assets/static/ui.js
index 2849a75a3..1c0fdacb8 100644
--- a/assets/static/ui.js
+++ b/assets/static/ui.js
@@ -5,6 +5,7 @@ const $ = require("jquery");
const alerts = require("./alerts");
const autocomplete = require("./autocomplete");
const filters = require("./filters");
+const grid = require("./grid");
const summary = require("./summary");
const templates = require("./templates");
const unsee = require("./unsee");
@@ -75,6 +76,29 @@ function setupGroupTooltips(groupElem) {
});
}
+function setupGroupAnnotationToggles(groupElem) {
+ $(groupElem).on("click", "[data-toggle=toggle-hidden-annotation]", function() {
+ var alert = $(this).parent();
+ var icon = $(this).find("i.fa");
+ var showingHidden = icon.hasClass("fa-search-minus");
+ if (showingHidden) {
+ // we're currently showing hidden annotations, so the action is to hide them
+ icon.removeClass("fa-search-minus").addClass("fa-search-plus");
+ $.each(alert.find(".hidden-annotation"), function(i, annotation){
+ $(annotation).addClass("hidden");
+ });
+ } else {
+ // we're currently hiding hidden annotations, so the action is to show them
+ icon.removeClass("fa-search-plus").addClass("fa-search-minus");
+ $.each(alert.find(".hidden-annotation"), function(i, annotation){
+ $(annotation).removeClass("hidden");
+ });
+ }
+ grid.redraw();
+ });
+}
+
exports.setupModal = setupModal;
exports.setupGroupTooltips = setupGroupTooltips;
exports.setupGroupLinkHover = setupGroupLinkHover;
+exports.setupGroupAnnotationToggles = setupGroupAnnotationToggles;
diff --git a/assets/templates/alertgroup.html b/assets/templates/alertgroup.html
index b524d00c6..a5a0faa1b 100644
--- a/assets/templates/alertgroup.html
+++ b/assets/templates/alertgroup.html
@@ -27,28 +27,32 @@
@@ -308,3 +312,14 @@
<%- label.text %>
<%= elem %>>
+
+
diff --git a/internal/alertmanager/models.go b/internal/alertmanager/models.go
index c1714d4a9..344eb0e19 100644
--- a/internal/alertmanager/models.go
+++ b/internal/alertmanager/models.go
@@ -172,8 +172,6 @@ func (am *Alertmanager) pullAlerts(version string) error {
},
}
- alert.Annotations, alert.Links = transform.DetectLinks(alert.Annotations)
-
transform.ColorLabel(colors, "@receiver", alert.Receiver)
for k, v := range alert.Labels {
transform.ColorLabel(colors, k, v)
diff --git a/internal/config/config.go b/internal/config/config.go
index 9a503448b..720e84186 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -24,20 +24,23 @@ func (mvd *spaceSeparatedList) Decode(value string) error {
}
type configEnvs struct {
- AlertmanagerTimeout time.Duration `envconfig:"ALERTMANAGER_TIMEOUT" default:"40s" help:"Timeout for all request send to Alertmanager"`
- AlertmanagerTTL time.Duration `envconfig:"ALERTMANAGER_TTL" default:"1m" help:"TTL for Alertmanager alerts and silences"`
- AlertmanagerURIs spaceSeparatedList `envconfig:"ALERTMANAGER_URIS" required:"true" help:"List of Alertmanager URIs (name:uri)"`
- ColorLabelsStatic spaceSeparatedList `envconfig:"COLOR_LABELS_STATIC" help:"List of label names that should have the same (but distinct) color"`
- ColorLabelsUnique spaceSeparatedList `envconfig:"COLOR_LABELS_UNIQUE" help:"List of label names that should have unique color"`
- Debug bool `envconfig:"DEBUG" default:"false" help:"Enable debug mode"`
- FilterDefault string `envconfig:"FILTER_DEFAULT" help:"Default filter string"`
- JiraRegexp spaceSeparatedList `envconfig:"JIRA_REGEX" help:"List of JIRA regex rules"`
- Port int `envconfig:"PORT" default:"8080" help:"HTTP port to listen on"`
- SentryDSN string `envconfig:"SENTRY_DSN" help:"Sentry DSN for Go exceptions"`
- SentryPublicDSN string `envconfig:"SENTRY_PUBLIC_DSN" help:"Sentry DSN for javascript exceptions"`
- StripLabels spaceSeparatedList `envconfig:"STRIP_LABELS" help:"List of labels to ignore"`
- KeepLabels spaceSeparatedList `envconfig:"KEEP_LABELS" help:"List of labels to keep, all other labels will be stripped"`
- WebPrefix string `envconfig:"WEB_PREFIX" default:"/" help:"URL prefix"`
+ AlertmanagerTimeout time.Duration `envconfig:"ALERTMANAGER_TIMEOUT" default:"40s" help:"Timeout for all request send to Alertmanager"`
+ AlertmanagerTTL time.Duration `envconfig:"ALERTMANAGER_TTL" default:"1m" help:"TTL for Alertmanager alerts and silences"`
+ AlertmanagerURIs spaceSeparatedList `envconfig:"ALERTMANAGER_URIS" required:"true" help:"List of Alertmanager URIs (name:uri)"`
+ AnnotationsHidden spaceSeparatedList `envconfig:"ANNOTATIONS_HIDDEN" help:"List of annotations that are hidden by default"`
+ AnnotationsDefaultHidden bool `envconfig:"ANNOTATIONS_DEFAULT_HIDDEN" default:"false" help:"Hide all annotations by default unless listed in ANNOTATIONS_VISIBLE"`
+ AnnotationsVisible spaceSeparatedList `envconfig:"ANNOTATIONS_VISIBLE" help:"List of annotations that are visible by default"`
+ ColorLabelsStatic spaceSeparatedList `envconfig:"COLOR_LABELS_STATIC" help:"List of label names that should have the same (but distinct) color"`
+ ColorLabelsUnique spaceSeparatedList `envconfig:"COLOR_LABELS_UNIQUE" help:"List of label names that should have unique color"`
+ Debug bool `envconfig:"DEBUG" default:"false" help:"Enable debug mode"`
+ FilterDefault string `envconfig:"FILTER_DEFAULT" help:"Default filter string"`
+ JiraRegexp spaceSeparatedList `envconfig:"JIRA_REGEX" help:"List of JIRA regex rules"`
+ Port int `envconfig:"PORT" default:"8080" help:"HTTP port to listen on"`
+ SentryDSN string `envconfig:"SENTRY_DSN" help:"Sentry DSN for Go exceptions"`
+ SentryPublicDSN string `envconfig:"SENTRY_PUBLIC_DSN" help:"Sentry DSN for javascript exceptions"`
+ StripLabels spaceSeparatedList `envconfig:"STRIP_LABELS" help:"List of labels to ignore"`
+ KeepLabels spaceSeparatedList `envconfig:"KEEP_LABELS" help:"List of labels to keep, all other labels will be stripped"`
+ WebPrefix string `envconfig:"WEB_PREFIX" default:"/" help:"URL prefix"`
}
// Config exposes all options required to run
diff --git a/internal/filters/filter_fuzzy.go b/internal/filters/filter_fuzzy.go
index 1dae8e2b2..d1dd7f876 100644
--- a/internal/filters/filter_fuzzy.go
+++ b/internal/filters/filter_fuzzy.go
@@ -27,7 +27,7 @@ func (filter *fuzzyFilter) init(name string, matcher *matcherT, rawText string,
func (filter *fuzzyFilter) Match(alert *models.Alert, matches int) bool {
if filter.IsValid {
for _, val := range alert.Annotations {
- if filter.Matcher.Compare(val, filter.Value) {
+ if filter.Matcher.Compare(val.Value, filter.Value) {
filter.Hits++
return true
}
diff --git a/internal/filters/filter_test.go b/internal/filters/filter_test.go
index e0dfb7515..9f39d8225 100644
--- a/internal/filters/filter_test.go
+++ b/internal/filters/filter_test.go
@@ -355,20 +355,32 @@ var tests = []filterTest{
filterTest{
Expression: "abc",
IsValid: true,
- Alert: models.Alert{Annotations: map[string]string{"key": "abc"}},
- IsMatch: true,
+ Alert: models.Alert{
+ Annotations: models.Annotations{
+ models.Annotation{Name: "key", Value: "abc"},
+ },
+ },
+ IsMatch: true,
},
filterTest{
Expression: "abc",
IsValid: true,
- Alert: models.Alert{Annotations: map[string]string{"key": "ccc abc"}},
- IsMatch: true,
+ Alert: models.Alert{
+ Annotations: models.Annotations{
+ models.Annotation{Name: "key", Value: "ccc abc"},
+ },
+ },
+ IsMatch: true,
},
filterTest{
Expression: "abc",
IsValid: true,
- Alert: models.Alert{Annotations: map[string]string{"abc": "zzz"}},
- IsMatch: false,
+ Alert: models.Alert{
+ Annotations: models.Annotations{
+ models.Annotation{Name: "abc", Value: "zzz"},
+ },
+ },
+ IsMatch: false,
},
filterTest{
Expression: "abc",
diff --git a/internal/mapper/v04/alerts.go b/internal/mapper/v04/alerts.go
index 2ba82b7f7..eb03db9e3 100644
--- a/internal/mapper/v04/alerts.go
+++ b/internal/mapper/v04/alerts.go
@@ -103,7 +103,7 @@ func (m AlertMapper) GetAlerts(uri string, timeout time.Duration) ([]models.Aler
}
a := models.Alert{
Receiver: rcv.Name,
- Annotations: a.Annotations,
+ Annotations: models.AnnotationsFromMap(a.Annotations),
Labels: a.Labels,
StartsAt: a.StartsAt,
EndsAt: a.EndsAt,
diff --git a/internal/mapper/v05/alerts.go b/internal/mapper/v05/alerts.go
index 8a9089247..6c6b422a3 100644
--- a/internal/mapper/v05/alerts.go
+++ b/internal/mapper/v05/alerts.go
@@ -102,7 +102,7 @@ func (m AlertMapper) GetAlerts(uri string, timeout time.Duration) ([]models.Aler
}
a := models.Alert{
Receiver: rcv.Name,
- Annotations: a.Annotations,
+ Annotations: models.AnnotationsFromMap(a.Annotations),
Labels: a.Labels,
StartsAt: a.StartsAt,
EndsAt: a.EndsAt,
diff --git a/internal/mapper/v061/alerts.go b/internal/mapper/v061/alerts.go
index 60283f359..32b4f8865 100644
--- a/internal/mapper/v061/alerts.go
+++ b/internal/mapper/v061/alerts.go
@@ -101,7 +101,7 @@ func (m AlertMapper) GetAlerts(uri string, timeout time.Duration) ([]models.Aler
}
a := models.Alert{
Receiver: rcv.Name,
- Annotations: a.Annotations,
+ Annotations: models.AnnotationsFromMap(a.Annotations),
Labels: a.Labels,
StartsAt: a.StartsAt,
EndsAt: a.EndsAt,
diff --git a/internal/mapper/v062/alerts.go b/internal/mapper/v062/alerts.go
index a3831397f..af0337fcf 100644
--- a/internal/mapper/v062/alerts.go
+++ b/internal/mapper/v062/alerts.go
@@ -105,7 +105,7 @@ func (m AlertMapper) GetAlerts(uri string, timeout time.Duration) ([]models.Aler
}
a := models.Alert{
Receiver: rcv.Name,
- Annotations: a.Annotations,
+ Annotations: models.AnnotationsFromMap(a.Annotations),
Labels: a.Labels,
StartsAt: a.StartsAt,
EndsAt: a.EndsAt,
diff --git a/internal/models/alert.go b/internal/models/alert.go
index 0d3573196..f4879f395 100644
--- a/internal/models/alert.go
+++ b/internal/models/alert.go
@@ -30,7 +30,7 @@ var AlertStateList = []string{
// it's pulled out of annotation map and returned under links field,
// unsee UI used this to show links differently than other annotations
type Alert struct {
- Annotations map[string]string `json:"annotations"`
+ Annotations Annotations `json:"annotations"`
Labels map[string]string `json:"labels"`
StartsAt time.Time `json:"startsAt"`
EndsAt time.Time `json:"endsAt"`
@@ -44,7 +44,6 @@ type Alert struct {
// unsee fields
Alertmanager []AlertmanagerInstance `json:"alertmanager"`
Receiver string `json:"receiver"`
- Links map[string]string `json:"links"`
// fingerprints are precomputed for speed
labelsFP string `hash:"-"`
contentFP string `hash:"-"`
diff --git a/internal/models/alert_test.go b/internal/models/alert_test.go
index 4cac8c5dc..cbc8817ad 100644
--- a/internal/models/alert_test.go
+++ b/internal/models/alert_test.go
@@ -72,9 +72,17 @@ func BenchmarkLabelsFingerprint(b *testing.B) {
func BenchmarkLabelsContent(b *testing.B) {
alert := models.Alert{
- Annotations: map[string]string{
- "foo": "bar",
- "abc": "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit...",
+ Annotations: models.Annotations{
+ models.Annotation{
+ Name: "foo",
+ Value: "bar",
+ Visible: true,
+ },
+ models.Annotation{
+ Name: "abc",
+ Value: "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit...",
+ Visible: true,
+ },
},
Labels: map[string]string{
"foo1": "bar1",
diff --git a/internal/models/annotation.go b/internal/models/annotation.go
new file mode 100644
index 000000000..573a40536
--- /dev/null
+++ b/internal/models/annotation.go
@@ -0,0 +1,84 @@
+package models
+
+import (
+ "net/url"
+ "sort"
+
+ "github.com/cloudflare/unsee/internal/config"
+ "github.com/cloudflare/unsee/internal/slices"
+)
+
+// Annotation extends Alertmanager scheme of key:value with additional data
+// to control how given annotation should be rendered
+type Annotation struct {
+ Name string `json:"name"`
+ Value string `json:"value"`
+ Visible bool `json:"visible"`
+ IsLink bool `json:"isLink"`
+}
+
+// Annotations is a slice of Annotation structs, needed to implement sorting
+type Annotations []Annotation
+
+func (a Annotations) Len() int {
+ return len(a)
+
+}
+func (a Annotations) Swap(i, j int) {
+ a[i], a[j] = a[j], a[i]
+}
+func (a Annotations) Less(i, j int) bool {
+ return a[i].Name < a[j].Name
+}
+
+// AnnotationsFromMap will convert a map[string]string to a list of Annotation
+// instances, it takes care of setting proper value for Visible attribute
+func AnnotationsFromMap(m map[string]string) Annotations {
+ annotations := Annotations{}
+ for name, value := range m {
+ a := Annotation{
+ Name: name,
+ Value: value,
+ Visible: isVisible(name),
+ IsLink: isLink(value),
+ }
+ annotations = append(annotations, a)
+ }
+ sort.Sort(annotations)
+ return annotations
+}
+
+var linkSchemes = []string{
+ "ftp",
+ "http",
+ "https",
+}
+
+func isLink(s string) bool {
+ u, err := url.Parse(s)
+ if err != nil {
+ return false
+ }
+ if slices.StringInSlice(linkSchemes, u.Scheme) {
+ // parses with url.Parse and scheme is in the list of supported schemes
+ return true
+ }
+ return false
+}
+
+func isVisible(name string) bool {
+ if slices.StringInSlice(config.Config.AnnotationsVisible, name) {
+ // annotation was explicitly marked as visible
+ return true
+ }
+ if slices.StringInSlice(config.Config.AnnotationsHidden, name) {
+ // annotation was explicitly marked as hidden
+ return false
+ }
+ if config.Config.AnnotationsDefaultHidden {
+ // user specified that default is to hide anything without explicit rules
+ return false
+ }
+ // default to show everything
+ return true
+}
diff --git a/internal/models/annotation_test.go b/internal/models/annotation_test.go
new file mode 100644
index 000000000..3b7f01d83
--- /dev/null
+++ b/internal/models/annotation_test.go
@@ -0,0 +1,85 @@
+package models_test
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/cloudflare/unsee/internal/models"
+)
+
+type annotationMapsTestCase struct {
+ annotationMap map[string]string
+ annotations models.Annotations
+}
+
+var annotationMapsTestCases = []annotationMapsTestCase{
+ annotationMapsTestCase{
+ annotationMap: map[string]string{
+ "foo": "bar",
+ },
+ annotations: models.Annotations{
+ models.Annotation{
+ Name: "foo",
+ Value: "bar",
+ Visible: true,
+ IsLink: false,
+ },
+ },
+ },
+ annotationMapsTestCase{
+ annotationMap: map[string]string{
+ "foo": "http://localhost",
+ },
+ annotations: models.Annotations{
+ models.Annotation{
+ Name: "foo",
+ Value: "http://localhost",
+ Visible: true,
+ IsLink: true,
+ },
+ },
+ },
+ annotationMapsTestCase{
+ annotationMap: map[string]string{
+ "foo": "ftp://localhost",
+ },
+ annotations: models.Annotations{
+ models.Annotation{
+ Name: "foo",
+ Value: "ftp://localhost",
+ Visible: true,
+ IsLink: true,
+ },
+ },
+ },
+ annotationMapsTestCase{
+ annotationMap: map[string]string{
+ "foo": "https://localhost/xxx",
+ "abc": "xyz",
+ },
+ annotations: models.Annotations{
+ models.Annotation{
+ Name: "abc",
+ Value: "xyz",
+ Visible: true,
+ IsLink: false,
+ },
+ models.Annotation{
+ Name: "foo",
+ Value: "https://localhost/xxx",
+ Visible: true,
+ IsLink: true,
+ },
+ },
+ },
+}
+
+func TestAnnotationsFromMap(t *testing.T) {
+ for _, testCase := range annotationMapsTestCases {
+ result := models.AnnotationsFromMap(testCase.annotationMap)
+ if !reflect.DeepEqual(testCase.annotations, result) {
+ t.Errorf("AnnotationsFromMap result mismatch for map %v, expected %v got %v",
+ testCase.annotationMap, testCase.annotations, result)
+ }
+ }
+}
diff --git a/internal/transform/links.go b/internal/transform/links.go
deleted file mode 100644
index 2fd9deb71..000000000
--- a/internal/transform/links.go
+++ /dev/null
@@ -1,35 +0,0 @@
-package transform
-
-import (
- "net/url"
-
- "github.com/cloudflare/unsee/internal/slices"
-)
-
-// list of URI schema which we turn into links in the UI
-var schemes = []string{
- "ftp",
- "http",
- "https",
-}
-
-// DetectLinks takes alert annotation dict and returns two dicts:
-// first with regular annotations
-// secondd with annotations where values are URLs
-func DetectLinks(sourceAnnotations map[string]string) (map[string]string, map[string]string) {
- links := make(map[string]string)
- annotations := make(map[string]string)
-
- for k, v := range sourceAnnotations {
- u, err := url.Parse(v)
- if err != nil {
- annotations[k] = v
- } else if slices.StringInSlice(schemes, u.Scheme) {
- links[k] = v
- } else {
- annotations[k] = v
- }
- }
-
- return annotations, links
-}
diff --git a/internal/transform/links_test.go b/internal/transform/links_test.go
deleted file mode 100644
index 77b92dca2..000000000
--- a/internal/transform/links_test.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package transform_test
-
-import (
- "reflect"
- "testing"
-
- "github.com/cloudflare/unsee/internal/transform"
-)
-
-type linkTest struct {
- before map[string]string
- after map[string]string
- links map[string]string
-}
-
-var linkTests = []linkTest{
- linkTest{
- before: map[string]string{},
- after: map[string]string{},
- links: map[string]string{},
- },
- linkTest{
- before: map[string]string{
- "key1": "value 1",
- "key2": "value2",
- "level": "info",
- },
- after: map[string]string{
- "key1": "value 1",
- "key2": "value2",
- "level": "info",
- },
- links: map[string]string{},
- },
- linkTest{
- before: map[string]string{
- "key1": "value 1",
- "key2": "http://localhost",
- "level": "info",
- },
- after: map[string]string{
- "key1": "value 1",
- "level": "info",
- },
- links: map[string]string{
- "key2": "http://localhost",
- },
- },
- linkTest{
- before: map[string]string{
- "key1": "value 1",
- "key2": "https://example.com/abc",
- "level": "info",
- },
- after: map[string]string{
- "key1": "value 1",
- "level": "info",
- },
- links: map[string]string{
- "key2": "https://example.com/abc",
- },
- },
- linkTest{
- before: map[string]string{
- "key1": "value 1",
- "key2": "file://example/abc",
- "level": "info",
- },
- after: map[string]string{
- "key1": "value 1",
- "key2": "file://example/abc",
- "level": "info",
- },
- links: map[string]string{},
- },
- linkTest{
- before: map[string]string{
- "key1": "value 1",
- "key2": "ftp://example/abc",
- "level": "info",
- },
- after: map[string]string{
- "key1": "value 1",
- "level": "info",
- },
- links: map[string]string{
- "key2": "ftp://example/abc",
- },
- },
-}
-
-func TestDetectLinks(t *testing.T) {
- for _, testCase := range linkTests {
- after, links := transform.DetectLinks(testCase.before)
- if !reflect.DeepEqual(after, testCase.after) {
- t.Errorf("DetectLinks returned invalid annotation map, expected %v, got %v", testCase.after, after)
- }
- if !reflect.DeepEqual(links, testCase.links) {
- t.Errorf("DetectLinks returned invalid link map, expected %v, got %v", testCase.links, links)
- }
- }
-}
diff --git a/views_test.go b/views_test.go
index fb1faed04..95832adb5 100644
--- a/views_test.go
+++ b/views_test.go
@@ -163,8 +163,14 @@ func TestAlerts(t *testing.T) {
}
for _, ag := range ur.AlertGroups {
for _, a := range ag.Alerts {
- if len(a.Links) != 1 {
- t.Errorf("Invalid number of links, got %d, expected 1, %v", len(a.Links), a)
+ linkCount := 0
+ for _, annotation := range a.Annotations {
+ if annotation.IsLink {
+ linkCount++
+ }
+ }
+ if linkCount != 1 {
+ t.Errorf("Invalid number of links, got %d, expected 1, %v", linkCount, a)
}
if len(a.Alertmanager) == 0 {
t.Errorf("Alertmanager instance list is empty, %v", a)