feat: create ssh type target in admin-ui (#1027)
* feat: 🎸 create ssh type target in admin-UI
This commit is contained in:
1 parent
a7bcc0c332
commit
9fae8f2a9f
7 files changed
+261
-163
No files matched your search
@@ -278,6 +278,9 @@ target:
|
||||
types:
|
||||
tcp: TCP
|
||||
ssh: SSH
|
||||
help:
|
||||
tcp: This target belongs to an TCP host catalog and will use TCP to connect
|
||||
ssh: This target belongs to an SSH host catalog and will use SSH to connect
|
||||
host-source:
|
||||
title: Host Source
|
||||
title_plural: Host Sources
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
<Rose::Form
|
||||
@onSubmit={{@submit}}
|
||||
@cancel={{@cancel}}
|
||||
@disabled={{@model.isSaving}}
|
||||
@error={{@model.errors.base}}
|
||||
@showEditToggle={{if @model.isNew false true}}
|
||||
as |form|
|
||||
>
|
||||
<form.input
|
||||
@name='name'
|
||||
@type='text'
|
||||
@value={{@model.name}}
|
||||
@label={{t 'form.name.label'}}
|
||||
@helperText={{t 'form.name.help'}}
|
||||
@error={{@model.errors.name}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.name}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.name as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
|
||||
<form.textarea
|
||||
@name='description'
|
||||
@type='text'
|
||||
@value={{@model.description}}
|
||||
@label={{t 'form.description.label'}}
|
||||
@helperText={{t 'form.description.help'}}
|
||||
@error={{@model.errors.description}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.description}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.description as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.textarea>
|
||||
|
||||
<form.fieldset>
|
||||
<:title>{{t 'form.type.label'}}</:title>
|
||||
<:body>
|
||||
<form.radioGroup
|
||||
@name='types'
|
||||
@selectedValue={{@model.type}}
|
||||
@changed={{@changeType}}
|
||||
as |radioGroup|
|
||||
>
|
||||
<radioGroup.card
|
||||
@label={{t (concat 'resources.target.types.' @model.type)}}
|
||||
@helperText={{t (concat 'resources.target.help.' @model.type)}}
|
||||
@value={{@model.type}}
|
||||
@layout='wide'
|
||||
readonly={{true}}
|
||||
@disabled={{true}}
|
||||
/>
|
||||
</form.radioGroup>
|
||||
</:body>
|
||||
</form.fieldset>
|
||||
|
||||
<form.input
|
||||
@name='session_max_seconds'
|
||||
@type='number'
|
||||
@value={{@model.session_max_seconds}}
|
||||
@label={{t 'form.session_max_seconds.label'}}
|
||||
@error={{@model.errors.session_max_seconds}}
|
||||
@helperText={{t 'form.session_max_seconds.help'}}
|
||||
min='0'
|
||||
step='1'
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.session_max_seconds}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.session_max_seconds as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
|
||||
<form.input
|
||||
@name='session_connection_limit'
|
||||
@type='number'
|
||||
@value={{@model.session_connection_limit}}
|
||||
@label={{t 'form.session_connection_limit.label'}}
|
||||
@error={{@model.errors.session_connection_limit}}
|
||||
@helperText={{t 'form.session_connection_limit.help'}}
|
||||
min='-1'
|
||||
step='1'
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.session_connection_limit}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.session_connection_limit as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
|
||||
<form.input
|
||||
@name='default_port'
|
||||
@type='number'
|
||||
@value={{@model.default_port}}
|
||||
@label={{t 'form.default_port.label'}}
|
||||
@error={{@model.errors.default_port}}
|
||||
@helperText={{t 'form.default_port.help'}}
|
||||
placeholder={{@defaultPort}}
|
||||
min='0'
|
||||
step='1'
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.default_port}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.default_port as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
{{#if (can 'save model' @model)}}
|
||||
<form.actions
|
||||
@disabled={{if @model.cannotSave @model.cannotSave}}
|
||||
@enableEditText={{t 'actions.edit-form'}}
|
||||
@submitText={{t 'actions.save'}}
|
||||
@cancelText={{t 'actions.cancel'}}
|
||||
/>
|
||||
{{/if}}
|
||||
</Rose::Form>
|
||||
@@ -1,122 +1,6 @@
|
||||
<Rose::Form
|
||||
@onSubmit={{@submit}}
|
||||
@cancel={{@cancel}}
|
||||
@disabled={{@model.isSaving}}
|
||||
@error={{@model.errors.base}}
|
||||
@showEditToggle={{if @model.isNew false true}}
|
||||
as |form|
|
||||
>
|
||||
|
||||
<form.input
|
||||
@value={{@model.type}}
|
||||
@label={{t 'form.type.label'}}
|
||||
readonly={{true}}
|
||||
@disabled={{true}}
|
||||
/>
|
||||
|
||||
<form.input
|
||||
@name='name'
|
||||
@type='text'
|
||||
@value={{@model.name}}
|
||||
@label={{t 'form.name.label'}}
|
||||
@helperText={{t 'form.name.help'}}
|
||||
@error={{@model.errors.name}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.name}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.name as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
|
||||
<form.textarea
|
||||
@name='description'
|
||||
@type='text'
|
||||
@value={{@model.description}}
|
||||
@label={{t 'form.description.label'}}
|
||||
@helperText={{t 'form.description.help'}}
|
||||
@error={{@model.errors.description}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.description}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.description as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.textarea>
|
||||
|
||||
<form.input
|
||||
@name='session_max_seconds'
|
||||
@type='number'
|
||||
@value={{@model.session_max_seconds}}
|
||||
@label={{t 'form.session_max_seconds.label'}}
|
||||
@error={{@model.errors.session_max_seconds}}
|
||||
@helperText={{t 'form.session_max_seconds.help'}}
|
||||
min='0'
|
||||
step='1'
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.session_max_seconds}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.session_max_seconds as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
|
||||
<form.input
|
||||
@name='session_connection_limit'
|
||||
@type='number'
|
||||
@value={{@model.session_connection_limit}}
|
||||
@label={{t 'form.session_connection_limit.label'}}
|
||||
@error={{@model.errors.session_connection_limit}}
|
||||
@helperText={{t 'form.session_connection_limit.help'}}
|
||||
min='-1'
|
||||
step='1'
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.session_connection_limit}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.session_connection_limit as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
|
||||
{{! TODO this is a type-specific field }}
|
||||
<form.input
|
||||
@name='default_port'
|
||||
@type='number'
|
||||
@value={{@model.default_port}}
|
||||
@label={{t 'form.default_port.label'}}
|
||||
@error={{@model.errors.default_port}}
|
||||
@helperText={{t 'form.default_port.help'}}
|
||||
min='0'
|
||||
step='1'
|
||||
as |field|
|
||||
>
|
||||
{{! TODO need to figure out how to reference `attributes.errors` here }}
|
||||
{{#if @model.errors.default_port}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.default_port as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
{{#if (can 'save model' @model)}}
|
||||
<form.actions
|
||||
@disabled={{if @model.cannotSave @model.cannotSave}}
|
||||
@enableEditText={{t 'actions.edit-form'}}
|
||||
@submitText={{t 'actions.save'}}
|
||||
@cancelText={{t 'actions.cancel'}}
|
||||
/>
|
||||
{{/if}}
|
||||
</Rose::Form>
|
||||
{{component
|
||||
(concat 'form/target/' @model.type)
|
||||
model=@model
|
||||
submit=@submit
|
||||
cancel=@cancel
|
||||
}}
|
||||
@@ -0,0 +1,6 @@
|
||||
<Form::Target::Details
|
||||
@model={{@model}}
|
||||
@defaultPort='22'
|
||||
@submit={{@submit}}
|
||||
@cancel={{@cancel}}
|
||||
/>
|
||||
@@ -0,0 +1,6 @@
|
||||
<Form::Target::Details
|
||||
@model={{@model}}
|
||||
@defaultPort='443'
|
||||
@submit={{@submit}}
|
||||
@cancel={{@cancel}}
|
||||
/>
|
||||
@@ -17,9 +17,26 @@
|
||||
<page.actions>
|
||||
{{#if (can 'create model' this.scope collection='targets')}}
|
||||
{{#if @model}}
|
||||
<Rose::LinkButton @route='scopes.scope.targets.new' @style='primary'>{{t
|
||||
'titles.new'
|
||||
}}</Rose::LinkButton>
|
||||
<Rose::Dropdown
|
||||
@text={{t 'titles.new'}}
|
||||
@dropdownRight={{true}}
|
||||
as |dropdown|
|
||||
>
|
||||
<dropdown.link
|
||||
@route='scopes.scope.targets.new'
|
||||
@query={{hash type='tcp'}}
|
||||
>
|
||||
{{t 'resources.target.types.tcp'}}
|
||||
</dropdown.link>
|
||||
{{#if (feature-flag 'ssh-target')}}
|
||||
<dropdown.link
|
||||
@route='scopes.scope.targets.new'
|
||||
@query={{hash type='ssh'}}
|
||||
>
|
||||
{{t 'resources.target.types.ssh'}}
|
||||
</dropdown.link>
|
||||
{{/if}}
|
||||
</Rose::Dropdown>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</page.actions>
|
||||
@@ -37,37 +54,74 @@
|
||||
</table.header>
|
||||
<table.body as |body|>
|
||||
{{#each @model as |target|}}
|
||||
<body.row as |row|>
|
||||
<row.headerCell>
|
||||
{{#if (can 'read model' target)}}
|
||||
<LinkTo
|
||||
@route='scopes.scope.targets.target'
|
||||
@model={{target.id}}
|
||||
>
|
||||
{{#if (feature-flag 'ssh-target')}}
|
||||
|
||||
<body.row as |row|>
|
||||
<row.headerCell>
|
||||
{{#if (can 'read model' target)}}
|
||||
<LinkTo
|
||||
@route='scopes.scope.targets.target'
|
||||
@model={{target.id}}
|
||||
>
|
||||
{{target.displayName}}
|
||||
</LinkTo>
|
||||
{{else}}
|
||||
{{target.displayName}}
|
||||
</LinkTo>
|
||||
{{else}}
|
||||
{{target.displayName}}
|
||||
{{/if}}
|
||||
<p>{{target.description}}</p>
|
||||
</row.headerCell>
|
||||
<row.cell>
|
||||
{{#if target.type}}
|
||||
<Rose::Badge>
|
||||
{{t (concat 'resources.target.types.' target.type)}}
|
||||
</Rose::Badge>
|
||||
{{/if}}
|
||||
</row.cell>
|
||||
<row.cell>
|
||||
<Copyable
|
||||
@text={{target.id}}
|
||||
@buttonText={{t 'actions.copy-to-clipboard'}}
|
||||
@acknowledgeText={{t 'states.copied'}}
|
||||
>
|
||||
<code>{{target.id}}</code>
|
||||
</Copyable>
|
||||
</row.cell>
|
||||
</body.row>
|
||||
{{/if}}
|
||||
<p>{{target.description}}</p>
|
||||
</row.headerCell>
|
||||
<row.cell>
|
||||
{{#if target.type}}
|
||||
<Rose::Badge>
|
||||
{{t (concat 'resources.target.types.' target.type)}}
|
||||
</Rose::Badge>
|
||||
{{/if}}
|
||||
</row.cell>
|
||||
<row.cell>
|
||||
<Copyable
|
||||
@text={{target.id}}
|
||||
@buttonText={{t 'actions.copy-to-clipboard'}}
|
||||
@acknowledgeText={{t 'states.copied'}}
|
||||
>
|
||||
<code>{{target.id}}</code>
|
||||
</Copyable>
|
||||
</row.cell>
|
||||
</body.row>
|
||||
{{else}}
|
||||
{{#if target.isTCP}}
|
||||
<body.row as |row|>
|
||||
<row.headerCell>
|
||||
{{#if (can 'read model' target)}}
|
||||
<LinkTo
|
||||
@route='scopes.scope.targets.target'
|
||||
@model={{target.id}}
|
||||
>
|
||||
{{target.displayName}}
|
||||
</LinkTo>
|
||||
{{else}}
|
||||
{{target.displayName}}
|
||||
{{/if}}
|
||||
<p>{{target.description}}</p>
|
||||
</row.headerCell>
|
||||
<row.cell>
|
||||
{{#if target.type}}
|
||||
<Rose::Badge>
|
||||
{{t (concat 'resources.target.types.' target.type)}}
|
||||
</Rose::Badge>
|
||||
{{/if}}
|
||||
</row.cell>
|
||||
<row.cell>
|
||||
<Copyable
|
||||
@text={{target.id}}
|
||||
@buttonText={{t 'actions.copy-to-clipboard'}}
|
||||
@acknowledgeText={{t 'states.copied'}}
|
||||
>
|
||||
<code>{{target.id}}</code>
|
||||
</Copyable>
|
||||
</row.cell>
|
||||
</body.row>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</table.body>
|
||||
</Rose::Table>
|
||||
|
||||
@@ -29,7 +29,8 @@ module('Acceptance | targets | create', function (hooks) {
|
||||
projectScope: null,
|
||||
targets: null,
|
||||
target: null,
|
||||
newTarget: null,
|
||||
newTCPTarget: null,
|
||||
newSSHTarget: null,
|
||||
};
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
@@ -52,16 +53,26 @@ module('Acceptance | targets | create', function (hooks) {
|
||||
urls.targets = `${urls.projectScope}/targets`;
|
||||
urls.target = `${urls.targets}/${instances.target.id}`;
|
||||
urls.unknownTarget = `${urls.targets}/foo`;
|
||||
urls.newTarget = `${urls.targets}/new`;
|
||||
urls.newTCPTarget = `${urls.targets}/new?type=tcp`;
|
||||
urls.newSSHTarget = `${urls.targets}/new?type=ssh`;
|
||||
// Generate resource couner
|
||||
getTargetCount = () => this.server.schema.targets.all().models.length;
|
||||
authenticateSession({});
|
||||
});
|
||||
|
||||
test('can create new targets', async function (assert) {
|
||||
test('can create new targets of type TCP', async function (assert) {
|
||||
assert.expect(1);
|
||||
const count = getTargetCount();
|
||||
await visit(urls.newTarget);
|
||||
await visit(urls.newTCPTarget);
|
||||
await fillIn('[name="name"]', 'random string');
|
||||
await click('[type="submit"]');
|
||||
assert.equal(getTargetCount(), count + 1);
|
||||
});
|
||||
|
||||
test('can create new targets of type SSH', async function (assert) {
|
||||
assert.expect(1);
|
||||
const count = getTargetCount();
|
||||
await visit(urls.newSSHTarget);
|
||||
await fillIn('[name="name"]', 'random string');
|
||||
await click('[type="submit"]');
|
||||
assert.equal(getTargetCount(), count + 1);
|
||||
@@ -75,7 +86,7 @@ module('Acceptance | targets | create', function (hooks) {
|
||||
'create'
|
||||
)
|
||||
);
|
||||
assert.ok(find(`[href="${urls.newTarget}"]`));
|
||||
assert.ok(find(`[href="${urls.newTCPTarget}"]`));
|
||||
});
|
||||
|
||||
test('cannot navigate to new targets route without proper authorization', async function (assert) {
|
||||
@@ -87,13 +98,13 @@ module('Acceptance | targets | create', function (hooks) {
|
||||
'create'
|
||||
)
|
||||
);
|
||||
assert.notOk(find(`[href="${urls.newTarget}"]`));
|
||||
assert.notOk(find(`[href="${urls.newTCPTarget}"]`));
|
||||
});
|
||||
|
||||
test('can cancel create new targets', async function (assert) {
|
||||
assert.expect(2);
|
||||
const count = getTargetCount();
|
||||
await visit(urls.newTarget);
|
||||
await visit(urls.newTCPTarget);
|
||||
await fillIn('[name="name"]', 'random string');
|
||||
await click('.rose-form-actions [type="button"]');
|
||||
assert.equal(currentURL(), urls.targets);
|
||||
@@ -121,7 +132,7 @@ module('Acceptance | targets | create', function (hooks) {
|
||||
}
|
||||
);
|
||||
});
|
||||
await visit(urls.newTarget);
|
||||
await visit(urls.newTCPTarget);
|
||||
await click('[type="submit"]');
|
||||
assert.ok(
|
||||
find('[role="alert"]').textContent.trim(),
|
||||
|
||||
Reference in new issue
Block a user