diff --git a/addons/api/addon/abilities/worker.js b/addons/api/addon/abilities/worker.js
new file mode 100644
index 000000000..3640bb83a
--- /dev/null
+++ b/addons/api/addon/abilities/worker.js
@@ -0,0 +1,15 @@
+import ModelAbility from './model';
+
+/**
+ * Provides abilities for workers.
+ */
+export default class WorkerAbility extends ModelAbility {
+ // =permissions
+
+ /**
+ * @type {boolean}
+ */
+ get canCreateWorkerLed() {
+ return this.hasAuthorizedCollectionAction('create:worker-led');
+ }
+}
diff --git a/addons/api/app/abilities/worker.js b/addons/api/app/abilities/worker.js
new file mode 100644
index 000000000..0d55e96fe
--- /dev/null
+++ b/addons/api/app/abilities/worker.js
@@ -0,0 +1 @@
+export { default } from 'api/abilities/worker';
diff --git a/addons/api/mirage/factories/scope.js b/addons/api/mirage/factories/scope.js
index c70e6dbf1..62624f6dd 100644
--- a/addons/api/mirage/factories/scope.js
+++ b/addons/api/mirage/factories/scope.js
@@ -14,8 +14,8 @@ export default factory.extend({
'delete',
],
- authorized_collection_actions: () => {
- return {
+ authorized_collection_actions() {
+ const collectionActions = {
scopes: ['create', 'list'],
users: ['create', 'list'],
sessions: ['list'],
@@ -26,6 +26,13 @@ export default factory.extend({
'auth-methods': ['create', 'list'],
'host-catalogs': ['create', 'list'],
};
+
+ // Worker permissions only available on the global scope
+ if (this.type === 'global') {
+ collectionActions.workers = ['create:worker-led', 'list'];
+ }
+
+ return collectionActions;
},
id: () => generateId('s_'),
diff --git a/addons/api/mirage/factories/worker.js b/addons/api/mirage/factories/worker.js
index 816ade736..6344dbcd6 100644
--- a/addons/api/mirage/factories/worker.js
+++ b/addons/api/mirage/factories/worker.js
@@ -1,4 +1,4 @@
-import factory from '../generated/factories/user';
+import factory from '../generated/factories/worker';
import permissions from '../helpers/permissions';
import generateId from '../helpers/id';
import { faker } from '@faker-js/faker';
diff --git a/addons/api/mirage/generated/factories/worker.js b/addons/api/mirage/generated/factories/worker.js
index b022ae882..d2bb534eb 100644
--- a/addons/api/mirage/generated/factories/worker.js
+++ b/addons/api/mirage/generated/factories/worker.js
@@ -2,8 +2,8 @@ import { Factory } from 'ember-cli-mirage';
import { faker } from '@faker-js/faker';
/**
- * GeneratedUserModelFactory
- * User contains all fields related to a User resource
+ * GeneratedWorkerModelFactory
+ * User contains all fields related to a Worker resource
*/
export default Factory.extend({
name: () => faker.random.words(),
@@ -11,7 +11,7 @@ export default Factory.extend({
created_time: () => faker.date.recent(),
updated_time: () => faker.date.recent(),
version: () => faker.datatype.number(),
- address: () => `${faker.internet.ipv6()}${faker.internet.port()}`,
+ address: () => `${faker.internet.ipv4()}:${faker.internet.port()}`,
last_status_time: () => faker.date.recent(),
- active_connection_count: () => faker.datatype.number(),
+ active_connection_count: () => faker.datatype.number({ max: 10 }),
});
diff --git a/addons/api/mirage/scenarios/default.js b/addons/api/mirage/scenarios/default.js
index b956aad19..113dd3194 100644
--- a/addons/api/mirage/scenarios/default.js
+++ b/addons/api/mirage/scenarios/default.js
@@ -65,4 +65,7 @@ export default function (server) {
server.createList('group', 3, { scope });
server.createList('role', 3, { scope });
});
+
+ // Workers
+ server.createList('worker', 3, { scope: globalScope });
}
diff --git a/addons/core/translations/resources/en-us.yaml b/addons/core/translations/resources/en-us.yaml
index ac4dbc6a6..4901c02bc 100644
--- a/addons/core/translations/resources/en-us.yaml
+++ b/addons/core/translations/resources/en-us.yaml
@@ -108,7 +108,7 @@ host-set:
create-and-add-host: Create and Add Host
add-hosts: Add Existing Host
form:
- filter:
+ filter:
label: Filter
help: Create a filter to select resources using values such as tag name or tag value. Filter format is specific to each provider
preferred_endpoints:
@@ -278,7 +278,7 @@ target:
types:
tcp: TCP
ssh: SSH
- help:
+ help:
tcp: Connections are made over TCP to support a broad range of protocols, including SSH, RDP, databases, and more. This is the most general-purpose type.
ssh: Protocol-aware connections are made over SSH. Use when requiring SSH connections to remote hosts.
host-source:
@@ -412,3 +412,10 @@ managed-group:
messages:
title: Welcome to Members
description: No members in this managed group.
+worker:
+ title: Worker
+ title_plural: Workers
+ description: Workers are the server components that perform the session handling.
+ messages:
+ welcome:
+ title: Welcome to Workers
diff --git a/ui/admin/app/abilities/worker.js b/ui/admin/app/abilities/worker.js
new file mode 100644
index 000000000..395427a39
--- /dev/null
+++ b/ui/admin/app/abilities/worker.js
@@ -0,0 +1,12 @@
+import WorkerAbility from 'api/abilities/worker';
+
+export default class OverrideWorkerAbility extends WorkerAbility {
+ /**
+ * Navigating to workers is allowed if either list or create grants
+ * are present.
+ * @type {boolean}
+ */
+ get canNavigate() {
+ return this.canList || this.canCreateWorkerLed;
+ }
+}
diff --git a/ui/admin/app/controllers/scopes/scope/workers.js b/ui/admin/app/controllers/scopes/scope/workers.js
new file mode 100644
index 000000000..7b7912d71
--- /dev/null
+++ b/ui/admin/app/controllers/scopes/scope/workers.js
@@ -0,0 +1,18 @@
+import Controller from '@ember/controller';
+import { inject as service } from '@ember/service';
+
+export default class ScopesScopeWorkersController extends Controller {
+ // =services
+
+ @service intl;
+
+ // =attributes
+
+ /**
+ * Translated workers breadcrumb
+ * @type {string}
+ */
+ get breadCrumb() {
+ return this.intl.t('resources.worker.title_plural');
+ }
+}
diff --git a/ui/admin/app/controllers/scopes/scope/workers/new.js b/ui/admin/app/controllers/scopes/scope/workers/new.js
new file mode 100644
index 000000000..1c6200e43
--- /dev/null
+++ b/ui/admin/app/controllers/scopes/scope/workers/new.js
@@ -0,0 +1,18 @@
+import Controller from '@ember/controller';
+import { inject as service } from '@ember/service';
+
+export default class ScopesScopeWorkersNewController extends Controller {
+ // =services
+
+ @service intl;
+
+ // =attributes
+
+ /**
+ * Translated new worker breadcrumb
+ * @type {string}
+ */
+ get breadCrumb() {
+ return this.intl.t('titles.new');
+ }
+}
diff --git a/ui/admin/app/router.js b/ui/admin/app/router.js
index dd32f9c13..ec9218e47 100644
--- a/ui/admin/app/router.js
+++ b/ui/admin/app/router.js
@@ -125,6 +125,9 @@ Router.map(function () {
);
this.route('new');
});
+ this.route('workers', function () {
+ this.route('new');
+ });
});
});
diff --git a/ui/admin/app/routes/scopes/scope/workers.js b/ui/admin/app/routes/scopes/scope/workers.js
new file mode 100644
index 000000000..0c8b821a3
--- /dev/null
+++ b/ui/admin/app/routes/scopes/scope/workers.js
@@ -0,0 +1,31 @@
+import Route from '@ember/routing/route';
+import { inject as service } from '@ember/service';
+
+export default class ScopesScopeWorkersRoute extends Route {
+ // =services
+
+ @service can;
+ @service session;
+ @service router;
+
+ // =methods
+
+ /**
+ * If arriving here unauthenticated, redirect to index for further processing.
+ */
+ beforeModel() {
+ if (!this.session.isAuthenticated) this.router.transitionTo('index');
+ }
+
+ /**
+ * Load all workers.
+ * @return {WorkerModel}
+ */
+ model() {
+ const scope = this.modelFor('scopes.scope');
+ const { id: scope_id } = scope;
+ if (this.can.can('list worker', scope, { collection: 'workers' })) {
+ return this.store.query('worker', { scope_id });
+ }
+ }
+}
diff --git a/ui/admin/app/routes/scopes/scope/workers/index.js b/ui/admin/app/routes/scopes/scope/workers/index.js
new file mode 100644
index 000000000..856802b1c
--- /dev/null
+++ b/ui/admin/app/routes/scopes/scope/workers/index.js
@@ -0,0 +1,11 @@
+import Route from '@ember/routing/route';
+
+export default class ScopesScopeWorkersIndexRoute extends Route {
+ // =methods
+
+ setupController(controller) {
+ const scope = this.modelFor('scopes.scope');
+ super.setupController(...arguments);
+ controller.setProperties({ scope });
+ }
+}
diff --git a/ui/admin/app/routes/scopes/scope/workers/new.js b/ui/admin/app/routes/scopes/scope/workers/new.js
new file mode 100644
index 000000000..876ce8a77
--- /dev/null
+++ b/ui/admin/app/routes/scopes/scope/workers/new.js
@@ -0,0 +1,3 @@
+import Route from '@ember/routing/route';
+
+export default class ScopesScopeWorkersNewRoute extends Route {}
diff --git a/ui/admin/app/templates/scopes/scope.hbs b/ui/admin/app/templates/scopes/scope.hbs
index 81267ca84..4dfc2e2f6 100644
--- a/ui/admin/app/templates/scopes/scope.hbs
+++ b/ui/admin/app/templates/scopes/scope.hbs
@@ -22,6 +22,17 @@
{{t 'resources.org.title_plural'}}
+ {{#if (can 'navigate worker' @model collection='workers')}}
+
+
+
+ {{t 'resources.worker.title_plural'}}
+
+
+ {{/if}}
{{/if}}
{{#if @model.isOrg}}
diff --git a/ui/admin/app/templates/scopes/scope/workers.hbs b/ui/admin/app/templates/scopes/scope/workers.hbs
new file mode 100644
index 000000000..5c48246ae
--- /dev/null
+++ b/ui/admin/app/templates/scopes/scope/workers.hbs
@@ -0,0 +1,2 @@
+{{page-title (t 'resources.worker.title_plural')}}
+{{outlet}}
\ No newline at end of file
diff --git a/ui/admin/app/templates/scopes/scope/workers/index.hbs b/ui/admin/app/templates/scopes/scope/workers/index.hbs
new file mode 100644
index 000000000..8b3168d9a
--- /dev/null
+++ b/ui/admin/app/templates/scopes/scope/workers/index.hbs
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+ {{t 'resources.worker.title_plural'}}
+
+
+
+
+
+ {{#if (can 'create worker led worker' this.scope collection='workers')}}
+ {{#if @model}}
+ {{t
+ 'titles.new'
+ }}
+ {{/if}}
+ {{/if}}
+
+
+
+ {{#if @model}}
+ {{! TODO: List view }}
+ {{else}}
+
+
+
+ {{#if (can 'list worker' this.scope collection='workers')}}
+ {{! can list (at least): show default welcome message}}
+ {{t 'resources.worker.description'}}
+ {{else if (can
+ 'create worker led worker' this.scope collection='workers'
+ )}}
+ {{! can create (only): show create-but-not-list welcome message}}
+ {{t
+ 'descriptions.create-but-not-list'
+ resource=(t 'resources.worker.title_plural')
+ }}
+ {{else}}
+ {{! can neither list nor create }}
+ {{t
+ 'descriptions.neither-list-nor-create'
+ resource=(t 'resources.worker.title_plural')
+ }}
+ {{/if}}
+
+ {{#if
+ (can 'create worker led worker' this.scope collection='workers')
+ }}
+
+
+ {{t 'titles.new'}}
+
+ {{/if}}
+
+
+ {{/if}}
+
+
+
\ No newline at end of file
diff --git a/ui/admin/app/templates/scopes/scope/workers/new.hbs b/ui/admin/app/templates/scopes/scope/workers/new.hbs
new file mode 100644
index 000000000..1110d4e5f
--- /dev/null
+++ b/ui/admin/app/templates/scopes/scope/workers/new.hbs
@@ -0,0 +1,2 @@
+{{page-title (t 'titles.new')}}
+{{outlet}}
\ No newline at end of file
diff --git a/ui/admin/config/environment.js b/ui/admin/config/environment.js
index bb4396415..a5e0842ae 100644
--- a/ui/admin/config/environment.js
+++ b/ui/admin/config/environment.js
@@ -97,6 +97,8 @@ module.exports = function (environment) {
'getting-started.desktop': '/getting-started/desktop',
'api-client.cli': '/api-client/cli',
'api-client.api': '/api-client/api',
+ worker: '/workers',
+ 'worker.new': '/workers/new',
},
},
diff --git a/ui/admin/tests/unit/controllers/scopes/scope/workers-test.js b/ui/admin/tests/unit/controllers/scopes/scope/workers-test.js
new file mode 100644
index 000000000..7ceb2e774
--- /dev/null
+++ b/ui/admin/tests/unit/controllers/scopes/scope/workers-test.js
@@ -0,0 +1,11 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Controller | scopes/scope/workers', function (hooks) {
+ setupTest(hooks);
+
+ test('it exists', function (assert) {
+ let controller = this.owner.lookup('controller:scopes/scope/workers');
+ assert.ok(controller);
+ });
+});
diff --git a/ui/admin/tests/unit/controllers/scopes/scope/workers/new-test.js b/ui/admin/tests/unit/controllers/scopes/scope/workers/new-test.js
new file mode 100644
index 000000000..b2268b8fa
--- /dev/null
+++ b/ui/admin/tests/unit/controllers/scopes/scope/workers/new-test.js
@@ -0,0 +1,12 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Controller | scopes/scope/workers/new', function (hooks) {
+ setupTest(hooks);
+
+ // TODO: Replace this with your real tests.
+ test('it exists', function (assert) {
+ let controller = this.owner.lookup('controller:scopes/scope/workers/new');
+ assert.ok(controller);
+ });
+});
diff --git a/ui/admin/tests/unit/routes/scopes/scope/workers-test.js b/ui/admin/tests/unit/routes/scopes/scope/workers-test.js
new file mode 100644
index 000000000..c5b9c0e85
--- /dev/null
+++ b/ui/admin/tests/unit/routes/scopes/scope/workers-test.js
@@ -0,0 +1,11 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Route | scopes/scope/workers', function (hooks) {
+ setupTest(hooks);
+
+ test('it exists', function (assert) {
+ let route = this.owner.lookup('route:scopes/scope/workers');
+ assert.ok(route);
+ });
+});
diff --git a/ui/admin/tests/unit/routes/scopes/scope/workers/index-test.js b/ui/admin/tests/unit/routes/scopes/scope/workers/index-test.js
new file mode 100644
index 000000000..6d6a9d803
--- /dev/null
+++ b/ui/admin/tests/unit/routes/scopes/scope/workers/index-test.js
@@ -0,0 +1,11 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Route | scopes/scope/workers/index', function (hooks) {
+ setupTest(hooks);
+
+ test('it exists', function (assert) {
+ let route = this.owner.lookup('route:scopes/scope/workers/index');
+ assert.ok(route);
+ });
+});
diff --git a/ui/admin/tests/unit/routes/scopes/scope/workers/new-test.js b/ui/admin/tests/unit/routes/scopes/scope/workers/new-test.js
new file mode 100644
index 000000000..85cdd041f
--- /dev/null
+++ b/ui/admin/tests/unit/routes/scopes/scope/workers/new-test.js
@@ -0,0 +1,11 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Route | scopes/scope/workers/new', function (hooks) {
+ setupTest(hooks);
+
+ test('it exists', function (assert) {
+ let route = this.owner.lookup('route:scopes/scope/workers/new');
+ assert.ok(route);
+ });
+});