diff --git a/.gitignore b/.gitignore index 6231ea4071..0ecdb823a2 100644 --- a/.gitignore +++ b/.gitignore @@ -157,7 +157,11 @@ use_dev_supervisor.txt *.unison.tmp *.# /awx/ui/.ui-built +/awx/ui_next/.ui-built /Dockerfile /_build/ /_build_kube_dev/ /Dockerfile.kube-dev + +awx/ui_next/src +awx/ui_next/build diff --git a/MANIFEST.in b/MANIFEST.in index 04cde5e9f6..09a5392c50 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -6,6 +6,7 @@ recursive-include awx/templates *.html recursive-include awx/api/templates *.md *.html *.yml recursive-include awx/ui/build *.html recursive-include awx/ui/build * +recursive-include awx/ui_next/build * recursive-include awx/playbooks *.yml recursive-include awx/lib/site-packages * recursive-include awx/plugins *.ps1 diff --git a/Makefile b/Makefile index 88b2047f8a..f4404e8809 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,5 @@ +-include awx/ui_next/Makefile + PYTHON ?= python3.9 DOCKER_COMPOSE ?= docker-compose OFFICIAL ?= no @@ -449,7 +451,7 @@ HEADLESS ?= no ifeq ($(HEADLESS), yes) dist/$(SDIST_TAR_FILE): else -dist/$(SDIST_TAR_FILE): $(UI_BUILD_FLAG_FILE) +dist/$(SDIST_TAR_FILE): $(UI_BUILD_FLAG_FILE) awx/ui_next/build endif $(PYTHON) -m build -s ln -sf $(SDIST_TAR_FILE) dist/awx.tar.gz @@ -497,8 +499,6 @@ docker-compose-sources: .git/hooks/pre-commit -e enable_prometheus=$(PROMETHEUS) \ -e enable_grafana=$(GRAFANA) $(EXTRA_SOURCES_ANSIBLE_OPTS) - - docker-compose: awx/projects docker-compose-sources $(DOCKER_COMPOSE) -f tools/docker-compose/_sources/docker-compose.yml $(COMPOSE_OPTS) up $(COMPOSE_UP_OPTS) --remove-orphans @@ -592,7 +592,7 @@ awx-kube-dev-build: Dockerfile.kube-dev -t $(DEV_DOCKER_TAG_BASE)/awx_kube_devel:$(COMPOSE_TAG) . ## Build awx image for deployment on Kubernetes environment. -awx-kube-build: Dockerfile +awx-kube-build: Dockerfile awx/ui_next/src DOCKER_BUILDKIT=1 docker build -f Dockerfile \ --build-arg VERSION=$(VERSION) \ --build-arg SETUPTOOLS_SCM_PRETEND_VERSION=$(VERSION) \ @@ -654,3 +654,11 @@ help/generate: } \ { lastLine = $$0 }' $(MAKEFILE_LIST) | sort -u @printf "\n" + +## Display help for a specific target folder +help/%: + @make -s help MAKEFILE_LIST="$*/Makefile" + +## Display help for a specific target folder +help/%/aliases: + @make -s help/all MAKEFILE_LIST="$*/Makefile.aliases" diff --git a/awx/main/conf.py b/awx/main/conf.py index f72e12fd0d..6634271b93 100644 --- a/awx/main/conf.py +++ b/awx/main/conf.py @@ -795,6 +795,16 @@ register( category_slug='bulk', ) +register( + 'UI_NEXT', + field_class=fields.BooleanField, + default=False, + label=_('Enable Preview of New User Interface'), + help_text=_('Enable preview of new user interface.'), + category=_('System'), + category_slug='system', +) + def logging_validate(serializer, attrs): if not serializer.instance or not hasattr(serializer.instance, 'LOG_AGGREGATOR_HOST') or not hasattr(serializer.instance, 'LOG_AGGREGATOR_TYPE'): diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index c0a5318638..b36dcc15ec 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -85,7 +85,11 @@ USE_L10N = True USE_TZ = True -STATICFILES_DIRS = [os.path.join(BASE_DIR, 'ui', 'build', 'static'), os.path.join(BASE_DIR, 'static')] +STATICFILES_DIRS = [ + os.path.join(BASE_DIR, 'ui', 'build', 'static'), + os.path.join(BASE_DIR, 'ui_next', 'build'), + os.path.join(BASE_DIR, 'static'), +] # Absolute filesystem path to the directory where static file are collected via # the collectstatic command. @@ -299,7 +303,12 @@ TEMPLATES = [ ], 'builtins': ['awx.main.templatetags.swagger'], }, - 'DIRS': [os.path.join(BASE_DIR, 'templates'), os.path.join(BASE_DIR, 'ui', 'build'), os.path.join(BASE_DIR, 'ui', 'public')], + 'DIRS': [ + os.path.join(BASE_DIR, 'templates'), + os.path.join(BASE_DIR, 'ui', 'build'), + os.path.join(BASE_DIR, 'ui', 'public'), + os.path.join(BASE_DIR, 'ui_next', 'build', 'awx'), + ], }, ] @@ -1017,3 +1026,5 @@ AWX_MOUNT_ISOLATED_PATHS_ON_K8S = False # This is overridden downstream via /etc/tower/conf.d/cluster_host_id.py CLUSTER_HOST_ID = socket.gethostname() + +UI_NEXT = True diff --git a/awx/ui_next/Makefile b/awx/ui_next/Makefile new file mode 100644 index 0000000000..6fed498d5f --- /dev/null +++ b/awx/ui_next/Makefile @@ -0,0 +1,142 @@ +## UI_NEXT_MKFILE_PATH: Path to this Makefile +UI_NEXT_MKFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST))) + +## UI_NEXT_DIR_ABS: Absolute path to the directory containing this Makefile +UI_NEXT_DIR_ABS := $(dir $(UI_NEXT_MKFILE_PATH)) + +## UI_NEXT_REL_DIR: Relative path to the directory containing this Makefile +# NOTE: UI_NEXT_REL_DIR swallowed the / because we want to be able to run `make src` from the ui_next dir +UI_NEXT_REL_DIR := $(subst $(CURDIR)/,, $(UI_NEXT_DIR_ABS)) + +## UI_NEXT_SRC_DIR: Path to the ui_next src directory +UI_NEXT_SRC_DIR := $(UI_NEXT_REL_DIR)src + +## UI_NEXT_BUILD_DIR: Path to the ui_next build directory +UI_NEXT_BUILD_DIR := $(UI_NEXT_REL_DIR)build + +## Path to your local clone of the UI_NEXT repo +# NOTE: This does not work with docker-compose development environment +UI_NEXT_LOCAL ?= + +# Git repo and branch to the UI_NEXT repo +UI_NEXT_GIT_REPO_SSH ?= git@github.com:ansible/ansible-ui.git +UI_NEXT_GIT_REPO_HTTPS ?= https://github.com/ansible/ansible-ui.git +UI_NEXT_GIT_BRANCH ?= main + +# awx-manage collect static require the awx/ui_next/build to exist +# therefore we have to commit the build directory to source control +# so that make docker-compose will be able to start up uwsgi +# UI_NEXT_BUILT_FILE is here so that we can use it as the non-phony build target +UI_NEXT_BUILT_FILE = $(UI_NEXT_REL_DIR).ui-built + +## Default target of this Makefile build the ui_next/build from source +ui-next: ui_next/build + +.PHONY: ui_next/clone-https +## Shallow clone the ui_next repo via https skip if UI_NEXT_GIT_REPO_HTTPS is undefined +ui_next/clone-https: + @if [ -z "$(UI_NEXT_GIT_REPO_HTTPS)" ]; then \ + echo "SKIP: ui_next/clone-https. UI_NEXT_GIT_REPO_HTTPS is not set."; \ + elif [ -d $(UI_NEXT_SRC_DIR) ]; then \ + echo "SKIP: ui_next/clone-https. $(UI_NEXT_SRC_DIR) already exists."; \ + else \ + git clone --depth 1 --branch $(UI_NEXT_GIT_BRANCH) $(UI_NEXT_GIT_REPO_HTTPS) $(UI_NEXT_SRC_DIR) || true; \ + fi + +.PHONY: ui_next/clone-ssh +## Shallow clone the ui_next repo via ssh. +ui_next/clone-ssh: + @if [ -z "$(UI_NEXT_GIT_REPO_SSH)" ]; then \ + echo "SKIP: ui_next/clone-ssh. UI_NEXT_GIT_REPO_SSH is not set."; \ + elif [ -d $(UI_NEXT_SRC_DIR) ]; then \ + echo "SKIP: ui_next/clone-ssh. $(UI_NEXT_SRC_DIR) already exists."; \ + else \ + git clone --depth 1 --branch $(UI_NEXT_GIT_BRANCH) $(UI_NEXT_GIT_REPO_SSH) $(UI_NEXT_SRC_DIR) || true; \ + fi + +.PHONY: ui_next/link-local +## Link to a existing local clone of ui_next repo. If method will not be able to build inside docker-compose environment +ui_next/link-local: + @if [ -z "$(UI_NEXT_LOCAL)" ]; then \ + echo "SKIP: ui_next/link-local. UI_NEXT_LOCAL is not set."; \ + elif [ -d $(UI_NEXT_SRC_DIR) ]; then \ + echo "SKIP: ui_next/link-local. $(UI_NEXT_SRC_DIR) already exists."; \ + else \ + ln -s $(UI_NEXT_LOCAL) $(UI_NEXT_SRC_DIR); \ + fi + +.PHONY: ui_next/src +## Try to link to a local clone of ui_next repo if it exist otherwise clone via ssh than https. +ui_next/src: + @if [ -d $(UI_NEXT_SRC_DIR) ]; then \ + echo "SKIP: ui_next. $(UI_NEXT_SRC_DIR) already exists."; \ + else \ + $(MAKE) ui_next/link-local ui_next/clone-ssh ui_next/clone-https; \ + fi + +## Alias for ui_next, will not run if ui_next/src already exist +$(UI_NEXT_SRC_DIR): + $(MAKE) ui_next/src + +## Alias for ui_next/build, will not run if ui_next/src/build already exist +$(UI_NEXT_SRC_DIR)/build: + $(MAKE) ui_next/src/build + +.PHONY: ui_next/build +## Build ui_next from source +ui_next/src/build: $(UI_NEXT_SRC_DIR) $(UI_NEXT_SRC_DIR)/node_modules/webpack + @cd $(UI_NEXT_SRC_DIR) && npm run build:awx + + +## Install webpack if does not exist. +ui_next/src/node_modules/webpack: $(UI_NEXT_SRC_DIR) + @if [ -d $(UI_NEXT_SRC_DIR)/node_modules/webpack ]; then \ + echo "SKIP: ui_next/src/node_modules/webpack. $(UI_NEXT_SRC_DIR)/node_modules/webpack already exists."; \ + else \ + cd $(UI_NEXT_SRC_DIR) && npm install webpack; \ + fi + +## Alias for ui_next/src/node_modules/webpack. will not run if webpack already exist +$(UI_NEXT_SRC_DIR)/node_modules/webpack: + $(MAKE) ui_next/src/node_modules/webpack + +## Copy ui_next/src/build to ui_next/build +ui_next/build: ui_next/clean/build + $(MAKE) $(UI_NEXT_SRC_DIR)/build + @mkdir $(UI_NEXT_DIR_ABS)build && \ + cp -r $(UI_NEXT_SRC_DIR)/build/awx $(UI_NEXT_DIR_ABS)build/awx &&\ + touch $(UI_NEXT_BUILT_FILE) + +## Alias for ui_next/build. Will not run if .ui-built file already exist +$(UI_NEXT_BUILT_FILE): + $(MAKE) ui_next/build + +## Alias for ui_next/build. +.PHONY: $(UI_NEXT_BUILD_DIR) +$(UI_NEXT_BUILD_DIR): + $(MAKE) ui_next/build + +.PHONY: $(UI_NEXT_REL_DIR)clean +## Alias for ui_next/clean. +$(UI_NEXT_REL_DIR)clean: + rm -rf $(UI_NEXT_SRC_DIR) + rm -rf $(UI_NEXT_BUILD_DIR) + +.PHONY: ui_next/clean +## Clean ui_next +ui_next/clean: ui_next/clean/build + rm -rf $(UI_NEXT_SRC_DIR) + +.PHONY: ui_next/clean/src +## Clean ui_next src +ui_next/clean/src: + rm -rf $(UI_NEXT_SRC_DIR) + +.PHONY: ui_next/clean/build +## Clean ui_next build +ui_next/clean/build: + rm -rf $(UI_NEXT_BUILD_DIR) + rm -rf $(UI_NEXT_BUILT_FILE) + +print-%: + @echo $($*) \ No newline at end of file diff --git a/awx/ui_next/README.md b/awx/ui_next/README.md new file mode 100644 index 0000000000..35c5beb7c2 --- /dev/null +++ b/awx/ui_next/README.md @@ -0,0 +1,33 @@ +# Instruction to build ui_next directly from this directory + +## Set src of the ui_next repo + +### via GIT + +```bash +export UI_NEXT_GIT_BRANCH_REPO_HTTPS=https:// +``` + +or + +```bash +export UI_NEXT_GIT_BRANCH_REPO_SSH=git@ +``` + +optionally set branch (default is main) + +```bash +export UI_NEXT_GIT_BRANCH_BRANCH=main +``` + +### via symlink to existing clone + +```bash +export UI_NEXT_LOCAL = /path/to/your/ui_next +``` + +## Build + +```bash +make ui_next/build +``` diff --git a/awx/ui_next/urls.py b/awx/ui_next/urls.py new file mode 100644 index 0000000000..e2aa74e420 --- /dev/null +++ b/awx/ui_next/urls.py @@ -0,0 +1,11 @@ +from django.urls import re_path +from django.views.generic.base import TemplateView + + +class IndexView(TemplateView): + template_name = 'index_awx.html' + + +app_name = 'ui_next' + +urlpatterns = [re_path(r'^$', IndexView.as_view(), name='index')] diff --git a/awx/urls.py b/awx/urls.py index c99eda011c..605f549d23 100644 --- a/awx/urls.py +++ b/awx/urls.py @@ -9,6 +9,7 @@ from awx.main.views import handle_400, handle_403, handle_404, handle_500, handl urlpatterns = [ re_path(r'', include('awx.ui.urls', namespace='ui')), + re_path(r'^ui_next/.*', include('awx.ui_next.urls', namespace='ui_next')), re_path(r'^api/', include('awx.api.urls', namespace='api')), re_path(r'^sso/', include('awx.sso.urls', namespace='sso')), re_path(r'^sso/', include('social_django.urls', namespace='social')),