Skip to content

Commit

Permalink
Merge pull request ansible#7929 from nixocio/ui_associate_instances
Browse files Browse the repository at this point in the history
Associate instances to instance groups

Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
  • Loading branch information
softwarefactory-project-zuul[bot] authored Aug 27, 2020
2 parents ad3e2cb + 632204d commit aa637d5
Show file tree
Hide file tree
Showing 20 changed files with 950 additions and 64 deletions.
9 changes: 6 additions & 3 deletions awx/ui_next/src/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Credentials from './models/Credentials';
import Groups from './models/Groups';
import Hosts from './models/Hosts';
import InstanceGroups from './models/InstanceGroups';
import Instances from './models/Instances';
import Inventories from './models/Inventories';
import InventoryScripts from './models/InventoryScripts';
import InventorySources from './models/InventorySources';
Expand All @@ -19,8 +20,8 @@ import NotificationTemplates from './models/NotificationTemplates';
import Organizations from './models/Organizations';
import ProjectUpdates from './models/ProjectUpdates';
import Projects from './models/Projects';
import Root from './models/Root';
import Roles from './models/Roles';
import Root from './models/Root';
import Schedules from './models/Schedules';
import SystemJobs from './models/SystemJobs';
import Teams from './models/Teams';
Expand All @@ -42,6 +43,7 @@ const CredentialsAPI = new Credentials();
const GroupsAPI = new Groups();
const HostsAPI = new Hosts();
const InstanceGroupsAPI = new InstanceGroups();
const InstancesAPI = new Instances();
const InventoriesAPI = new Inventories();
const InventoryScriptsAPI = new InventoryScripts();
const InventorySourcesAPI = new InventorySources();
Expand All @@ -54,8 +56,8 @@ const NotificationTemplatesAPI = new NotificationTemplates();
const OrganizationsAPI = new Organizations();
const ProjectUpdatesAPI = new ProjectUpdates();
const ProjectsAPI = new Projects();
const RootAPI = new Root();
const RolesAPI = new Roles();
const RootAPI = new Root();
const SchedulesAPI = new Schedules();
const SystemJobsAPI = new SystemJobs();
const TeamsAPI = new Teams();
Expand All @@ -78,6 +80,7 @@ export {
GroupsAPI,
HostsAPI,
InstanceGroupsAPI,
InstancesAPI,
InventoriesAPI,
InventoryScriptsAPI,
InventorySourcesAPI,
Expand All @@ -90,8 +93,8 @@ export {
OrganizationsAPI,
ProjectUpdatesAPI,
ProjectsAPI,
RootAPI,
RolesAPI,
RootAPI,
SchedulesAPI,
SystemJobsAPI,
TeamsAPI,
Expand Down
31 changes: 31 additions & 0 deletions awx/ui_next/src/api/models/InstanceGroups.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,37 @@ class InstanceGroups extends Base {
constructor(http) {
super(http);
this.baseUrl = '/api/v2/instance_groups/';

this.associateInstance = this.associateInstance.bind(this);
this.disassociateInstance = this.disassociateInstance.bind(this);
this.readInstanceOptions = this.readInstanceOptions.bind(this);
this.readInstances = this.readInstances.bind(this);
this.readJobs = this.readJobs.bind(this);
}

associateInstance(instanceGroupId, instanceId) {
return this.http.post(`${this.baseUrl}${instanceGroupId}/instances/`, {
id: instanceId,
});
}

disassociateInstance(instanceGroupId, instanceId) {
return this.http.post(`${this.baseUrl}${instanceGroupId}/instances/`, {
id: instanceId,
disassociate: true,
});
}

readInstances(id, params) {
return this.http.get(`${this.baseUrl}${id}/instances/`, { params });
}

readInstanceOptions(id) {
return this.http.options(`${this.baseUrl}${id}/instances/`);
}

readJobs(id) {
return this.http.get(`${this.baseUrl}${id}/jobs/`);
}
}

Expand Down
10 changes: 10 additions & 0 deletions awx/ui_next/src/api/models/Instances.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Base from '../Base';

class Instances extends Base {
constructor(http) {
super(http);
this.baseUrl = '/api/v2/instances/';
}
}

export default Instances;
27 changes: 17 additions & 10 deletions awx/ui_next/src/components/AssociateModal/AssociateModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import useRequest from '../../util/useRequest';
import { getQSConfig, parseQueryString } from '../../util/qs';
import useSelected from '../../util/useSelected';

const QS_CONFIG = getQSConfig('associate', {
page: 1,
page_size: 5,
order_by: 'name',
});
const QS_CONFIG = (order_by = 'name') => {
return getQSConfig('associate', {
page: 1,
page_size: 5,
order_by,
});
};

function AssociateModal({
i18n,
Expand All @@ -23,6 +25,7 @@ function AssociateModal({
fetchRequest,
optionsRequest,
isModalOpen = false,
displayKey = 'name',
}) {
const history = useHistory();
const { selected, handleSelect } = useSelected([]);
Expand All @@ -34,7 +37,10 @@ function AssociateModal({
isLoading,
} = useRequest(
useCallback(async () => {
const params = parseQueryString(QS_CONFIG, history.location.search);
const params = parseQueryString(
QS_CONFIG(displayKey),
history.location.search
);
const [
{
data: { count, results },
Expand All @@ -52,7 +58,7 @@ function AssociateModal({
actionsResponse.data.actions?.GET || {}
).filter(key => actionsResponse.data.actions?.GET[key].filterable),
};
}, [fetchRequest, optionsRequest, history.location.search]),
}, [fetchRequest, optionsRequest, history.location.search, displayKey]),
{
items: [],
itemCount: 0,
Expand Down Expand Up @@ -112,21 +118,22 @@ function AssociateModal({
]}
>
<OptionsList
displayKey={displayKey}
contentError={contentError}
deselectItem={handleSelect}
header={header}
isLoading={isLoading}
multiple
optionCount={itemCount}
options={items}
qsConfig={QS_CONFIG}
qsConfig={QS_CONFIG(displayKey)}
readOnly={false}
selectItem={handleSelect}
value={selected}
searchColumns={[
{
name: i18n._(t`Name`),
key: 'name__icontains',
key: `${displayKey}__icontains`,
isDefault: true,
},
{
Expand All @@ -141,7 +148,7 @@ function AssociateModal({
sortColumns={[
{
name: i18n._(t`Name`),
key: 'name',
key: `${displayKey}`,
},
]}
searchableKeys={searchableKeys}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ function DisassociateButton({
modalNote = '',
modalTitle = i18n._(t`Disassociate?`),
onDisassociate,
verifyCannotDisassociate = true,
}) {
const [isOpen, setIsOpen] = useState(false);

Expand All @@ -25,33 +26,41 @@ function DisassociateButton({
}

function cannotDisassociate(item) {
return !item.summary_fields.user_capabilities.delete;
return !item.summary_fields?.user_capabilities?.delete;
}

function renderTooltip() {
const itemsUnableToDisassociate = itemsToDisassociate
.filter(cannotDisassociate)
.map(item => item.name)
.join(', ');
if (verifyCannotDisassociate) {
const itemsUnableToDisassociate = itemsToDisassociate
.filter(cannotDisassociate)
.map(item => item.name)
.join(', ');

if (itemsToDisassociate.some(cannotDisassociate)) {
return (
<div>
{i18n._(
t`You do not have permission to disassociate the following: ${itemsUnableToDisassociate}`
)}
</div>
);
if (itemsToDisassociate.some(cannotDisassociate)) {
return (
<div>
{i18n._(
t`You do not have permission to disassociate the following: ${itemsUnableToDisassociate}`
)}
</div>
);
}
}

if (itemsToDisassociate.length) {
return i18n._(t`Disassociate`);
}
return i18n._(t`Select a row to disassociate`);
}

const isDisabled =
itemsToDisassociate.length === 0 ||
itemsToDisassociate.some(cannotDisassociate);
let isDisabled = false;
if (verifyCannotDisassociate) {
isDisabled =
itemsToDisassociate.length === 0 ||
itemsToDisassociate.some(cannotDisassociate);
} else {
isDisabled = itemsToDisassociate.length === 0;
}

// NOTE: Once PF supports tooltips on disabled elements,
// we can delete the extra <div> around the <DeleteButton> below.
Expand Down Expand Up @@ -102,7 +111,7 @@ function DisassociateButton({

{itemsToDisassociate.map(item => (
<span key={item.id}>
<strong>{item.name}</strong>
<strong>{item.hostname ? item.hostname : item.name}</strong>
<br />
</span>
))}
Expand Down
81 changes: 81 additions & 0 deletions awx/ui_next/src/components/InstanceToggle/InstanceToggle.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React, { useState, useEffect, useCallback } from 'react';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { Switch, Tooltip } from '@patternfly/react-core';
import AlertModal from '../AlertModal';
import ErrorDetail from '../ErrorDetail';
import useRequest from '../../util/useRequest';
import { InstancesAPI } from '../../api';
import { useConfig } from '../../contexts/Config';

function InstanceToggle({
className,
fetchInstances,
instance,
onToggle,
i18n,
}) {
const { me } = useConfig();
const [isEnabled, setIsEnabled] = useState(instance.enabled);
const [showError, setShowError] = useState(false);

const { result, isLoading, error, request: toggleInstance } = useRequest(
useCallback(async () => {
await InstancesAPI.update(instance.id, { enabled: !isEnabled });
await fetchInstances();
return !isEnabled;
}, [instance, isEnabled, fetchInstances]),
instance.enabled
);

useEffect(() => {
if (result !== isEnabled) {
setIsEnabled(result);
if (onToggle) {
onToggle(result);
}
}
}, [result, isEnabled, onToggle]);

useEffect(() => {
if (error) {
setShowError(true);
}
}, [error]);

return (
<>
<Tooltip
content={i18n._(
t`Set the instance online or offline. If offline, jobs will not be assigned to this instance.`
)}
position="top"
>
<Switch
className={className}
css="display: inline-flex;"
id={`host-${instance.id}-toggle`}
label={i18n._(t`On`)}
labelOff={i18n._(t`Off`)}
isChecked={isEnabled}
isDisabled={isLoading || !me.is_superuser}
onChange={toggleInstance}
aria-label={i18n._(t`Toggle instance`)}
/>
</Tooltip>
{showError && error && !isLoading && (
<AlertModal
variant="error"
title={i18n._(t`Error!`)}
isOpen={error && !isLoading}
onClose={() => setShowError(false)}
>
{i18n._(t`Failed to toggle instance.`)}
<ErrorDetail error={error} />
</AlertModal>
)}
</>
);
}

export default withI18n()(InstanceToggle);
Loading

0 comments on commit aa637d5

Please sign in to comment.