diff --git a/ee/app/assets/javascripts/threat_monitoring/components/app.vue b/ee/app/assets/javascripts/threat_monitoring/components/app.vue
index 521f5057fd2a34ca3e2df49baee24c2dd29a9c43..3d1fb5802e31064944780baf216941963b2c55af 100644
--- a/ee/app/assets/javascripts/threat_monitoring/components/app.vue
+++ b/ee/app/assets/javascripts/threat_monitoring/components/app.vue
@@ -1,15 +1,6 @@
 <script>
 import { mapActions } from 'vuex';
-import {
-  GlAlert,
-  GlEmptyState,
-  GlIcon,
-  GlLink,
-  GlPopover,
-  GlSprintf,
-  GlTabs,
-  GlTab,
-} from '@gitlab/ui';
+import { GlAlert, GlIcon, GlLink, GlPopover, GlTabs, GlTab } from '@gitlab/ui';
 import { s__ } from '~/locale';
 import axios from '~/lib/utils/axios_utils';
 import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@@ -17,22 +8,22 @@ import Alerts from './alerts/alerts.vue';
 import ThreatMonitoringFilters from './threat_monitoring_filters.vue';
 import ThreatMonitoringSection from './threat_monitoring_section.vue';
 import NetworkPolicyList from './network_policy_list.vue';
