diff --git a/ui/app/templates/components/agent-monitor.hbs b/ui/app/templates/components/agent-monitor.hbs
index f3d863e66..b63460191 100644
--- a/ui/app/templates/components/agent-monitor.hbs
+++ b/ui/app/templates/components/agent-monitor.hbs
@@ -14,7 +14,7 @@
{{x-icon (if logger.isStreaming "media-pause" "media-play") class="is-text"}}
-
diff --git a/ui/tests/integration/agent-monitor-test.js b/ui/tests/integration/agent-monitor-test.js
new file mode 100644
index 000000000..3d86e3bd0
--- /dev/null
+++ b/ui/tests/integration/agent-monitor-test.js
@@ -0,0 +1,178 @@
+import { run } from '@ember/runloop';
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'ember-qunit';
+import { find, render, settled } from '@ember/test-helpers';
+import hbs from 'htmlbars-inline-precompile';
+import Pretender from 'pretender';
+import sinon from 'sinon';
+import { logEncode } from '../../mirage/data/logs';
+import { selectOpen, selectOpenChoose } from '../utils/ember-power-select-extensions';
+
+module('Integration | Component | agent-monitor', function(hooks) {
+ setupRenderingTest(hooks);
+
+ const LOG_MESSAGE = 'log message goes here';
+
+ hooks.beforeEach(function() {
+ // Normally this would be called server, but server is a prop of this component.
+ this.pretender = new Pretender(function() {
+ this.get('/v1/regions', () => [200, {}, '[]']);
+ this.get('/v1/agent/monitor', ({ queryParams }) => [
+ 200,
+ {},
+ logEncode([`[${(queryParams.log_level || 'info').toUpperCase()}] ${LOG_MESSAGE}\n`], 0),
+ ]);
+ });
+ });
+
+ hooks.afterEach(function() {
+ this.pretender.shutdown();
+ });
+
+ const INTERVAL = 200;
+
+ const commonTemplate = hbs`
+
+ `;
+
+ test('basic appearance', async function(assert) {
+ this.setProperties({
+ level: 'info',
+ client: { id: 'client1' },
+ });
+
+ run.later(run, run.cancelTimers, INTERVAL);
+
+ await render(commonTemplate);
+
+ assert.ok(find('[data-test-level-switcher]'));
+ assert.ok(find('[data-test-toggle]'));
+ assert.ok(find('[data-test-log-box]'));
+ assert.ok(find('[data-test-log-box].is-full-bleed.is-dark'));
+ });
+
+ test('when provided with a client, AgentMonitor streams logs for the client', async function(assert) {
+ this.setProperties({
+ level: 'info',
+ client: { id: 'client1' },
+ });
+
+ run.later(run, run.cancelTimers, INTERVAL);
+
+ await render(commonTemplate);
+
+ const logRequest = this.pretender.handledRequests[1];
+ assert.ok(logRequest.url.startsWith('/v1/agent/monitor'));
+ assert.ok(logRequest.url.includes('client_id=client1'));
+ assert.ok(logRequest.url.includes('log_level=info'));
+ assert.notOk(logRequest.url.includes('server_id'));
+ });
+
+ test('when provided with a server, AgentMonitor streams logs for the server', async function(assert) {
+ this.setProperties({
+ level: 'warn',
+ server: { id: 'server1' },
+ });
+
+ run.later(run, run.cancelTimers, INTERVAL);
+
+ await render(commonTemplate);
+
+ const logRequest = this.pretender.handledRequests[1];
+ assert.ok(logRequest.url.startsWith('/v1/agent/monitor'));
+ assert.ok(logRequest.url.includes('server_id=server1'));
+ assert.ok(logRequest.url.includes('log_level=warn'));
+ assert.notOk(logRequest.url.includes('client_id'));
+ });
+
+ test('switching levels calls onLevelChange and restarts the logger', async function(assert) {
+ const onLevelChange = sinon.spy();
+ const newLevel = 'trace';
+
+ this.setProperties({
+ level: 'info',
+ client: { id: 'client1' },
+ onLevelChange,
+ });
+
+ run.later(run, run.cancelTimers, INTERVAL);
+
+ await render(commonTemplate);
+ await settled();
+
+ const contentId = await selectOpen('[data-test-level-switcher]');
+ run.later(run, run.cancelTimers, INTERVAL);
+ await selectOpenChoose(contentId, newLevel.capitalize());
+ await settled();
+
+ assert.ok(onLevelChange.calledOnce);
+ assert.ok(onLevelChange.calledWith(newLevel));
+
+ const secondLogRequest = this.pretender.handledRequests[2];
+ assert.ok(secondLogRequest.url.includes(`log_level=${newLevel}`));
+ });
+
+ test('when switching levels, the scrollback is preserved and annotated with a switch message', async function(assert) {
+ const newLevel = 'trace';
+ const onLevelChange = sinon.spy();
+
+ this.setProperties({
+ level: 'info',
+ client: { id: 'client1' },
+ onLevelChange,
+ });
+
+ run.later(run, run.cancelTimers, INTERVAL);
+
+ await render(commonTemplate);
+ await settled();
+ assert.equal(find('[data-test-log-cli]').textContent, `[INFO] ${LOG_MESSAGE}\n`);
+
+ const contentId = await selectOpen('[data-test-level-switcher]');
+ run.later(run, run.cancelTimers, INTERVAL);
+ await selectOpenChoose(contentId, newLevel.capitalize());
+ await settled();
+
+ assert.equal(
+ find('[data-test-log-cli]').textContent,
+ `[INFO] ${LOG_MESSAGE}\n\n...changing log level to ${newLevel}...\n\n[TRACE] ${LOG_MESSAGE}\n`
+ );
+ });
+
+ test('when switching levels and there is no scrollback, there is no appended switch message', async function(assert) {
+ const newLevel = 'trace';
+ const onLevelChange = sinon.spy();
+
+ // Emit nothing for the first request
+ this.pretender.get('/v1/agent/monitor', ({ queryParams }) => [
+ 200,
+ {},
+ queryParams.log_level === 'info'
+ ? logEncode([''], 0)
+ : logEncode([`[${(queryParams.log_level || 'info').toUpperCase()}] ${LOG_MESSAGE}\n`], 0),
+ ]);
+
+ this.setProperties({
+ level: 'info',
+ client: { id: 'client1' },
+ onLevelChange,
+ });
+
+ run.later(run, run.cancelTimers, INTERVAL);
+
+ await render(commonTemplate);
+ await settled();
+ assert.equal(find('[data-test-log-cli]').textContent, '');
+
+ const contentId = await selectOpen('[data-test-level-switcher]');
+ run.later(run, run.cancelTimers, INTERVAL);
+ await selectOpenChoose(contentId, newLevel.capitalize());
+ await settled();
+
+ assert.equal(find('[data-test-log-cli]').textContent, `[TRACE] ${LOG_MESSAGE}\n`);
+ });
+});