Skip to content

Commit

Permalink
Host page react conversion
Browse files Browse the repository at this point in the history
  • Loading branch information
MelsHyrule committed Feb 3, 2023
1 parent 7f947b6 commit 9935982
Show file tree
Hide file tree
Showing 18 changed files with 7,019 additions and 678 deletions.
78 changes: 0 additions & 78 deletions app/controllers/host_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -161,84 +161,6 @@ def edit
end
end

def update
assert_privileges("host_edit")
case params[:button]
when "cancel"
if session[:host_items] # canceling editing credentials for multiple Hosts
flash_and_redirect(_("Edit of credentials for selected Hosts was cancelled by the user"))
else
@host = find_record_with_rbac(Host, params[:id])
flash_and_redirect(_("Edit of Host \"%{name}\" was cancelled by the user") % {:name => @host.name})
end

when "save"
if session[:host_items].nil?
@host = find_record_with_rbac(Host, params[:id])
old_host_attributes = @host.attributes.clone
valid_host = find_record_with_rbac(Host, params[:id])
set_record_vars(valid_host, :validate) # Set the record variables, but don't save
if valid_record? && set_record_vars(@host) && @host.save
AuditEvent.success(build_saved_audit_hash_angular(old_host_attributes, @host, false))
flash_and_redirect(_("Host \"%{name}\" was saved") % {:name => @host.name})
nil
else
@errors.each { |msg| add_flash(msg, :error) }
@host.errors.each do |error|
add_flash("#{error.attribute.to_s.capitalize} #{error.message}", :error)
end
drop_breadcrumb(:name => _("Edit Host '%{name}'") % {:name => @host.name}, :url => "/host/edit/#{@host.id}")
@in_a_form = true
javascript_flash
end
else
valid_host = find_record_with_rbac(Host, params[:validate_id].presence || session[:host_items].first.to_i)
# Set the record variables, but don't save
creds = set_credentials(valid_host, :validate)
if valid_record?
@error = Host.batch_update_authentication(session[:host_items], creds)
end
if @error || @error.blank?
flash_and_redirect(_("Credentials/Settings saved successfully"))
else
drop_breadcrumb(:name => _("Edit Host '%{name}'") % {:name => @host.name}, :url => "/host/edit/#{@host.id}")
@in_a_form = true
javascript_flash
end
end
when "reset"
params[:edittype] = @edit[:edittype] # remember the edit type
flash_to_session(_("All changes have been reset"), :warning)
@in_a_form = true
javascript_redirect(:action => 'edit', :id => @host.id.to_s)
when "validate"
verify_host = find_record_with_rbac(Host, params[:validate_id] ? params[:validate_id].to_i : params[:id])
if session[:host_items].nil?
set_record_vars(verify_host, :validate)
else
set_credentials(verify_host, :validate)
end
@in_a_form = true
@changed = session[:changed]
require "net/ssh"
begin
verify_host.verify_credentials(params[:type], :remember_host => params.key?(:remember_host))
rescue Net::SSH::HostKeyMismatch # Capture the Host key mismatch from the verify
render :update do |page|
page << javascript_prologue
new_url = url_for_only_path(:action => "update", :button => "validate", :type => params[:type], :remember_host => "true", :escape => false)
page << "if (confirm('#{_('The Host SSH key has changed, do you want to accept the new key?')}')) miqAjax('#{new_url}', true);"
end
return
rescue StandardError => bang
add_flash(bang.to_s, :error)
else
add_flash(_("Credential validation was successful"))
end
javascript_flash
end
end

# handle buttons pressed on the button bar
def button
@edit = session[:edit] # Restore @edit for adv search box
Expand Down
3 changes: 3 additions & 0 deletions app/javascript/components/host-edit-form/editing-context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { createContext } from 'react';

export default createContext({});
123 changes: 123 additions & 0 deletions app/javascript/components/host-edit-form/host-edit-form.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/* eslint-disable camelcase */
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { keyBy } from 'lodash';
import MiqFormRenderer from '../../forms/data-driven-form';
import createSchema from './host-edit-form.schema';
import miqRedirectBack from '../../helpers/miq-redirect-back';
import ValidateHostCredentials from './validate-host-credentials';
import mapper from '../../forms/mappers/componentMapper';

