mirror of
https://github.com/ZwareBear/awx.git
synced 2026-03-19 23:33:37 -05:00
Merge branch 'devel' into wsrelay
This commit is contained in:
@@ -411,9 +411,11 @@ class AWXReceptorJob:
|
|||||||
unit_status = receptor_ctl.simple_command(f'work status {self.unit_id}')
|
unit_status = receptor_ctl.simple_command(f'work status {self.unit_id}')
|
||||||
detail = unit_status.get('Detail', None)
|
detail = unit_status.get('Detail', None)
|
||||||
state_name = unit_status.get('StateName', None)
|
state_name = unit_status.get('StateName', None)
|
||||||
|
stdout_size = unit_status.get('StdoutSize', 0)
|
||||||
except Exception:
|
except Exception:
|
||||||
detail = ''
|
detail = ''
|
||||||
state_name = ''
|
state_name = ''
|
||||||
|
stdout_size = 0
|
||||||
logger.exception(f'An error was encountered while getting status for work unit {self.unit_id}')
|
logger.exception(f'An error was encountered while getting status for work unit {self.unit_id}')
|
||||||
|
|
||||||
if 'exceeded quota' in detail:
|
if 'exceeded quota' in detail:
|
||||||
@@ -424,9 +426,16 @@ class AWXReceptorJob:
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
resultsock = receptor_ctl.get_work_results(self.unit_id, return_sockfile=True)
|
receptor_output = ''
|
||||||
lines = resultsock.readlines()
|
if state_name == 'Failed' and self.task.runner_callback.event_ct == 0:
|
||||||
receptor_output = b"".join(lines).decode()
|
# if receptor work unit failed and no events were emitted, work results may
|
||||||
|
# contain useful information about why the job failed. In case stdout is
|
||||||
|
# massive, only ask for last 1000 bytes
|
||||||
|
startpos = max(stdout_size - 1000, 0)
|
||||||
|
resultsock, resultfile = receptor_ctl.get_work_results(self.unit_id, startpos=startpos, return_socket=True, return_sockfile=True)
|
||||||
|
resultsock.setblocking(False) # this makes resultfile reads non blocking
|
||||||
|
lines = resultfile.readlines()
|
||||||
|
receptor_output = b"".join(lines).decode()
|
||||||
if receptor_output:
|
if receptor_output:
|
||||||
self.task.runner_callback.delay_update(result_traceback=receptor_output)
|
self.task.runner_callback.delay_update(result_traceback=receptor_output)
|
||||||
elif detail:
|
elif detail:
|
||||||
|
|||||||
@@ -103,6 +103,10 @@ ColorHandler = logging.StreamHandler
|
|||||||
if settings.COLOR_LOGS is True:
|
if settings.COLOR_LOGS is True:
|
||||||
try:
|
try:
|
||||||
from logutils.colorize import ColorizingStreamHandler
|
from logutils.colorize import ColorizingStreamHandler
|
||||||
|
import colorama
|
||||||
|
|
||||||
|
colorama.deinit()
|
||||||
|
colorama.init(wrap=False, convert=False, strip=False)
|
||||||
|
|
||||||
class ColorHandler(ColorizingStreamHandler):
|
class ColorHandler(ColorizingStreamHandler):
|
||||||
def colorize(self, line, record):
|
def colorize(self, line, record):
|
||||||
|
|||||||
@@ -420,7 +420,7 @@ describe('<AdvancedSearch />', () => {
|
|||||||
const selectOptions = wrapper.find(
|
const selectOptions = wrapper.find(
|
||||||
'Select[aria-label="Related search type"] SelectOption'
|
'Select[aria-label="Related search type"] SelectOption'
|
||||||
);
|
);
|
||||||
expect(selectOptions).toHaveLength(2);
|
expect(selectOptions).toHaveLength(3);
|
||||||
expect(
|
expect(
|
||||||
selectOptions.find('SelectOption[id="name-option-select"]').prop('value')
|
selectOptions.find('SelectOption[id="name-option-select"]').prop('value')
|
||||||
).toBe('name__icontains');
|
).toBe('name__icontains');
|
||||||
|
|||||||
@@ -31,6 +31,12 @@ function RelatedLookupTypeInput({
|
|||||||
value="name__icontains"
|
value="name__icontains"
|
||||||
description={t`Fuzzy search on name field.`}
|
description={t`Fuzzy search on name field.`}
|
||||||
/>
|
/>
|
||||||
|
<SelectOption
|
||||||
|
id="name-exact-option-select"
|
||||||
|
key="name"
|
||||||
|
value="name"
|
||||||
|
description={t`Exact search on name field.`}
|
||||||
|
/>
|
||||||
<SelectOption
|
<SelectOption
|
||||||
id="id-option-select"
|
id="id-option-select"
|
||||||
key="id"
|
key="id"
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ function JobEvent({
|
|||||||
if (lineNumber < 0) {
|
if (lineNumber < 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const canToggle = index === toggleLineIndex;
|
const canToggle = index === toggleLineIndex && !event.isTracebackOnly;
|
||||||
return (
|
return (
|
||||||
<JobEventLine
|
<JobEventLine
|
||||||
onClick={isClickable ? onJobEventClick : undefined}
|
onClick={isClickable ? onJobEventClick : undefined}
|
||||||
@@ -55,7 +55,7 @@ function JobEvent({
|
|||||||
onToggle={onToggleCollapsed}
|
onToggle={onToggleCollapsed}
|
||||||
/>
|
/>
|
||||||
<JobEventLineNumber>
|
<JobEventLineNumber>
|
||||||
{lineNumber}
|
{!event.isTracebackOnly ? lineNumber : ''}
|
||||||
<JobEventEllipsis isCollapsed={isCollapsed && canToggle} />
|
<JobEventEllipsis isCollapsed={isCollapsed && canToggle} />
|
||||||
</JobEventLineNumber>
|
</JobEventLineNumber>
|
||||||
<JobEventLineText
|
<JobEventLineText
|
||||||
|
|||||||
@@ -187,7 +187,9 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const pendingRequests = Object.values(eventByUuidRequests.current || {});
|
const pendingRequests = Object.values(eventByUuidRequests.current || {});
|
||||||
setHasContentLoading(true); // prevents "no content found" screen from flashing
|
setHasContentLoading(true); // prevents "no content found" screen from flashing
|
||||||
setIsFollowModeEnabled(false);
|
if (location.search) {
|
||||||
|
setIsFollowModeEnabled(false);
|
||||||
|
}
|
||||||
Promise.allSettled(pendingRequests).then(() => {
|
Promise.allSettled(pendingRequests).then(() => {
|
||||||
setRemoteRowCount(0);
|
setRemoteRowCount(0);
|
||||||
clearLoadedEvents();
|
clearLoadedEvents();
|
||||||
@@ -251,6 +253,9 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
|
|||||||
});
|
});
|
||||||
const updated = oldWsEvents.concat(newEvents);
|
const updated = oldWsEvents.concat(newEvents);
|
||||||
jobSocketCounter.current = updated.length;
|
jobSocketCounter.current = updated.length;
|
||||||
|
if (!oldWsEvents.length && min > remoteRowCount + 1) {
|
||||||
|
loadJobEvents(min);
|
||||||
|
}
|
||||||
return updated.sort((a, b) => a.counter - b.counter);
|
return updated.sort((a, b) => a.counter - b.counter);
|
||||||
});
|
});
|
||||||
setCssMap((prevCssMap) => ({
|
setCssMap((prevCssMap) => ({
|
||||||
@@ -358,7 +363,7 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadJobEvents = async () => {
|
const loadJobEvents = async (firstWsCounter = null) => {
|
||||||
const [params, loadRange] = getEventRequestParams(job, 50, [1, 50]);
|
const [params, loadRange] = getEventRequestParams(job, 50, [1, 50]);
|
||||||
|
|
||||||
if (isMounted.current) {
|
if (isMounted.current) {
|
||||||
@@ -371,6 +376,9 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
|
|||||||
if (isFlatMode) {
|
if (isFlatMode) {
|
||||||
params.not__stdout = '';
|
params.not__stdout = '';
|
||||||
}
|
}
|
||||||
|
if (firstWsCounter) {
|
||||||
|
params.counter__lt = firstWsCounter;
|
||||||
|
}
|
||||||
const qsParams = parseQueryString(QS_CONFIG, location.search);
|
const qsParams = parseQueryString(QS_CONFIG, location.search);
|
||||||
const eventPromise = getJobModel(job.type).readEvents(job.id, {
|
const eventPromise = getJobModel(job.type).readEvents(job.id, {
|
||||||
...params,
|
...params,
|
||||||
@@ -435,7 +443,7 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
|
|||||||
if (getEvent(counter)) {
|
if (getEvent(counter)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (index > remoteRowCount && index < remoteRowCount + wsEvents.length) {
|
if (index >= remoteRowCount && index < remoteRowCount + wsEvents.length) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return currentlyLoading.includes(counter);
|
return currentlyLoading.includes(counter);
|
||||||
@@ -462,7 +470,7 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
|
|||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
!event &&
|
!event &&
|
||||||
index > remoteRowCount &&
|
index >= remoteRowCount &&
|
||||||
index < remoteRowCount + wsEvents.length
|
index < remoteRowCount + wsEvents.length
|
||||||
) {
|
) {
|
||||||
event = wsEvents[index - remoteRowCount];
|
event = wsEvents[index - remoteRowCount];
|
||||||
@@ -629,10 +637,14 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
|
|||||||
setIsFollowModeEnabled(false);
|
setIsFollowModeEnabled(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const scrollToEnd = () => {
|
const scrollToEnd = useCallback(() => {
|
||||||
scrollToRow(-1);
|
scrollToRow(-1);
|
||||||
setTimeout(() => scrollToRow(-1), 100);
|
let timeout;
|
||||||
};
|
if (isFollowModeEnabled) {
|
||||||
|
setTimeout(() => scrollToRow(-1), 100);
|
||||||
|
}
|
||||||
|
return () => clearTimeout(timeout);
|
||||||
|
}, [isFollowModeEnabled]);
|
||||||
|
|
||||||
const handleScrollLast = () => {
|
const handleScrollLast = () => {
|
||||||
scrollToEnd();
|
scrollToEnd();
|
||||||
|
|||||||
@@ -29,8 +29,11 @@ export function prependTraceback(job, events) {
|
|||||||
start_line: 0,
|
start_line: 0,
|
||||||
};
|
};
|
||||||
const firstIndex = events.findIndex((jobEvent) => jobEvent.counter === 1);
|
const firstIndex = events.findIndex((jobEvent) => jobEvent.counter === 1);
|
||||||
if (firstIndex && events[firstIndex]?.stdout) {
|
if (firstIndex > -1) {
|
||||||
const stdoutLines = events[firstIndex].stdout.split('\r\n');
|
if (!events[firstIndex].stdout) {
|
||||||
|
events[firstIndex].isTracebackOnly = true;
|
||||||
|
}
|
||||||
|
const stdoutLines = events[firstIndex].stdout?.split('\r\n') || [];
|
||||||
stdoutLines[0] = tracebackEvent.stdout;
|
stdoutLines[0] = tracebackEvent.stdout;
|
||||||
events[firstIndex].stdout = stdoutLines.join('\r\n');
|
events[firstIndex].stdout = stdoutLines.join('\r\n');
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -141,14 +141,14 @@ function JobsEdit() {
|
|||||||
<FormColumnLayout>
|
<FormColumnLayout>
|
||||||
<InputField
|
<InputField
|
||||||
name="AWX_ISOLATION_BASE_PATH"
|
name="AWX_ISOLATION_BASE_PATH"
|
||||||
config={jobs.AWX_ISOLATION_BASE_PATH}
|
config={jobs.AWX_ISOLATION_BASE_PATH ?? null}
|
||||||
isRequired
|
isRequired={Boolean(options?.AWX_ISOLATION_BASE_PATH)}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
name="SCHEDULE_MAX_JOBS"
|
name="SCHEDULE_MAX_JOBS"
|
||||||
config={jobs.SCHEDULE_MAX_JOBS}
|
config={jobs.SCHEDULE_MAX_JOBS ?? null}
|
||||||
type="number"
|
type={options?.SCHEDULE_MAX_JOBS ? 'number' : undefined}
|
||||||
isRequired
|
isRequired={Boolean(options?.SCHEDULE_MAX_JOBS)}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
name="DEFAULT_JOB_TIMEOUT"
|
name="DEFAULT_JOB_TIMEOUT"
|
||||||
|
|||||||
@@ -122,4 +122,22 @@ describe('<JobsEdit />', () => {
|
|||||||
await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
|
await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
|
||||||
expect(wrapper.find('ContentError').length).toBe(1);
|
expect(wrapper.find('ContentError').length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Form input fields that are invisible (due to being set manually via a settings file) should not prevent submitting the form', async () => {
|
||||||
|
const mockOptions = Object.assign({}, mockAllOptions);
|
||||||
|
// If AWX_ISOLATION_BASE_PATH has been set in a settings file it will be absent in the PUT options
|
||||||
|
delete mockOptions['actions']['PUT']['AWX_ISOLATION_BASE_PATH'];
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<SettingsProvider value={mockOptions.actions}>
|
||||||
|
<JobsEdit />
|
||||||
|
</SettingsProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('Form').invoke('onSubmit')();
|
||||||
|
});
|
||||||
|
expect(SettingsAPI.updateAll).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -397,7 +397,10 @@ const InputField = ({ name, config, type = 'text', isRequired = false }) => {
|
|||||||
};
|
};
|
||||||
InputField.propTypes = {
|
InputField.propTypes = {
|
||||||
name: string.isRequired,
|
name: string.isRequired,
|
||||||
config: shape({}).isRequired,
|
config: shape({}),
|
||||||
|
};
|
||||||
|
InputField.defaultProps = {
|
||||||
|
config: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const TextAreaField = ({ name, config, isRequired = false }) => {
|
const TextAreaField = ({ name, config, isRequired = false }) => {
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ RUN dnf -y update && dnf install -y 'dnf-command(config-manager)' && \
|
|||||||
python3-psycopg2 \
|
python3-psycopg2 \
|
||||||
python3-setuptools \
|
python3-setuptools \
|
||||||
rsync \
|
rsync \
|
||||||
"rsyslog >= 8.1911.0" \
|
rsyslog-8.2102.0-106.el9 \
|
||||||
subversion \
|
subversion \
|
||||||
sudo \
|
sudo \
|
||||||
vim-minimal \
|
vim-minimal \
|
||||||
|
|||||||
Reference in New Issue
Block a user