+import NoEnvironmentEmptyState from './no_environment_empty_state.vue';
 
 export default {
   name: 'ThreatMonitoring',
   components: {
     GlAlert,
-    GlEmptyState,
     GlIcon,
     GlLink,
     GlPopover,
-    GlSprintf,
     GlTabs,
     GlTab,
     Alerts,
     ThreatMonitoringFilters,
     ThreatMonitoringSection,
     NetworkPolicyList,
+    NoEnvironmentEmptyState,
   },
   mixins: [glFeatureFlagsMixin()],
   inject: ['documentationPath'],
@@ -45,10 +36,6 @@ export default {
       type: String,
       required: true,
     },
-    emptyStateSvgPath: {
-      type: String,
-      required: true,
-    },
     wafNoDataSvgPath: {
       type: String,
       required: true,
@@ -122,10 +109,6 @@ export default {
     `ThreatMonitoring|Container Network Policies are not installed or have been disabled. To view
      this data, ensure your Network Policies are installed and enabled for your cluster.`,
   ),
-  emptyStateDescription: s__(
-    `ThreatMonitoring|To view this data, ensure you have configured an environment
-    for this project and that at least one threat monitoring feature is enabled. %{linkStart}More information%{linkEnd}`,
-  ),
   alertText: s__(
     `ThreatMonitoring|The graph below is an overview of traffic coming to your
     application as tracked by the Web Application Firewall (WAF). View the docs
@@ -138,22 +121,7 @@ export default {
 </script>
 
 <template>
-  <gl-empty-state
-    v-if="!isSetUpMaybe"
-    ref="emptyState"
-    :title="s__('ThreatMonitoring|No environments detected')"
-    :svg-path="emptyStateSvgPath"
-  >
-    <template #description>
-      <gl-sprintf :message="$options.emptyStateDescription">
-        <template #link="{ content }">
-          <gl-link :href="documentationPath" target="_blank">{{ content }}</gl-link>
-        </template>
-      </gl-sprintf>
-    </template>
-  </gl-empty-state>
-
-  <section v-else>
+  <section>
     <gl-alert
       v-if="showAlert"
       class="my-3"
@@ -190,45 +158,55 @@ export default {
         <alerts />
       </gl-tab>
       <gl-tab ref="networkPolicyTab" :title="s__('ThreatMonitoring|Policies')">
+        <no-environment-empty-state v-if="!isSetUpMaybe" />
         <network-policy-list
+          v-else
           :documentation-path="documentationPath"
           :new-policy-path="newPolicyPath"
         />
       </gl-tab>
-      <gl-tab :title="s__('ThreatMonitoring|Statistics')">
-        <threat-monitoring-filters />
+      <gl-tab
+        :title="s__('ThreatMonitoring|Statistics')"
+        data-testid="threat-monitoring-statistics-tab"
+      >
+        <no-environment-empty-state v-if="!isSetUpMaybe" />
+        <template v-else>
+          <threat-monitoring-filters />
 
-        <threat-monitoring-section
-          ref="wafSection"
-          store-namespace="threatMonitoringWaf"
-          :title="s__('ThreatMonitoring|Web Application Firewall')"
-          :subtitle="s__('ThreatMonitoring|Requests')"
-          :anomalous-title="s__('ThreatMonitoring|Anomalous Requests')"
-          :nominal-title="s__('ThreatMonitoring|Total Requests')"
-          :y-legend="s__('ThreatMonitoring|Requests')"
-          :chart-empty-state-title="s__('ThreatMonitoring|Application firewall not detected')"
-          :chart-empty-state-text="$options.wafChartEmptyStateDescription"
-          :chart-empty-state-svg-path="wafNoDataSvgPath"
-          :documentation-path="documentationPath"
-          documentation-anchor="web-application-firewall"
-        />
+          <threat-monitoring-section
+            ref="wafSection"
+            store-namespace="threatMonitoringWaf"
+            :title="s__('ThreatMonitoring|Web Application Firewall')"
+            :subtitle="s__('ThreatMonitoring|Requests')"
+            :anomalous-title="s__('ThreatMonitoring|Anomalous Requests')"
+            :nominal-title="s__('ThreatMonitoring|Total Requests')"
+            :y-legend="s__('ThreatMonitoring|Requests')"
+            :chart-empty-state-title="s__('ThreatMonitoring|Application firewall not detected')"
+            :chart-empty-state-text="$options.wafChartEmptyStateDescription"
+            :chart-empty-state-svg-path="wafNoDataSvgPath"
+            :documentation-path="documentationPath"
+            documentation-anchor="web-application-firewall"
+          />
 
-        <hr />
+          <hr />
 
-        <threat-monitoring-section
-          ref="networkPolicySection"
-          store-namespace="threatMonitoringNetworkPolicy"
-          :title="s__('ThreatMonitoring|Container Network Policy')"
-          :subtitle="s__('ThreatMonitoring|Packet Activity')"
-          :anomalous-title="s__('ThreatMonitoring|Dropped Packets')"
-          :nominal-title="s__('ThreatMonitoring|Total Packets')"
-          :y-legend="s__('ThreatMonitoring|Operations Per Second')"
-          :chart-empty-state-title="s__('ThreatMonitoring|Container NetworkPolicies not detected')"
-          :chart-empty-state-text="$options.networkPolicyChartEmptyStateDescription"
-          :chart-empty-state-svg-path="networkPolicyNoDataSvgPath"
-          :documentation-path="documentationPath"
-          documentation-anchor="container-network-policy"
-        />
+          <threat-monitoring-section
+            ref="networkPolicySection"
+            store-namespace="threatMonitoringNetworkPolicy"
+            :title="s__('ThreatMonitoring|Container Network Policy')"
+            :subtitle="s__('ThreatMonitoring|Packet Activity')"
+            :anomalous-title="s__('ThreatMonitoring|Dropped Packets')"
+            :nominal-title="s__('ThreatMonitoring|Total Packets')"
+            :y-legend="s__('ThreatMonitoring|Operations Per Second')"
+            :chart-empty-state-title="
+              s__('ThreatMonitoring|Container NetworkPolicies not detected')
+            "
+            :chart-empty-state-text="$options.networkPolicyChartEmptyStateDescription"
+            :chart-empty-state-svg-path="networkPolicyNoDataSvgPath"
+            :documentation-path="documentationPath"
+            documentation-anchor="container-network-policy"
+          />
+        </template>
       </gl-tab>
     </gl-tabs>
   </section>
diff --git a/ee/app/assets/javascripts/threat_monitoring/components/constants.js b/ee/app/assets/javascripts/threat_monitoring/components/constants.js
index a5175234fc090e40d46ed7f17119ef99f91d9b8a..1a0ddcf1547458321afff57985d7b52a334c86a2 100644
--- a/ee/app/assets/javascripts/threat_monitoring/components/constants.js
+++ b/ee/app/assets/javascripts/threat_monitoring/components/constants.js
@@ -5,6 +5,11 @@ export const TOTAL_REQUESTS = s__('ThreatMonitoring|Total Requests');
 export const ANOMALOUS_REQUESTS = s__('ThreatMonitoring|Anomalous Requests');
 export const TIME = s__('ThreatMonitoring|Time');
 export const REQUESTS = s__('ThreatMonitoring|Requests');
+export const NO_ENVIRONMENT_TITLE = s__('ThreatMonitoring|No environments detected');
+export const EMPTY_STATE_DESCRIPTION = s__(
+  `ThreatMonitoring|To view this data, ensure you have configured an environment
+    for this project and that at least one threat monitoring feature is enabled. %{linkStart}More information%{linkEnd}`,
+);
 
 export const COLORS = {
   nominal: gray700,
diff --git a/ee/app/assets/javascripts/threat_monitoring/components/no_environment_empty_state.vue b/ee/app/assets/javascripts/threat_monitoring/components/no_environment_empty_state.vue
new file mode 100644
index 0000000000000000000000000000000000000000..485cec83f3dde5dc0bf9efc5ea0fd9e7d78d9f9f
--- /dev/null
+++ b/ee/app/assets/javascripts/threat_monitoring/components/no_environment_empty_state.vue
@@ -0,0 +1,30 @@
+<script>
+import { GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui';
+import { EMPTY_STATE_DESCRIPTION, NO_ENVIRONMENT_TITLE } from './constants';
+
+export default {
+  i18n: { EMPTY_STATE_DESCRIPTION, NO_ENVIRONMENT_TITLE },
+  name: 'NoEnvironmentEmptyState',
+  components: {
+    GlEmptyState,
+    GlLink,
+    GlSprintf,
+  },
+  inject: {
+    documentationPath: { type: String, default: '' },
+    emptyStateSvgPath: { type: String, default: '' },
+  },
+};
+</script>
+
+<template>
+  <gl-empty-state :title="$options.i18n.NO_ENVIRONMENT_TITLE" :svg-path="emptyStateSvgPath">
+    <template #description>
+      <gl-sprintf :message="$options.i18n.EMPTY_STATE_DESCRIPTION">
+        <template #link="{ content }">
+          <gl-link :href="documentationPath" target="_blank">{{ content }}</gl-link>
+        </template>
+      </gl-sprintf>
+    </template>
+  </gl-empty-state>
+</template>
diff --git a/ee/app/assets/javascripts/threat_monitoring/index.js b/ee/app/assets/javascripts/threat_monitoring/index.js
index e9279f317b8c4d51e1dcddc3c25daf4cc82abb15..e3ec94c8fa73014bd9496faa7904d93bbfd014de 100644
--- a/ee/app/assets/javascripts/threat_monitoring/index.js
+++ b/ee/app/assets/javascripts/threat_monitoring/index.js
@@ -46,6 +46,7 @@ export default () => {
     el,
     provide: {
       documentationPath,
+      emptyStateSvgPath,
       projectPath,
     },
     store,
@@ -53,7 +54,6 @@ export default () => {
       return createElement(ThreatMonitoringApp, {
         props: {
           chartEmptyStateSvgPath,
-          emptyStateSvgPath,
           wafNoDataSvgPath,
           networkPolicyNoDataSvgPath,
           defaultEnvironmentId: parseInt(defaultEnvironmentId, 10),
diff --git a/ee/changelogs/unreleased/296179-change-empty-state-location.yml b/ee/changelogs/unreleased/296179-change-empty-state-location.yml
new file mode 100644
index 0000000000000000000000000000000000000000..70325397e5be93ae8de21cd2064dd2e011f929b5
--- /dev/null
+++ b/ee/changelogs/unreleased/296179-change-empty-state-location.yml
@@ -0,0 +1,5 @@
+---
+title: Move empty state into the threat monitoring tabs
+merge_request: 51748
+author:
+type: changed
diff --git a/ee/spec/frontend/threat_monitoring/components/__snapshots__/no_environment_empty_state_spec.js.snap b/ee/spec/frontend/threat_monitoring/components/__snapshots__/no_environment_empty_state_spec.js.snap
new file mode 100644
index 0000000000000000000000000000000000000000..76f6792b632d94ea1228064e1173b8ae00368b6e
--- /dev/null
+++ b/ee/spec/frontend/threat_monitoring/components/__snapshots__/no_environment_empty_state_spec.js.snap
@@ -0,0 +1,12 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`NoEnvironmentEmptyState component default state matches the snapshot 1`] = `
+<gl-empty-state-stub
+  svgpath="/svgs"
+  title="No environments detected"
+>
+  <gl-sprintf-stub
+    message="To view this data, ensure you have configured an environment for this project and that at least one threat monitoring feature is enabled. %{linkStart}More information%{linkEnd}"
+  />
+</gl-empty-state-stub>
+`;
diff --git a/ee/spec/frontend/threat_monitoring/components/app_spec.js b/ee/spec/frontend/threat_monitoring/components/app_spec.js
index f7b59b2e77988b5dd74e893a6438a0f94c1bdd61..c5445236cb34314dae706bb395b227b5d8f1e2e7 100644
--- a/ee/spec/frontend/threat_monitoring/components/app_spec.js
+++ b/ee/spec/frontend/threat_monitoring/components/app_spec.js
@@ -1,9 +1,12 @@
-import { GlAlert, GlSprintf } from '@gitlab/ui';
+import { GlAlert } from '@gitlab/ui';
 import { shallowMount } from '@vue/test-utils';
 import MockAdapter from 'axios-mock-adapter';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
 import ThreatMonitoringAlerts from 'ee/threat_monitoring/components/alerts/alerts.vue';
 import ThreatMonitoringApp from 'ee/threat_monitoring/components/app.vue';
 import ThreatMonitoringFilters from 'ee/threat_monitoring/components/threat_monitoring_filters.vue';
+import NetworkPolicyList from 'ee/threat_monitoring/components/network_policy_list.vue';
+import NoEnvironmentEmptyState from 'ee/threat_monitoring/components/no_environment_empty_state.vue';
 import createStore from 'ee/threat_monitoring/store';
 import { TEST_HOST } from 'helpers/test_constants';
 import axios from '~/lib/utils/axios_utils';
@@ -25,7 +28,7 @@ describe('ThreatMonitoringApp component', () => {
   let store;
   let wrapper;
 
-  const factory = ({ propsData, provide = {}, state, options } = {}) => {
+  const factory = ({ propsData, provide = {}, state, stubs = {} } = {}) => {
     store = createStore();
     Object.assign(store.state.threatMonitoring, {
       environmentsEndpoint,
@@ -36,38 +39,41 @@ describe('ThreatMonitoringApp component', () => {
 
     jest.spyOn(store, 'dispatch').mockImplementation();
 
-    wrapper = shallowMount(ThreatMonitoringApp, {
-      propsData: {
-        defaultEnvironmentId,
-        chartEmptyStateSvgPath,
-        emptyStateSvgPath,
-        wafNoDataSvgPath,
-        networkPolicyNoDataSvgPath,
-        newPolicyPath,
-        showUserCallout: true,
-        userCalloutId,
-        userCalloutsPath,
-        ...propsData,
-      },
-      provide: {
-        documentationPath,
-        glFeatures: { threatMonitoringAlerts: false },
-        ...provide,
-      },
-      store,
-      ...options,
-    });
+    wrapper = extendedWrapper(
+      shallowMount(ThreatMonitoringApp, {
+        propsData: {
+          defaultEnvironmentId,
+          chartEmptyStateSvgPath,
+          emptyStateSvgPath,
+          wafNoDataSvgPath,
+          networkPolicyNoDataSvgPath,
+          newPolicyPath,
+          showUserCallout: true,
+          userCalloutId,
+          userCalloutsPath,
+          ...propsData,
+        },
+        provide: {
+          documentationPath,
+          glFeatures: { threatMonitoringAlerts: false },
+          ...provide,
+        },
+        store,
+        stubs,
+      }),
+    );
   };
 
   const findAlert = () => wrapper.find(GlAlert);
   const findAlertsView = () => wrapper.find(ThreatMonitoringAlerts);
+  const findNetworkPolicyList = () => wrapper.find(NetworkPolicyList);
   const findFilters = () => wrapper.find(ThreatMonitoringFilters);
   const findWafSection = () => wrapper.find({ ref: 'wafSection' });
   const findNetworkPolicySection = () => wrapper.find({ ref: 'networkPolicySection' });
-  const findEmptyState = () => wrapper.find({ ref: 'emptyState' });
-  const findEmptyStateMessage = () => wrapper.find(GlSprintf);
+  const findNoEnvironmentEmptyStates = () => wrapper.findAll(NoEnvironmentEmptyState);
   const findNetworkPolicyTab = () => wrapper.find({ ref: 'networkPolicyTab' });
-  const findAlertTab = () => wrapper.find('[data-testid="threat-monitoring-alerts-tab"]');
+  const findAlertTab = () => wrapper.findByTestId('threat-monitoring-alerts-tab');
+  const findStatisticsTab = () => wrapper.findByTestId('threat-monitoring-statistics-tab');
 
   afterEach(() => {
     wrapper.destroy();
@@ -82,6 +88,7 @@ describe('ThreatMonitoringApp component', () => {
           propsData: {
             defaultEnvironmentId: invalidEnvironmentId,
           },
+          stubs: { GlTabs: false },
         });
       });
 
@@ -89,13 +96,21 @@ describe('ThreatMonitoringApp component', () => {
         expect(store.dispatch).not.toHaveBeenCalled();
       });
 
-      it('shows only the empty state', () => {
-        const emptyState = findEmptyState();
-        expect(wrapper.element).toBe(emptyState.element);
-        expect(findEmptyStateMessage().exists()).toBe(true);
-        expect(emptyState.props()).toMatchObject({
-          svgPath: emptyStateSvgPath,
-        });
+      it('shows the "no environment" empty state', () => {
+        expect(findNoEnvironmentEmptyStates().length).toBe(2);
+      });
+
+      it('shows the tabs', () => {
+        expect(findNetworkPolicyTab().exists()).toBe(true);
+        expect(findStatisticsTab().exists()).toBe(true);
+      });
+
+      it('does not show the network policy list', () => {
+        expect(findNetworkPolicyList().exists()).toBe(false);
+      });
+
+      it('does not show the threat monitoring section', () => {
+        expect(findNetworkPolicySection().exists()).toBe(false);
       });
     },
   );
diff --git a/ee/spec/frontend/threat_monitoring/components/no_environment_empty_state_spec.js b/ee/spec/frontend/threat_monitoring/components/no_environment_empty_state_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..e62d7156f3afdf102175b56e3cc880de4bb18104
--- /dev/null
+++ b/ee/spec/frontend/threat_monitoring/components/no_environment_empty_state_spec.js
@@ -0,0 +1,54 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlEmptyState, GlSprintf } from '@gitlab/ui';
+import {
+  EMPTY_STATE_DESCRIPTION,
+  NO_ENVIRONMENT_TITLE,
+} from 'ee/threat_monitoring/components/constants';
+import NoEnvironmentEmptyState from 'ee/threat_monitoring/components/no_environment_empty_state.vue';
+
+const documentationPath = '/docs';
+const emptyStateSvgPath = '/svgs';
+
+describe('NoEnvironmentEmptyState component', () => {
+  let wrapper;
+
+  const findGlEmptyState = () => wrapper.find(GlEmptyState);
+  const findGlSprintf = () => wrapper.find(GlSprintf);
+
+  const factory = () => {
+    wrapper = shallowMount(NoEnvironmentEmptyState, {
+      provide: {
+        documentationPath,
+        emptyStateSvgPath,
+      },
+    });
+  };
+
+  afterEach(() => {
+    wrapper.destroy();
+    wrapper = null;
+  });
+
+  describe('default state', () => {
+    beforeEach(() => {
+      factory();
+    });
+
+    it('matches the snapshot', () => {
+      expect(wrapper.element).toMatchSnapshot();
+    });
+
+    it('shows the GlEmptyState component', () => {
+      expect(findGlEmptyState().exists()).toBe(true);
+      expect(findGlEmptyState().attributes()).toMatchObject({
+        title: NO_ENVIRONMENT_TITLE,
+        svgpath: emptyStateSvgPath,
+      });
+    });
+
+    it('shows the message', () => {
+      expect(findGlSprintf().exists()).toBe(true);
+      expect(findGlSprintf().attributes('message')).toBe(EMPTY_STATE_DESCRIPTION);
+    });
+  });
+});