const HostEditForm = ({ ids }) => {
const [{
initialValues, isLoading, fields,
}, setState] = useState({
isLoading: true,
fields: [],
});

const getHostData = (id) => {
miqSparkleOn();
API.get(`/api/hosts/${id}?expand=resources&attributes=authentications`).then((initialValues) => {
const authentications = initialValues ? keyBy(initialValues.authentications, 'authtype') : {};
initialValues.host_validate_against = id;
const foo = {
...initialValues,
authentications,
};
API.options(`/api/hosts/${id}`).then((values) => loadSchema(values, foo)).then(miqSparkleOff);
});
};

const loadSchema = (response, appendState = {}) => {
setState((state) => ({
...state,
...appendState,
isLoading: false,
initialValues: appendState,
fields: response.data.form_schema.fields,
}));
};

const emptySchema = (appendState = {}) => {
const fields = [];
setState((state) => ({
...state,
...appendState,
fields,
}));
};

const onReset = () => {
const fields = [];
if (ids.length > 1) {
setState((state) => ({
...state,
fields,
}));
}
};

useEffect(() => {
if (ids.length === 1) {
getHostData(ids[0]);
}
setState((state) => ({ ...state, isLoading: false }));
}, [ids]);

const onSubmit = (values) => {
miqSparkleOn();
const payload = {
action: 'edit',
resource: values,
};
const selectedHostId = ids.length === 1 ? ids[0] : values.host_validate_against;
const request = API.post(`/api/hosts/${selectedHostId}`, payload);

request.then(() => {
const message = sprintf(__('Modification of Cloud Database %s has been successfully queued.'), values.name);
miqRedirectBack(message, 'success', `/host/show/${selectedHostId}`);
}).catch(miqSparkleOff);
};

const onCancel = () => {
miqSparkleOn();
let message = '';
let url = '';
if (initialValues) {
message = sprintf(__(`Edit of Host "%s" was cancelled.`), initialValues.name);
url = `/host/show/${initialValues.id}`;
} else {
message = __(`Edit of Hosts was cancelled.`);
url = '/host/show_list';
}
miqRedirectBack(message, 'success', url);
};

const componentMapper = {
...mapper,
'validate-host-credentials': ValidateHostCredentials,
};

return !isLoading && (
<MiqFormRenderer
componentMapper={componentMapper}
schema={createSchema(ids, fields, emptySchema, getHostData)}
initialValues={initialValues}
canReset
onReset={onReset}
onSubmit={onSubmit}
onCancel={onCancel}
/>
);
};

HostEditForm.propTypes = {
ids: PropTypes.arrayOf(PropTypes.any),
};

HostEditForm.defaultProps = {
ids: [],
};

export default HostEditForm;
58 changes: 58 additions & 0 deletions app/javascript/components/host-edit-form/host-edit-form.schema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { componentTypes, validatorTypes } from '@@ddf';

// Called only when multiple hosts are selected
const loadHosts = (ids) =>
API.get(`/api/hosts?expand=resources&attributes=id,name&filter[]=id=[${ids}]`)
.then(({ resources }) => {
const temp = resources.map(({ id, name }) => ({ value: id, label: name }));
temp.unshift({ label: `<${__('Choose')}>`, value: '-1' });
return temp;
});

const changeValue = (value, getHostData, emptySchema) => {
if (value === '-1') {
emptySchema();
} else {
getHostData(value);
}
};

function createSchema(ids, endpointFields, emptySchema, getHostData) {
const fields = [
...(ids.length === 1
? [{
component: componentTypes.TEXT_FIELD,
name: 'name',
id: 'name',
label: __('Name:'),
isDisabled: true,
},
{
component: componentTypes.TEXT_FIELD,
name: 'hostname',
id: 'hostname',
label: __('Hostname (or IPv4 or IPv6 address:'),
isDisabled: true,
},
{
component: componentTypes.TEXT_FIELD,
name: 'custom_identifier',
id: 'custom_identifier',
label: __('Custom Identifier:'),
isDisabled: true,
}] : [{
component: componentTypes.SELECT,
name: 'host_validate_against',
id: 'host_validate_against',
label: __('Select a Host to validate against'),
isRequired: true,
validate: [{ type: validatorTypes.REQUIRED }],
loadOptions: () => (ids ? loadHosts(ids) : Promise.resolve([])),
onChange: (value) => changeValue(value, getHostData, emptySchema),
}]),
...(endpointFields || []),
];
return { fields };
}

export default createSchema;
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import { pick } from 'lodash';

// TODO all this file properly
import AsyncCredentials from '../async-credentials/async-credentials';
import EditingContext from './editing-context';

const ValidateHostCredentials = ({ ...props }) => {
const { hostId } = useContext(EditingContext); // TODO this isnt grabbing the hostId, but it is also undefined in the provider example im referencing so idk

const asyncValidate = (fields, fieldNames) => new Promise((resolve, reject) => {
const url = hostId ? `/api/hosts/${hostId}` : '/api/hosts';
const resource = pick(fields, fieldNames); // TODO make sure this is correct

// NOTES:
// host actions include "check_compliance", "edit", "delete"
// looking for check compliance found in host.rb file
// supports :check_compliance_queue
// which ibelieve if defined in compliance.rb file
// def self.check_compliance_queue(targets, inputs = {})

// app/models/host.rb: def verify_credentials(auth_type = nil, options = {})

API.post(url, { action: 'verify_credentials', resource }).then(({ results: [result] = [], ...single }) => {
// eslint-disable-next-line camelcase
const { task_id, success } = result || single;
// The request here can either create a background task or fail
return success ? API.wait_for_task(task_id) : Promise.reject(result);
// The wait_for_task request can succeed with valid or invalid credentials
// with the message that the task is completed successfully. Based on the
// task_results we resolve() or reject() with an unknown error.
// Any known errors are passed to the catch(), which will reject() with a
// message describing what went wrong.
}).then((result) => (result.task_results ? resolve() : reject(__('Validation failed: unknown error'))))
.catch(({ message }) => reject([__('Validation failed:'), message].join(' ')));
});

// The order of props is important here, because they have to be overridden
return <AsyncCredentials {...props} asyncValidate={asyncValidate} edit={!!hostId} />;
};

ValidateHostCredentials.propTypes = {
...AsyncCredentials.propTypes,
asyncValidate: PropTypes.func,
validation: PropTypes.bool,
};
ValidateHostCredentials.defaultProps = {
validation: true,
...AsyncCredentials.defaultProps,
};

export default ValidateHostCredentials;
Loading

0 comments on commit 9935982

Please sign in to comment.