mirror of
https://github.com/ansible/awx.git
synced 2026-02-05 19:44:43 -03:30
Compare commits
458 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
beb8021580 | ||
|
|
36078651d3 | ||
|
|
8b768bcb01 | ||
|
|
16d9e1cfc7 | ||
|
|
0fd9153cf7 | ||
|
|
c95624e27f | ||
|
|
5cf33f57a4 | ||
|
|
5c331934e2 | ||
|
|
7ac21b4922 | ||
|
|
04fe18d840 | ||
|
|
b9829e2bde | ||
|
|
a99d4a8419 | ||
|
|
676b29346c | ||
|
|
208dbc1f92 | ||
|
|
2cb5046ec6 | ||
|
|
356b674a49 | ||
|
|
185c581007 | ||
|
|
789397d56c | ||
|
|
e816f73ecf | ||
|
|
bbe5789e70 | ||
|
|
ad1a7fc9c9 | ||
|
|
dd5f25186b | ||
|
|
ecb7147614 | ||
|
|
87396f968c | ||
|
|
87bfb82907 | ||
|
|
65e988b44c | ||
|
|
4381a7d75c | ||
|
|
3a6528dc0d | ||
|
|
c113c2db52 | ||
|
|
b7d2d6ad89 | ||
|
|
01d77d5407 | ||
|
|
87c6ed52cd | ||
|
|
0d5a46a6e1 | ||
|
|
f3ab3de1be | ||
|
|
871695ea5e | ||
|
|
0f5b694514 | ||
|
|
9567dc9e17 | ||
|
|
24d35e9593 | ||
|
|
b6be8ca445 | ||
|
|
beb10feb0c | ||
|
|
6ec9c45341 | ||
|
|
9a394a5726 | ||
|
|
25f4aa19b7 | ||
|
|
7ff5bacce5 | ||
|
|
3e820a88e1 | ||
|
|
c1ab118481 | ||
|
|
3952be9429 | ||
|
|
35f414ccf2 | ||
|
|
304bf6805b | ||
|
|
e733506477 | ||
|
|
f4366be419 | ||
|
|
64018a71bb | ||
|
|
0c9c349fb9 | ||
|
|
6dd4d04bf5 | ||
|
|
21b4455ee6 | ||
|
|
314e345825 | ||
|
|
90e047821d | ||
|
|
01fe89e43c | ||
|
|
1f2edd81ab | ||
|
|
862de0b6f3 | ||
|
|
d75c2d9b44 | ||
|
|
1b8ff1f846 | ||
|
|
a93b1aa339 | ||
|
|
4c6191041c | ||
|
|
edb3f6df55 | ||
|
|
7a3ece7fd2 | ||
|
|
73e867b6f5 | ||
|
|
acc34c1393 | ||
|
|
3d5a002676 | ||
|
|
bb6d9af90b | ||
|
|
da94b2dc9e | ||
|
|
a1c2db3db5 | ||
|
|
d849e81891 | ||
|
|
a5afac62ca | ||
|
|
66c98ca9bc | ||
|
|
a00e7c7050 | ||
|
|
cd1ff6b16a | ||
|
|
b560a21ca3 | ||
|
|
63fa367e9d | ||
|
|
d33daeee91 | ||
|
|
9d449c419b | ||
|
|
e34e88549f | ||
|
|
c073e39c69 | ||
|
|
4fcd2c594c | ||
|
|
457dc956f1 | ||
|
|
3e5428131c | ||
|
|
d08f59272e | ||
|
|
8b95d7be94 | ||
|
|
6c22ddf608 | ||
|
|
8227054d11 | ||
|
|
73b33e1435 | ||
|
|
deaf2028ad | ||
|
|
d812972d3c | ||
|
|
54b553b78c | ||
|
|
3e08bbeb93 | ||
|
|
22524589e3 | ||
|
|
85ec73bf4b | ||
|
|
ccd36b9560 | ||
|
|
61755e2838 | ||
|
|
be56a1d3df | ||
|
|
46c86ea6c0 | ||
|
|
401c7c3da2 | ||
|
|
f1120d39db | ||
|
|
80617df22d | ||
|
|
b5e5fea117 | ||
|
|
e3ec63e8e5 | ||
|
|
e232cd392c | ||
|
|
8301254f57 | ||
|
|
9cdfc19215 | ||
|
|
c50705a2dc | ||
|
|
9f948e90d9 | ||
|
|
e7f36eb2ea | ||
|
|
c261d6acf0 | ||
|
|
32ef805e23 | ||
|
|
d009ce49f5 | ||
|
|
d14bf00f6c | ||
|
|
5dc4e30820 | ||
|
|
afbeacf499 | ||
|
|
fc80cf5241 | ||
|
|
4a6db13daa | ||
|
|
d5372dae36 | ||
|
|
0b702ede4e | ||
|
|
3c7f596288 | ||
|
|
6207dad226 | ||
|
|
2b48e43946 | ||
|
|
4709f57f46 | ||
|
|
b055aad641 | ||
|
|
acfa6d056f | ||
|
|
51a069fcc4 | ||
|
|
fc89b627d7 | ||
|
|
e90ee5113d | ||
|
|
4ccca08cda | ||
|
|
b757fdebf8 | ||
|
|
3ee6f1f3c7 | ||
|
|
d4ba32d0c5 | ||
|
|
d97f516c3a | ||
|
|
52a8935b20 | ||
|
|
07752f48f6 | ||
|
|
10b5a10728 | ||
|
|
e11ff69f3e | ||
|
|
d3fa34c665 | ||
|
|
48a615231b | ||
|
|
b09ac71647 | ||
|
|
d5dd3c521f | ||
|
|
db43341f96 | ||
|
|
3234f246db | ||
|
|
6d6d99bcf8 | ||
|
|
a6cd32522f | ||
|
|
1fe28463da | ||
|
|
51a6194b8d | ||
|
|
e75f7b0beb | ||
|
|
179c62e2f3 | ||
|
|
98f5525d28 | ||
|
|
60a137225a | ||
|
|
c1bfcd73fb | ||
|
|
322b4ee1e4 | ||
|
|
98dc6179f5 | ||
|
|
07807c2dec | ||
|
|
16ecf17c69 | ||
|
|
1f0acef844 | ||
|
|
5a164cae15 | ||
|
|
b57405b696 | ||
|
|
5fdf6cf60f | ||
|
|
c1c382a941 | ||
|
|
a997b40852 | ||
|
|
99cd2e601d | ||
|
|
fc402aff29 | ||
|
|
2ec035f918 | ||
|
|
fe046b47b5 | ||
|
|
3e0e4b6c8f | ||
|
|
7fe57268f6 | ||
|
|
9eecb24c32 | ||
|
|
a8a45fca84 | ||
|
|
33df6f8ad2 | ||
|
|
44223003aa | ||
|
|
a60e7a7855 | ||
|
|
e971ec993b | ||
|
|
989ef3538e | ||
|
|
4db3e823bf | ||
|
|
c374316648 | ||
|
|
5dba49a7bc | ||
|
|
7b880c6552 | ||
|
|
5574cf0595 | ||
|
|
e706e0a2e2 | ||
|
|
5364e78397 | ||
|
|
f93ca814ac | ||
|
|
3bf1ad3028 | ||
|
|
e096ad18cb | ||
|
|
5ca73f1101 | ||
|
|
7e8fb29658 | ||
|
|
258689a9ed | ||
|
|
e80e3f7410 | ||
|
|
154b9c36ac | ||
|
|
deced917cf | ||
|
|
88b7256e96 | ||
|
|
033848a605 | ||
|
|
0e663921d6 | ||
|
|
0582079606 | ||
|
|
6536f5a453 | ||
|
|
c5079607aa | ||
|
|
26dcb000f6 | ||
|
|
8ba4f728c0 | ||
|
|
ee090d34fa | ||
|
|
bd30951a4f | ||
|
|
43cce83ba1 | ||
|
|
946d643795 | ||
|
|
1a6ea99d37 | ||
|
|
350046d495 | ||
|
|
b532012748 | ||
|
|
1c4042340c | ||
|
|
787c4af222 | ||
|
|
768280c9ba | ||
|
|
2e4e687d69 | ||
|
|
d8513a4e86 | ||
|
|
badd667efa | ||
|
|
7908f25747 | ||
|
|
0eef67713f | ||
|
|
6591efc160 | ||
|
|
fcc679489e | ||
|
|
94df58a55b | ||
|
|
0685b2fa35 | ||
|
|
232ea1e50c | ||
|
|
3423db6ed0 | ||
|
|
c32452d6b6 | ||
|
|
018dd4c1c3 | ||
|
|
4fc2c58ae7 | ||
|
|
b4014ebabf | ||
|
|
9955ee6548 | ||
|
|
c08d402e66 | ||
|
|
1c505beba6 | ||
|
|
8a0432efb7 | ||
|
|
320276f8ca | ||
|
|
f89061da41 | ||
|
|
c23d605a7a | ||
|
|
6d90cac3f9 | ||
|
|
89e92bd337 | ||
|
|
9271127c53 | ||
|
|
9fa5942791 | ||
|
|
e028ed878e | ||
|
|
838b2b7d1e | ||
|
|
7c0ad461a5 | ||
|
|
68926dad27 | ||
|
|
ceb6f6c47d | ||
|
|
167e99fce9 | ||
|
|
c930011616 | ||
|
|
aaaca63f83 | ||
|
|
d8a9f663b1 | ||
|
|
b0d0ccf44f | ||
|
|
c57754a29b | ||
|
|
65057c1fb7 | ||
|
|
d8be6490c2 | ||
|
|
b34208d1b6 | ||
|
|
0d5a9e9c8c | ||
|
|
22d4e60028 | ||
|
|
eaa766df77 | ||
|
|
7e5776c66f | ||
|
|
8b1806d4ca | ||
|
|
07232f3694 | ||
|
|
37a33f931a | ||
|
|
4912cbd2da | ||
|
|
4c40819791 | ||
|
|
a65fd497c6 | ||
|
|
d824209485 | ||
|
|
7ae1c7c3d2 | ||
|
|
341c6ae767 | ||
|
|
e6a94ed0cf | ||
|
|
3e6b6c05a6 | ||
|
|
544d4cd3b0 | ||
|
|
e0df2f511e | ||
|
|
255fd0a9cb | ||
|
|
f31adf8a85 | ||
|
|
a2b169626a | ||
|
|
6ffc78bcb0 | ||
|
|
8e9fc550f6 | ||
|
|
779d190855 | ||
|
|
89a4b03d45 | ||
|
|
ccd4cdd71a | ||
|
|
31dbf38a35 | ||
|
|
d0bec97bbb | ||
|
|
22307bba97 | ||
|
|
b4f5d44f65 | ||
|
|
d469870686 | ||
|
|
f561bf5754 | ||
|
|
2e3547d5cf | ||
|
|
ce8897d3e8 | ||
|
|
df77147d65 | ||
|
|
9b11df04b3 | ||
|
|
58c06d5aea | ||
|
|
1d3bb97b07 | ||
|
|
ba3253e2e2 | ||
|
|
e6f0c01aa6 | ||
|
|
9310d59e0a | ||
|
|
f2e1e71302 | ||
|
|
e6e31a9fc6 | ||
|
|
801aaf9323 | ||
|
|
2a8679234a | ||
|
|
54ab671512 | ||
|
|
866dd6b259 | ||
|
|
eba893c99b | ||
|
|
fd3f410cc6 | ||
|
|
03aaf93cef | ||
|
|
9aef57003a | ||
|
|
6065eb0e65 | ||
|
|
7e4634c81f | ||
|
|
a03d73776f | ||
|
|
f14eb4327d | ||
|
|
4ebd721cc5 | ||
|
|
21a92176b9 | ||
|
|
ad04b02e24 | ||
|
|
bc0511fe66 | ||
|
|
1accb9f939 | ||
|
|
9253f16e36 | ||
|
|
42387166bf | ||
|
|
0b5f892193 | ||
|
|
1a0d36a6fd | ||
|
|
cf3ed0dc88 | ||
|
|
8d26d7861e | ||
|
|
8e0ad2ef6e | ||
|
|
0aba4c36af | ||
|
|
44cd199078 | ||
|
|
ce909093c0 | ||
|
|
df13a8fea9 | ||
|
|
ff823c9fdb | ||
|
|
a42ff9865b | ||
|
|
7e13f78567 | ||
|
|
e2fb83db98 | ||
|
|
06eb1b6683 | ||
|
|
d62994ec02 | ||
|
|
f20859c85f | ||
|
|
b5b8adb451 | ||
|
|
70b287490b | ||
|
|
0976e9e569 | ||
|
|
83a96757db | ||
|
|
9013dcfea7 | ||
|
|
4ebc2573a3 | ||
|
|
fe9b03a189 | ||
|
|
d2f6c367f0 | ||
|
|
34b717d00c | ||
|
|
0d31b05f98 | ||
|
|
87a0e40331 | ||
|
|
764c0b2e15 | ||
|
|
23677b4963 | ||
|
|
96d9d41f19 | ||
|
|
a737f35653 | ||
|
|
ed8133be2d | ||
|
|
7c8c6b5333 | ||
|
|
46fceb03a5 | ||
|
|
4dee5eddeb | ||
|
|
709482bdac | ||
|
|
62ef1baace | ||
|
|
1fc3d2e914 | ||
|
|
d271a8c9fa | ||
|
|
3bd7b3b0f8 | ||
|
|
8075cda34c | ||
|
|
09d6da117a | ||
|
|
8f6b679c85 | ||
|
|
32e017bd03 | ||
|
|
74a31224e0 | ||
|
|
667b27fe78 | ||
|
|
4c8a4013cc | ||
|
|
5e4d73b6a3 | ||
|
|
da486d7788 | ||
|
|
30d97e2fa8 | ||
|
|
3a95114c3a | ||
|
|
1f3ad85403 | ||
|
|
90cb02e0bf | ||
|
|
6e2bd828a1 | ||
|
|
fbbf5046ac | ||
|
|
47abb6f85f | ||
|
|
717698b659 | ||
|
|
6a29a0898a | ||
|
|
1833872be9 | ||
|
|
4d06c812e6 | ||
|
|
3b71d2a37b | ||
|
|
0c0cacb0d6 | ||
|
|
f57fff732e | ||
|
|
54ddeaf046 | ||
|
|
69a1a02c70 | ||
|
|
c824f0d590 | ||
|
|
c336c989e7 | ||
|
|
f6523ab5a0 | ||
|
|
47c783da37 | ||
|
|
74afc7b424 | ||
|
|
4ac5a1e15a | ||
|
|
48eeeea7f3 | ||
|
|
aa6857fd38 | ||
|
|
25fe2a2ce6 | ||
|
|
3d1e3741cd | ||
|
|
2ef57e0221 | ||
|
|
bc08c02b89 | ||
|
|
50c74a2ec8 | ||
|
|
887469d73e | ||
|
|
f9debb8f94 | ||
|
|
b3929d1177 | ||
|
|
e3cfdb74ba | ||
|
|
1d0e752989 | ||
|
|
05a3bb0622 | ||
|
|
bc7fd26af6 | ||
|
|
048d4dbd95 | ||
|
|
c70e5357d3 | ||
|
|
7576ba2ade | ||
|
|
877e630a90 | ||
|
|
ef854aabb7 | ||
|
|
fc3f19bd2b | ||
|
|
2bbcd2d663 | ||
|
|
a786118415 | ||
|
|
65429e581a | ||
|
|
eb6f4dca55 | ||
|
|
ce09c4b3cd | ||
|
|
c971e9d61c | ||
|
|
e34bf90ca7 | ||
|
|
700296a558 | ||
|
|
492ea0616e | ||
|
|
eddb6e1faf | ||
|
|
f98b274177 | ||
|
|
662ff41fe9 | ||
|
|
fd146dde30 | ||
|
|
e394d0a6f6 | ||
|
|
5a1a47b7aa | ||
|
|
3d5c32c354 | ||
|
|
01cc0ac8f1 | ||
|
|
5a9248e619 | ||
|
|
1d84d03566 | ||
|
|
50ba4f9759 | ||
|
|
de55af6ae6 | ||
|
|
ca478ac880 | ||
|
|
78ea643460 | ||
|
|
0db0f81e53 | ||
|
|
c94680eaba | ||
|
|
5b4ed6dd59 | ||
|
|
4e811c744a | ||
|
|
cd6d2299a9 | ||
|
|
590199baff | ||
|
|
3b9dd3ba8c | ||
|
|
446021cf22 | ||
|
|
ef3ab29649 | ||
|
|
f4e09eee80 | ||
|
|
af4e4b4064 | ||
|
|
10c6297706 | ||
|
|
73a9541e39 | ||
|
|
3a2a61af82 | ||
|
|
774e7fb248 | ||
|
|
a5e3d9558f | ||
|
|
1ae86ae752 | ||
|
|
1a30a0e397 | ||
|
|
490b76b141 | ||
|
|
3831efb3be | ||
|
|
a8fa816165 | ||
|
|
11ccfd8449 | ||
|
|
c33cc82d53 | ||
|
|
c7516ec50e | ||
|
|
92cc597e84 | ||
|
|
7402ac29a8 | ||
|
|
5c3fe51982 | ||
|
|
f61af39f08 | ||
|
|
1ad7e663a1 | ||
|
|
ca85020b26 | ||
|
|
5d2912605f |
21
CHANGELOG.md
21
CHANGELOG.md
@@ -2,6 +2,27 @@
|
||||
|
||||
This is a list of high-level changes for each release of AWX. A full list of commits can be found at `https://github.com/ansible/awx/releases/tag/<version>`.
|
||||
|
||||
## 9.3.0 (Mar 12, 2020)
|
||||
- Added the ability to specify an OAuth2 token description in the AWX CLI (https://github.com/ansible/awx/issues/6122)
|
||||
- Added support for K8S service account annotations to the installer (https://github.com/ansible/awx/pull/6007)
|
||||
- Added support for K8S imagePullSecrets to the installer (https://github.com/ansible/awx/pull/5989)
|
||||
- Launching jobs (and workflows) using the --monitor flag in the AWX CLI now returns a non-zero exit code on job failure (https://github.com/ansible/awx/issues/5920)
|
||||
- Improved UI performance for various job views when many simultaneous users are logged into AWX (https://github.com/ansible/awx/issues/5883)
|
||||
- Updated to the latest version of Django to address a few open CVEs (https://github.com/ansible/awx/pull/6080)
|
||||
- Fixed a critical bug which can cause AWX to hang and stop launching playbooks after a periodic of time (https://github.com/ansible/awx/issues/5617)
|
||||
- Fixed a bug which caused delays in project update stdout for certain large SCM clones (as of Ansible 2.9+) (https://github.com/ansible/awx/pull/6254)
|
||||
- Fixed a bug which caused certain smart inventory filters to mistakenly return duplicate hosts (https://github.com/ansible/awx/pull/5972)
|
||||
- Fixed an unclear server error when creating smart inventories with the AWX collection (https://github.com/ansible/awx/issues/6250)
|
||||
- Fixed a bug that broke Grafana notification support (https://github.com/ansible/awx/issues/6137)
|
||||
- Fixed a UI bug which prevent users with read access to an organization from editing credentials for that organization (https://github.com/ansible/awx/pull/6241)
|
||||
- Fixed a bug which prevent workflow approval records from recording a `started` and `elapsed` date (https://github.com/ansible/awx/issues/6202)
|
||||
- Fixed a bug which caused workflow nodes to have a confusing option for `verbosity` (https://github.com/ansible/awx/issues/6196)
|
||||
- Fixed an RBAC bug which prevented projects and inventory schedules from being created by certain users in certain contexts (https://github.com/ansible/awx/issues/5717)
|
||||
- Fixed a bug that caused `role_path` in a project's config to not be respected due to an error processing `/etc/ansible/ansible.cfg` (https://github.com/ansible/awx/pull/6038)
|
||||
- Fixed a bug that broke inventory updates for installs with custom home directories for the awx user (https://github.com/ansible/awx/pull/6152)
|
||||
- Fixed a bug that broke fact data collection when AWX encounters invalid/unexpected fact data (https://github.com/ansible/awx/issues/5935)
|
||||
|
||||
|
||||
## 9.2.0 (Feb 12, 2020)
|
||||
- Added the ability to configure the convergence behavior of workflow nodes https://github.com/ansible/awx/issues/3054
|
||||
- AWX now allows for a configurable global limit for fork count (per-job run). The default maximum is 200. https://github.com/ansible/awx/pull/5604
|
||||
|
||||
@@ -506,10 +506,6 @@ If you wish to tag and push built images to a Docker registry, set the following
|
||||
|
||||
> Username of the user that will push images to the registry. Defaults to *developer*.
|
||||
|
||||
*docker_remove_local_images*
|
||||
|
||||
> Due to the way that the docker_image module behaves, images will not be pushed to a remote repository if they are present locally. Set this to delete local versions of the images that will be pushed to the remote. This will fail if containers are currently running from those images.
|
||||
|
||||
**Note**
|
||||
|
||||
> These settings are ignored if using official images
|
||||
|
||||
26
Makefile
26
Makefile
@@ -167,8 +167,7 @@ virtualenv_awx:
|
||||
fi; \
|
||||
if [ ! -d "$(VENV_BASE)/awx" ]; then \
|
||||
virtualenv -p $(PYTHON) $(VENV_BASE)/awx; \
|
||||
$(VENV_BASE)/awx/bin/pip install $(PIP_OPTIONS) $(VENV_BOOTSTRAP) && \
|
||||
$(VENV_BASE)/awx/bin/pip install $(PIP_OPTIONS) flit; \
|
||||
$(VENV_BASE)/awx/bin/pip install $(PIP_OPTIONS) $(VENV_BOOTSTRAP); \
|
||||
fi; \
|
||||
fi
|
||||
|
||||
@@ -403,6 +402,7 @@ prepare_collection_venv:
|
||||
COLLECTION_TEST_DIRS ?= awx_collection/test/awx
|
||||
COLLECTION_PACKAGE ?= awx
|
||||
COLLECTION_NAMESPACE ?= awx
|
||||
COLLECTION_INSTALL = ~/.ansible/collections/ansible_collections/$(COLLECTION_NAMESPACE)/$(COLLECTION_PACKAGE)
|
||||
|
||||
test_collection:
|
||||
@if [ "$(VENV_BASE)" ]; then \
|
||||
@@ -415,20 +415,26 @@ flake8_collection:
|
||||
|
||||
test_collection_all: prepare_collection_venv test_collection flake8_collection
|
||||
|
||||
test_collection_sanity:
|
||||
rm -rf sanity
|
||||
mkdir -p sanity/ansible_collections/awx
|
||||
cp -Ra awx_collection sanity/ansible_collections/awx/awx # symlinks do not work
|
||||
cd sanity/ansible_collections/awx/awx && git init && git add . # requires both this file structure and a git repo, so there you go
|
||||
cd sanity/ansible_collections/awx/awx && ansible-test sanity
|
||||
# WARNING: symlinking a collection is fundamentally unstable
|
||||
# this is for rapid development iteration with playbooks, do not use with other test targets
|
||||
symlink_collection:
|
||||
rm -rf $(COLLECTION_INSTALL)
|
||||
mkdir -p ~/.ansible/collections/ansible_collections/$(COLLECTION_NAMESPACE) # in case it does not exist
|
||||
ln -s $(shell pwd)/awx_collection $(COLLECTION_INSTALL)
|
||||
|
||||
build_collection:
|
||||
ansible-playbook -i localhost, awx_collection/template_galaxy.yml -e collection_package=$(COLLECTION_PACKAGE) -e collection_namespace=$(COLLECTION_NAMESPACE) -e collection_version=$(VERSION)
|
||||
ansible-galaxy collection build awx_collection --force --output-path=awx_collection
|
||||
|
||||
install_collection: build_collection
|
||||
rm -rf ~/.ansible/collections/ansible_collections/awx/awx
|
||||
ansible-galaxy collection install awx_collection/awx-awx-$(VERSION).tar.gz
|
||||
rm -rf $(COLLECTION_INSTALL)
|
||||
ansible-galaxy collection install awx_collection/$(COLLECTION_NAMESPACE)-$(COLLECTION_PACKAGE)-$(VERSION).tar.gz
|
||||
|
||||
test_collection_sanity: install_collection
|
||||
cd $(COLLECTION_INSTALL) && ansible-test sanity
|
||||
|
||||
test_collection_integration: install_collection
|
||||
cd $(COLLECTION_INSTALL) && ansible-test integration
|
||||
|
||||
test_unit:
|
||||
@if [ "$(VENV_BASE)" ]; then \
|
||||
|
||||
@@ -192,7 +192,7 @@ class APIView(views.APIView):
|
||||
response.data['detail'] += ' To establish a login session, visit /api/login/.'
|
||||
logger.info(status_msg)
|
||||
else:
|
||||
logger.warn(status_msg)
|
||||
logger.warning(status_msg)
|
||||
response = super(APIView, self).finalize_response(request, response, *args, **kwargs)
|
||||
time_started = getattr(self, 'time_started', None)
|
||||
response['X-API-Node'] = settings.CLUSTER_HOST_ID
|
||||
|
||||
@@ -20,6 +20,7 @@ from rest_framework.fields import JSONField as DRFJSONField
|
||||
from rest_framework.request import clone_request
|
||||
|
||||
# AWX
|
||||
from awx.api.fields import ChoiceNullField
|
||||
from awx.main.fields import JSONField, ImplicitRoleField
|
||||
from awx.main.models import InventorySource, NotificationTemplate
|
||||
from awx.main.scheduler.kubernetes import PodManager
|
||||
@@ -96,7 +97,15 @@ class Metadata(metadata.SimpleMetadata):
|
||||
field_info['children'] = self.get_serializer_info(field)
|
||||
|
||||
if not isinstance(field, (RelatedField, ManyRelatedField)) and hasattr(field, 'choices'):
|
||||
field_info['choices'] = [(choice_value, choice_name) for choice_value, choice_name in field.choices.items()]
|
||||
choices = [
|
||||
(choice_value, choice_name) for choice_value, choice_name in field.choices.items()
|
||||
]
|
||||
if not any(choice in ('', None) for choice, _ in choices):
|
||||
if field.allow_blank:
|
||||
choices = [("", "---------")] + choices
|
||||
if field.allow_null and not isinstance(field, ChoiceNullField):
|
||||
choices = [(None, "---------")] + choices
|
||||
field_info['choices'] = choices
|
||||
|
||||
# Indicate if a field is write-only.
|
||||
if getattr(field, 'write_only', False):
|
||||
|
||||
@@ -1600,7 +1600,7 @@ class InventorySerializer(BaseSerializerWithVariables):
|
||||
})
|
||||
SmartFilter().query_from_string(host_filter)
|
||||
except RuntimeError as e:
|
||||
raise models.base.ValidationError(e)
|
||||
raise models.base.ValidationError(str(e))
|
||||
return host_filter
|
||||
|
||||
def validate(self, attrs):
|
||||
@@ -2115,7 +2115,13 @@ class InventorySourceSerializer(UnifiedJobTemplateSerializer, InventorySourceOpt
|
||||
def get_field_from_model_or_attrs(fd):
|
||||
return attrs.get(fd, self.instance and getattr(self.instance, fd) or None)
|
||||
|
||||
if get_field_from_model_or_attrs('source') != 'scm':
|
||||
if get_field_from_model_or_attrs('source') == 'scm':
|
||||
if (('source' in attrs or 'source_project' in attrs) and
|
||||
get_field_from_model_or_attrs('source_project') is None):
|
||||
raise serializers.ValidationError(
|
||||
{"source_project": _("Project required for scm type sources.")}
|
||||
)
|
||||
else:
|
||||
redundant_scm_fields = list(filter(
|
||||
lambda x: attrs.get(x, None),
|
||||
['source_project', 'source_path', 'update_on_project_update']
|
||||
@@ -3716,7 +3722,7 @@ class WorkflowJobNodeSerializer(LaunchConfigurationBaseSerializer):
|
||||
class Meta:
|
||||
model = WorkflowJobNode
|
||||
fields = ('*', 'job', 'workflow_job', '-name', '-description', 'id', 'url', 'related',
|
||||
'unified_job_template', 'success_nodes', 'failure_nodes', 'always_nodes',
|
||||
'unified_job_template', 'success_nodes', 'failure_nodes', 'always_nodes',
|
||||
'all_parents_must_converge', 'do_not_run',)
|
||||
|
||||
def get_related(self, obj):
|
||||
@@ -4048,6 +4054,13 @@ class JobLaunchSerializer(BaseSerializer):
|
||||
**attrs)
|
||||
self._ignored_fields = rejected
|
||||
|
||||
# Basic validation - cannot run a playbook without a playbook
|
||||
if not template.project:
|
||||
errors['project'] = _("A project is required to run a job.")
|
||||
elif template.project.status in ('error', 'failed'):
|
||||
errors['playbook'] = _("Missing a revision to run due to failed project update.")
|
||||
|
||||
# cannot run a playbook without an inventory
|
||||
if template.inventory and template.inventory.pending_deletion is True:
|
||||
errors['inventory'] = _("The inventory associated with this Job Template is being deleted.")
|
||||
elif 'inventory' in accepted and accepted['inventory'].pending_deletion:
|
||||
|
||||
@@ -4303,7 +4303,7 @@ class NotificationTemplateTest(GenericAPIView):
|
||||
msg = "Tower Notification Test {} {}".format(obj.id, settings.TOWER_URL_BASE)
|
||||
if obj.notification_type in ('email', 'pagerduty'):
|
||||
body = "Ansible Tower Test Notification {} {}".format(obj.id, settings.TOWER_URL_BASE)
|
||||
elif obj.notification_type == 'webhook':
|
||||
elif obj.notification_type in ('webhook', 'grafana'):
|
||||
body = '{{"body": "Ansible Tower Test Notification {} {}"}}'.format(obj.id, settings.TOWER_URL_BASE)
|
||||
else:
|
||||
body = {"body": "Ansible Tower Test Notification {} {}".format(obj.id, settings.TOWER_URL_BASE)}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
6083
awx/locale/zh/LC_MESSAGES/django.po
Normal file
6083
awx/locale/zh/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2429,6 +2429,9 @@ class ScheduleAccess(BaseAccess):
|
||||
def can_add(self, data):
|
||||
if not JobLaunchConfigAccess(self.user).can_add(data):
|
||||
return False
|
||||
if not data:
|
||||
return Role.objects.filter(role_field__in=['update_role', 'execute_role'], ancestors__in=self.user.roles.all()).exists()
|
||||
|
||||
return self.check_related('unified_job_template', UnifiedJobTemplate, data, role_field='execute_role', mandatory=True)
|
||||
|
||||
@check_superuser
|
||||
|
||||
@@ -31,7 +31,7 @@ data _since_ the last report date - i.e., new data in the last 24 hours)
|
||||
'''
|
||||
|
||||
|
||||
@register('config', '1.0')
|
||||
@register('config', '1.1')
|
||||
def config(since):
|
||||
license_info = get_license(show_key=False)
|
||||
install_type = 'traditional'
|
||||
@@ -53,6 +53,7 @@ def config(since):
|
||||
'ansible_version': get_ansible_version(),
|
||||
'license_type': license_info.get('license_type', 'UNLICENSED'),
|
||||
'free_instances': license_info.get('free_instances', 0),
|
||||
'total_licensed_instances': license_info.get('instance_count', 0),
|
||||
'license_expiry': license_info.get('time_remaining', 0),
|
||||
'pendo_tracking': settings.PENDO_TRACKING_STATE,
|
||||
'authentication_backends': settings.AUTHENTICATION_BACKENDS,
|
||||
|
||||
@@ -43,7 +43,7 @@ aim_inputs = {
|
||||
'id': 'object_query',
|
||||
'label': _('Object Query'),
|
||||
'type': 'string',
|
||||
'help_text': _('Lookup query for the object. Ex: "Safe=TestSafe;Object=testAccountName123"'),
|
||||
'help_text': _('Lookup query for the object. Ex: Safe=TestSafe;Object=testAccountName123'),
|
||||
}, {
|
||||
'id': 'object_query_format',
|
||||
'label': _('Object Query Format'),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import logging
|
||||
import threading
|
||||
import os
|
||||
import time
|
||||
from multiprocessing import Process
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import connections
|
||||
@@ -14,33 +15,36 @@ logger = logging.getLogger('awx.main.dispatch.periodic')
|
||||
class Scheduler(Scheduler):
|
||||
|
||||
def run_continuously(self):
|
||||
cease_continuous_run = threading.Event()
|
||||
idle_seconds = max(
|
||||
1,
|
||||
min(self.jobs).period.total_seconds() / 2
|
||||
)
|
||||
|
||||
class ScheduleThread(threading.Thread):
|
||||
@classmethod
|
||||
def run(cls):
|
||||
while not cease_continuous_run.is_set():
|
||||
try:
|
||||
for conn in connections.all():
|
||||
# If the database connection has a hiccup, re-establish a new
|
||||
# connection
|
||||
conn.close_if_unusable_or_obsolete()
|
||||
self.run_pending()
|
||||
except Exception:
|
||||
logger.exception(
|
||||
'encountered an error while scheduling periodic tasks'
|
||||
)
|
||||
time.sleep(idle_seconds)
|
||||
logger.debug('periodic thread exiting...')
|
||||
def run():
|
||||
ppid = os.getppid()
|
||||
logger.warn(f'periodic beat started')
|
||||
while True:
|
||||
if os.getppid() != ppid:
|
||||
# if the parent PID changes, this process has been orphaned
|
||||
# via e.g., segfault or sigkill, we should exit too
|
||||
pid = os.getpid()
|
||||
logger.warn(f'periodic beat exiting gracefully pid:{pid}')
|
||||
raise SystemExit()
|
||||
try:
|
||||
for conn in connections.all():
|
||||
# If the database connection has a hiccup, re-establish a new
|
||||
# connection
|
||||
conn.close_if_unusable_or_obsolete()
|
||||
self.run_pending()
|
||||
except Exception:
|
||||
logger.exception(
|
||||
'encountered an error while scheduling periodic tasks'
|
||||
)
|
||||
time.sleep(idle_seconds)
|
||||
|
||||
thread = ScheduleThread()
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
return cease_continuous_run
|
||||
process = Process(target=run)
|
||||
process.daemon = True
|
||||
process.start()
|
||||
|
||||
|
||||
def run_continuously():
|
||||
@@ -49,4 +53,4 @@ def run_continuously():
|
||||
apply_async = TaskWorker.resolve_callable(task['task']).apply_async
|
||||
total_seconds = task['schedule'].total_seconds()
|
||||
scheduler.every(total_seconds).seconds.do(apply_async)
|
||||
return scheduler.run_continuously()
|
||||
scheduler.run_continuously()
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import signal
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
from uuid import uuid4
|
||||
|
||||
@@ -244,7 +246,7 @@ class WorkerPool(object):
|
||||
' qsize={{ w.managed_tasks|length }}'
|
||||
' rss={{ w.mb }}MB'
|
||||
'{% for task in w.managed_tasks.values() %}'
|
||||
'\n - {% if loop.index0 == 0 %}running {% else %}queued {% endif %}'
|
||||
'\n - {% if loop.index0 == 0 %}running {% if "age" in task %}for: {{ "%.1f" % task["age"] }}s {% endif %}{% else %}queued {% endif %}'
|
||||
'{{ task["uuid"] }} '
|
||||
'{% if "task" in task %}'
|
||||
'{{ task["task"].rsplit(".", 1)[-1] }}'
|
||||
@@ -365,6 +367,26 @@ class AutoscalePool(WorkerPool):
|
||||
logger.warn('scaling down worker pid:{}'.format(w.pid))
|
||||
w.quit()
|
||||
self.workers.remove(w)
|
||||
if w.alive:
|
||||
# if we discover a task manager invocation that's been running
|
||||
# too long, reap it (because otherwise it'll just hold the postgres
|
||||
# advisory lock forever); the goal of this code is to discover
|
||||
# deadlocks or other serious issues in the task manager that cause
|
||||
# the task manager to never do more work
|
||||
current_task = w.current_task
|
||||
if current_task and isinstance(current_task, dict):
|
||||
if current_task.get('task', '').endswith('tasks.run_task_manager'):
|
||||
if 'started' not in current_task:
|
||||
w.managed_tasks[
|
||||
current_task['uuid']
|
||||
]['started'] = time.time()
|
||||
age = time.time() - current_task['started']
|
||||
w.managed_tasks[current_task['uuid']]['age'] = age
|
||||
if age > (60 * 5):
|
||||
logger.error(
|
||||
f'run_task_manager has held the advisory lock for >5m, sending SIGTERM to {w.pid}'
|
||||
) # noqa
|
||||
os.kill(w.pid, signal.SIGTERM)
|
||||
|
||||
for m in orphaned:
|
||||
# if all the workers are dead, spawn at least one
|
||||
|
||||
@@ -1006,12 +1006,6 @@ class Command(BaseCommand):
|
||||
except re.error:
|
||||
raise CommandError('invalid regular expression for --host-filter')
|
||||
|
||||
'''
|
||||
TODO: Remove this deprecation when we remove support for rax.py
|
||||
'''
|
||||
if self.source == "rax.py":
|
||||
logger.info("Rackspace inventory sync is Deprecated in Tower 3.1.0 and support for Rackspace will be removed in a future release.")
|
||||
|
||||
begin = time.time()
|
||||
self.load_inventory_from_database()
|
||||
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
# Copyright (c) 2015 Ansible, Inc.
|
||||
# All Rights Reserved.
|
||||
import os
|
||||
import logging
|
||||
from multiprocessing import Process
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache as django_cache
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import connection as django_connection, connections
|
||||
from django.db import connection as django_connection
|
||||
from kombu import Exchange, Queue
|
||||
|
||||
from awx.main.utils.handlers import AWXProxyHandler
|
||||
@@ -55,7 +53,7 @@ class Command(BaseCommand):
|
||||
|
||||
# spawn a daemon thread to periodically enqueues scheduled tasks
|
||||
# (like the node heartbeat)
|
||||
cease_continuous_run = periodic.run_continuously()
|
||||
periodic.run_continuously()
|
||||
|
||||
reaper.reap()
|
||||
consumer = None
|
||||
@@ -89,7 +87,6 @@ class Command(BaseCommand):
|
||||
)
|
||||
consumer.run()
|
||||
except KeyboardInterrupt:
|
||||
cease_continuous_run.set()
|
||||
logger.debug('Terminating Task Dispatcher')
|
||||
if consumer:
|
||||
consumer.stop()
|
||||
|
||||
@@ -78,8 +78,7 @@ class HostManager(models.Manager):
|
||||
self.core_filters = {}
|
||||
|
||||
qs = qs & q
|
||||
unique_by_name = qs.order_by('name', 'pk').distinct('name')
|
||||
return qs.filter(pk__in=unique_by_name)
|
||||
return qs.order_by('name', 'pk').distinct('name')
|
||||
return qs
|
||||
|
||||
|
||||
|
||||
@@ -829,8 +829,10 @@ class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin, TaskMana
|
||||
continue
|
||||
host.ansible_facts = ansible_facts
|
||||
host.ansible_facts_modified = now()
|
||||
ansible_local_system_id = ansible_facts.get('ansible_local', {}).get('insights', {}).get('system_id', None)
|
||||
ansible_facts_system_id = ansible_facts.get('insights', {}).get('system_id', None)
|
||||
ansible_local = ansible_facts.get('ansible_local', {}).get('insights', {})
|
||||
ansible_facts = ansible_facts.get('insights', {})
|
||||
ansible_local_system_id = ansible_local.get('system_id', None) if isinstance(ansible_local, dict) else None
|
||||
ansible_facts_system_id = ansible_facts.get('system_id', None) if isinstance(ansible_facts, dict) else None
|
||||
if ansible_local_system_id:
|
||||
print("Setting local {}".format(ansible_local_system_id))
|
||||
logger.debug("Insights system_id {} found for host <{}, {}> in"
|
||||
|
||||
@@ -270,21 +270,19 @@ class JobNotificationMixin(object):
|
||||
'elapsed', 'job_explanation', 'execution_node', 'controller_node', 'allow_simultaneous',
|
||||
'scm_revision', 'diff_mode', 'job_slice_number', 'job_slice_count', 'custom_virtualenv',
|
||||
'approval_status', 'approval_node_name', 'workflow_url',
|
||||
{'host_status_counts': ['skipped', 'ok', 'changed', 'failures', 'dark']},
|
||||
{'playbook_counts': ['play_count', 'task_count']},
|
||||
{'host_status_counts': ['skipped', 'ok', 'changed', 'failed', 'failures', 'dark'
|
||||
'processed', 'rescued', 'ignored']},
|
||||
{'summary_fields': [{'inventory': ['id', 'name', 'description', 'has_active_failures',
|
||||
'total_hosts', 'hosts_with_active_failures', 'total_groups',
|
||||
'has_inventory_sources',
|
||||
'total_inventory_sources', 'inventory_sources_with_failures',
|
||||
'organization_id', 'kind']},
|
||||
{'project': ['id', 'name', 'description', 'status', 'scm_type']},
|
||||
{'project_update': ['id', 'name', 'description', 'status', 'failed']},
|
||||
{'job_template': ['id', 'name', 'description']},
|
||||
{'unified_job_template': ['id', 'name', 'description', 'unified_job_type']},
|
||||
{'instance_group': ['name', 'id']},
|
||||
{'created_by': ['id', 'username', 'first_name', 'last_name']},
|
||||
{'labels': ['count', 'results']},
|
||||
{'source_workflow_job': ['description', 'elapsed', 'failed', 'id', 'name', 'status']}]}]
|
||||
{'labels': ['count', 'results']}]}]
|
||||
|
||||
@classmethod
|
||||
def context_stub(cls):
|
||||
@@ -303,7 +301,7 @@ class JobNotificationMixin(object):
|
||||
'finished': False,
|
||||
'force_handlers': False,
|
||||
'forks': 0,
|
||||
'host_status_counts': {'skipped': 1, 'ok': 5, 'changed': 3, 'failures': 0, 'dark': 0},
|
||||
'host_status_counts': {'skipped': 1, 'ok': 5, 'changed': 3, 'failures': 0, 'dark': 0, 'failed': False, 'processed': 0, 'rescued': 0},
|
||||
'id': 42,
|
||||
'job_explanation': 'Sample job explanation',
|
||||
'job_slice_count': 1,
|
||||
@@ -314,7 +312,6 @@ class JobNotificationMixin(object):
|
||||
'limit': 'bar_limit',
|
||||
'modified': datetime.datetime(2018, 12, 13, 6, 4, 0, 0, tzinfo=datetime.timezone.utc),
|
||||
'name': 'Stub JobTemplate',
|
||||
'playbook_counts': {'play_count': 5, 'task_count': 10},
|
||||
'playbook': 'ping.yml',
|
||||
'scm_revision': '',
|
||||
'skip_tags': '',
|
||||
@@ -347,18 +344,10 @@ class JobNotificationMixin(object):
|
||||
'name': 'Stub project',
|
||||
'scm_type': 'git',
|
||||
'status': 'successful'},
|
||||
'project_update': {'id': 5, 'name': 'Stub Project Update', 'description': 'Project Update',
|
||||
'status': 'running', 'failed': False},
|
||||
'unified_job_template': {'description': 'Sample unified job template description',
|
||||
'id': 39,
|
||||
'name': 'Stub Job Template',
|
||||
'unified_job_type': 'job'},
|
||||
'source_workflow_job': {'description': 'Sample workflow job description',
|
||||
'elapsed': 0.000,
|
||||
'failed': False,
|
||||
'id': 88,
|
||||
'name': 'Stub WorkflowJobTemplate',
|
||||
'status': 'running'}},
|
||||
'unified_job_type': 'job'}},
|
||||
'timeout': 0,
|
||||
'type': 'job',
|
||||
'url': '/api/v2/jobs/13/',
|
||||
@@ -392,10 +381,20 @@ class JobNotificationMixin(object):
|
||||
The context will contain whitelisted content retrieved from a serialized job object
|
||||
(see JobNotificationMixin.JOB_FIELDS_WHITELIST), the job's friendly name,
|
||||
and a url to the job run."""
|
||||
context = {'job': {},
|
||||
'job_friendly_name': self.get_notification_friendly_name(),
|
||||
'url': self.get_ui_url(),
|
||||
'job_metadata': json.dumps(self.notification_data(), indent=4)}
|
||||
job_context = {'host_status_counts': {}}
|
||||
summary = None
|
||||
if hasattr(self, 'job_host_summaries'):
|
||||
summary = self.job_host_summaries.first()
|
||||
if summary:
|
||||
from awx.api.serializers import JobHostSummarySerializer
|
||||
summary_data = JobHostSummarySerializer(summary).to_representation(summary)
|
||||
job_context['host_status_counts'] = summary_data
|
||||
context = {
|
||||
'job': job_context,
|
||||
'job_friendly_name': self.get_notification_friendly_name(),
|
||||
'url': self.get_ui_url(),
|
||||
'job_metadata': json.dumps(self.notification_data(), indent=4)
|
||||
}
|
||||
|
||||
def build_context(node, fields, whitelisted_fields):
|
||||
for safe_field in whitelisted_fields:
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
# Python
|
||||
from io import StringIO
|
||||
import datetime
|
||||
import codecs
|
||||
import json
|
||||
import logging
|
||||
@@ -1218,12 +1219,17 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
|
||||
status_data['instance_group_name'] = self.instance_group.name
|
||||
else:
|
||||
status_data['instance_group_name'] = None
|
||||
elif status in ['successful', 'failed', 'canceled'] and self.finished:
|
||||
status_data['finished'] = datetime.datetime.strftime(self.finished, "%Y-%m-%dT%H:%M:%S.%fZ")
|
||||
status_data.update(self.websocket_emit_data())
|
||||
status_data['group_name'] = 'jobs'
|
||||
if getattr(self, 'unified_job_template_id', None):
|
||||
status_data['unified_job_template_id'] = self.unified_job_template_id
|
||||
emit_channel_notification('jobs-status_changed', status_data)
|
||||
|
||||
if self.spawned_by_workflow:
|
||||
status_data['group_name'] = "workflow_events"
|
||||
status_data['workflow_job_template_id'] = self.unified_job_template.id
|
||||
emit_channel_notification('workflow_events-' + str(self.workflow_job_id), status_data)
|
||||
except IOError: # includes socket errors
|
||||
logger.exception('%s failed to emit channel msg about status change', self.log_format)
|
||||
|
||||
@@ -750,6 +750,8 @@ class WorkflowApproval(UnifiedJob, JobNotificationMixin):
|
||||
def signal_start(self, **kwargs):
|
||||
can_start = super(WorkflowApproval, self).signal_start(**kwargs)
|
||||
self.send_approval_notification('running')
|
||||
self.started = self.created
|
||||
self.save(update_fields=['started'])
|
||||
return can_start
|
||||
|
||||
def send_approval_notification(self, approval_status):
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# All Rights Reserved.
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import requests
|
||||
import dateutil.parser as dp
|
||||
@@ -23,6 +24,33 @@ class GrafanaBackend(AWXBaseEmailBackend, CustomNotificationBase):
|
||||
recipient_parameter = "grafana_url"
|
||||
sender_parameter = None
|
||||
|
||||
DEFAULT_BODY = "{{ job_metadata }}"
|
||||
default_messages = {
|
||||
"started": {
|
||||
"body": DEFAULT_BODY, "message": CustomNotificationBase.DEFAULT_MSG
|
||||
},
|
||||
"success": {
|
||||
"body": DEFAULT_BODY, "message": CustomNotificationBase.DEFAULT_MSG
|
||||
},
|
||||
"error": {
|
||||
"body": DEFAULT_BODY, "message": CustomNotificationBase.DEFAULT_MSG
|
||||
},
|
||||
"workflow_approval": {
|
||||
"running": {
|
||||
"message": CustomNotificationBase.DEFAULT_APPROVAL_RUNNING_MSG, "body": None
|
||||
},
|
||||
"approved": {
|
||||
"message": CustomNotificationBase.DEFAULT_APPROVAL_APPROVED_MSG, "body": None
|
||||
},
|
||||
"timed_out": {
|
||||
"message": CustomNotificationBase.DEFAULT_APPROVAL_TIMEOUT_MSG, "body": None
|
||||
},
|
||||
"denied": {
|
||||
"message": CustomNotificationBase.DEFAULT_APPROVAL_DENIED_MSG, "body": None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def __init__(self, grafana_key,dashboardId=None, panelId=None, annotation_tags=None, grafana_no_verify_ssl=False, isRegion=True,
|
||||
fail_silently=False, **kwargs):
|
||||
super(GrafanaBackend, self).__init__(fail_silently=fail_silently)
|
||||
@@ -34,6 +62,13 @@ class GrafanaBackend(AWXBaseEmailBackend, CustomNotificationBase):
|
||||
self.isRegion = isRegion
|
||||
|
||||
def format_body(self, body):
|
||||
# expect body to be a string representing a dict
|
||||
try:
|
||||
potential_body = json.loads(body)
|
||||
if isinstance(potential_body, dict):
|
||||
body = potential_body
|
||||
except json.JSONDecodeError:
|
||||
body = {}
|
||||
return body
|
||||
|
||||
def send_messages(self, messages):
|
||||
@@ -41,14 +76,16 @@ class GrafanaBackend(AWXBaseEmailBackend, CustomNotificationBase):
|
||||
for m in messages:
|
||||
grafana_data = {}
|
||||
grafana_headers = {}
|
||||
try:
|
||||
epoch=datetime.datetime.utcfromtimestamp(0)
|
||||
grafana_data['time'] = int((dp.parse(m.body['started']).replace(tzinfo=None) - epoch).total_seconds() * 1000)
|
||||
grafana_data['timeEnd'] = int((dp.parse(m.body['finished']).replace(tzinfo=None) - epoch).total_seconds() * 1000)
|
||||
except ValueError:
|
||||
logger.error(smart_text(_("Error converting time {} or timeEnd {} to int.").format(m.body['started'],m.body['finished'])))
|
||||
if not self.fail_silently:
|
||||
raise Exception(smart_text(_("Error converting time {} and/or timeEnd {} to int.").format(m.body['started'],m.body['finished'])))
|
||||
if 'started' in m.body:
|
||||
try:
|
||||
epoch=datetime.datetime.utcfromtimestamp(0)
|
||||
grafana_data['time'] = grafana_data['timeEnd'] = int((dp.parse(m.body['started']).replace(tzinfo=None) - epoch).total_seconds() * 1000)
|
||||
if m.body.get('finished'):
|
||||
grafana_data['timeEnd'] = int((dp.parse(m.body['finished']).replace(tzinfo=None) - epoch).total_seconds() * 1000)
|
||||
except ValueError:
|
||||
logger.error(smart_text(_("Error converting time {} or timeEnd {} to int.").format(m.body['started'],m.body['finished'])))
|
||||
if not self.fail_silently:
|
||||
raise Exception(smart_text(_("Error converting time {} and/or timeEnd {} to int.").format(m.body['started'],m.body['finished'])))
|
||||
grafana_data['isRegion'] = self.isRegion
|
||||
grafana_data['dashboardId'] = self.dashboardId
|
||||
grafana_data['panelId'] = self.panelId
|
||||
|
||||
@@ -8,7 +8,7 @@ REPLACE_STR = '$encrypted$'
|
||||
|
||||
class UriCleaner(object):
|
||||
REPLACE_STR = REPLACE_STR
|
||||
SENSITIVE_URI_PATTERN = re.compile(r'(\w+:(\/?\/?)[^\s]+)', re.MULTILINE) # NOQA
|
||||
SENSITIVE_URI_PATTERN = re.compile(r'(\w{1,20}:(\/?\/?)[^\s]+)', re.MULTILINE) # NOQA
|
||||
|
||||
@staticmethod
|
||||
def remove_sensitive(cleartext):
|
||||
|
||||
@@ -83,8 +83,8 @@ class WorkflowDAG(SimpleDAG):
|
||||
elif p.job and p.job.status == "failed":
|
||||
status = "failure_nodes"
|
||||
#check that the nodes status matches either a pathway of the same status or is an always path.
|
||||
if (p not in [node['node_object'] for node in self.get_parents(obj, status)]
|
||||
and p not in [node['node_object'] for node in self.get_parents(obj, "always_nodes")]):
|
||||
if (p not in [node['node_object'] for node in self.get_parents(obj, status)] and
|
||||
p not in [node['node_object'] for node in self.get_parents(obj, "always_nodes")]):
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -102,13 +102,13 @@ class WorkflowDAG(SimpleDAG):
|
||||
elif obj.job:
|
||||
if obj.job.status in ['failed', 'error', 'canceled']:
|
||||
nodes.extend(self.get_children(obj, 'failure_nodes') +
|
||||
self.get_children(obj, 'always_nodes'))
|
||||
self.get_children(obj, 'always_nodes'))
|
||||
elif obj.job.status == 'successful':
|
||||
nodes.extend(self.get_children(obj, 'success_nodes') +
|
||||
self.get_children(obj, 'always_nodes'))
|
||||
self.get_children(obj, 'always_nodes'))
|
||||
elif obj.unified_job_template is None:
|
||||
nodes.extend(self.get_children(obj, 'failure_nodes') +
|
||||
self.get_children(obj, 'always_nodes'))
|
||||
self.get_children(obj, 'always_nodes'))
|
||||
else:
|
||||
# This catches root nodes or ANY convergence nodes
|
||||
if not obj.all_parents_must_converge and self._are_relevant_parents_finished(n):
|
||||
@@ -167,8 +167,8 @@ class WorkflowDAG(SimpleDAG):
|
||||
failed_path_nodes_id_status.append((str(obj.id), obj.job.status))
|
||||
|
||||
if res is True:
|
||||
s = _("No error handle path for workflow job node(s) [{node_status}] workflow job "
|
||||
"node(s) missing unified job template and error handle path [{no_ufjt}].")
|
||||
s = _("No error handling path for workflow job node(s) [{node_status}]. Workflow job "
|
||||
"node(s) missing unified job template and error handling path [{no_ufjt}].")
|
||||
parms = {
|
||||
'node_status': '',
|
||||
'no_ufjt': '',
|
||||
@@ -231,9 +231,9 @@ class WorkflowDAG(SimpleDAG):
|
||||
|
||||
|
||||
r'''
|
||||
determine if the current node is a convergence node by checking if all the
|
||||
parents are finished then checking to see if all parents meet the needed
|
||||
path criteria to run the convergence child.
|
||||
determine if the current node is a convergence node by checking if all the
|
||||
parents are finished then checking to see if all parents meet the needed
|
||||
path criteria to run the convergence child.
|
||||
(i.e. parent must fail, parent must succeed, etc. to proceed)
|
||||
|
||||
Return a list object
|
||||
|
||||
@@ -584,7 +584,7 @@ def handle_work_error(task_id, *args, **kwargs):
|
||||
first_instance = instance
|
||||
first_instance_type = each_task['type']
|
||||
|
||||
if instance.celery_task_id != task_id and not instance.cancel_flag:
|
||||
if instance.celery_task_id != task_id and not instance.cancel_flag and not instance.status == 'successful':
|
||||
instance.status = 'failed'
|
||||
instance.failed = True
|
||||
if not instance.job_explanation:
|
||||
@@ -1180,7 +1180,11 @@ class BaseTask(object):
|
||||
'''
|
||||
Ansible runner callback to tell the job when/if it is canceled
|
||||
'''
|
||||
self.instance = self.update_model(self.instance.pk)
|
||||
unified_job_id = self.instance.pk
|
||||
self.instance = self.update_model(unified_job_id)
|
||||
if not self.instance:
|
||||
logger.error('unified job {} was deleted while running, canceling'.format(unified_job_id))
|
||||
return True
|
||||
if self.instance.cancel_flag or self.instance.status == 'canceled':
|
||||
cancel_wait = (now() - self.instance.modified).seconds if self.instance.modified else 0
|
||||
if cancel_wait > 5:
|
||||
@@ -1692,7 +1696,7 @@ class RunJob(BaseTask):
|
||||
if settings.MAX_FORKS > 0 and job.forks > settings.MAX_FORKS:
|
||||
logger.warning(f'Maximum number of forks ({settings.MAX_FORKS}) exceeded.')
|
||||
args.append('--forks=%d' % settings.MAX_FORKS)
|
||||
else:
|
||||
else:
|
||||
args.append('--forks=%d' % job.forks)
|
||||
if job.force_handlers:
|
||||
args.append('--force-handlers')
|
||||
@@ -1805,7 +1809,7 @@ class RunJob(BaseTask):
|
||||
current_revision = git_repo.head.commit.hexsha
|
||||
if desired_revision == current_revision:
|
||||
job_revision = desired_revision
|
||||
logger.info('Skipping project sync for {} because commit is locally available'.format(job.log_format))
|
||||
logger.debug('Skipping project sync for {} because commit is locally available'.format(job.log_format))
|
||||
else:
|
||||
sync_needs = all_sync_needs
|
||||
except (ValueError, BadGitName):
|
||||
@@ -1904,7 +1908,8 @@ class RunJob(BaseTask):
|
||||
except Inventory.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
update_inventory_computed_fields.delay(inventory.id)
|
||||
if inventory is not None:
|
||||
update_inventory_computed_fields.delay(inventory.id)
|
||||
|
||||
|
||||
@task()
|
||||
|
||||
@@ -599,9 +599,9 @@ class TestControlledBySCM:
|
||||
delete(inv_src.get_absolute_url(), admin_user, expect=204)
|
||||
assert scm_inventory.inventory_sources.count() == 0
|
||||
|
||||
def test_adding_inv_src_ok(self, post, scm_inventory, admin_user):
|
||||
def test_adding_inv_src_ok(self, post, scm_inventory, project, admin_user):
|
||||
post(reverse('api:inventory_inventory_sources_list', kwargs={'pk': scm_inventory.id}),
|
||||
{'name': 'new inv src', 'update_on_project_update': False, 'source': 'scm', 'overwrite_vars': True},
|
||||
{'name': 'new inv src', 'source_project': project.pk, 'update_on_project_update': False, 'source': 'scm', 'overwrite_vars': True},
|
||||
admin_user, expect=201)
|
||||
|
||||
def test_adding_inv_src_prohibited(self, post, scm_inventory, project, admin_user):
|
||||
|
||||
@@ -365,3 +365,77 @@ def test_zoneinfo(get, admin_user):
|
||||
url = reverse('api:schedule_zoneinfo')
|
||||
r = get(url, admin_user, expect=200)
|
||||
assert {'name': 'America/New_York'} in r.data
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_normal_user_can_create_jt_schedule(options, post, project, inventory, alice):
|
||||
jt = JobTemplate.objects.create(
|
||||
name='test-jt',
|
||||
project=project,
|
||||
playbook='helloworld.yml',
|
||||
inventory=inventory
|
||||
)
|
||||
jt.save()
|
||||
url = reverse('api:schedule_list')
|
||||
|
||||
# can't create a schedule on the JT because we don't have execute rights
|
||||
params = {
|
||||
'name': 'My Example Schedule',
|
||||
'rrule': RRULE_EXAMPLE,
|
||||
'unified_job_template': jt.id,
|
||||
}
|
||||
assert 'POST' not in options(url, user=alice).data['actions'].keys()
|
||||
post(url, params, alice, expect=403)
|
||||
|
||||
# now we can, because we're allowed to execute the JT
|
||||
jt.execute_role.members.add(alice)
|
||||
assert 'POST' in options(url, user=alice).data['actions'].keys()
|
||||
post(url, params, alice, expect=201)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_normal_user_can_create_project_schedule(options, post, project, alice):
|
||||
url = reverse('api:schedule_list')
|
||||
|
||||
# can't create a schedule on the project because we don't have update rights
|
||||
params = {
|
||||
'name': 'My Example Schedule',
|
||||
'rrule': RRULE_EXAMPLE,
|
||||
'unified_job_template': project.id,
|
||||
}
|
||||
assert 'POST' not in options(url, user=alice).data['actions'].keys()
|
||||
post(url, params, alice, expect=403)
|
||||
|
||||
# use role does *not* grant the ability to schedule
|
||||
project.use_role.members.add(alice)
|
||||
assert 'POST' not in options(url, user=alice).data['actions'].keys()
|
||||
post(url, params, alice, expect=403)
|
||||
|
||||
# now we can, because we're allowed to update project
|
||||
project.update_role.members.add(alice)
|
||||
assert 'POST' in options(url, user=alice).data['actions'].keys()
|
||||
post(url, params, alice, expect=201)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_normal_user_can_create_inventory_update_schedule(options, post, inventory_source, alice):
|
||||
url = reverse('api:schedule_list')
|
||||
|
||||
# can't create a schedule on the project because we don't have update rights
|
||||
params = {
|
||||
'name': 'My Example Schedule',
|
||||
'rrule': RRULE_EXAMPLE,
|
||||
'unified_job_template': inventory_source.id,
|
||||
}
|
||||
assert 'POST' not in options(url, user=alice).data['actions'].keys()
|
||||
post(url, params, alice, expect=403)
|
||||
|
||||
# use role does *not* grant the ability to schedule
|
||||
inventory_source.inventory.use_role.members.add(alice)
|
||||
assert 'POST' not in options(url, user=alice).data['actions'].keys()
|
||||
post(url, params, alice, expect=403)
|
||||
|
||||
# now we can, because we're allowed to update project
|
||||
inventory_source.inventory.update_role.members.add(alice)
|
||||
assert 'POST' in options(url, user=alice).data['actions'].keys()
|
||||
post(url, params, alice, expect=201)
|
||||
|
||||
@@ -23,8 +23,11 @@ class TestJobNotificationMixin(object):
|
||||
'finished': bool,
|
||||
'force_handlers': bool,
|
||||
'forks': int,
|
||||
'host_status_counts': {'skipped': int, 'ok': int, 'changed': int,
|
||||
'failures': int, 'dark': int},
|
||||
'host_status_counts': {
|
||||
'skipped': int, 'ok': int, 'changed': int,
|
||||
'failures': int, 'dark': int, 'processed': int,
|
||||
'rescued': int, 'failed': bool
|
||||
},
|
||||
'id': int,
|
||||
'job_explanation': str,
|
||||
'job_slice_count': int,
|
||||
@@ -36,7 +39,6 @@ class TestJobNotificationMixin(object):
|
||||
'modified': datetime.datetime,
|
||||
'name': str,
|
||||
'playbook': str,
|
||||
'playbook_counts': {'play_count': int, 'task_count': int},
|
||||
'scm_revision': str,
|
||||
'skip_tags': str,
|
||||
'start_at_task': str,
|
||||
@@ -68,17 +70,10 @@ class TestJobNotificationMixin(object):
|
||||
'name': str,
|
||||
'scm_type': str,
|
||||
'status': str},
|
||||
'project_update': {'id': int, 'name': str, 'description': str, 'status': str, 'failed': bool},
|
||||
'unified_job_template': {'description': str,
|
||||
'id': int,
|
||||
'name': str,
|
||||
'unified_job_type': str},
|
||||
'source_workflow_job': {'description': str,
|
||||
'elapsed': float,
|
||||
'failed': bool,
|
||||
'id': int,
|
||||
'name': str,
|
||||
'status': str}},
|
||||
'unified_job_type': str}},
|
||||
|
||||
'timeout': int,
|
||||
'type': str,
|
||||
|
||||
@@ -89,6 +89,27 @@ def test_finish_job_fact_cache_with_existing_data(job, hosts, inventory, mocker,
|
||||
hosts[1].save.assert_called_once_with()
|
||||
|
||||
|
||||
def test_finish_job_fact_cache_with_malformed_fact(job, hosts, inventory, mocker, tmpdir):
|
||||
fact_cache = os.path.join(tmpdir, 'facts')
|
||||
modified_times = {}
|
||||
job.start_job_fact_cache(fact_cache, modified_times, 0)
|
||||
|
||||
for h in hosts:
|
||||
h.save = mocker.Mock()
|
||||
|
||||
for h in hosts:
|
||||
filepath = os.path.join(fact_cache, h.name)
|
||||
with open(filepath, 'w') as f:
|
||||
json.dump({'ansible_local': {'insights': 'this is an unexpected error from ansible'}}, f)
|
||||
new_modification_time = time.time() + 3600
|
||||
os.utime(filepath, (new_modification_time, new_modification_time))
|
||||
|
||||
job.finish_job_fact_cache(fact_cache, modified_times)
|
||||
|
||||
for h in hosts:
|
||||
assert h.insights_system_id is None
|
||||
|
||||
|
||||
def test_finish_job_fact_cache_with_bad_data(job, hosts, inventory, mocker, tmpdir):
|
||||
fact_cache = os.path.join(tmpdir, 'facts')
|
||||
modified_times = {}
|
||||
|
||||
@@ -133,6 +133,7 @@ class TestDNR():
|
||||
assert 1 == len(do_not_run_nodes)
|
||||
assert nodes[3] == do_not_run_nodes[0]
|
||||
|
||||
|
||||
class TestAllWorkflowNodes():
|
||||
# test workflow convergence is functioning as expected
|
||||
@pytest.fixture
|
||||
@@ -150,9 +151,9 @@ class TestAllWorkflowNodes():
|
||||
1 2
|
||||
\ /
|
||||
F \ / S
|
||||
\/
|
||||
\/
|
||||
3
|
||||
|
||||
|
||||
'''
|
||||
g.add_edge(nodes[0], nodes[1], "success_nodes")
|
||||
g.add_edge(nodes[0], nodes[2], "success_nodes")
|
||||
@@ -186,7 +187,7 @@ class TestAllWorkflowNodes():
|
||||
S| 1
|
||||
| /
|
||||
|/ A
|
||||
2
|
||||
2
|
||||
'''
|
||||
g.add_edge(nodes[0], nodes[1], "failure_nodes")
|
||||
g.add_edge(nodes[0], nodes[2], "success_nodes")
|
||||
@@ -222,7 +223,7 @@ class TestAllWorkflowNodes():
|
||||
F| 1
|
||||
| /
|
||||
|/ A
|
||||
2
|
||||
2
|
||||
'''
|
||||
g.add_edge(nodes[0], nodes[1], "success_nodes")
|
||||
g.add_edge(nodes[0], nodes[2], "failure_nodes")
|
||||
@@ -341,7 +342,7 @@ class TestAllWorkflowNodes():
|
||||
g.add_node(n)
|
||||
r'''
|
||||
0 1 2
|
||||
\ | /
|
||||
\ | /
|
||||
S \ S| / F
|
||||
\ | /
|
||||
\|/
|
||||
@@ -349,7 +350,7 @@ class TestAllWorkflowNodes():
|
||||
3
|
||||
/\
|
||||
S / \ S
|
||||
/ \
|
||||
/ \
|
||||
4| | 5
|
||||
\ /
|
||||
S \ / S
|
||||
@@ -466,8 +467,8 @@ class TestIsWorkflowDone():
|
||||
|
||||
assert g.is_workflow_done() is True
|
||||
assert g.has_workflow_failed() == \
|
||||
(True, smart_text(_("No error handle path for workflow job node(s) [({},{})] workflow job node(s)"
|
||||
" missing unified job template and error handle path [].").format(nodes[2].id, nodes[2].job.status)))
|
||||
(True, smart_text(_("No error handling path for workflow job node(s) [({},{})]. Workflow job node(s)"
|
||||
" missing unified job template and error handling path [].").format(nodes[2].id, nodes[2].job.status)))
|
||||
|
||||
def test_is_workflow_done_no_unified_job_tempalte_end(self, workflow_dag_failed):
|
||||
(g, nodes) = workflow_dag_failed
|
||||
@@ -476,8 +477,8 @@ class TestIsWorkflowDone():
|
||||
|
||||
assert g.is_workflow_done() is True
|
||||
assert g.has_workflow_failed() == \
|
||||
(True, smart_text(_("No error handle path for workflow job node(s) [] workflow job node(s) missing"
|
||||
" unified job template and error handle path [{}].").format(nodes[2].id)))
|
||||
(True, smart_text(_("No error handling path for workflow job node(s) []. Workflow job node(s) missing"
|
||||
" unified job template and error handling path [{}].").format(nodes[2].id)))
|
||||
|
||||
def test_is_workflow_done_no_unified_job_tempalte_begin(self, workflow_dag_1):
|
||||
(g, nodes) = workflow_dag_1
|
||||
@@ -487,22 +488,22 @@ class TestIsWorkflowDone():
|
||||
|
||||
assert g.is_workflow_done() is True
|
||||
assert g.has_workflow_failed() == \
|
||||
(True, smart_text(_("No error handle path for workflow job node(s) [] workflow job node(s) missing"
|
||||
" unified job template and error handle path [{}].").format(nodes[0].id)))
|
||||
(True, smart_text(_("No error handling path for workflow job node(s) []. Workflow job node(s) missing"
|
||||
" unified job template and error handling path [{}].").format(nodes[0].id)))
|
||||
|
||||
def test_canceled_should_fail(self, workflow_dag_canceled):
|
||||
(g, nodes) = workflow_dag_canceled
|
||||
|
||||
assert g.has_workflow_failed() == \
|
||||
(True, smart_text(_("No error handle path for workflow job node(s) [({},{})] workflow job node(s)"
|
||||
" missing unified job template and error handle path [].").format(nodes[0].id, nodes[0].job.status)))
|
||||
(True, smart_text(_("No error handling path for workflow job node(s) [({},{})]. Workflow job node(s)"
|
||||
" missing unified job template and error handling path [].").format(nodes[0].id, nodes[0].job.status)))
|
||||
|
||||
def test_failure_should_fail(self, workflow_dag_failure):
|
||||
(g, nodes) = workflow_dag_failure
|
||||
|
||||
assert g.has_workflow_failed() == \
|
||||
(True, smart_text(_("No error handle path for workflow job node(s) [({},{})] workflow job node(s)"
|
||||
" missing unified job template and error handle path [].").format(nodes[0].id, nodes[0].job.status)))
|
||||
(True, smart_text(_("No error handling path for workflow job node(s) [({},{})]. Workflow job node(s)"
|
||||
" missing unified job template and error handling path [].").format(nodes[0].id, nodes[0].job.status)))
|
||||
|
||||
|
||||
class TestBFSNodesToRun():
|
||||
|
||||
@@ -152,3 +152,10 @@ def test_uri_scm_cleartext_redact_and_replace(test_data):
|
||||
# Ensure the host didn't get redacted
|
||||
assert redacted_str.count(uri.host) == test_data['host_occurrences']
|
||||
|
||||
|
||||
@pytest.mark.timeout(1)
|
||||
def test_large_string_performance():
|
||||
length = 100000
|
||||
redacted = UriCleaner.remove_sensitive('x' * length)
|
||||
assert len(redacted) == length
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ def could_be_inventory(project_path, dir_path, filename):
|
||||
def read_ansible_config(project_path, variables_of_interest):
|
||||
fnames = ['/etc/ansible/ansible.cfg']
|
||||
if project_path:
|
||||
fnames.insert(0, os.path.join(project_path, 'ansible.cfg'))
|
||||
fnames.append(os.path.join(project_path, 'ansible.cfg'))
|
||||
values = {}
|
||||
try:
|
||||
parser = ConfigParser()
|
||||
|
||||
@@ -44,7 +44,7 @@ JOBOUTPUT_ROOT = '/var/lib/awx/job_status/'
|
||||
SCHEDULE_METADATA_LOCATION = '/var/lib/awx/.tower_cycle'
|
||||
|
||||
# Ansible base virtualenv paths and enablement
|
||||
BASE_VENV_PATH = "/var/lib/awx/venv"
|
||||
BASE_VENV_PATH = os.path.realpath("/var/lib/awx/venv")
|
||||
ANSIBLE_VENV_PATH = os.path.join(BASE_VENV_PATH, "ansible")
|
||||
|
||||
# Tower base virtualenv paths and enablement
|
||||
|
||||
@@ -72,7 +72,7 @@ function AddEditCredentialsController (
|
||||
vm.form.credential_type._displayValue = credentialType.get('name');
|
||||
vm.isTestable = (isEditable && credentialType.get('kind') === 'external');
|
||||
|
||||
if (credential.get('related.input_sources.results.length' > 0)) {
|
||||
if (credential.get('related.input_sources.results').length > 0) {
|
||||
vm.form.credential_type._disabled = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,6 @@ function CredentialsResolve (
|
||||
return $q.all(promises)
|
||||
.then(models => {
|
||||
const typeId = models.credential.get('credential_type');
|
||||
const orgId = models.credential.get('organization');
|
||||
|
||||
Rest.setUrl(GetBasePath('credentials'));
|
||||
const params = { target_input_sources__target_credential: id };
|
||||
@@ -48,7 +47,9 @@ function CredentialsResolve (
|
||||
|
||||
const dependents = {
|
||||
credentialType: new CredentialType('get', typeId),
|
||||
organization: new Organization('get', orgId),
|
||||
organization: new Organization('get', {
|
||||
resource: models.credential.get('summary_fields.organization')
|
||||
}),
|
||||
credentialInputSources: models.credential.extend('GET', 'input_sources'),
|
||||
sourceCredentials: sourceCredentialsPromise
|
||||
};
|
||||
|
||||
@@ -25,15 +25,15 @@ function ListJobsController (
|
||||
|
||||
vm.strings = strings;
|
||||
|
||||
let newJobs = [];
|
||||
|
||||
// smart-search
|
||||
const name = 'jobs';
|
||||
const iterator = 'job';
|
||||
let paginateQuerySet = {};
|
||||
|
||||
let launchModalOpen = false;
|
||||
let refreshAfterLaunchClose = false;
|
||||
let pendingRefresh = false;
|
||||
let refreshTimerRunning = false;
|
||||
let newJobsTimerRunning = false;
|
||||
|
||||
vm.searchBasePath = SearchBasePath;
|
||||
|
||||
@@ -104,23 +104,53 @@ function ListJobsController (
|
||||
$scope.$emit('updateCount', vm.job_dataset.count, 'jobs');
|
||||
});
|
||||
|
||||
$scope.$on('ws-jobs', () => {
|
||||
if (!launchModalOpen) {
|
||||
if (!refreshTimerRunning) {
|
||||
refreshJobs();
|
||||
} else {
|
||||
pendingRefresh = true;
|
||||
const canAddRowsDynamically = () => {
|
||||
const orderByValue = _.get($state.params, 'job_search.order_by');
|
||||
const pageValue = _.get($state.params, 'job_search.page');
|
||||
const idInValue = _.get($state.params, 'job_search.id__in');
|
||||
|
||||
return (!idInValue && (!pageValue || pageValue === '1')
|
||||
&& (orderByValue === '-finished' || orderByValue === '-started'));
|
||||
};
|
||||
|
||||
const updateJobRow = (msg) => {
|
||||
// Loop across the jobs currently shown and update the row
|
||||
// if it exists
|
||||
for (let i = 0; i < vm.jobs.length; i++) {
|
||||
if (vm.jobs[i].id === msg.unified_job_id) {
|
||||
// Update the job status.
|
||||
vm.jobs[i].status = msg.status;
|
||||
if (msg.finished) {
|
||||
vm.jobs[i].finished = msg.finished;
|
||||
const orderByValue = _.get($state.params, 'job_search.order_by');
|
||||
if (orderByValue === '-finished') {
|
||||
// Attempt to sort the rows in the list by their finish
|
||||
// timestamp in descending order
|
||||
vm.jobs.sort((a, b) =>
|
||||
(!b.finished) - (!a.finished)
|
||||
|| new Date(b.finished) - new Date(a.finished));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
refreshAfterLaunchClose = true;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.$on('ws-jobs', (e, msg) => {
|
||||
if (msg.status === 'pending' && canAddRowsDynamically()) {
|
||||
newJobs.push(msg.unified_job_id);
|
||||
if (!launchModalOpen && !newJobsTimerRunning) {
|
||||
fetchNewJobs();
|
||||
}
|
||||
} else if (!newJobs.includes(msg.unified_job_id)) {
|
||||
updateJobRow(msg);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$on('launchModalOpen', (evt, isOpen) => {
|
||||
evt.stopPropagation();
|
||||
if (!isOpen && refreshAfterLaunchClose) {
|
||||
refreshAfterLaunchClose = false;
|
||||
refreshJobs();
|
||||
if (!isOpen && newJobs.length > 0) {
|
||||
fetchNewJobs();
|
||||
}
|
||||
launchModalOpen = isOpen;
|
||||
});
|
||||
@@ -289,22 +319,49 @@ function ListJobsController (
|
||||
});
|
||||
};
|
||||
|
||||
function refreshJobs () {
|
||||
qs.search(SearchBasePath, $state.params.job_search, { 'X-WS-Session-Quiet': true })
|
||||
const fetchNewJobs = () => {
|
||||
newJobsTimerRunning = true;
|
||||
const newJobIdsFilter = newJobs.join(',');
|
||||
newJobs = [];
|
||||
const newJobsSearchParams = Object.assign({}, $state.params.job_search);
|
||||
newJobsSearchParams.count_disabled = 1;
|
||||
newJobsSearchParams.id__in = newJobIdsFilter;
|
||||
delete newJobsSearchParams.page_size;
|
||||
const stringifiedSearchParams = qs.encodeQueryset(newJobsSearchParams, false);
|
||||
Rest.setUrl(`${vm.searchBasePath}${stringifiedSearchParams}`);
|
||||
Rest.get()
|
||||
.then(({ data }) => {
|
||||
vm.jobs = data.results;
|
||||
vm.job_dataset = data;
|
||||
vm.job_dataset.count += data.results.length;
|
||||
const pageSize = parseInt($state.params.job_search.page_size, 10) || 20;
|
||||
const joinedJobs = data.results.concat(vm.jobs);
|
||||
vm.jobs = joinedJobs.length > pageSize
|
||||
? joinedJobs.slice(0, pageSize)
|
||||
: joinedJobs;
|
||||
$timeout(() => {
|
||||
if (canAddRowsDynamically()) {
|
||||
if (newJobs.length > 0 && !launchModalOpen) {
|
||||
fetchNewJobs();
|
||||
} else {
|
||||
newJobsTimerRunning = false;
|
||||
}
|
||||
} else {
|
||||
// Bail out - one of [order_by, page, id__in] params has changed since we
|
||||
// received these new job messages
|
||||
newJobs = [];
|
||||
newJobsTimerRunning = false;
|
||||
}
|
||||
}, 5000);
|
||||
})
|
||||
.catch(({ data, status }) => {
|
||||
ProcessErrors($scope, data, status, null, {
|
||||
hdr: strings.get('error.HEADER'),
|
||||
msg: strings.get('error.CALL', {
|
||||
path: `${vm.searchBasePath}${stringifiedSearchParams}`,
|
||||
status
|
||||
})
|
||||
});
|
||||
});
|
||||
pendingRefresh = false;
|
||||
refreshTimerRunning = true;
|
||||
$timeout(() => {
|
||||
if (pendingRefresh) {
|
||||
refreshJobs();
|
||||
} else {
|
||||
refreshTimerRunning = false;
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
};
|
||||
|
||||
vm.isCollapsed = true;
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ export const OUTPUT_ELEMENT_LAST = '#atStdoutMenuLast';
|
||||
export const OUTPUT_MAX_BUFFER_LENGTH = 1000;
|
||||
export const OUTPUT_MAX_LAG = 120;
|
||||
export const OUTPUT_NO_COUNT_JOB_TYPES = ['ad_hoc_command', 'system_job', 'inventory_update'];
|
||||
export const OUTPUT_ORDER_BY = 'counter';
|
||||
export const OUTPUT_ORDER_BY = 'start_line';
|
||||
export const OUTPUT_PAGE_CACHE = true;
|
||||
export const OUTPUT_PAGE_LIMIT = 5;
|
||||
export const OUTPUT_PAGE_SIZE = 50;
|
||||
|
||||
@@ -113,11 +113,6 @@ function projectsListController (
|
||||
// And we found the affected project
|
||||
$log.debug(`Received event for project: ${project.name}`);
|
||||
$log.debug(`Status changed to: ${data.status}`);
|
||||
if (data.status === 'successful' || data.status === 'failed' || data.status === 'canceled') {
|
||||
reloadList();
|
||||
} else {
|
||||
project.scm_update_tooltip = vm.strings.get('update.UPDATE_RUNNING');
|
||||
}
|
||||
project.status = data.status;
|
||||
buildTooltips(project);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ function ListTemplatesController(
|
||||
qs,
|
||||
GetBasePath,
|
||||
ngToast,
|
||||
$timeout
|
||||
) {
|
||||
const vm = this || {};
|
||||
const [jobTemplate, workflowTemplate] = resolvedModels;
|
||||
@@ -32,10 +31,6 @@ function ListTemplatesController(
|
||||
const choices = workflowTemplate.options('actions.GET.type.choices')
|
||||
.concat(jobTemplate.options('actions.GET.type.choices'));
|
||||
|
||||
let launchModalOpen = false;
|
||||
let refreshAfterLaunchClose = false;
|
||||
let pendingRefresh = false;
|
||||
let refreshTimerRunning = false;
|
||||
let paginateQuerySet = {};
|
||||
|
||||
vm.strings = strings;
|
||||
@@ -120,25 +115,39 @@ function ListTemplatesController(
|
||||
setToolbarSort();
|
||||
}, true);
|
||||
|
||||
$scope.$on(`ws-jobs`, () => {
|
||||
if (!launchModalOpen) {
|
||||
if (!refreshTimerRunning) {
|
||||
refreshTemplates();
|
||||
} else {
|
||||
pendingRefresh = true;
|
||||
}
|
||||
} else {
|
||||
refreshAfterLaunchClose = true;
|
||||
}
|
||||
});
|
||||
$scope.$on(`ws-jobs`, (e, msg) => {
|
||||
if (msg.unified_job_template_id && vm.templates) {
|
||||
const template = vm.templates.find((t) => t.id === msg.unified_job_template_id);
|
||||
if (template) {
|
||||
if (msg.status === 'pending') {
|
||||
// This is a new job - add it to the front of the
|
||||
// recent_jobs array
|
||||
if (template.summary_fields.recent_jobs.length === 10) {
|
||||
template.summary_fields.recent_jobs.pop();
|
||||
}
|
||||
|
||||
$scope.$on('launchModalOpen', (evt, isOpen) => {
|
||||
evt.stopPropagation();
|
||||
if (!isOpen && refreshAfterLaunchClose) {
|
||||
refreshAfterLaunchClose = false;
|
||||
refreshTemplates();
|
||||
template.summary_fields.recent_jobs.unshift({
|
||||
id: msg.unified_job_id,
|
||||
status: msg.status,
|
||||
type: msg.type
|
||||
});
|
||||
} else {
|
||||
// This is an update to an existing job. Check to see
|
||||
// if we have it in our array of recent_jobs
|
||||
for (let i=0; i<template.summary_fields.recent_jobs.length; i++) {
|
||||
const recentJob = template.summary_fields.recent_jobs[i];
|
||||
if (recentJob.id === msg.unified_job_id) {
|
||||
recentJob.status = msg.status;
|
||||
if (msg.finished) {
|
||||
recentJob.finished = msg.finished;
|
||||
template.last_job_run = msg.finished;
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
launchModalOpen = isOpen;
|
||||
});
|
||||
|
||||
vm.isInvalid = (template) => {
|
||||
@@ -265,15 +274,6 @@ function ListTemplatesController(
|
||||
vm.templates = vm.dataset.results;
|
||||
})
|
||||
.finally(() => Wait('stop'));
|
||||
pendingRefresh = false;
|
||||
refreshTimerRunning = true;
|
||||
$timeout(() => {
|
||||
if (pendingRefresh) {
|
||||
refreshTemplates();
|
||||
} else {
|
||||
refreshTimerRunning = false;
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
function createErrorHandler(path, action) {
|
||||
@@ -483,8 +483,7 @@ ListTemplatesController.$inject = [
|
||||
'Wait',
|
||||
'QuerySet',
|
||||
'GetBasePath',
|
||||
'ngToast',
|
||||
'$timeout'
|
||||
'ngToast'
|
||||
];
|
||||
|
||||
export default ListTemplatesController;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const SUPPORTED_LOCALES = ['en', 'es', 'fr', 'ja', 'nl'];
|
||||
const SUPPORTED_LOCALES = ['en', 'es', 'fr', 'ja', 'nl', 'zh'];
|
||||
const DEFAULT_LOCALE = 'en';
|
||||
const BASE_PATH = global.$basePath ? `${global.$basePath}languages/` : '/static/languages/';
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ export default
|
||||
templateUrl: templateUrl('home/dashboard/lists/job-templates/job-templates-list')
|
||||
};
|
||||
|
||||
function link(scope, element, attr) {
|
||||
function link(scope) {
|
||||
|
||||
scope.$watch("data", function(data) {
|
||||
if (data) {
|
||||
@@ -22,7 +22,7 @@ export default
|
||||
scope.noJobTemplates = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}, true);
|
||||
|
||||
scope.canAddJobTemplate = false;
|
||||
let url = GetBasePath('job_templates');
|
||||
|
||||
@@ -12,18 +12,152 @@ export default ['$scope','Wait', '$timeout', 'i18n',
|
||||
var dataCount = 0;
|
||||
let launchModalOpen = false;
|
||||
let refreshAfterLaunchClose = false;
|
||||
let pendingRefresh = false;
|
||||
let refreshTimerRunning = false;
|
||||
let pendingDashboardRefresh = false;
|
||||
let dashboardTimerRunning = false;
|
||||
let newJobsTimerRunning = false;
|
||||
let newTemplatesTimerRunning = false;
|
||||
let newJobs = [];
|
||||
let newTemplates =[];
|
||||
|
||||
$scope.$on('ws-jobs', function () {
|
||||
if (!launchModalOpen) {
|
||||
if (!refreshTimerRunning) {
|
||||
refreshLists();
|
||||
const fetchDashboardData = () => {
|
||||
Rest.setUrl(GetBasePath('dashboard'));
|
||||
Rest.get()
|
||||
.then(({data}) => {
|
||||
$scope.dashboardData = data;
|
||||
})
|
||||
.catch(({data, status}) => {
|
||||
ProcessErrors($scope, data, status, null, { hdr: i18n._('Error!'), msg: i18n._(`Failed to get dashboard host graph data: ${status}`) });
|
||||
});
|
||||
|
||||
if ($scope.graphData) {
|
||||
Rest.setUrl(`${GetBasePath('dashboard')}graphs/jobs/?period=${$scope.graphData.period}&job_type=${$scope.graphData.jobType}`);
|
||||
Rest.setHeader({'X-WS-Session-Quiet': true});
|
||||
Rest.get()
|
||||
.then(function(value) {
|
||||
if($scope.graphData.status === "successful" || $scope.graphData.status === "failed"){
|
||||
delete value.data.jobs[$scope.graphData.status];
|
||||
}
|
||||
$scope.graphData.jobStatus = value.data;
|
||||
})
|
||||
.catch(function({data, status}) {
|
||||
ProcessErrors(null, data, status, null, { hdr: i18n._('Error!'), msg: i18n._(`Failed to get dashboard graph data: ${status}`)});
|
||||
});
|
||||
}
|
||||
|
||||
pendingDashboardRefresh = false;
|
||||
dashboardTimerRunning = true;
|
||||
$timeout(() => {
|
||||
if (pendingDashboardRefresh) {
|
||||
fetchDashboardData();
|
||||
} else {
|
||||
pendingRefresh = true;
|
||||
dashboardTimerRunning = false;
|
||||
}
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
const fetchNewJobs = () => {
|
||||
newJobsTimerRunning = true;
|
||||
const newJobIdsFilter = newJobs.join(',');
|
||||
newJobs = [];
|
||||
Rest.setUrl(`${GetBasePath("unified_jobs")}?id__in=${newJobIdsFilter}&order_by=-finished&finished__isnull=false&type=workflow_job,job&count_disabled=1`);
|
||||
Rest.get()
|
||||
.then(({ data }) => {
|
||||
const joinedJobs = data.results.concat($scope.dashboardJobsListData);
|
||||
$scope.dashboardJobsListData =
|
||||
joinedJobs.length > 5 ? joinedJobs.slice(0, 5) : joinedJobs;
|
||||
$timeout(() => {
|
||||
if (newJobs.length > 0) {
|
||||
fetchNewJobs();
|
||||
} else {
|
||||
newJobsTimerRunning = false;
|
||||
}
|
||||
}, 5000);
|
||||
})
|
||||
.catch(({ data, status }) => {
|
||||
ProcessErrors($scope, data, status, null, {
|
||||
hdr: i18n._('Error!'),
|
||||
msg: i18n._(`Failed to get new jobs for dashboard: ${status}`)
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const fetchNewTemplates = () => {
|
||||
newTemplatesTimerRunning = true;
|
||||
const newTemplateIdsFilter = newTemplates.join(',');
|
||||
newTemplates = [];
|
||||
Rest.setUrl(`${GetBasePath("unified_job_templates")}?id__in=${newTemplateIdsFilter}&order_by=-last_job_run&last_job_run__isnull=false&type=workflow_job_template,job_template&count_disabled=1"`);
|
||||
Rest.get()
|
||||
.then(({ data }) => {
|
||||
const joinedTemplates = data.results.concat($scope.dashboardJobTemplatesListData).sort((a, b) => new Date(b.last_job_run) - new Date(a.last_job_run));
|
||||
$scope.dashboardJobTemplatesListData =
|
||||
joinedTemplates.length > 5 ? joinedTemplates.slice(0, 5) : joinedTemplates;
|
||||
$timeout(() => {
|
||||
if (newTemplates.length > 0 && !launchModalOpen) {
|
||||
fetchNewTemplates();
|
||||
} else {
|
||||
newTemplatesTimerRunning = false;
|
||||
}
|
||||
}, 5000);
|
||||
})
|
||||
.catch(({ data, status }) => {
|
||||
ProcessErrors($scope, data, status, null, {
|
||||
hdr: i18n._('Error!'),
|
||||
msg: i18n._(`Failed to get new templates for dashboard: ${status}`)
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.$on('ws-jobs', function (e, msg) {
|
||||
if (msg.status === 'successful' || msg.status === 'failed' || msg.status === 'canceled' || msg.status === 'error') {
|
||||
newJobs.push(msg.unified_job_id);
|
||||
if (!newJobsTimerRunning) {
|
||||
fetchNewJobs();
|
||||
}
|
||||
if (!launchModalOpen) {
|
||||
if (!dashboardTimerRunning) {
|
||||
fetchDashboardData();
|
||||
} else {
|
||||
pendingDashboardRefresh = true;
|
||||
}
|
||||
} else {
|
||||
refreshAfterLaunchClose = true;
|
||||
}
|
||||
}
|
||||
|
||||
const template = $scope.dashboardJobTemplatesListData.find((t) => t.id === msg.unified_job_template_id);
|
||||
if (template) {
|
||||
if (msg.status === 'pending') {
|
||||
if (template.summary_fields.recent_jobs.length === 10) {
|
||||
template.summary_fields.recent_jobs.pop();
|
||||
}
|
||||
|
||||
template.summary_fields.recent_jobs.unshift({
|
||||
id: msg.unified_job_id,
|
||||
status: msg.status,
|
||||
type: msg.type
|
||||
});
|
||||
} else {
|
||||
for (let i=0; i<template.summary_fields.recent_jobs.length; i++) {
|
||||
const recentJob = template.summary_fields.recent_jobs[i];
|
||||
if (recentJob.id === msg.unified_job_id) {
|
||||
recentJob.status = msg.status;
|
||||
if (msg.finished) {
|
||||
recentJob.finished = msg.finished;
|
||||
template.last_job_run = msg.finished;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (msg.status === 'successful' || msg.status === 'failed' || msg.status === 'canceled') {
|
||||
$scope.dashboardJobTemplatesListData.sort((a, b) => new Date(b.last_job_run) - new Date(a.last_job_run));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
refreshAfterLaunchClose = true;
|
||||
newTemplates.push(msg.unified_job_template_id);
|
||||
if (!launchModalOpen && !newTemplatesTimerRunning) {
|
||||
fetchNewTemplates();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -31,7 +165,10 @@ export default ['$scope','Wait', '$timeout', 'i18n',
|
||||
evt.stopPropagation();
|
||||
if (!isOpen && refreshAfterLaunchClose) {
|
||||
refreshAfterLaunchClose = false;
|
||||
refreshLists();
|
||||
fetchDashboardData();
|
||||
if (newTemplates.length > 0) {
|
||||
fetchNewTemplates();
|
||||
}
|
||||
}
|
||||
launchModalOpen = isOpen;
|
||||
});
|
||||
@@ -75,61 +212,6 @@ export default ['$scope','Wait', '$timeout', 'i18n',
|
||||
$scope.$emit('dashboardDataLoadComplete');
|
||||
});
|
||||
|
||||
function refreshLists () {
|
||||
Rest.setUrl(GetBasePath('dashboard'));
|
||||
Rest.get()
|
||||
.then(({data}) => {
|
||||
$scope.dashboardData = data;
|
||||
})
|
||||
.catch(({data, status}) => {
|
||||
ProcessErrors($scope, data, status, null, { hdr: i18n._('Error!'), msg: i18n._(`Failed to get dashboard host graph data: ${status}`) });
|
||||
});
|
||||
|
||||
Rest.setUrl(GetBasePath("unified_jobs") + "?order_by=-finished&page_size=5&finished__isnull=false&type=workflow_job,job&count_disabled=1");
|
||||
Rest.setHeader({'X-WS-Session-Quiet': true});
|
||||
Rest.get()
|
||||
.then(({data}) => {
|
||||
$scope.dashboardJobsListData = data.results;
|
||||
})
|
||||
.catch(({data, status}) => {
|
||||
ProcessErrors($scope, data, status, null, { hdr: i18n._('Error!'), msg: i18n._(`Failed to get dashboard jobs list: ${status}`) });
|
||||
});
|
||||
|
||||
Rest.setUrl(GetBasePath("unified_job_templates") + "?order_by=-last_job_run&page_size=5&last_job_run__isnull=false&type=workflow_job_template,job_template&count_disabled=1");
|
||||
Rest.get()
|
||||
.then(({data}) => {
|
||||
$scope.dashboardJobTemplatesListData = data.results;
|
||||
})
|
||||
.catch(({data, status}) => {
|
||||
ProcessErrors($scope, data, status, null, { hdr: i18n._('Error!'), msg: i18n._(`Failed to get dashboard jobs list: ${status}`) });
|
||||
});
|
||||
|
||||
if ($scope.graphData) {
|
||||
Rest.setUrl(`${GetBasePath('dashboard')}graphs/jobs/?period=${$scope.graphData.period}&job_type=${$scope.graphData.jobType}`);
|
||||
Rest.setHeader({'X-WS-Session-Quiet': true});
|
||||
Rest.get()
|
||||
.then(function(value) {
|
||||
if($scope.graphData.status === "successful" || $scope.graphData.status === "failed"){
|
||||
delete value.data.jobs[$scope.graphData.status];
|
||||
}
|
||||
$scope.graphData.jobStatus = value.data;
|
||||
})
|
||||
.catch(function({data, status}) {
|
||||
ProcessErrors(null, data, status, null, { hdr: i18n._('Error!'), msg: i18n._(`Failed to get dashboard graph data: ${status}`)});
|
||||
});
|
||||
}
|
||||
|
||||
pendingRefresh = false;
|
||||
refreshTimerRunning = true;
|
||||
$timeout(() => {
|
||||
if (pendingRefresh) {
|
||||
refreshLists();
|
||||
} else {
|
||||
refreshTimerRunning = false;
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
Wait('start');
|
||||
Rest.setUrl(GetBasePath('dashboard'));
|
||||
Rest.get()
|
||||
|
||||
@@ -42,21 +42,7 @@
|
||||
$scope.$on(`ws-jobs`, function(e, data){
|
||||
inventory_source = Find({ list: $scope.inventory_sources, key: 'id', val: data.inventory_source_id });
|
||||
|
||||
if (inventory_source === undefined || inventory_source === null) {
|
||||
inventory_source = {};
|
||||
}
|
||||
|
||||
if(data.status === 'failed' || data.status === 'successful'){
|
||||
let path = GetBasePath('inventory') + $stateParams.inventory_id + '/inventory_sources';
|
||||
|
||||
qs.search(path, $state.params[`${list.iterator}_search`])
|
||||
.then((searchResponse)=> {
|
||||
$scope[`${list.iterator}_dataset`] = searchResponse.data;
|
||||
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
|
||||
_.forEach($scope[list.name], buildStatusIndicators);
|
||||
optionsRequestDataProcessing();
|
||||
});
|
||||
} else {
|
||||
if (inventory_source) {
|
||||
var status = GetSyncStatusMsg({
|
||||
status: data.status
|
||||
});
|
||||
|
||||
@@ -256,7 +256,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="Modal-footer">
|
||||
<button ng-click="cancelLicenseLookup()" class="btn Modal-footerButton Modal-defaultButton">CANCEL</button>
|
||||
<button ng-click="cancelLicenseLookup()" class="btn Modal-footerButton Modal-defaultButton" translate>CANCEL</button>
|
||||
<button
|
||||
ng-click="confirmLicenseSelection()"
|
||||
class="btn Modal-footerButton btn-success"
|
||||
|
||||
@@ -718,7 +718,7 @@ export default ['i18n', function(i18n) {
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)',
|
||||
},
|
||||
running_message: {
|
||||
label: i18n._('Workflow Running Message'),
|
||||
label: i18n._('Workflow Pending Approval Message'),
|
||||
class: 'Form-formGroup--fullWidth',
|
||||
type: 'syntax_highlight',
|
||||
mode: 'jinja2',
|
||||
@@ -729,7 +729,7 @@ export default ['i18n', function(i18n) {
|
||||
ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)',
|
||||
},
|
||||
running_body: {
|
||||
label: i18n._('Workflow Running Message Body'),
|
||||
label: i18n._('Workflow Pending Approval Message Body'),
|
||||
class: 'Form-formGroup--fullWidth',
|
||||
type: 'syntax_highlight',
|
||||
mode: 'jinja2',
|
||||
|
||||
@@ -4,25 +4,45 @@
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default ['$scope', '$rootScope',
|
||||
'$stateParams', 'Rest', 'ProcessErrors',
|
||||
'GetBasePath', 'Wait',
|
||||
'$state', 'OrgJobTemplateList', 'OrgJobTemplateDataset', 'QuerySet',
|
||||
function($scope, $rootScope,
|
||||
$stateParams, Rest, ProcessErrors,
|
||||
GetBasePath, Wait,
|
||||
$state, OrgJobTemplateList, Dataset, qs) {
|
||||
export default ['$scope', '$stateParams', 'Rest', 'GetBasePath', '$state', 'OrgJobTemplateList', 'OrgJobTemplateDataset',
|
||||
function($scope, $stateParams, Rest, GetBasePath, $state, OrgJobTemplateList, Dataset) {
|
||||
|
||||
var list = OrgJobTemplateList,
|
||||
orgBase = GetBasePath('organizations');
|
||||
|
||||
$scope.$on(`ws-jobs`, function () {
|
||||
let path = GetBasePath(list.basePath) || GetBasePath(list.name);
|
||||
qs.search(path, $state.params[`${list.iterator}_search`])
|
||||
.then(function(searchResponse) {
|
||||
$scope[`${list.iterator}_dataset`] = searchResponse.data;
|
||||
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
|
||||
});
|
||||
$scope.$on(`ws-jobs`, function (e, msg) {
|
||||
if (msg.unified_job_template_id && $scope[list.name]) {
|
||||
const template = $scope[list.name].find((t) => t.id === msg.unified_job_template_id);
|
||||
if (template) {
|
||||
if (msg.status === 'pending') {
|
||||
// This is a new job - add it to the front of the
|
||||
// recent_jobs array
|
||||
if (template.summary_fields.recent_jobs.length === 10) {
|
||||
template.summary_fields.recent_jobs.pop();
|
||||
}
|
||||
|
||||
template.summary_fields.recent_jobs.unshift({
|
||||
id: msg.unified_job_id,
|
||||
status: msg.status,
|
||||
type: msg.type
|
||||
});
|
||||
} else {
|
||||
// This is an update to an existing job. Check to see
|
||||
// if we have it in our array of recent_jobs
|
||||
for (let i=0; i<template.summary_fields.recent_jobs.length; i++) {
|
||||
const recentJob = template.summary_fields.recent_jobs[i];
|
||||
if (recentJob.id === msg.unified_job_id) {
|
||||
recentJob.status = msg.status;
|
||||
if (msg.finished) {
|
||||
recentJob.finished = msg.finished;
|
||||
template.last_job_run = msg.finished;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
init();
|
||||
|
||||
@@ -90,9 +90,9 @@ export default ['$scope', '$filter', 'i18n', 'JobsStrings',
|
||||
$scope.sparkArray = sparkData;
|
||||
$scope.placeholders = new Array(10 - sparkData.length);
|
||||
}
|
||||
$scope.$watchCollection('jobs', function(){
|
||||
$scope.$watch('jobs', function(){
|
||||
init();
|
||||
});
|
||||
}, true);
|
||||
|
||||
}];
|
||||
|
||||
|
||||
6252
awx/ui/po/es.po
6252
awx/ui/po/es.po
File diff suppressed because it is too large
Load Diff
6267
awx/ui/po/fr.po
6267
awx/ui/po/fr.po
File diff suppressed because it is too large
Load Diff
5623
awx/ui/po/ja.po
5623
awx/ui/po/ja.po
File diff suppressed because it is too large
Load Diff
6071
awx/ui/po/nl.po
6071
awx/ui/po/nl.po
File diff suppressed because it is too large
Load Diff
7944
awx/ui/po/zh.po
Normal file
7944
awx/ui/po/zh.po
Normal file
File diff suppressed because it is too large
Load Diff
@@ -138,6 +138,9 @@ Inside these folders, the internal structure is:
|
||||
- **/shared** - Components that are meant to be used specifically by a particular route, but might be sharable across pages of that route. For example, a form component which is used on both add and edit screens.
|
||||
- **/util** - Stateless helper functions that aren't tied to react.
|
||||
|
||||
### Patterns
|
||||
- A **screen** shouldn't import from another screen. If a component _needs_ to be shared between two or more screens, it is a generic and should be moved to `src/components`.
|
||||
|
||||
#### Bootstrapping the application (root src/ files)
|
||||
|
||||
In the root of `/src`, there are a few files which are used to initialize the react app. These are
|
||||
|
||||
2
awx/ui_next/dist/index.html
vendored
2
awx/ui_next/dist/index.html
vendored
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<div id="app" style="height: 100%"></div>
|
||||
<script src="/bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -11,6 +11,7 @@ module.exports = {
|
||||
'\\.(css|scss|less)$': '<rootDir>/__mocks__/styleMock.js',
|
||||
'^@api(.*)$': '<rootDir>/src/api$1',
|
||||
'^@components(.*)$': '<rootDir>/src/components$1',
|
||||
'^@constants$': '<rootDir>/src/constants.js',
|
||||
'^@contexts(.*)$': '<rootDir>/src/contexts$1',
|
||||
'^@screens(.*)$': '<rootDir>/src/screens$1',
|
||||
'^@util(.*)$': '<rootDir>/src/util$1',
|
||||
|
||||
262
awx/ui_next/package-lock.json
generated
262
awx/ui_next/package-lock.json
generated
@@ -4135,36 +4135,51 @@
|
||||
"dev": true
|
||||
},
|
||||
"@patternfly/patternfly": {
|
||||
"version": "2.56.3",
|
||||
"resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-2.56.3.tgz",
|
||||
"integrity": "sha512-merUreEz4ul84s+OXwJ27AtMtcBdzExDX+Xn/T84OD6OAgG8iU1x1dnWdFBeEFKOlTpKxwjXxS/Zc8tSxpfRBw=="
|
||||
"version": "2.66.0",
|
||||
"resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-2.66.0.tgz",
|
||||
"integrity": "sha512-fZMr2q9LZhVtKAEcDJ4rzcCGC6iN93mEQPoLlv2T9td5Hba1bLw8Bpgp5fdTm95Fv/++AY0PsdUPZUzh1cx7Sg=="
|
||||
},
|
||||
"@patternfly/react-core": {
|
||||
"version": "3.135.0",
|
||||
"resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-3.135.0.tgz",
|
||||
"integrity": "sha512-5UotimA2VUiYWt/v8j4x/z8MMyvbgXi7z7BKPzicIiKGwmREJCgQ+kf0eAqGUg/YjFLKcDKncoBrPNqVZ8Ykpg==",
|
||||
"version": "3.140.11",
|
||||
"resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-3.140.11.tgz",
|
||||
"integrity": "sha512-841DeN5BTuUS02JfVXAAVJYtWY0HWc4ewqMD32Xog2MAR/pn74jzjnQOSQr4LUyVrH5QufB68SK4Alm2+IUzSw==",
|
||||
"requires": {
|
||||
"@patternfly/react-icons": "^3.14.39",
|
||||
"@patternfly/react-styles": "^3.6.27",
|
||||
"@patternfly/react-tokens": "^2.7.25",
|
||||
"@patternfly/react-icons": "^3.15.3",
|
||||
"@patternfly/react-styles": "^3.7.4",
|
||||
"@patternfly/react-tokens": "^2.8.4",
|
||||
"emotion": "^9.2.9",
|
||||
"exenv": "^1.2.2",
|
||||
"focus-trap-react": "^4.0.1",
|
||||
"tippy.js": "5.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@patternfly/react-icons": {
|
||||
"version": "3.15.4",
|
||||
"resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-3.15.4.tgz",
|
||||
"integrity": "sha512-tOVirISoZDIn0bWYFctGN9B7Q8wQ19FaK4XIUD2sgIDRBzDbe9JWuqdef7ogJFF78eQnZNsWOci6nhvVCVF/zA==",
|
||||
"requires": {
|
||||
"@fortawesome/free-brands-svg-icons": "^5.8.1"
|
||||
}
|
||||
},
|
||||
"@patternfly/react-tokens": {
|
||||
"version": "2.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-2.8.4.tgz",
|
||||
"integrity": "sha512-GlLyutls0bG39Nwl/sv2FUkicwyRNrXQFso+e7Y4470+VOUtSsVSdQz+rTjgPxQ38olKPsSZdtEjqN9o2PbDiw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@patternfly/react-icons": {
|
||||
"version": "3.14.39",
|
||||
"resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-3.14.39.tgz",
|
||||
"integrity": "sha512-/1hhKEFRtvBYNa8BFRurqHdlUYYzdovmllwtEWcxye5lffDC1Ghco1NGQzjm0FtzkxX1hPFvw04HRR2jBBG8xQ==",
|
||||
"version": "3.15.4",
|
||||
"resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-3.15.4.tgz",
|
||||
"integrity": "sha512-tOVirISoZDIn0bWYFctGN9B7Q8wQ19FaK4XIUD2sgIDRBzDbe9JWuqdef7ogJFF78eQnZNsWOci6nhvVCVF/zA==",
|
||||
"requires": {
|
||||
"@fortawesome/free-brands-svg-icons": "^5.8.1"
|
||||
}
|
||||
},
|
||||
"@patternfly/react-styles": {
|
||||
"version": "3.6.27",
|
||||
"resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-3.6.27.tgz",
|
||||
"integrity": "sha512-XGI+lR0/QIqkGnGHcnzbynR9JImTftDztsXBgaB0FoDu/DmPY4EABgtsF9HvfoAjsIg8KxRs32tqoXN3yN0TWg==",
|
||||
"version": "3.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-3.7.4.tgz",
|
||||
"integrity": "sha512-D+wu0OIfWVgxWNShQhTK9cadw+KdMCoBYR8gbWjV9Q1aCsCEV/aL/x1nMyyaUQ3c2dqizHhujDG4z9jUZCmCcw==",
|
||||
"requires": {
|
||||
"camel-case": "^3.0.0",
|
||||
"css": "^2.2.3",
|
||||
@@ -4174,9 +4189,9 @@
|
||||
}
|
||||
},
|
||||
"@patternfly/react-tokens": {
|
||||
"version": "2.7.25",
|
||||
"resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-2.7.25.tgz",
|
||||
"integrity": "sha512-04hRDWt07pyjLUO1VN9QbrPpQMJzjd+nQYp8vgoe6+mYBzw+D4banJeudZ1oTFii9hWV+mLEu6aiwPtTigPM1Q=="
|
||||
"version": "2.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-2.8.4.tgz",
|
||||
"integrity": "sha512-GlLyutls0bG39Nwl/sv2FUkicwyRNrXQFso+e7Y4470+VOUtSsVSdQz+rTjgPxQ38olKPsSZdtEjqN9o2PbDiw=="
|
||||
},
|
||||
"@types/babel__core": {
|
||||
"version": "7.1.1",
|
||||
@@ -7047,9 +7062,9 @@
|
||||
}
|
||||
},
|
||||
"csstype": {
|
||||
"version": "2.6.8",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.8.tgz",
|
||||
"integrity": "sha512-msVS9qTuMT5zwAGCVm4mxfrZ18BNc6Csd0oJAtiFMZ1FAx1CCvy2+5MDmYoix63LM/6NDbNtodCiGYGmFgO0dA=="
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.9.tgz",
|
||||
"integrity": "sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q=="
|
||||
},
|
||||
"currently-unhandled": {
|
||||
"version": "0.4.1",
|
||||
@@ -7115,9 +7130,9 @@
|
||||
"integrity": "sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ=="
|
||||
},
|
||||
"d3-brush": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.1.3.tgz",
|
||||
"integrity": "sha512-v8bbYyCFKjyCzFk/tdWqXwDykY8YWqhXYjcYxfILIit085VZOpj4XJKOMccTsvWxgzSLMJQg5SiqHjslsipEDg==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.1.5.tgz",
|
||||
"integrity": "sha512-rEaJ5gHlgLxXugWjIkolTA0OyMvw8UWU1imYXy1v642XyyswmI1ybKOv05Ft+ewq+TFmdliD3VuK0pRp1VT/5A==",
|
||||
"requires": {
|
||||
"d3-dispatch": "1",
|
||||
"d3-drag": "1",
|
||||
@@ -7141,9 +7156,9 @@
|
||||
"integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A=="
|
||||
},
|
||||
"d3-color": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.3.0.tgz",
|
||||
"integrity": "sha512-NHODMBlj59xPAwl2BDiO2Mog6V+PrGRtBfWKqKRrs9MCqlSkIEb0Z/SfY7jW29ReHTDC/j+vwXhnZcXI3+3fbg=="
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.0.tgz",
|
||||
"integrity": "sha512-TzNPeJy2+iEepfiL92LAAB7fvnp/dV2YwANPVHdDWmYMm23qIJBYww3qT8I8C1wXrmrg4UWs7BKc2tKIgyjzHg=="
|
||||
},
|
||||
"d3-contour": {
|
||||
"version": "1.3.2",
|
||||
@@ -7154,23 +7169,23 @@
|
||||
}
|
||||
},
|
||||
"d3-dispatch": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.5.tgz",
|
||||
"integrity": "sha512-vwKx+lAqB1UuCeklr6Jh1bvC4SZgbSqbkGBLClItFBIYH4vqDJCA7qfoy14lXmJdnBOdxndAMxjCbImJYW7e6g=="
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz",
|
||||
"integrity": "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA=="
|
||||
},
|
||||
"d3-drag": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.4.tgz",
|
||||
"integrity": "sha512-ICPurDETFAelF1CTHdIyiUM4PsyZLaM+7oIBhmyP+cuVjze5vDZ8V//LdOFjg0jGnFIZD/Sfmk0r95PSiu78rw==",
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.5.tgz",
|
||||
"integrity": "sha512-rD1ohlkKQwMZYkQlYVCrSFxsWPzI97+W+PaEIBNTMxRuxz9RF0Hi5nJWHGVJ3Om9d2fRTe1yOBINJyy/ahV95w==",
|
||||
"requires": {
|
||||
"d3-dispatch": "1",
|
||||
"d3-selection": "1"
|
||||
}
|
||||
},
|
||||
"d3-dsv": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.1.1.tgz",
|
||||
"integrity": "sha512-1EH1oRGSkeDUlDRbhsFytAXU6cAmXFzc52YUe6MRlPClmWb85MP1J5x+YJRzya4ynZWnbELdSAvATFW/MbxaXw==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.2.0.tgz",
|
||||
"integrity": "sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g==",
|
||||
"requires": {
|
||||
"commander": "2",
|
||||
"iconv-lite": "0.4",
|
||||
@@ -7178,9 +7193,9 @@
|
||||
}
|
||||
},
|
||||
"d3-ease": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.5.tgz",
|
||||
"integrity": "sha512-Ct1O//ly5y5lFM9YTdu+ygq7LleSgSE4oj7vUt9tPLHUi8VCV7QoizGpdWRWAwCO9LdYzIrQDg97+hGVdsSGPQ=="
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.6.tgz",
|
||||
"integrity": "sha512-SZ/lVU7LRXafqp7XtIcBdxnWl8yyLpgOmzAk0mWBI9gXNzLDx5ybZgnRbH9dN/yY5tzVBqCQ9avltSnqVwessQ=="
|
||||
},
|
||||
"d3-fetch": {
|
||||
"version": "1.1.2",
|
||||
@@ -7202,45 +7217,45 @@
|
||||
}
|
||||
},
|
||||
"d3-format": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.1.tgz",
|
||||
"integrity": "sha512-TUswGe6hfguUX1CtKxyG2nymO+1lyThbkS1ifLX0Sr+dOQtAD5gkrffpHnx+yHNKUZ0Bmg5T4AjUQwugPDrm0g=="
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.2.tgz",
|
||||
"integrity": "sha512-gco1Ih54PgMsyIXgttLxEhNy/mXxq8+rLnCb5shQk+P5TsiySrwWU5gpB4zen626J4LIwBxHvDChyA8qDm57ww=="
|
||||
},
|
||||
"d3-geo": {
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.11.6.tgz",
|
||||
"integrity": "sha512-z0J8InXR9e9wcgNtmVnPTj0TU8nhYT6lD/ak9may2PdKqXIeHUr8UbFLoCtrPYNsjv6YaLvSDQVl578k6nm7GA==",
|
||||
"version": "1.11.9",
|
||||
"resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.11.9.tgz",
|
||||
"integrity": "sha512-9edcH6J3s/Aa3KJITWqFJbyB/8q3mMlA9Fi7z6yy+FAYMnRaxmC7jBhUnsINxVWD14GmqX3DK8uk7nV6/Ekt4A==",
|
||||
"requires": {
|
||||
"d3-array": "1"
|
||||
}
|
||||
},
|
||||
"d3-hierarchy": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.8.tgz",
|
||||
"integrity": "sha512-L+GHMSZNwTpiq4rt9GEsNcpLa4M96lXMR8M/nMG9p5hBE0jy6C+3hWtyZMenPQdwla249iJy7Nx0uKt3n+u9+w=="
|
||||
"version": "1.1.9",
|
||||
"resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz",
|
||||
"integrity": "sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ=="
|
||||
},
|
||||
"d3-interpolate": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.3.2.tgz",
|
||||
"integrity": "sha512-NlNKGopqaz9qM1PXh9gBF1KSCVh+jSFErrSlD/4hybwoNX/gt1d8CDbDW+3i+5UOHhjC6s6nMvRxcuoMVNgL2w==",
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz",
|
||||
"integrity": "sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==",
|
||||
"requires": {
|
||||
"d3-color": "1"
|
||||
}
|
||||
},
|
||||
"d3-path": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.8.tgz",
|
||||
"integrity": "sha512-J6EfUNwcMQ+aM5YPOB8ZbgAZu6wc82f/0WFxrxwV6Ll8wBwLaHLKCqQ5Imub02JriCVVdPjgI+6P3a4EWJCxAg=="
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz",
|
||||
"integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="
|
||||
},
|
||||
"d3-polygon": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.5.tgz",
|
||||
"integrity": "sha512-RHhh1ZUJZfhgoqzWWuRhzQJvO7LavchhitSTHGu9oj6uuLFzYZVeBzaWTQ2qSO6bz2w55RMoOCf0MsLCDB6e0w=="
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.6.tgz",
|
||||
"integrity": "sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ=="
|
||||
},
|
||||
"d3-quadtree": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.6.tgz",
|
||||
"integrity": "sha512-NUgeo9G+ENQCQ1LsRr2qJg3MQ4DJvxcDNCiohdJGHt5gRhBW6orIB5m5FJ9kK3HNL8g9F4ERVoBzcEwQBfXWVA=="
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.7.tgz",
|
||||
"integrity": "sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA=="
|
||||
},
|
||||
"d3-random": {
|
||||
"version": "1.1.2",
|
||||
@@ -7270,40 +7285,40 @@
|
||||
}
|
||||
},
|
||||
"d3-selection": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.0.tgz",
|
||||
"integrity": "sha512-EYVwBxQGEjLCKF2pJ4+yrErskDnz5v403qvAid96cNdCMr8rmCYfY5RGzWz24mdIbxmDf6/4EAH+K9xperD5jg=="
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.1.tgz",
|
||||
"integrity": "sha512-BTIbRjv/m5rcVTfBs4AMBLKs4x8XaaLkwm28KWu9S2vKNqXkXt2AH2Qf0sdPZHjFxcWg/YL53zcqAz+3g4/7PA=="
|
||||
},
|
||||
"d3-shape": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.5.tgz",
|
||||
"integrity": "sha512-VKazVR3phgD+MUCldapHD7P9kcrvPcexeX/PkMJmkUov4JM8IxsSg1DvbYoYich9AtdTsa5nNk2++ImPiDiSxg==",
|
||||
"version": "1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz",
|
||||
"integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==",
|
||||
"requires": {
|
||||
"d3-path": "1"
|
||||
}
|
||||
},
|
||||
"d3-time": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.0.11.tgz",
|
||||
"integrity": "sha512-Z3wpvhPLW4vEScGeIMUckDW7+3hWKOQfAWg/U7PlWBnQmeKQ00gCUsTtWSYulrKNA7ta8hJ+xXc6MHrMuITwEw=="
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz",
|
||||
"integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA=="
|
||||
},
|
||||
"d3-time-format": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.1.3.tgz",
|
||||
"integrity": "sha512-6k0a2rZryzGm5Ihx+aFMuO1GgelgIz+7HhB4PH4OEndD5q2zGn1mDfRdNrulspOfR6JXkb2sThhDK41CSK85QA==",
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.2.2.tgz",
|
||||
"integrity": "sha512-pweL2Ri2wqMY+wlW/wpkl8T3CUzKAha8S9nmiQlMABab8r5MJN0PD1V4YyRNVaKQfeh4Z0+VO70TLw6ESVOYzw==",
|
||||
"requires": {
|
||||
"d3-time": "1"
|
||||
}
|
||||
},
|
||||
"d3-timer": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.9.tgz",
|
||||
"integrity": "sha512-rT34J5HnQUHhcLvhSB9GjCkN0Ddd5Y8nCwDBG2u6wQEeYxT/Lf51fTFFkldeib/sE/J0clIe0pnCfs6g/lRbyg=="
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz",
|
||||
"integrity": "sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw=="
|
||||
},
|
||||
"d3-transition": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.2.0.tgz",
|
||||
"integrity": "sha512-VJ7cmX/FPIPJYuaL2r1o1EMHLttvoIuZhhuAlRoOxDzogV8iQS6jYulDm3xEU3TqL80IZIhI551/ebmCMrkvhw==",
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.3.2.tgz",
|
||||
"integrity": "sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==",
|
||||
"requires": {
|
||||
"d3-color": "1",
|
||||
"d3-dispatch": "1",
|
||||
@@ -9367,8 +9382,7 @@
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.2.0",
|
||||
@@ -9389,14 +9403,12 @@
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -9411,20 +9423,17 @@
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
@@ -9541,8 +9550,7 @@
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
@@ -9554,7 +9562,6 @@
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
@@ -9569,7 +9576,6 @@
|
||||
"version": "3.0.4",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
@@ -9577,14 +9583,12 @@
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.3.5",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.0"
|
||||
@@ -9603,7 +9607,6 @@
|
||||
"version": "0.5.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
@@ -9684,8 +9687,7 @@
|
||||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
@@ -9697,7 +9699,6 @@
|
||||
"version": "1.4.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
@@ -9783,8 +9784,7 @@
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
@@ -9820,7 +9820,6 @@
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
@@ -9840,7 +9839,6 @@
|
||||
"version": "3.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
@@ -9884,14 +9882,12 @@
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.0.3",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -10090,11 +10086,11 @@
|
||||
"dev": true
|
||||
},
|
||||
"graphlib": {
|
||||
"version": "2.1.7",
|
||||
"resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.7.tgz",
|
||||
"integrity": "sha512-TyI9jIy2J4j0qgPmOOrHTCtpPqJGN/aurBwc6ZT+bRii+di1I+Wv3obRhVrmBEXet+qkMaEX67dXrwsd3QQM6w==",
|
||||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz",
|
||||
"integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==",
|
||||
"requires": {
|
||||
"lodash": "^4.17.5"
|
||||
"lodash": "^4.17.15"
|
||||
}
|
||||
},
|
||||
"growly": {
|
||||
@@ -13206,6 +13202,12 @@
|
||||
"yallist": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"luxon": {
|
||||
"version": "1.22.0",
|
||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-1.22.0.tgz",
|
||||
"integrity": "sha512-3sLvlfbFo+AxVEY3IqxymbumtnlgBwjDExxK60W3d+trrUzErNAz/PfvPT+mva+vEUrdIodeCOs7fB6zHtRSrw==",
|
||||
"optional": true
|
||||
},
|
||||
"make-dir": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
|
||||
@@ -15374,9 +15376,9 @@
|
||||
}
|
||||
},
|
||||
"react": {
|
||||
"version": "16.10.2",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-16.10.2.tgz",
|
||||
"integrity": "sha512-MFVIq0DpIhrHFyqLU0S3+4dIcBhhOvBE8bJ/5kHPVOVaGdo0KuiQzpcjCPsf585WvhypqtrMILyoE2th6dT+Lw==",
|
||||
"version": "16.13.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-16.13.0.tgz",
|
||||
"integrity": "sha512-TSavZz2iSLkq5/oiE7gnFzmURKZMltmi193rm5HEoUDAXpzT9Kzw6oNZnGoai/4+fUnm7FqS5dwgUL34TujcWQ==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
@@ -15389,20 +15391,20 @@
|
||||
"integrity": "sha512-D7y9qZ05FbUh9blqECaJMdDwKluQiO3A9xB+fssd5jKM7YAXucRuEOlX32mJQumUvHUkHRHqXIPBjm6g0FW0Ag=="
|
||||
},
|
||||
"react-dom": {
|
||||
"version": "16.10.2",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.10.2.tgz",
|
||||
"integrity": "sha512-kWGDcH3ItJK4+6Pl9DZB16BXYAZyrYQItU4OMy0jAkv5aNqc+mAKb4TpFtAteI6TJZu+9ZlNhaeNQSVQDHJzkw==",
|
||||
"version": "16.13.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.0.tgz",
|
||||
"integrity": "sha512-y09d2c4cG220DzdlFkPTnVvGTszVvNpC73v+AaLGLHbkpy3SSgvYq8x0rNwPJ/Rk/CicTNgk0hbHNw1gMEZAXg==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.16.2"
|
||||
"scheduler": "^0.19.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"scheduler": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.16.2.tgz",
|
||||
"integrity": "sha512-BqYVWqwz6s1wZMhjFvLfVR5WXP7ZY32M/wYPo04CcuPM7XZEbV2TBNW7Z0UkguPTl0dWMA59VbNXxK6q+pHItg==",
|
||||
"version": "0.19.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.0.tgz",
|
||||
"integrity": "sha512-xowbVaTPe9r7y7RUejcK73/j8tt2jfiyTednOvHbA8JoClvMYCp+r8QegLwK/n8zWQAtZb1fFnER4XLBZXrCxA==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1"
|
||||
@@ -16221,6 +16223,22 @@
|
||||
"inherits": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"rrule": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/rrule/-/rrule-2.6.4.tgz",
|
||||
"integrity": "sha512-sLdnh4lmjUqq8liFiOUXD5kWp/FcnbDLPwq5YAc/RrN6120XOPb86Ae5zxF7ttBVq8O3LxjjORMEit1baluahA==",
|
||||
"requires": {
|
||||
"luxon": "^1.21.3",
|
||||
"tslib": "^1.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz",
|
||||
"integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"rst-selector-parser": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz",
|
||||
|
||||
@@ -58,10 +58,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@lingui/react": "^2.7.2",
|
||||
"@patternfly/patternfly": "^2.56.3",
|
||||
"@patternfly/react-core": "^3.135.0",
|
||||
"@patternfly/react-icons": "^3.14.39",
|
||||
"@patternfly/react-tokens": "^2.7.25",
|
||||
"@patternfly/patternfly": "^2.66.0",
|
||||
"@patternfly/react-core": "^3.140.11",
|
||||
"@patternfly/react-icons": "^3.15.4",
|
||||
"@patternfly/react-tokens": "^2.8.4",
|
||||
"ansi-to-html": "^0.6.11",
|
||||
"axios": "^0.18.1",
|
||||
"codemirror": "^5.47.0",
|
||||
@@ -72,11 +72,12 @@
|
||||
"html-entities": "^1.2.1",
|
||||
"js-yaml": "^3.13.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.10.2",
|
||||
"react": "^16.13.0",
|
||||
"react-codemirror2": "^6.0.0",
|
||||
"react-dom": "^16.10.2",
|
||||
"react-dom": "^16.13.0",
|
||||
"react-router-dom": "^5.1.2",
|
||||
"react-virtualized": "^9.21.1",
|
||||
"rrule": "^2.6.4",
|
||||
"styled-components": "^4.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,8 +163,9 @@ class App extends Component {
|
||||
const sidebar = (
|
||||
<PageSidebar
|
||||
isNavOpen={isNavOpen}
|
||||
theme="dark"
|
||||
nav={
|
||||
<Nav aria-label={navLabel}>
|
||||
<Nav aria-label={navLabel} theme="dark">
|
||||
<NavList>
|
||||
{routeGroups.map(({ groupId, groupTitle, routes }) => (
|
||||
<NavExpandableGroup
|
||||
@@ -204,7 +205,7 @@ class App extends Component {
|
||||
/>
|
||||
<AlertModal
|
||||
isOpen={configError}
|
||||
variant="danger"
|
||||
variant="error"
|
||||
title={i18n._(t`Error!`)}
|
||||
onClose={this.handleConfigErrorClose}
|
||||
>
|
||||
|
||||
@@ -17,12 +17,15 @@ import Organizations from './models/Organizations';
|
||||
import Projects from './models/Projects';
|
||||
import ProjectUpdates from './models/ProjectUpdates';
|
||||
import Root from './models/Root';
|
||||
import Schedules from './models/Schedules';
|
||||
import SystemJobs from './models/SystemJobs';
|
||||
import Teams from './models/Teams';
|
||||
import UnifiedJobTemplates from './models/UnifiedJobTemplates';
|
||||
import UnifiedJobs from './models/UnifiedJobs';
|
||||
import Users from './models/Users';
|
||||
import WorkflowApprovalTemplates from './models/WorkflowApprovalTemplates';
|
||||
import WorkflowJobs from './models/WorkflowJobs';
|
||||
import WorkflowJobTemplateNodes from './models/WorkflowJobTemplateNodes';
|
||||
import WorkflowJobTemplates from './models/WorkflowJobTemplates';
|
||||
|
||||
const AdHocCommandsAPI = new AdHocCommands();
|
||||
@@ -44,12 +47,15 @@ const OrganizationsAPI = new Organizations();
|
||||
const ProjectsAPI = new Projects();
|
||||
const ProjectUpdatesAPI = new ProjectUpdates();
|
||||
const RootAPI = new Root();
|
||||
const SchedulesAPI = new Schedules();
|
||||
const SystemJobsAPI = new SystemJobs();
|
||||
const TeamsAPI = new Teams();
|
||||
const UnifiedJobTemplatesAPI = new UnifiedJobTemplates();
|
||||
const UnifiedJobsAPI = new UnifiedJobs();
|
||||
const UsersAPI = new Users();
|
||||
const WorkflowApprovalTemplatesAPI = new WorkflowApprovalTemplates();
|
||||
const WorkflowJobsAPI = new WorkflowJobs();
|
||||
const WorkflowJobTemplateNodesAPI = new WorkflowJobTemplateNodes();
|
||||
const WorkflowJobTemplatesAPI = new WorkflowJobTemplates();
|
||||
|
||||
export {
|
||||
@@ -72,11 +78,14 @@ export {
|
||||
ProjectsAPI,
|
||||
ProjectUpdatesAPI,
|
||||
RootAPI,
|
||||
SchedulesAPI,
|
||||
SystemJobsAPI,
|
||||
TeamsAPI,
|
||||
UnifiedJobTemplatesAPI,
|
||||
UnifiedJobsAPI,
|
||||
UsersAPI,
|
||||
WorkflowApprovalTemplatesAPI,
|
||||
WorkflowJobsAPI,
|
||||
WorkflowJobTemplateNodesAPI,
|
||||
WorkflowJobTemplatesAPI,
|
||||
};
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
const InstanceGroupsMixin = parent =>
|
||||
class extends parent {
|
||||
readInstanceGroups(resourceId, params) {
|
||||
return this.http.get(
|
||||
`${this.baseUrl}${resourceId}/instance_groups/`,
|
||||
params
|
||||
);
|
||||
return this.http.get(`${this.baseUrl}${resourceId}/instance_groups/`, {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
associateInstanceGroup(resourceId, instanceGroupId) {
|
||||
|
||||
12
awx/ui_next/src/api/mixins/Schedules.mixin.js
Normal file
12
awx/ui_next/src/api/mixins/Schedules.mixin.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const SchedulesMixin = parent =>
|
||||
class extends parent {
|
||||
readSchedules(id, params) {
|
||||
return this.http.get(`${this.baseUrl}${id}/schedules/`, { params });
|
||||
}
|
||||
|
||||
readScheduleOptions(id) {
|
||||
return this.http.options(`${this.baseUrl}${id}/schedules/`);
|
||||
}
|
||||
};
|
||||
|
||||
export default SchedulesMixin;
|
||||
@@ -4,6 +4,14 @@ class Credentials extends Base {
|
||||
constructor(http) {
|
||||
super(http);
|
||||
this.baseUrl = '/api/v2/credentials/';
|
||||
|
||||
this.readAccessList = this.readAccessList.bind(this);
|
||||
}
|
||||
|
||||
readAccessList(id, params) {
|
||||
return this.http.get(`${this.baseUrl}${id}/access_list/`, {
|
||||
params,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,25 @@ class Groups extends Base {
|
||||
constructor(http) {
|
||||
super(http);
|
||||
this.baseUrl = '/api/v2/groups/';
|
||||
|
||||
this.createHost = this.createHost.bind(this);
|
||||
this.readAllHosts = this.readAllHosts.bind(this);
|
||||
this.disassociateHost = this.disassociateHost.bind(this);
|
||||
}
|
||||
|
||||
createHost(id, data) {
|
||||
return this.http.post(`${this.baseUrl}${id}/hosts/`, data);
|
||||
}
|
||||
|
||||
readAllHosts(id, params) {
|
||||
return this.http.get(`${this.baseUrl}${id}/all_hosts/`, { params });
|
||||
}
|
||||
|
||||
disassociateHost(id, host) {
|
||||
return this.http.post(`${this.baseUrl}${id}/hosts/`, {
|
||||
id: host.id,
|
||||
disassociate: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ class Inventories extends InstanceGroupsMixin(Base) {
|
||||
|
||||
this.readAccessList = this.readAccessList.bind(this);
|
||||
this.readHosts = this.readHosts.bind(this);
|
||||
this.readHostDetail = this.readHostDetail.bind(this);
|
||||
this.readGroups = this.readGroups.bind(this);
|
||||
this.readGroupsOptions = this.readGroupsOptions.bind(this);
|
||||
this.promoteGroup = this.promoteGroup.bind(this);
|
||||
@@ -27,6 +28,22 @@ class Inventories extends InstanceGroupsMixin(Base) {
|
||||
return this.http.get(`${this.baseUrl}${id}/hosts/`, { params });
|
||||
}
|
||||
|
||||
async readHostDetail(inventoryId, hostId) {
|
||||
const {
|
||||
data: { results },
|
||||
} = await this.http.get(
|
||||
`${this.baseUrl}${inventoryId}/hosts/?id=${hostId}`
|
||||
);
|
||||
|
||||
if (Array.isArray(results) && results.length) {
|
||||
return results[0];
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`How did you get here? Host not found for Inventory ID: ${inventoryId}`
|
||||
);
|
||||
}
|
||||
|
||||
readGroups(id, params) {
|
||||
return this.http.get(`${this.baseUrl}${id}/groups/`, { params });
|
||||
}
|
||||
@@ -35,6 +52,10 @@ class Inventories extends InstanceGroupsMixin(Base) {
|
||||
return this.http.options(`${this.baseUrl}${id}/groups/`);
|
||||
}
|
||||
|
||||
readHostsOptions(id) {
|
||||
return this.http.options(`${this.baseUrl}${id}/hosts/`);
|
||||
}
|
||||
|
||||
promoteGroup(inventoryId, groupId) {
|
||||
return this.http.post(`${this.baseUrl}${inventoryId}/groups/`, {
|
||||
id: groupId,
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import Base from '../Base';
|
||||
import NotificationsMixin from '../mixins/Notifications.mixin';
|
||||
import InstanceGroupsMixin from '../mixins/InstanceGroups.mixin';
|
||||
import SchedulesMixin from '../mixins/Schedules.mixin';
|
||||
|
||||
class JobTemplates extends InstanceGroupsMixin(NotificationsMixin(Base)) {
|
||||
class JobTemplates extends SchedulesMixin(
|
||||
InstanceGroupsMixin(NotificationsMixin(Base))
|
||||
) {
|
||||
constructor(http) {
|
||||
super(http);
|
||||
this.baseUrl = '/api/v2/job_templates/';
|
||||
@@ -61,6 +64,24 @@ class JobTemplates extends InstanceGroupsMixin(NotificationsMixin(Base)) {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
readScheduleList(id, params) {
|
||||
return this.http.get(`${this.baseUrl}${id}/schedules/`, {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
readSurvey(id) {
|
||||
return this.http.get(`${this.baseUrl}${id}/survey_spec/`);
|
||||
}
|
||||
|
||||
updateSurvey(id, survey = null) {
|
||||
return this.http.post(`${this.baseUrl}${id}/survey_spec/`, survey);
|
||||
}
|
||||
|
||||
destroySurvey(id) {
|
||||
return this.http.delete(`${this.baseUrl}${id}/survey_spec/`);
|
||||
}
|
||||
}
|
||||
|
||||
export default JobTemplates;
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import Base from '../Base';
|
||||
import NotificationsMixin from '../mixins/Notifications.mixin';
|
||||
import LaunchUpdateMixin from '../mixins/LaunchUpdate.mixin';
|
||||
import SchedulesMixin from '../mixins/Schedules.mixin';
|
||||
|
||||
class Projects extends LaunchUpdateMixin(NotificationsMixin(Base)) {
|
||||
class Projects extends SchedulesMixin(
|
||||
LaunchUpdateMixin(NotificationsMixin(Base))
|
||||
) {
|
||||
constructor(http) {
|
||||
super(http);
|
||||
this.baseUrl = '/api/v2/projects/';
|
||||
|
||||
18
awx/ui_next/src/api/models/Schedules.js
Normal file
18
awx/ui_next/src/api/models/Schedules.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import Base from '../Base';
|
||||
|
||||
class Schedules extends Base {
|
||||
constructor(http) {
|
||||
super(http);
|
||||
this.baseUrl = '/api/v2/schedules/';
|
||||
}
|
||||
|
||||
createPreview(data) {
|
||||
return this.http.post(`${this.baseUrl}preview/`, data);
|
||||
}
|
||||
|
||||
readCredentials(resourceId, params) {
|
||||
return this.http.get(`${this.baseUrl}${resourceId}/credentials/`, params);
|
||||
}
|
||||
}
|
||||
|
||||
export default Schedules;
|
||||
10
awx/ui_next/src/api/models/WorkflowApprovalTemplates.js
Normal file
10
awx/ui_next/src/api/models/WorkflowApprovalTemplates.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import Base from '../Base';
|
||||
|
||||
class WorkflowApprovalTemplates extends Base {
|
||||
constructor(http) {
|
||||
super(http);
|
||||
this.baseUrl = '/api/v2/workflow_approval_templates/';
|
||||
}
|
||||
}
|
||||
|
||||
export default WorkflowApprovalTemplates;
|
||||
60
awx/ui_next/src/api/models/WorkflowJobTemplateNodes.js
Normal file
60
awx/ui_next/src/api/models/WorkflowJobTemplateNodes.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import Base from '../Base';
|
||||
|
||||
class WorkflowJobTemplateNodes extends Base {
|
||||
constructor(http) {
|
||||
super(http);
|
||||
this.baseUrl = '/api/v2/workflow_job_template_nodes/';
|
||||
}
|
||||
|
||||
createApprovalTemplate(id, data) {
|
||||
return this.http.post(
|
||||
`${this.baseUrl}${id}/create_approval_template/`,
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
associateSuccessNode(id, idToAssociate) {
|
||||
return this.http.post(`${this.baseUrl}${id}/success_nodes/`, {
|
||||
id: idToAssociate,
|
||||
});
|
||||
}
|
||||
|
||||
associateFailureNode(id, idToAssociate) {
|
||||
return this.http.post(`${this.baseUrl}${id}/failure_nodes/`, {
|
||||
id: idToAssociate,
|
||||
});
|
||||
}
|
||||
|
||||
associateAlwaysNode(id, idToAssociate) {
|
||||
return this.http.post(`${this.baseUrl}${id}/always_nodes/`, {
|
||||
id: idToAssociate,
|
||||
});
|
||||
}
|
||||
|
||||
disassociateSuccessNode(id, idToDissociate) {
|
||||
return this.http.post(`${this.baseUrl}${id}/success_nodes/`, {
|
||||
id: idToDissociate,
|
||||
disassociate: true,
|
||||
});
|
||||
}
|
||||
|
||||
disassociateFailuresNode(id, idToDissociate) {
|
||||
return this.http.post(`${this.baseUrl}${id}/failure_nodes/`, {
|
||||
id: idToDissociate,
|
||||
disassociate: true,
|
||||
});
|
||||
}
|
||||
|
||||
disassociateAlwaysNode(id, idToDissociate) {
|
||||
return this.http.post(`${this.baseUrl}${id}/always_nodes/`, {
|
||||
id: idToDissociate,
|
||||
disassociate: true,
|
||||
});
|
||||
}
|
||||
|
||||
readCredentials(id) {
|
||||
return this.http.get(`${this.baseUrl}${id}/credentials/`);
|
||||
}
|
||||
}
|
||||
|
||||
export default WorkflowJobTemplateNodes;
|
||||
@@ -1,13 +1,55 @@
|
||||
import Base from '../Base';
|
||||
import SchedulesMixin from '../mixins/Schedules.mixin';
|
||||
import NotificationsMixin from '../mixins/Notifications.mixin';
|
||||
|
||||
class WorkflowJobTemplates extends Base {
|
||||
class WorkflowJobTemplates extends SchedulesMixin(NotificationsMixin(Base)) {
|
||||
constructor(http) {
|
||||
super(http);
|
||||
this.baseUrl = '/api/v2/workflow_job_templates/';
|
||||
}
|
||||
|
||||
readWebhookKey(id) {
|
||||
return this.http.get(`${this.baseUrl}${id}/webhook_key/`);
|
||||
}
|
||||
|
||||
updateWebhookKey(id) {
|
||||
return this.http.post(`${this.baseUrl}${id}/webhook_key/`);
|
||||
}
|
||||
|
||||
associateLabel(id, label, orgId) {
|
||||
return this.http.post(`${this.baseUrl}${id}/labels/`, {
|
||||
name: label.name,
|
||||
organization: orgId,
|
||||
});
|
||||
}
|
||||
|
||||
createNode(id, data) {
|
||||
return this.http.post(`${this.baseUrl}${id}/workflow_nodes/`, data);
|
||||
}
|
||||
|
||||
disassociateLabel(id, label) {
|
||||
return this.http.post(`${this.baseUrl}${id}/labels/`, {
|
||||
id: label.id,
|
||||
disassociate: true,
|
||||
});
|
||||
}
|
||||
|
||||
launch(id, data) {
|
||||
return this.http.post(`${this.baseUrl}${id}/launch/`, data);
|
||||
}
|
||||
|
||||
readLaunch(id) {
|
||||
return this.http.get(`${this.baseUrl}${id}/launch/`);
|
||||
}
|
||||
|
||||
readNodes(id, params) {
|
||||
return this.http.get(`${this.baseUrl}${id}/workflow_nodes/`, { params });
|
||||
return this.http.get(`${this.baseUrl}${id}/workflow_nodes/`, {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
readAccessList(id, params) {
|
||||
return this.http.get(`${this.baseUrl}${id}/access_list/`, { params });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,10 @@ class WorkflowJobs extends RelaunchMixin(Base) {
|
||||
super(http);
|
||||
this.baseUrl = '/api/v2/workflow_jobs/';
|
||||
}
|
||||
|
||||
readNodes(id, params) {
|
||||
return this.http.get(`${this.baseUrl}${id}/workflow_nodes/`, { params });
|
||||
}
|
||||
}
|
||||
|
||||
export default WorkflowJobs;
|
||||
|
||||
@@ -1,256 +0,0 @@
|
||||
// https://github.com/patternfly/patternfly-react/issues/1294
|
||||
#app {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
//
|
||||
// sidebar overrides
|
||||
//
|
||||
|
||||
.pf-c-page__sidebar {
|
||||
--pf-c-page__sidebar--md--Width: 255px;
|
||||
|
||||
.pf-c-nav {
|
||||
overflow-y: auto;
|
||||
|
||||
.pf-c-nav__section {
|
||||
--pf-c-nav__section--MarginTop: 8px;
|
||||
}
|
||||
|
||||
.pf-c-nav__section + .pf-c-nav__section {
|
||||
--pf-c-nav__section--MarginTop: 8px;
|
||||
}
|
||||
|
||||
.pf-c-nav__simple-list .pf-c-nav__link {
|
||||
--pf-c-nav__simple-list-link--PaddingBottom: 6px;
|
||||
--pf-c-nav__simple-list-link--PaddingTop: 6px;
|
||||
}
|
||||
|
||||
.pf-c-nav__section-title {
|
||||
--pf-c-nav__section-title--PaddingLeft: 24px;
|
||||
}
|
||||
|
||||
.pf-c-nav__link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-right: 64px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// data list overrides
|
||||
//
|
||||
|
||||
.pf-c-data-list {
|
||||
--pf-global--target-size--MinHeight: 32px;
|
||||
--pf-global--target-size--MinWidth: 32px;
|
||||
--pf-global--FontSize--md: 14px;
|
||||
|
||||
.pf-c-badge:not(:last-child),
|
||||
.pf-c-switch:not(:last-child) {
|
||||
margin-right: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.pf-c-data-list__item-row {
|
||||
--pf-c-data-list__item-row--PaddingRight: 20px;
|
||||
--pf-c-data-list__item-row--PaddingLeft: 20px;
|
||||
}
|
||||
|
||||
.pf-c-data-list__item-content {
|
||||
--pf-c-data-list__item-content--PaddingBottom: 16px;
|
||||
|
||||
min-height: 59px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pf-c-data-list__item-control {
|
||||
--pf-c-data-list__item-control--PaddingTop: 16px;
|
||||
--pf-c-data-list__item-control--MarginRight: 8px;
|
||||
--pf-c-data-list__item-control--PaddingBottom: 16px;
|
||||
}
|
||||
|
||||
.pf-c-data-list__item {
|
||||
--pf-c-data-list__item--PaddingLeft: 20px;
|
||||
--pf-c-data-list__item--PaddingRight: 20px;
|
||||
}
|
||||
|
||||
.pf-c-data-list__cell {
|
||||
--pf-c-data-list__cell--PaddingTop: 16px;
|
||||
--pf-c-data-list__cell-cell--PaddingTop: 16px;
|
||||
|
||||
&.pf-c-data-list__cell--divider {
|
||||
--pf-c-data-list__cell-cell--MarginRight: 0;
|
||||
--pf-c-data-list__cell--PaddingTop: 12px;
|
||||
flex-grow: 0;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// pf modal overrides
|
||||
//
|
||||
|
||||
.awx-c-modal.pf-c-modal-box {
|
||||
margin: 0;
|
||||
padding: 24px;
|
||||
width: 600px;
|
||||
|
||||
.pf-c-modal-box__body {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.pf-c-modal-box__footer > .pf-c-button:not(:last-child) {
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.pf-c-modal-box__footer {
|
||||
--pf-c-modal-box__footer--PaddingTop: 20px;
|
||||
--pf-c-modal-box__footer--PaddingRight: 20px;
|
||||
--pf-c-modal-box__footer--PaddingBottom: 20px;
|
||||
--pf-c-modal-box__footer--PaddingLeft: 20px;
|
||||
--pf-c-modal-box__footer--MarginTop: 24px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.pf-c-modal-box__header {
|
||||
--pf-c-modal-box__header--PaddingTop: 10px;
|
||||
--pf-c-modal-box__header--PaddingRight: 0;
|
||||
--pf-c-modal-box__header--PaddingBottom: 0;
|
||||
--pf-c-modal-box__header--PaddingLeft: 20px;
|
||||
}
|
||||
|
||||
.pf-c-modal-box__body {
|
||||
--pf-c-modal-box__body--PaddingLeft: 20px;
|
||||
--pf-c-modal-box__body--PaddingRight: 20px;
|
||||
--pf-c-modal-box__body--PaddingBottom: 5px;
|
||||
}
|
||||
|
||||
//
|
||||
// pf tooltip overrides
|
||||
//
|
||||
|
||||
.pf-c-tooltip__content {
|
||||
--pf-c-tooltip__content--PaddingTop: 0.71rem;
|
||||
--pf-c-tooltip__content--PaddingRight: 0.71rem;
|
||||
--pf-c-tooltip__content--PaddingBottom: 0.71rem;
|
||||
--pf-c-tooltip__content--PaddingLeft: 0.71rem;
|
||||
}
|
||||
// higher specificity needed to override PF styles added dynamically to page
|
||||
.pf-c-tooltip .pf-c-tooltip__content {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
//
|
||||
// pf empty state overrides
|
||||
//
|
||||
|
||||
.pf-c-empty-state {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
//
|
||||
// assorted custom component styles
|
||||
// note that these should be given a consistent prefix
|
||||
// and bem style, as well as moved into component-based scss files
|
||||
//
|
||||
|
||||
.awx-c-card {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
//
|
||||
// PF Alert notification component overrides
|
||||
//
|
||||
|
||||
.pf-c-alert__title {
|
||||
--pf-c-alert__title--PaddingTop: 20px;
|
||||
--pf-c-alert__title--PaddingRight: 20px;
|
||||
--pf-c-alert__title--PaddingBottom: 20px;
|
||||
--pf-c-alert__title--PaddingLeft: 20px;
|
||||
}
|
||||
|
||||
.pf-c-alert__description {
|
||||
--pf-c-alert__description--PaddingRight: 20px;
|
||||
--pf-c-alert__description--PaddingBottom: 20px;
|
||||
--pf-c-alert__description--PaddingLeft: 20px;
|
||||
}
|
||||
|
||||
.pf-c-alert {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
//
|
||||
// AlertModal styles
|
||||
//
|
||||
|
||||
.at-c-alertModal.pf-c-modal-box {
|
||||
border: 0;
|
||||
border-left: 56px solid black;
|
||||
|
||||
.at-c-alertModal__icon {
|
||||
position: absolute;
|
||||
font-size: 23px;
|
||||
top: 28px;
|
||||
left: -39px;
|
||||
}
|
||||
}
|
||||
|
||||
.at-c-alertModal--warning.pf-c-modal-box {
|
||||
border-color: var(--pf-global--warning-color--100);
|
||||
|
||||
.pf-c-title {
|
||||
color: var(--pf-global--warning-color--200);
|
||||
}
|
||||
|
||||
.at-c-alertModal__icon {
|
||||
color: var(--pf-global--warning-color--200);
|
||||
}
|
||||
}
|
||||
|
||||
.at-c-alertModal--danger.pf-c-modal-box {
|
||||
border-color: var(--pf-global--danger-color--100);
|
||||
|
||||
.pf-c-title {
|
||||
color: var(--pf-global--danger-color--200);
|
||||
}
|
||||
|
||||
.at-c-alertModal__icon {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.at-c-alertModal--info.pf-c-modal-box {
|
||||
border-color: var(--pf-global--info-color--100);
|
||||
|
||||
.pf-c-title {
|
||||
color: var(--pf-global--info-color--200);
|
||||
}
|
||||
|
||||
.at-c-alertModal__icon {
|
||||
color: var(--pf-global--info-color--200);
|
||||
}
|
||||
}
|
||||
|
||||
.at-c-alertModal--success.pf-c-modal-box {
|
||||
border-color: var(--pf-global--success-color--100);
|
||||
|
||||
.pf-c-title {
|
||||
color: var(--pf-global--success-color--200);
|
||||
}
|
||||
|
||||
.at-c-alertModal__icon {
|
||||
color: var(--pf-global--success-color--200);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// LoginModal overrides
|
||||
//
|
||||
|
||||
.pf-m-error p.pf-c-form__helper-text {
|
||||
color: var(--pf-global--danger-color--100);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import DataListCell from '@components/DataListCell';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const ActionButtonCell = styled(DataListCell)`
|
||||
& > :not(:first-child) {
|
||||
margin-left: 20px;
|
||||
}
|
||||
`;
|
||||
ActionButtonCell.displayName = 'ActionButtonCell';
|
||||
export default ActionButtonCell;
|
||||
@@ -1,10 +0,0 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import ActionButtonCell from './ActionButtonCell';
|
||||
|
||||
describe('ActionButtonCell', () => {
|
||||
test('renders the expected content', () => {
|
||||
const wrapper = mount(<ActionButtonCell />);
|
||||
expect(wrapper).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './ActionButtonCell';
|
||||
@@ -2,10 +2,10 @@ import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { Wizard } from '@patternfly/react-core';
|
||||
import SelectableCard from '@components/SelectableCard';
|
||||
import Wizard from '@components/Wizard';
|
||||
import SelectResourceStep from './SelectResourceStep';
|
||||
import SelectRoleStep from './SelectRoleStep';
|
||||
import SelectableCard from './SelectableCard';
|
||||
import { TeamsAPI, UsersAPI } from '../../api';
|
||||
|
||||
const readUsers = async queryParams =>
|
||||
|
||||
@@ -85,7 +85,6 @@ class SelectResourceStep extends React.Component {
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{isLoading && <div>{i18n._(t`Loading...`)}</div>}
|
||||
{isInitialized && (
|
||||
<Fragment>
|
||||
<div>
|
||||
@@ -102,6 +101,7 @@ class SelectResourceStep extends React.Component {
|
||||
/>
|
||||
)}
|
||||
<PaginatedDataList
|
||||
hasContentLoading={isLoading}
|
||||
items={resources}
|
||||
itemCount={count}
|
||||
qsConfig={this.qsConfig}
|
||||
|
||||
@@ -77,7 +77,7 @@ describe('<SelectResourceStep />', () => {
|
||||
'/organizations/1/access?resource.page=1&resource.order_by=-username',
|
||||
],
|
||||
});
|
||||
const wrapper = await mountWithContexts(
|
||||
const wrapper = mountWithContexts(
|
||||
<SelectResourceStep
|
||||
searchColumns={searchColumns}
|
||||
sortColumns={sortColumns}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export { default as AddResourceRole } from './AddResourceRole';
|
||||
export { default as CheckboxCard } from './CheckboxCard';
|
||||
export { default as SelectableCard } from './SelectableCard';
|
||||
export { default as SelectResourceStep } from './SelectResourceStep';
|
||||
export { default as SelectRoleStep } from './SelectRoleStep';
|
||||
|
||||
@@ -1,41 +1,78 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Modal } from '@patternfly/react-core';
|
||||
|
||||
import { Modal, Title } from '@patternfly/react-core';
|
||||
import {
|
||||
ExclamationTriangleIcon,
|
||||
ExclamationCircleIcon,
|
||||
InfoCircleIcon,
|
||||
CheckCircleIcon,
|
||||
ExclamationCircleIcon,
|
||||
ExclamationTriangleIcon,
|
||||
InfoCircleIcon,
|
||||
TimesCircleIcon,
|
||||
} from '@patternfly/react-icons';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const getIcon = variant => {
|
||||
let icon;
|
||||
if (variant === 'warning') {
|
||||
icon = <ExclamationTriangleIcon className="at-c-alertModal__icon" />;
|
||||
} else if (variant === 'danger') {
|
||||
icon = <ExclamationCircleIcon className="at-c-alertModal__icon" />;
|
||||
const Header = styled.div`
|
||||
display: flex;
|
||||
svg {
|
||||
margin-right: 16px;
|
||||
}
|
||||
if (variant === 'info') {
|
||||
icon = <InfoCircleIcon className="at-c-alertModal__icon" />;
|
||||
}
|
||||
if (variant === 'success') {
|
||||
icon = <CheckCircleIcon className="at-c-alertModal__icon" />;
|
||||
}
|
||||
return icon;
|
||||
};
|
||||
`;
|
||||
|
||||
export default function AlertModal({
|
||||
isOpen = null,
|
||||
title,
|
||||
variant,
|
||||
children,
|
||||
...props
|
||||
}) {
|
||||
const variantIcons = {
|
||||
danger: (
|
||||
<ExclamationCircleIcon
|
||||
size="lg"
|
||||
css="color: var(--pf-global--danger-color--100)"
|
||||
/>
|
||||
),
|
||||
error: (
|
||||
<TimesCircleIcon
|
||||
size="lg"
|
||||
css="color: var(--pf-global--danger-color--100)"
|
||||
/>
|
||||
),
|
||||
info: (
|
||||
<InfoCircleIcon
|
||||
size="lg"
|
||||
css="color: var(--pf-global--info-color--100)"
|
||||
/>
|
||||
),
|
||||
success: (
|
||||
<CheckCircleIcon
|
||||
size="lg"
|
||||
css="color: var(--pf-global--success-color--100)"
|
||||
/>
|
||||
),
|
||||
warning: (
|
||||
<ExclamationTriangleIcon
|
||||
size="lg"
|
||||
css="color: var(--pf-global--warning-color--100)"
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
const customHeader = (
|
||||
<Header>
|
||||
{variant ? variantIcons[variant] : null}
|
||||
<Title size="2xl">{title}</Title>
|
||||
</Header>
|
||||
);
|
||||
|
||||
export default ({ variant, children, ...props }) => {
|
||||
const { isOpen = null } = props;
|
||||
props.isOpen = Boolean(isOpen);
|
||||
return (
|
||||
<Modal
|
||||
className={`awx-c-modal${variant &&
|
||||
` at-c-alertModal at-c-alertModal--${variant}`}`}
|
||||
header={customHeader}
|
||||
isFooterLeftAligned
|
||||
isOpen={Boolean(isOpen)}
|
||||
isSmall
|
||||
title={title}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
{getIcon(variant)}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ import AlertModal from './AlertModal';
|
||||
|
||||
describe('AlertModal', () => {
|
||||
test('renders the expected content', () => {
|
||||
const wrapper = mount(<AlertModal title="Danger!" />);
|
||||
const wrapper = mount(
|
||||
<AlertModal title="Danger!">Are you sure?</AlertModal>
|
||||
);
|
||||
expect(wrapper).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
PageSectionVariants,
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbHeading as PFBreadcrumbHeading,
|
||||
BreadcrumbHeading,
|
||||
} from '@patternfly/react-core';
|
||||
import { Link, Route, withRouter } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
@@ -15,12 +15,6 @@ const PageSection = styled(PFPageSection)`
|
||||
padding-bottom: 10px;
|
||||
`;
|
||||
|
||||
const BreadcrumbHeading = styled(PFBreadcrumbHeading)`
|
||||
--pf-c-breadcrumb__heading--FontSize: 20px;
|
||||
line-height: 24px;
|
||||
flex: 100%;
|
||||
`;
|
||||
|
||||
const Breadcrumbs = ({ breadcrumbConfig }) => {
|
||||
const { light } = PageSectionVariants;
|
||||
|
||||
|
||||
@@ -3,13 +3,8 @@ import { CardActions } from '@patternfly/react-core';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const CardActionsWrapper = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 20px;
|
||||
|
||||
& > .pf-c-card__actions > :not(:first-child) {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
--pf-c-card__actions--PaddingLeft: 0;
|
||||
`;
|
||||
|
||||
function CardActionsRow({ children }) {
|
||||
|
||||
@@ -7,7 +7,7 @@ const TabbedCardHeader = styled(CardHeader)`
|
||||
--pf-c-card--child--PaddingRight: 0;
|
||||
--pf-c-card__header--not-last-child--PaddingBottom: 24px;
|
||||
--pf-c-card__header--not-last-child--PaddingBottom: 0;
|
||||
position: relative;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
export default TabbedCardHeader;
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
import React from 'react';
|
||||
import { string } from 'prop-types';
|
||||
import { Link as RRLink } from 'react-router-dom';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Button } from '@patternfly/react-core';
|
||||
import { TimesIcon } from '@patternfly/react-icons';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Link = styled(RRLink)`
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 4px;
|
||||
color: var(--pf-c-button--m-plain--Color);
|
||||
`;
|
||||
|
||||
function CardCloseButton({ linkTo, i18n, i18nHash, ...props }) {
|
||||
if (linkTo) {
|
||||
return (
|
||||
<Link
|
||||
className="pf-c-button"
|
||||
className="pf-c-button pf-m-plain"
|
||||
aria-label={i18n._(t`Close`)}
|
||||
title={i18n._(t`Close`)}
|
||||
to={linkTo}
|
||||
|
||||
@@ -5,21 +5,22 @@ import {
|
||||
DataListItemRow,
|
||||
DataListItemCells,
|
||||
DataListCell,
|
||||
DataListCheck,
|
||||
Radio,
|
||||
} from '@patternfly/react-core';
|
||||
import DataListCheck from '@components/DataListCheck';
|
||||
import DataListRadio from '@components/DataListRadio';
|
||||
import VerticalSeparator from '../VerticalSeparator';
|
||||
|
||||
const CheckboxListItem = ({
|
||||
isDisabled = false,
|
||||
isRadio = false,
|
||||
isSelected = false,
|
||||
itemId,
|
||||
name,
|
||||
label,
|
||||
isSelected,
|
||||
onSelect,
|
||||
name,
|
||||
onDeselect,
|
||||
isRadio,
|
||||
onSelect,
|
||||
}) => {
|
||||
const CheckboxRadio = isRadio ? DataListRadio : DataListCheck;
|
||||
const CheckboxRadio = isRadio ? Radio : DataListCheck;
|
||||
|
||||
return (
|
||||
<DataListItem
|
||||
key={itemId}
|
||||
@@ -28,21 +29,18 @@ const CheckboxListItem = ({
|
||||
>
|
||||
<DataListItemRow>
|
||||
<CheckboxRadio
|
||||
id={`selected-${itemId}`}
|
||||
checked={isSelected}
|
||||
onChange={isSelected ? onDeselect : onSelect}
|
||||
aria-label={`check-action-item-${itemId}`}
|
||||
aria-labelledby={`check-action-item-${itemId}`}
|
||||
checked={isSelected}
|
||||
disabled={isDisabled}
|
||||
id={`selected-${itemId}`}
|
||||
isChecked={isSelected}
|
||||
name={name}
|
||||
onChange={isSelected ? onDeselect : onSelect}
|
||||
value={itemId}
|
||||
/>
|
||||
<DataListItemCells
|
||||
dataListCells={[
|
||||
<DataListCell
|
||||
key="divider"
|
||||
className="pf-c-data-list__cell--divider"
|
||||
>
|
||||
<VerticalSeparator />
|
||||
</DataListCell>,
|
||||
<DataListCell key="name">
|
||||
<label
|
||||
id={`check-action-item-${itemId}`}
|
||||
@@ -60,12 +58,12 @@ const CheckboxListItem = ({
|
||||
};
|
||||
|
||||
CheckboxListItem.propTypes = {
|
||||
itemId: PropTypes.number.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
isSelected: PropTypes.bool.isRequired,
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
itemId: PropTypes.number.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
onDeselect: PropTypes.func.isRequired,
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default CheckboxListItem;
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import { Chip } from '@patternfly/react-core';
|
||||
import styled from 'styled-components';
|
||||
|
||||
Chip.displayName = 'PFChip';
|
||||
export default styled(Chip)`
|
||||
--pf-c-chip--m-read-only--PaddingTop: 3px;
|
||||
--pf-c-chip--m-read-only--PaddingBottom: 3px;
|
||||
--pf-c-chip--m-read-only--PaddingLeft: 8px;
|
||||
--pf-c-chip--m-read-only--PaddingRight: 8px;
|
||||
|
||||
.pf-c-button {
|
||||
--pf-c-button--PaddingTop: 3px;
|
||||
--pf-c-button--PaddingBottom: 3px;
|
||||
--pf-c-button--PaddingLeft: 8px;
|
||||
--pf-c-button--PaddingRight: 8px;
|
||||
}
|
||||
`;
|
||||
@@ -1,10 +0,0 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import Chip from './Chip';
|
||||
|
||||
describe('Chip', () => {
|
||||
test('renders the expected content', () => {
|
||||
const wrapper = mount(<Chip />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,14 +0,0 @@
|
||||
import { ChipGroup } from '@patternfly/react-core';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export default styled(ChipGroup)`
|
||||
--pf-c-chip-group--c-chip--MarginRight: 10px;
|
||||
--pf-c-chip-group--c-chip--MarginBottom: 10px;
|
||||
|
||||
> .pf-c-chip.pf-m-overflow .pf-c-button {
|
||||
--pf-c-button--PaddingTop: 3px;
|
||||
--pf-c-button--PaddingBottom: 3px;
|
||||
--pf-c-chip--m-overflow--c-button--PaddingLeft: 8px;
|
||||
--pf-c-chip--m-overflow--c-button--PaddingRight: 8px;
|
||||
}
|
||||
`;
|
||||
@@ -1,10 +0,0 @@
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import { ChipGroup } from '.';
|
||||
|
||||
describe('<ChipGroup />', () => {
|
||||
test('renders the expected content', () => {
|
||||
const wrapper = mount(<ChipGroup />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,171 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Chip renders the expected content 1`] = `
|
||||
<Chip>
|
||||
<StyledComponent
|
||||
forwardedComponent={
|
||||
Object {
|
||||
"$$typeof": Symbol(react.forward_ref),
|
||||
"attrs": Array [],
|
||||
"componentStyle": ComponentStyle {
|
||||
"componentId": "Chip-sc-1rzr8oo-0",
|
||||
"isStatic": true,
|
||||
"lastClassName": "iczEeI",
|
||||
"rules": Array [
|
||||
"--pf-c-chip--m-read-only--PaddingTop:3px;--pf-c-chip--m-read-only--PaddingBottom:3px;--pf-c-chip--m-read-only--PaddingLeft:8px;--pf-c-chip--m-read-only--PaddingRight:8px;.pf-c-button{--pf-c-button--PaddingTop:3px;--pf-c-button--PaddingBottom:3px;--pf-c-button--PaddingLeft:8px;--pf-c-button--PaddingRight:8px;}",
|
||||
],
|
||||
},
|
||||
"displayName": "Chip",
|
||||
"foldedComponentIds": Array [],
|
||||
"render": [Function],
|
||||
"styledComponentId": "Chip-sc-1rzr8oo-0",
|
||||
"target": [Function],
|
||||
"toString": [Function],
|
||||
"warnTooManyClasses": [Function],
|
||||
"withComponent": [Function],
|
||||
}
|
||||
}
|
||||
forwardedRef={null}
|
||||
>
|
||||
<PFChip
|
||||
className="Chip-sc-1rzr8oo-0 iczEeI"
|
||||
>
|
||||
<ComponentWithOuia
|
||||
component={[Function]}
|
||||
componentProps={
|
||||
Object {
|
||||
"className": "Chip-sc-1rzr8oo-0 iczEeI",
|
||||
}
|
||||
}
|
||||
consumerContext={null}
|
||||
>
|
||||
<Chip
|
||||
className="Chip-sc-1rzr8oo-0 iczEeI"
|
||||
closeBtnAriaLabel="close"
|
||||
component="div"
|
||||
isOverflowChip={false}
|
||||
isReadOnly={false}
|
||||
onClick={[Function]}
|
||||
ouiaContext={
|
||||
Object {
|
||||
"isOuia": false,
|
||||
"ouiaId": null,
|
||||
}
|
||||
}
|
||||
tooltipPosition="top"
|
||||
>
|
||||
<GenerateId
|
||||
prefix="pf-random-id-"
|
||||
>
|
||||
<div
|
||||
className="pf-c-chip Chip-sc-1rzr8oo-0 iczEeI"
|
||||
>
|
||||
<span
|
||||
className="pf-c-chip__text"
|
||||
id="pf-random-id-0"
|
||||
/>
|
||||
<ChipButton
|
||||
aria-labelledby="remove_pf-random-id-0 pf-random-id-0"
|
||||
ariaLabel="close"
|
||||
id="remove_pf-random-id-0"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<Component
|
||||
aria-label="close"
|
||||
aria-labelledby="remove_pf-random-id-0 pf-random-id-0"
|
||||
className=""
|
||||
id="remove_pf-random-id-0"
|
||||
onClick={[Function]}
|
||||
variant="plain"
|
||||
>
|
||||
<ComponentWithOuia
|
||||
component={[Function]}
|
||||
componentProps={
|
||||
Object {
|
||||
"aria-label": "close",
|
||||
"aria-labelledby": "remove_pf-random-id-0 pf-random-id-0",
|
||||
"children": <TimesCircleIcon
|
||||
aria-hidden="true"
|
||||
color="currentColor"
|
||||
noVerticalAlign={false}
|
||||
size="sm"
|
||||
title={null}
|
||||
/>,
|
||||
"className": "",
|
||||
"id": "remove_pf-random-id-0",
|
||||
"onClick": [Function],
|
||||
"variant": "plain",
|
||||
}
|
||||
}
|
||||
consumerContext={
|
||||
Object {
|
||||
"isOuia": false,
|
||||
"ouiaId": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Button
|
||||
aria-label="close"
|
||||
aria-labelledby="remove_pf-random-id-0 pf-random-id-0"
|
||||
className=""
|
||||
id="remove_pf-random-id-0"
|
||||
onClick={[Function]}
|
||||
ouiaContext={
|
||||
Object {
|
||||
"isOuia": false,
|
||||
"ouiaId": null,
|
||||
}
|
||||
}
|
||||
variant="plain"
|
||||
>
|
||||
<button
|
||||
aria-disabled={null}
|
||||
aria-label="close"
|
||||
aria-labelledby="remove_pf-random-id-0 pf-random-id-0"
|
||||
className="pf-c-button pf-m-plain"
|
||||
disabled={false}
|
||||
id="remove_pf-random-id-0"
|
||||
onClick={[Function]}
|
||||
tabIndex={null}
|
||||
type="button"
|
||||
>
|
||||
<TimesCircleIcon
|
||||
aria-hidden="true"
|
||||
color="currentColor"
|
||||
noVerticalAlign={false}
|
||||
size="sm"
|
||||
title={null}
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
aria-labelledby={null}
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
role="img"
|
||||
style={
|
||||
Object {
|
||||
"verticalAlign": "-0.125em",
|
||||
}
|
||||
}
|
||||
viewBox="0 0 512 512"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm121.6 313.1c4.7 4.7 4.7 12.3 0 17L338 377.6c-4.7 4.7-12.3 4.7-17 0L256 312l-65.1 65.6c-4.7 4.7-12.3 4.7-17 0L134.4 338c-4.7-4.7-4.7-12.3 0-17l65.6-65-65.6-65.1c-4.7-4.7-4.7-12.3 0-17l39.6-39.6c4.7-4.7 12.3-4.7 17 0l65 65.7 65.1-65.6c4.7-4.7 12.3-4.7 17 0l39.6 39.6c4.7 4.7 4.7 12.3 0 17L312 256l65.6 65.1z"
|
||||
transform=""
|
||||
/>
|
||||
</svg>
|
||||
</TimesCircleIcon>
|
||||
</button>
|
||||
</Button>
|
||||
</ComponentWithOuia>
|
||||
</Component>
|
||||
</ChipButton>
|
||||
</div>
|
||||
</GenerateId>
|
||||
</Chip>
|
||||
</ComponentWithOuia>
|
||||
</PFChip>
|
||||
</StyledComponent>
|
||||
</Chip>
|
||||
`;
|
||||
@@ -1,40 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<ChipGroup /> renders the expected content 1`] = `
|
||||
<ChipGroup>
|
||||
<StyledComponent
|
||||
forwardedComponent={
|
||||
Object {
|
||||
"$$typeof": Symbol(react.forward_ref),
|
||||
"attrs": Array [],
|
||||
"componentStyle": ComponentStyle {
|
||||
"componentId": "ChipGroup-sc-10zu8t0-0",
|
||||
"isStatic": true,
|
||||
"lastClassName": "bwbBYO",
|
||||
"rules": Array [
|
||||
"--pf-c-chip-group--c-chip--MarginRight:10px;--pf-c-chip-group--c-chip--MarginBottom:10px;> .pf-c-chip.pf-m-overflow .pf-c-button{--pf-c-button--PaddingTop:3px;--pf-c-button--PaddingBottom:3px;--pf-c-chip--m-overflow--c-button--PaddingLeft:8px;--pf-c-chip--m-overflow--c-button--PaddingRight:8px;}",
|
||||
],
|
||||
},
|
||||
"displayName": "ChipGroup",
|
||||
"foldedComponentIds": Array [],
|
||||
"render": [Function],
|
||||
"styledComponentId": "ChipGroup-sc-10zu8t0-0",
|
||||
"target": [Function],
|
||||
"toString": [Function],
|
||||
"warnTooManyClasses": [Function],
|
||||
"withComponent": [Function],
|
||||
}
|
||||
}
|
||||
forwardedRef={null}
|
||||
>
|
||||
<ChipGroup
|
||||
className="ChipGroup-sc-10zu8t0-0 bwbBYO"
|
||||
collapsedText="\${remaining} more"
|
||||
defaultIsOpen={false}
|
||||
expandedText="Show Less"
|
||||
numChips={3}
|
||||
withToolbar={false}
|
||||
/>
|
||||
</StyledComponent>
|
||||
</ChipGroup>
|
||||
`;
|
||||
@@ -1,3 +0,0 @@
|
||||
export { default as ChipGroup } from './ChipGroup';
|
||||
export { default as Chip } from './Chip';
|
||||
export { default as CredentialChip } from './CredentialChip';
|
||||
@@ -3,18 +3,6 @@ import PropTypes from 'prop-types';
|
||||
import { Button, Tooltip } from '@patternfly/react-core';
|
||||
import { CopyIcon } from '@patternfly/react-icons';
|
||||
|
||||
import styled from 'styled-components';
|
||||
|
||||
const CopyButton = styled(Button)`
|
||||
padding: 2px 4px;
|
||||
margin-left: 8px;
|
||||
border: none;
|
||||
&:hover {
|
||||
background-color: #0066cc;
|
||||
color: white;
|
||||
}
|
||||
`;
|
||||
|
||||
export const clipboardCopyFunc = (event, text) => {
|
||||
const clipboard = event.currentTarget.parentElement;
|
||||
const el = document.createElement('input');
|
||||
@@ -62,13 +50,13 @@ class ClipboardCopyButton extends React.Component {
|
||||
trigger="mouseenter focus click"
|
||||
content={copied ? clickTip : hoverTip}
|
||||
>
|
||||
<CopyButton
|
||||
<Button
|
||||
variant="plain"
|
||||
onClick={this.handleCopyClick}
|
||||
aria-label={hoverTip}
|
||||
>
|
||||
<CopyIcon />
|
||||
</CopyButton>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { string, number } from 'prop-types';
|
||||
import { string, node, number } from 'prop-types';
|
||||
import { Split, SplitItem, TextListItemVariants } from '@patternfly/react-core';
|
||||
import { DetailName, DetailValue } from '@components/DetailList';
|
||||
import MultiButtonToggle from '@components/MultiButtonToggle';
|
||||
import { yamlToJson, jsonToYaml, isJson } from '@util/yaml';
|
||||
import CodeMirrorInput from './CodeMirrorInput';
|
||||
import YamlJsonToggle from './YamlJsonToggle';
|
||||
import { JSON_MODE, YAML_MODE } from './constants';
|
||||
|
||||
function getValueAsMode(value, mode) {
|
||||
@@ -50,8 +50,9 @@ function VariablesDetail({ value, label, rows }) {
|
||||
</div>
|
||||
</SplitItem>
|
||||
<SplitItem>
|
||||
<YamlJsonToggle
|
||||
mode={mode}
|
||||
<MultiButtonToggle
|
||||
buttons={[[YAML_MODE, 'YAML'], [JSON_MODE, 'JSON']]}
|
||||
value={mode}
|
||||
onChange={newMode => {
|
||||
try {
|
||||
setCurrentValue(getValueAsMode(currentValue, newMode));
|
||||
@@ -90,7 +91,7 @@ function VariablesDetail({ value, label, rows }) {
|
||||
}
|
||||
VariablesDetail.propTypes = {
|
||||
value: string.isRequired,
|
||||
label: string.isRequired,
|
||||
label: node.isRequired,
|
||||
rows: number,
|
||||
};
|
||||
VariablesDetail.defaultProps = {
|
||||
|
||||
@@ -31,12 +31,12 @@ describe('<VariablesDetail>', () => {
|
||||
const wrapper = shallow(
|
||||
<VariablesDetail value="---foo: bar" label="Variables" />
|
||||
);
|
||||
wrapper.find('YamlJsonToggle').invoke('onChange')('javascript');
|
||||
wrapper.find('MultiButtonToggle').invoke('onChange')('javascript');
|
||||
const input = wrapper.find('Styled(CodeMirrorInput)');
|
||||
expect(input.prop('mode')).toEqual('javascript');
|
||||
expect(input.prop('value')).toEqual('{\n "foo": "bar"\n}');
|
||||
|
||||
wrapper.find('YamlJsonToggle').invoke('onChange')('yaml');
|
||||
wrapper.find('MultiButtonToggle').invoke('onChange')('yaml');
|
||||
const input2 = wrapper.find('Styled(CodeMirrorInput)');
|
||||
expect(input2.prop('mode')).toEqual('yaml');
|
||||
expect(input2.prop('value')).toEqual('foo: bar\n');
|
||||
@@ -53,7 +53,7 @@ describe('<VariablesDetail>', () => {
|
||||
<VariablesDetail value="---foo: bar" label="Variables" />
|
||||
);
|
||||
act(() => {
|
||||
wrapper.find('YamlJsonToggle').invoke('onChange')('javascript');
|
||||
wrapper.find('MultiButtonToggle').invoke('onChange')('javascript');
|
||||
});
|
||||
wrapper.setProps({
|
||||
value: '---bar: baz',
|
||||
@@ -73,7 +73,7 @@ describe('<VariablesDetail>', () => {
|
||||
test('should default empty json to "{}"', () => {
|
||||
const wrapper = mount(<VariablesDetail value="" label="Variables" />);
|
||||
act(() => {
|
||||
wrapper.find('YamlJsonToggle').invoke('onChange')('javascript');
|
||||
wrapper.find('MultiButtonToggle').invoke('onChange')('javascript');
|
||||
});
|
||||
wrapper.setProps({ value: '' });
|
||||
const input = wrapper.find('Styled(CodeMirrorInput)');
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user