App Settings
-
diff --git a/frontend/pages/Admin/AppSettingsPage/AppSettingsPage.tests.jsx b/frontend/pages/Admin/AppSettingsPage/AppSettingsPage.tests.jsx
index 4d3e1d200b..a11b7ae414 100644
--- a/frontend/pages/Admin/AppSettingsPage/AppSettingsPage.tests.jsx
+++ b/frontend/pages/Admin/AppSettingsPage/AppSettingsPage.tests.jsx
@@ -28,7 +28,7 @@ describe('AppSettingsPage - component', () => {
connectedComponent(AppSettingsPage, { mockStore })
).find('AppSettingsPage');
- const smtpWarning = page.find('SmtpWarning');
+ const smtpWarning = page.find('WarningBanner');
expect(smtpWarning.length).toEqual(1);
expect(smtpWarning.find('Icon').length).toEqual(1);
@@ -41,12 +41,12 @@ describe('AppSettingsPage - component', () => {
connectedComponent(AppSettingsPage, { mockStore })
);
- const smtpWarning = page.find('SmtpWarning');
+ const smtpWarning = page.find('WarningBanner');
const dismissButton = smtpWarning.find('Button').first();
dismissButton.simulate('click');
- expect(page.find('SmtpWarning').html()).toNotExist();
+ expect(page.find('WarningBanner').html()).toNotExist();
});
it('does not render a warning if SMTP has been configured', () => {
@@ -55,6 +55,6 @@ describe('AppSettingsPage - component', () => {
connectedComponent(AppSettingsPage, { mockStore })
).find('AppSettingsPage');
- expect(page.find('SmtpWarning').html()).toNotExist();
+ expect(page.find('WarningBanner').html()).toNotExist();
});
});
diff --git a/frontend/pages/Admin/UserManagementPage/UserManagementPage.jsx b/frontend/pages/Admin/UserManagementPage/UserManagementPage.jsx
index 4da203a32a..79b40d055e 100644
--- a/frontend/pages/Admin/UserManagementPage/UserManagementPage.jsx
+++ b/frontend/pages/Admin/UserManagementPage/UserManagementPage.jsx
@@ -14,7 +14,7 @@ import InviteUserForm from 'components/forms/InviteUserForm';
import Modal from 'components/modals/Modal';
import paths from 'router/paths';
import { renderFlash } from 'redux/nodes/notifications/actions';
-import SmtpWarning from 'components/SmtpWarning';
+import WarningBanner from 'components/WarningBanner';
import { updateUser } from 'redux/nodes/auth/actions';
import userActions from 'redux/nodes/entities/users/actions';
import UserBlock from 'components/UserBlock';
@@ -249,7 +249,8 @@ export class UserManagementPage extends Component {
return (
-
diff --git a/frontend/pages/Admin/UserManagementPage/UserManagementPage.tests.jsx b/frontend/pages/Admin/UserManagementPage/UserManagementPage.tests.jsx
index 82f3a46ca4..59dc881406 100644
--- a/frontend/pages/Admin/UserManagementPage/UserManagementPage.tests.jsx
+++ b/frontend/pages/Admin/UserManagementPage/UserManagementPage.tests.jsx
@@ -136,8 +136,8 @@ describe('UserManagementPage - component', () => {
mockStore: configuredMockStore,
}));
- expect(notConfiguredPage.find('SmtpWarning').html()).toExist();
- expect(configuredPage.find('SmtpWarning').html()).toNotExist();
+ expect(notConfiguredPage.find('WarningBanner').html()).toExist();
+ expect(configuredPage.find('WarningBanner').html()).toNotExist();
});
});
@@ -146,7 +146,7 @@ describe('UserManagementPage - component', () => {
const mockStore = reduxMockStore(notConfiguredStore);
const page = mount(connectedComponent(ConnectedUserManagementPage, { mockStore }));
- const smtpWarning = page.find('SmtpWarning');
+ const smtpWarning = page.find('WarningBanner');
smtpWarning.find('Button').simulate('click');
diff --git a/frontend/pages/queries/QueryPage/QueryPage.jsx b/frontend/pages/queries/QueryPage/QueryPage.jsx
index 478643c9dc..814e57100f 100644
--- a/frontend/pages/queries/QueryPage/QueryPage.jsx
+++ b/frontend/pages/queries/QueryPage/QueryPage.jsx
@@ -17,6 +17,7 @@ import { formatSelectedTargetsForApi } from 'kolide/helpers';
import helpers from 'pages/queries/QueryPage/helpers';
import hostActions from 'redux/nodes/entities/hosts/actions';
import hostInterface from 'interfaces/host';
+import WarningBanner from 'components/WarningBanner';
import QueryForm from 'components/forms/queries/QueryForm';
import osqueryTableInterface from 'interfaces/osquery_table';
import queryActions from 'redux/nodes/entities/queries/actions';
@@ -91,6 +92,10 @@ export class QueryPage extends Component {
dispatch(hostActions.loadAll());
}
+ Kolide.status.result_store().catch((response) => {
+ this.setState({ resultStoreError: response.message.errors[0].reason });
+ });
+
helpers.selectHosts(dispatch, {
hosts: selectedHosts,
selectedTargets,
@@ -445,6 +450,20 @@ export class QueryPage extends Component {
return false;
}
+ renderResultStoreWarning = () => {
+ const { resultStoreError } = this.state;
+
+ if (!resultStoreError) {
+ return false;
+ }
+
+ const message = `Live query disabled due to Redis error: ${resultStoreError}`;
+
+ return (
+
+ );
+ }
+
renderResultsTable = () => {
const {
campaign,
@@ -485,7 +504,7 @@ export class QueryPage extends Component {
renderTargetsInput = () => {
const { onFetchTargets, onRunQuery, onStopQuery, onTargetSelect } = this;
- const { campaign, queryIsRunning, targetsCount, targetsError, runQueryMilliseconds } = this.state;
+ const { campaign, queryIsRunning, targetsCount, targetsError, runQueryMilliseconds, resultStoreError } = this.state;
const { selectedTargets } = this.props;
return (
@@ -500,6 +519,7 @@ export class QueryPage extends Component {
selectedTargets={selectedTargets}
targetsCount={targetsCount}
queryTimerMilliseconds={runQueryMilliseconds}
+ disableRun={resultStoreError !== undefined}
/>
);
}
@@ -515,6 +535,7 @@ export class QueryPage extends Component {
onUpdateQuery,
renderResultsTable,
renderTargetsInput,
+ renderResultStoreWarning,
} = this;
const { queryIsRunning } = this.state;
const {
@@ -547,6 +568,7 @@ export class QueryPage extends Component {
title={title}
/>
+ {renderResultStoreWarning()}
{renderTargetsInput()}
{renderResultsTable()}
diff --git a/frontend/pages/queries/QueryPage/_styles.scss b/frontend/pages/queries/QueryPage/_styles.scss
index 3698898adb..04bce3122f 100644
--- a/frontend/pages/queries/QueryPage/_styles.scss
+++ b/frontend/pages/queries/QueryPage/_styles.scss
@@ -12,4 +12,8 @@
position: relative;
min-height: 400px;
}
+
+ &__warning {
+ margin: 0px;
+ }
}
diff --git a/server/kolide/query_results.go b/server/kolide/query_results.go
index f4afc44b71..e0abbca042 100644
--- a/server/kolide/query_results.go
+++ b/server/kolide/query_results.go
@@ -16,4 +16,8 @@ type QueryResultStore interface {
// query results. Channel values should be either
// DistributedQueryResult or error
ReadChannel(ctx context.Context, query DistributedQueryCampaign) (<-chan interface{}, error)
+
+ // HealthCheck returns nil if the store is functioning properly, or an
+ // error describing the problem.
+ HealthCheck() error
}
diff --git a/server/kolide/service.go b/server/kolide/service.go
index 9fd45f85a8..9f76e0f322 100644
--- a/server/kolide/service.go
+++ b/server/kolide/service.go
@@ -17,4 +17,5 @@ type Service interface {
ScheduledQueryService
OptionService
FileIntegrityMonitoringService
+ StatusService
}
diff --git a/server/kolide/status.go b/server/kolide/status.go
new file mode 100644
index 0000000000..97f4d3a850
--- /dev/null
+++ b/server/kolide/status.go
@@ -0,0 +1,9 @@
+package kolide
+
+import "context"
+
+type StatusService interface {
+ // StatusResultStore returns nil if the result store is functioning
+ // correctly, or an error indicating the problem.
+ StatusResultStore(ctx context.Context) error
+}
diff --git a/server/pubsub/inmem_query_results.go b/server/pubsub/inmem_query_results.go
index b560538443..cd4ba29c80 100644
--- a/server/pubsub/inmem_query_results.go
+++ b/server/pubsub/inmem_query_results.go
@@ -60,3 +60,7 @@ func (im *inmemQueryResults) ReadChannel(ctx context.Context, query kolide.Distr
}()
return channel, nil
}
+
+func (im *inmemQueryResults) HealthCheck() error {
+ return nil
+}
diff --git a/server/service/endpoint_status.go b/server/service/endpoint_status.go
new file mode 100644
index 0000000000..d13ec148c3
--- /dev/null
+++ b/server/service/endpoint_status.go
@@ -0,0 +1,24 @@
+package service
+
+import (
+ "context"
+
+ "github.com/go-kit/kit/endpoint"
+ "github.com/kolide/fleet/server/kolide"
+)
+
+type statusResultStoreResponse struct {
+ Err error `json:"error,omitempty"`
+}
+
+func (m statusResultStoreResponse) error() error { return m.Err }
+
+func makeStatusResultStoreEndpoint(svc kolide.Service) endpoint.Endpoint {
+ return func(ctx context.Context, req interface{}) (interface{}, error) {
+ var resp statusResultStoreResponse
+ if err := svc.StatusResultStore(ctx); err != nil {
+ resp.Err = err
+ }
+ return resp, nil
+ }
+}
diff --git a/server/service/handler.go b/server/service/handler.go
index 06388aa81d..b13e58475c 100644
--- a/server/service/handler.go
+++ b/server/service/handler.go
@@ -96,6 +96,7 @@ type KolideEndpoints struct {
SSOSettings endpoint.Endpoint
GetFIM endpoint.Endpoint
ModifyFIM endpoint.Endpoint
+ StatusResultStore endpoint.Endpoint
}
// MakeKolideServerEndpoints creates the Kolide API endpoints.
@@ -188,6 +189,9 @@ func MakeKolideServerEndpoints(svc kolide.Service, jwtKey string) KolideEndpoint
GetFIM: authenticatedUser(jwtKey, svc, makeGetFIMEndpoint(svc)),
ModifyFIM: authenticatedUser(jwtKey, svc, makeModifyFIMEndpoint(svc)),
+ // Authenticated status endpoints
+ StatusResultStore: authenticatedUser(jwtKey, svc, makeStatusResultStoreEndpoint(svc)),
+
// Osquery endpoints
EnrollAgent: makeEnrollAgentEndpoint(svc),
GetClientConfig: authenticatedHost(svc, makeGetClientConfigEndpoint(svc)),
@@ -279,6 +283,7 @@ type kolideHandlers struct {
SettingsSSO http.Handler
ModifyFIM http.Handler
GetFIM http.Handler
+ StatusResultStore http.Handler
}
func makeKolideKitHandlers(e KolideEndpoints, opts []kithttp.ServerOption) *kolideHandlers {
@@ -367,6 +372,7 @@ func makeKolideKitHandlers(e KolideEndpoints, opts []kithttp.ServerOption) *koli
SettingsSSO: newServer(e.SSOSettings, decodeNoParamsRequest),
ModifyFIM: newServer(e.ModifyFIM, decodeModifyFIMRequest),
GetFIM: newServer(e.GetFIM, decodeNoParamsRequest),
+ StatusResultStore: newServer(e.StatusResultStore, decodeNoParamsRequest),
}
}
@@ -496,6 +502,8 @@ func attachKolideAPIRoutes(r *mux.Router, h *kolideHandlers) {
r.Handle("/api/v1/kolide/targets", h.SearchTargets).Methods("POST").Name("search_targets")
+ r.Handle("/api/v1/kolide/status/result_store", h.StatusResultStore).Methods("GET").Name("status_result_store")
+
r.Handle("/api/v1/osquery/enroll", h.EnrollAgent).Methods("POST").Name("enroll_agent")
r.Handle("/api/v1/osquery/config", h.GetClientConfig).Methods("POST").Name("get_client_config")
r.Handle("/api/v1/osquery/distributed/read", h.GetDistributedQueries).Methods("POST").Name("get_distributed_queries")
diff --git a/server/service/service_status.go b/server/service/service_status.go
new file mode 100644
index 0000000000..e278e22c81
--- /dev/null
+++ b/server/service/service_status.go
@@ -0,0 +1,7 @@
+package service
+
+import "context"
+
+func (svc service) StatusResultStore(ctx context.Context) error {
+ return svc.resultStore.HealthCheck()
+}