mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-22 16:48:30 +01:00
Merge remote-tracking branch 'upstream/dev' into 170321_url
This commit is contained in:
commit
d83554013b
@ -101,7 +101,7 @@ script:
|
||||
|
||||
- docker ps
|
||||
- ./tests/notarytest.sh
|
||||
- go run tests/startuptest.go https://localhost/
|
||||
- ./tests/startuptest.sh
|
||||
- go run tests/userlogintest.go -name ${HARBOR_ADMIN} -passwd ${HARBOR_ADMIN_PASSWD}
|
||||
|
||||
# - sudo ./tests/testprepare.sh
|
||||
|
886
Makefile
886
Makefile
@ -1,443 +1,443 @@
|
||||
# Makefile for Harbor project
|
||||
#
|
||||
# Targets:
|
||||
#
|
||||
# all: prepare env, compile binarys, build images and install images
|
||||
# prepare: prepare env
|
||||
# compile: compile adminserver, ui and jobservice code
|
||||
#
|
||||
# compile_golangimage:
|
||||
# compile from golang image
|
||||
# for example: make compile_golangimage -e GOBUILDIMAGE= \
|
||||
# golang:1.7.3
|
||||
# compile_adminserver, compile_ui, compile_jobservice: compile specific binary
|
||||
#
|
||||
# build: build Harbor docker images (defuault: build_photon)
|
||||
# for example: make build -e BASEIMAGE=photon
|
||||
# build_photon: build Harbor docker images from photon baseimage
|
||||
#
|
||||
# install: include compile binarys, build images, prepare specific \
|
||||
# version composefile and startup Harbor instance
|
||||
#
|
||||
# start: startup Harbor instance
|
||||
#
|
||||
# down: shutdown Harbor instance
|
||||
#
|
||||
# package_online:
|
||||
# prepare online install package
|
||||
# for example: make package_online -e DEVFLAG=false\
|
||||
# REGISTRYSERVER=reg-bj.eng.vmware.com \
|
||||
# REGISTRYPROJECTNAME=harborrelease
|
||||
#
|
||||
# package_offline:
|
||||
# prepare offline install package
|
||||
#
|
||||
# pushimage: push Harbor images to specific registry server
|
||||
# for example: make pushimage -e DEVFLAG=false REGISTRYUSER=admin \
|
||||
# REGISTRYPASSWORD=***** \
|
||||
# REGISTRYSERVER=reg-bj.eng.vmware.com/ \
|
||||
# REGISTRYPROJECTNAME=harborrelease
|
||||
# note**: need add "/" on end of REGISTRYSERVER. If not setting \
|
||||
# this value will push images directly to dockerhub.
|
||||
# make pushimage -e DEVFLAG=false REGISTRYUSER=vmware \
|
||||
# REGISTRYPASSWORD=***** \
|
||||
# REGISTRYPROJECTNAME=vmware
|
||||
#
|
||||
# clean: remove binary, Harbor images, specific version docker-compose \
|
||||
# file, specific version tag and online/offline install package
|
||||
# cleanbinary: remove adminserver, ui and jobservice binary
|
||||
# cleanimage: remove Harbor images
|
||||
# cleandockercomposefile:
|
||||
# remove specific version docker-compose
|
||||
# cleanversiontag:
|
||||
# cleanpackageremove specific version tag
|
||||
# cleanpackage: remove online/offline install package
|
||||
#
|
||||
# other example:
|
||||
# clean specific version binarys and images:
|
||||
# make clean -e VERSIONTAG=[TAG]
|
||||
# note**: If commit new code to github, the git commit TAG will \
|
||||
# change. Better use this commond clean previous images and \
|
||||
# files with specific TAG.
|
||||
# By default DEVFLAG=true, if you want to release new version of Harbor, \
|
||||
# should setting the flag to false.
|
||||
# make XXXX -e DEVFLAG=false
|
||||
|
||||
SHELL := /bin/bash
|
||||
BUILDPATH=$(CURDIR)
|
||||
MAKEPATH=$(BUILDPATH)/make
|
||||
MAKEDEVPATH=$(MAKEPATH)/dev
|
||||
SRCPATH=./src
|
||||
TOOLSPATH=$(BUILDPATH)/tools
|
||||
UIPATH=$(BUILDPATH)/src/ui
|
||||
UINGPATH=$(BUILDPATH)/src/ui_ng
|
||||
GOBASEPATH=/go/src/github.com/vmware
|
||||
CHECKENVCMD=checkenv.sh
|
||||
BASEIMAGE=photon
|
||||
COMPILETAG=compile_normal
|
||||
REGISTRYSERVER=
|
||||
REGISTRYPROJECTNAME=vmware
|
||||
DEVFLAG=true
|
||||
NOTARYFLAG=false
|
||||
REGISTRYVERSION=2.6.0
|
||||
NGINXVERSION=1.11.5
|
||||
PHOTONVERSION=1.0
|
||||
NOTARYVERSION=server-0.5.0
|
||||
NOTARYSIGNERVERSION=signer-0.5.0
|
||||
MARIADBVERSION=mariadb-10.1.10
|
||||
HTTPPROXY=
|
||||
|
||||
#clarity parameters
|
||||
CLARITYIMAGE=danieljt/harbor-clarity-base[:tag]
|
||||
CLARITYSEEDPATH=/clarity-seed
|
||||
CLARITYBUILDSCRIPT=/entrypoint.sh
|
||||
|
||||
# docker parameters
|
||||
DOCKERCMD=$(shell which docker)
|
||||
DOCKERBUILD=$(DOCKERCMD) build
|
||||
DOCKERRMIMAGE=$(DOCKERCMD) rmi
|
||||
DOCKERPULL=$(DOCKERCMD) pull
|
||||
DOCKERIMASES=$(DOCKERCMD) images
|
||||
DOCKERSAVE=$(DOCKERCMD) save
|
||||
DOCKERCOMPOSECMD=$(shell which docker-compose)
|
||||
DOCKERTAG=$(DOCKERCMD) tag
|
||||
|
||||
# go parameters
|
||||
GOCMD=$(shell which go)
|
||||
GOBUILD=$(GOCMD) build
|
||||
GOCLEAN=$(GOCMD) clean
|
||||
GOINSTALL=$(GOCMD) install
|
||||
GOTEST=$(GOCMD) test
|
||||
GODEP=$(GOTEST) -i
|
||||
GOFMT=gofmt -w
|
||||
GOBUILDIMAGE=reg.mydomain.com/library/harborgo[:tag]
|
||||
GOBUILDPATH=$(GOBASEPATH)/harbor
|
||||
GOIMAGEBUILDCMD=/usr/local/go/bin/go
|
||||
GOIMAGEBUILD=$(GOIMAGEBUILDCMD) build
|
||||
GOBUILDPATH_ADMINSERVER=$(GOBUILDPATH)/src/adminserver
|
||||
GOBUILDPATH_UI=$(GOBUILDPATH)/src/ui
|
||||
GOBUILDPATH_JOBSERVICE=$(GOBUILDPATH)/src/jobservice
|
||||
GOBUILDMAKEPATH=$(GOBUILDPATH)/make
|
||||
GOBUILDMAKEPATH_ADMINSERVER=$(GOBUILDMAKEPATH)/dev/adminserver
|
||||
GOBUILDMAKEPATH_UI=$(GOBUILDMAKEPATH)/dev/ui
|
||||
GOBUILDMAKEPATH_JOBSERVICE=$(GOBUILDMAKEPATH)/dev/jobservice
|
||||
GOLANGDOCKERFILENAME=Dockerfile.golang
|
||||
|
||||
# binary
|
||||
ADMINSERVERSOURCECODE=$(SRCPATH)/adminserver
|
||||
ADMINSERVERBINARYPATH=$(MAKEDEVPATH)/adminserver
|
||||
ADMINSERVERBINARYNAME=harbor_adminserver
|
||||
UISOURCECODE=$(SRCPATH)/ui
|
||||
UIBINARYPATH=$(MAKEDEVPATH)/ui
|
||||
UIBINARYNAME=harbor_ui
|
||||
JOBSERVICESOURCECODE=$(SRCPATH)/jobservice
|
||||
JOBSERVICEBINARYPATH=$(MAKEDEVPATH)/jobservice
|
||||
JOBSERVICEBINARYNAME=harbor_jobservice
|
||||
|
||||
# prepare parameters
|
||||
PREPAREPATH=$(TOOLSPATH)
|
||||
PREPARECMD=prepare
|
||||
|
||||
# configfile
|
||||
CONFIGPATH=$(MAKEPATH)
|
||||
CONFIGFILE=harbor.cfg
|
||||
|
||||
# makefile
|
||||
MAKEFILEPATH_PHOTON=$(MAKEPATH)/photon
|
||||
|
||||
# common dockerfile
|
||||
DOCKERFILEPATH_COMMON=$(MAKEPATH)/common
|
||||
DOCKERFILEPATH_DB=$(DOCKERFILEPATH_COMMON)/db
|
||||
DOCKERFILENAME_DB=Dockerfile
|
||||
|
||||
# docker image name
|
||||
DOCKERIMAGENAME_ADMINSERVER=vmware/harbor-adminserver
|
||||
DOCKERIMAGENAME_UI=vmware/harbor-ui
|
||||
DOCKERIMAGENAME_JOBSERVICE=vmware/harbor-jobservice
|
||||
DOCKERIMAGENAME_LOG=vmware/harbor-log
|
||||
DOCKERIMAGENAME_DB=vmware/harbor-db
|
||||
|
||||
# docker-compose files
|
||||
DOCKERCOMPOSEFILEPATH=$(MAKEPATH)
|
||||
DOCKERCOMPOSETPLFILENAME=docker-compose.tpl
|
||||
DOCKERCOMPOSEFILENAME=docker-compose.yml
|
||||
DOCKERCOMPOSENOTARYFILENAME=docker-compose.notary.yml
|
||||
|
||||
# version prepare
|
||||
VERSIONFILEPATH=$(CURDIR)
|
||||
VERSIONFILENAME=VERSION
|
||||
GITCMD=$(shell which git)
|
||||
GITTAG=$(GITCMD) describe --tags
|
||||
ifeq ($(DEVFLAG), true)
|
||||
VERSIONTAG=dev
|
||||
else
|
||||
VERSIONTAG=$(shell $(GITTAG))
|
||||
endif
|
||||
|
||||
SEDCMD=$(shell which sed)
|
||||
|
||||
# package
|
||||
TARCMD=$(shell which tar)
|
||||
ZIPCMD=$(shell which gzip)
|
||||
DOCKERIMGFILE=harbor
|
||||
HARBORPKG=harbor
|
||||
|
||||
# pushimage
|
||||
PUSHSCRIPTPATH=$(MAKEPATH)
|
||||
PUSHSCRIPTNAME=pushimage.sh
|
||||
REGISTRYUSER=user
|
||||
REGISTRYPASSWORD=default
|
||||
|
||||
version:
|
||||
@printf $(VERSIONTAG) > $(VERSIONFILEPATH)/$(VERSIONFILENAME);
|
||||
|
||||
check_environment:
|
||||
@$(MAKEPATH)/$(CHECKENVCMD)
|
||||
|
||||
compile_adminserver:
|
||||
@echo "compiling binary for adminserver..."
|
||||
@$(GOBUILD) -o $(ADMINSERVERBINARYPATH)/$(ADMINSERVERBINARYNAME) $(ADMINSERVERSOURCECODE)
|
||||
@echo "Done."
|
||||
|
||||
compile_ui:
|
||||
@echo "compiling binary for ui..."
|
||||
@$(GOBUILD) -o $(UIBINARYPATH)/$(UIBINARYNAME) $(UISOURCECODE)
|
||||
@echo "Done."
|
||||
|
||||
compile_jobservice:
|
||||
@echo "compiling binary for jobservice..."
|
||||
@$(GOBUILD) -o $(JOBSERVICEBINARYPATH)/$(JOBSERVICEBINARYNAME) $(JOBSERVICESOURCECODE)
|
||||
@echo "Done."
|
||||
|
||||
compile_clarity:
|
||||
@echo "compiling binary for clarity ui..."
|
||||
@if [ "$(HTTPPROXY)" != "" ] ; then \
|
||||
$(DOCKERCMD) run --rm -v $(UIPATH)/static:$(CLARITYSEEDPATH)/dist -v $(UINGPATH)/src:$(CLARITYSEEDPATH)/src $(CLARITYIMAGE) $(SHELL) $(CLARITYBUILDSCRIPT) -p $(HTTPPROXY); \
|
||||
else \
|
||||
$(DOCKERCMD) run --rm -v $(UIPATH)/static:$(CLARITYSEEDPATH)/dist -v $(UINGPATH)/src:$(CLARITYSEEDPATH)/src $(CLARITYIMAGE) $(SHELL) $(CLARITYBUILDSCRIPT); \
|
||||
fi
|
||||
@echo "Done."
|
||||
|
||||
compile_normal: compile_clarity compile_adminserver compile_ui compile_jobservice
|
||||
|
||||
compile_golangimage: compile_clarity
|
||||
@echo "compiling binary for adminserver (golang image)..."
|
||||
@echo $(GOBASEPATH)
|
||||
@echo $(GOBUILDPATH)
|
||||
@$(DOCKERCMD) run --rm -v $(BUILDPATH):$(GOBUILDPATH) -w $(GOBUILDPATH_ADMINSERVER) $(GOBUILDIMAGE) $(GOIMAGEBUILD) -v -o $(GOBUILDMAKEPATH_ADMINSERVER)/$(ADMINSERVERBINARYNAME)
|
||||
@echo "Done."
|
||||
|
||||
@echo "compiling binary for ui (golang image)..."
|
||||
@echo $(GOBASEPATH)
|
||||
@echo $(GOBUILDPATH)
|
||||
@$(DOCKERCMD) run --rm -v $(BUILDPATH):$(GOBUILDPATH) -w $(GOBUILDPATH_UI) $(GOBUILDIMAGE) $(GOIMAGEBUILD) -v -o $(GOBUILDMAKEPATH_UI)/$(UIBINARYNAME)
|
||||
@echo "Done."
|
||||
|
||||
@echo "compiling binary for jobservice (golang image)..."
|
||||
@$(DOCKERCMD) run --rm -v $(BUILDPATH):$(GOBUILDPATH) -w $(GOBUILDPATH_JOBSERVICE) $(GOBUILDIMAGE) $(GOIMAGEBUILD) -v -o $(GOBUILDMAKEPATH_JOBSERVICE)/$(JOBSERVICEBINARYNAME)
|
||||
@echo "Done."
|
||||
|
||||
compile:check_environment $(COMPILETAG)
|
||||
|
||||
prepare:
|
||||
@echo "preparing..."
|
||||
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
||||
$(MAKEPATH)/$(PREPARECMD) --conf $(CONFIGPATH)/$(CONFIGFILE) --with-notary; \
|
||||
else \
|
||||
$(MAKEPATH)/$(PREPARECMD) --conf $(CONFIGPATH)/$(CONFIGFILE) ; \
|
||||
fi
|
||||
|
||||
build_common: version
|
||||
@echo "buildging db container for photon..."
|
||||
@cd $(DOCKERFILEPATH_DB) && $(DOCKERBUILD) -f $(DOCKERFILENAME_DB) -t $(DOCKERIMAGENAME_DB):$(VERSIONTAG) .
|
||||
@echo "Done."
|
||||
|
||||
build_photon: build_common
|
||||
make -f $(MAKEFILEPATH_PHOTON)/Makefile build -e DEVFLAG=$(DEVFLAG)
|
||||
|
||||
build: build_$(BASEIMAGE)
|
||||
|
||||
modify_composefile:
|
||||
@echo "preparing docker-compose file..."
|
||||
@cp $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSETPLFILENAME) $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME)
|
||||
@$(SEDCMD) -i 's/image\: vmware.*/&:$(VERSIONTAG)/g' $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME)
|
||||
|
||||
install: compile build prepare modify_composefile start
|
||||
|
||||
package_online: modify_composefile
|
||||
@echo "packing online package ..."
|
||||
@cp -r make $(HARBORPKG)
|
||||
@if [ -n "$(REGISTRYSERVER)" ] ; then \
|
||||
$(SEDCMD) -i 's/image\: vmware/image\: $(REGISTRYSERVER)\/$(REGISTRYPROJECTNAME)/' \
|
||||
$(HARBORPKG)/docker-compose.yml ; \
|
||||
fi
|
||||
@cp LICENSE $(HARBORPKG)/LICENSE
|
||||
@cp NOTICE $(HARBORPKG)/NOTICE
|
||||
|
||||
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
||||
$(TARCMD) -zcvf harbor-online-installer-$(VERSIONTAG).tgz \
|
||||
$(HARBORPKG)/common/templates $(HARBORPKG)/prepare \
|
||||
$(HARBORPKG)/LICENSE $(HARBORPKG)/NOTICE \
|
||||
$(HARBORPKG)/install.sh $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) \
|
||||
$(HARBORPKG)/harbor.cfg $(HARBORPKG)/$(DOCKERCOMPOSENOTARYFILENAME); \
|
||||
else \
|
||||
$(TARCMD) -zcvf harbor-online-installer-$(VERSIONTAG).tgz \
|
||||
$(HARBORPKG)/common/templates $(HARBORPKG)/prepare \
|
||||
$(HARBORPKG)/LICENSE $(HARBORPKG)/NOTICE \
|
||||
$(HARBORPKG)/install.sh $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) \
|
||||
$(HARBORPKG)/harbor.cfg ; \
|
||||
fi
|
||||
|
||||
@rm -rf $(HARBORPKG)
|
||||
@echo "Done."
|
||||
|
||||
package_offline: compile build modify_composefile
|
||||
@echo "packing offline package ..."
|
||||
@cp -r make $(HARBORPKG)
|
||||
|
||||
@cp LICENSE $(HARBORPKG)/LICENSE
|
||||
@cp NOTICE $(HARBORPKG)/NOTICE
|
||||
|
||||
@echo "pulling nginx and registry..."
|
||||
@$(DOCKERPULL) registry:$(REGISTRYVERSION)
|
||||
@$(DOCKERPULL) nginx:$(NGINXVERSION)
|
||||
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
||||
echo "pulling notary and harbor-notary-db..."; \
|
||||
$(DOCKERPULL) vmware/notary-photon:$(NOTARYVERSION); \
|
||||
$(DOCKERPULL) vmware/notary-photon:$(NOTARYSIGNERVERSION); \
|
||||
$(DOCKERPULL) vmware/harbor-notary-db:$(MARIADBVERSION); \
|
||||
fi
|
||||
|
||||
@echo "saving harbor docker image"
|
||||
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
||||
$(DOCKERSAVE) -o $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \
|
||||
$(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) \
|
||||
$(DOCKERIMAGENAME_UI):$(VERSIONTAG) \
|
||||
$(DOCKERIMAGENAME_LOG):$(VERSIONTAG) \
|
||||
$(DOCKERIMAGENAME_DB):$(VERSIONTAG) \
|
||||
$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) \
|
||||
nginx:$(NGINXVERSION) registry:$(REGISTRYVERSION) photon:$(PHOTONVERSION) \
|
||||
vmware/notary-photon:$(NOTARYVERSION) vmware/notary-photon:$(NOTARYSIGNERVERSION) vmware/harbor-notary-db:$(MARIADBVERSION); \
|
||||
else \
|
||||
$(DOCKERSAVE) -o $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \
|
||||
$(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) \
|
||||
$(DOCKERIMAGENAME_UI):$(VERSIONTAG) \
|
||||
$(DOCKERIMAGENAME_LOG):$(VERSIONTAG) \
|
||||
$(DOCKERIMAGENAME_DB):$(VERSIONTAG) \
|
||||
$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) \
|
||||
nginx:$(NGINXVERSION) registry:$(REGISTRYVERSION) photon:$(PHOTONVERSION) ; \
|
||||
fi
|
||||
|
||||
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
||||
$(TARCMD) -zcvf harbor-offline-installer-$(VERSIONTAG).tgz \
|
||||
$(HARBORPKG)/common/templates $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \
|
||||
$(HARBORPKG)/prepare $(HARBORPKG)/NOTICE \
|
||||
$(HARBORPKG)/LICENSE $(HARBORPKG)/install.sh \
|
||||
$(HARBORPKG)/harbor.cfg $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) \
|
||||
$(HARBORPKG)/$(DOCKERCOMPOSENOTARYFILENAME) ; \
|
||||
else \
|
||||
$(TARCMD) -zcvf harbor-offline-installer-$(VERSIONTAG).tgz \
|
||||
$(HARBORPKG)/common/templates $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \
|
||||
$(HARBORPKG)/prepare $(HARBORPKG)/NOTICE \
|
||||
$(HARBORPKG)/LICENSE $(HARBORPKG)/install.sh \
|
||||
$(HARBORPKG)/harbor.cfg $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) ; \
|
||||
fi
|
||||
|
||||
@rm -rf $(HARBORPKG)
|
||||
@echo "Done."
|
||||
|
||||
pushimage:
|
||||
@echo "pushing harbor images ..."
|
||||
@$(DOCKERTAG) $(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG)
|
||||
@$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) \
|
||||
$(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER)
|
||||
@$(DOCKERRMIMAGE) $(REGISTRYSERVER)$(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG)
|
||||
|
||||
@$(DOCKERTAG) $(DOCKERIMAGENAME_UI):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_UI):$(VERSIONTAG)
|
||||
@$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_UI):$(VERSIONTAG) \
|
||||
$(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER)
|
||||
@$(DOCKERRMIMAGE) $(REGISTRYSERVER)$(DOCKERIMAGENAME_UI):$(VERSIONTAG)
|
||||
|
||||
@$(DOCKERTAG) $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG)
|
||||
@$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) \
|
||||
$(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER)
|
||||
@$(DOCKERRMIMAGE) $(REGISTRYSERVER)$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG)
|
||||
|
||||
@$(DOCKERTAG) $(DOCKERIMAGENAME_LOG):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_LOG):$(VERSIONTAG)
|
||||
@$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_LOG):$(VERSIONTAG) \
|
||||
$(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER)
|
||||
@$(DOCKERRMIMAGE) $(REGISTRYSERVER)$(DOCKERIMAGENAME_LOG):$(VERSIONTAG)
|
||||
|
||||
@$(DOCKERTAG) $(DOCKERIMAGENAME_DB):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_DB):$(VERSIONTAG)
|
||||
@$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_DB):$(VERSIONTAG) \
|
||||
$(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER)
|
||||
@$(DOCKERRMIMAGE) $(REGISTRYSERVER)$(DOCKERIMAGENAME_DB):$(VERSIONTAG)
|
||||
|
||||
start:
|
||||
@echo "loading harbor images..."
|
||||
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
||||
$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSENOTARYFILENAME) up -d ; \
|
||||
else \
|
||||
$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) up -d ; \
|
||||
fi
|
||||
@echo "Start complete. You can visit harbor now."
|
||||
|
||||
down:
|
||||
@echo "Please make sure to set -e NOTARYFLAG=true if you are using Notary in Harbor, otherwise the Notary containers cannot be stop automaticlly."
|
||||
@while [ -z "$$CONTINUE" ]; do \
|
||||
read -r -p "Type anything but Y or y to exit. [Y/N]: " CONTINUE; \
|
||||
done ; \
|
||||
[ $$CONTINUE = "y" ] || [ $$CONTINUE = "Y" ] || (echo "Exiting."; exit 1;)
|
||||
@echo "stoping harbor instance..."
|
||||
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
||||
$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSENOTARYFILENAME) down ; \
|
||||
else \
|
||||
$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) down ; \
|
||||
fi
|
||||
@echo "Done."
|
||||
|
||||
cleanbinary:
|
||||
@echo "cleaning binary..."
|
||||
@if [ -f $(ADMINSERVERBINARYPATH)/$(ADMINSERVERBINARYNAME) ] ; then rm $(ADMINSERVERBINARYPATH)/$(ADMINSERVERBINARYNAME) ; fi
|
||||
@if [ -f $(UIBINARYPATH)/$(UIBINARYNAME) ] ; then rm $(UIBINARYPATH)/$(UIBINARYNAME) ; fi
|
||||
@if [ -f $(JOBSERVICEBINARYPATH)/$(JOBSERVICEBINARYNAME) ] ; then rm $(JOBSERVICEBINARYPATH)/$(JOBSERVICEBINARYNAME) ; fi
|
||||
|
||||
cleanimage:
|
||||
@echo "cleaning image for photon..."
|
||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG)
|
||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_UI):$(VERSIONTAG)
|
||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_DB):$(VERSIONTAG)
|
||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG)
|
||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_LOG):$(VERSIONTAG)
|
||||
# - $(DOCKERRMIMAGE) -f registry:$(REGISTRYVERSION)
|
||||
# - $(DOCKERRMIMAGE) -f nginx:1.11.5
|
||||
|
||||
cleandockercomposefile:
|
||||
@echo "cleaning $(DOCKERCOMPOSEFILEPATH)/docker-compose.yml"
|
||||
@if [ -f $(DOCKERCOMPOSEFILEPATH)/docker-compose.yml ] ; then rm $(DOCKERCOMPOSEFILEPATH)/docker-compose.yml ; fi
|
||||
|
||||
cleanversiontag:
|
||||
@echo "cleaning version TAG"
|
||||
@rm -rf $(VERSIONFILEPATH)/$(VERSIONFILENAME)
|
||||
|
||||
cleanpackage:
|
||||
@echo "cleaning harbor install package"
|
||||
@if [ -d $(BUILDPATH)/harbor ] ; then rm -rf $(BUILDPATH)/harbor ; fi
|
||||
@if [ -f $(BUILDPATH)/harbor-online-installer-$(VERSIONTAG).tgz ] ; \
|
||||
then rm $(BUILDPATH)/harbor-online-installer-$(VERSIONTAG).tgz ; fi
|
||||
@if [ -f $(BUILDPATH)/harbor-offline-installer-$(VERSIONTAG).tgz ] ; \
|
||||
then rm $(BUILDPATH)/harbor-offline-installer-$(VERSIONTAG).tgz ; fi
|
||||
|
||||
.PHONY: cleanall
|
||||
cleanall: cleanbinary cleanimage cleandockercomposefile cleanversiontag cleanpackage
|
||||
|
||||
clean:
|
||||
@echo " make cleanall: remove binary, Harbor images, specific version docker-compose"
|
||||
@echo " file, specific version tag, online and offline install package"
|
||||
@echo " make cleanbinary: remove ui and jobservice binary"
|
||||
@echo " make cleanimage: remove Harbor images"
|
||||
@echo " make cleandockercomposefile: remove specific version docker-compose"
|
||||
@echo " make cleanversiontag: cleanpackageremove specific version tag"
|
||||
@echo " make cleanpackage: remove online and offline install package"
|
||||
|
||||
all: install
|
||||
# Makefile for Harbor project
|
||||
#
|
||||
# Targets:
|
||||
#
|
||||
# all: prepare env, compile binarys, build images and install images
|
||||
# prepare: prepare env
|
||||
# compile: compile adminserver, ui and jobservice code
|
||||
#
|
||||
# compile_golangimage:
|
||||
# compile from golang image
|
||||
# for example: make compile_golangimage -e GOBUILDIMAGE= \
|
||||
# golang:1.7.3
|
||||
# compile_adminserver, compile_ui, compile_jobservice: compile specific binary
|
||||
#
|
||||
# build: build Harbor docker images (defuault: build_photon)
|
||||
# for example: make build -e BASEIMAGE=photon
|
||||
# build_photon: build Harbor docker images from photon baseimage
|
||||
#
|
||||
# install: include compile binarys, build images, prepare specific \
|
||||
# version composefile and startup Harbor instance
|
||||
#
|
||||
# start: startup Harbor instance
|
||||
#
|
||||
# down: shutdown Harbor instance
|
||||
#
|
||||
# package_online:
|
||||
# prepare online install package
|
||||
# for example: make package_online -e DEVFLAG=false\
|
||||
# REGISTRYSERVER=reg-bj.eng.vmware.com \
|
||||
# REGISTRYPROJECTNAME=harborrelease
|
||||
#
|
||||
# package_offline:
|
||||
# prepare offline install package
|
||||
#
|
||||
# pushimage: push Harbor images to specific registry server
|
||||
# for example: make pushimage -e DEVFLAG=false REGISTRYUSER=admin \
|
||||
# REGISTRYPASSWORD=***** \
|
||||
# REGISTRYSERVER=reg-bj.eng.vmware.com/ \
|
||||
# REGISTRYPROJECTNAME=harborrelease
|
||||
# note**: need add "/" on end of REGISTRYSERVER. If not setting \
|
||||
# this value will push images directly to dockerhub.
|
||||
# make pushimage -e DEVFLAG=false REGISTRYUSER=vmware \
|
||||
# REGISTRYPASSWORD=***** \
|
||||
# REGISTRYPROJECTNAME=vmware
|
||||
#
|
||||
# clean: remove binary, Harbor images, specific version docker-compose \
|
||||
# file, specific version tag and online/offline install package
|
||||
# cleanbinary: remove adminserver, ui and jobservice binary
|
||||
# cleanimage: remove Harbor images
|
||||
# cleandockercomposefile:
|
||||
# remove specific version docker-compose
|
||||
# cleanversiontag:
|
||||
# cleanpackageremove specific version tag
|
||||
# cleanpackage: remove online/offline install package
|
||||
#
|
||||
# other example:
|
||||
# clean specific version binarys and images:
|
||||
# make clean -e VERSIONTAG=[TAG]
|
||||
# note**: If commit new code to github, the git commit TAG will \
|
||||
# change. Better use this commond clean previous images and \
|
||||
# files with specific TAG.
|
||||
# By default DEVFLAG=true, if you want to release new version of Harbor, \
|
||||
# should setting the flag to false.
|
||||
# make XXXX -e DEVFLAG=false
|
||||
|
||||
SHELL := /bin/bash
|
||||
BUILDPATH=$(CURDIR)
|
||||
MAKEPATH=$(BUILDPATH)/make
|
||||
MAKEDEVPATH=$(MAKEPATH)/dev
|
||||
SRCPATH=./src
|
||||
TOOLSPATH=$(BUILDPATH)/tools
|
||||
UIPATH=$(BUILDPATH)/src/ui
|
||||
UINGPATH=$(BUILDPATH)/src/ui_ng
|
||||
GOBASEPATH=/go/src/github.com/vmware
|
||||
CHECKENVCMD=checkenv.sh
|
||||
BASEIMAGE=photon
|
||||
COMPILETAG=compile_normal
|
||||
REGISTRYSERVER=
|
||||
REGISTRYPROJECTNAME=vmware
|
||||
DEVFLAG=true
|
||||
NOTARYFLAG=false
|
||||
REGISTRYVERSION=photon-2.6.0
|
||||
NGINXVERSION=1.11.5
|
||||
PHOTONVERSION=1.0
|
||||
NOTARYVERSION=server-0.5.0
|
||||
NOTARYSIGNERVERSION=signer-0.5.0
|
||||
MARIADBVERSION=mariadb-10.1.10
|
||||
HTTPPROXY=
|
||||
|
||||
#clarity parameters
|
||||
CLARITYIMAGE=danieljt/harbor-clarity-base[:tag]
|
||||
CLARITYSEEDPATH=/clarity-seed
|
||||
CLARITYBUILDSCRIPT=/entrypoint.sh
|
||||
|
||||
# docker parameters
|
||||
DOCKERCMD=$(shell which docker)
|
||||
DOCKERBUILD=$(DOCKERCMD) build
|
||||
DOCKERRMIMAGE=$(DOCKERCMD) rmi
|
||||
DOCKERPULL=$(DOCKERCMD) pull
|
||||
DOCKERIMASES=$(DOCKERCMD) images
|
||||
DOCKERSAVE=$(DOCKERCMD) save
|
||||
DOCKERCOMPOSECMD=$(shell which docker-compose)
|
||||
DOCKERTAG=$(DOCKERCMD) tag
|
||||
|
||||
# go parameters
|
||||
GOCMD=$(shell which go)
|
||||
GOBUILD=$(GOCMD) build
|
||||
GOCLEAN=$(GOCMD) clean
|
||||
GOINSTALL=$(GOCMD) install
|
||||
GOTEST=$(GOCMD) test
|
||||
GODEP=$(GOTEST) -i
|
||||
GOFMT=gofmt -w
|
||||
GOBUILDIMAGE=reg.mydomain.com/library/harborgo[:tag]
|
||||
GOBUILDPATH=$(GOBASEPATH)/harbor
|
||||
GOIMAGEBUILDCMD=/usr/local/go/bin/go
|
||||
GOIMAGEBUILD=$(GOIMAGEBUILDCMD) build
|
||||
GOBUILDPATH_ADMINSERVER=$(GOBUILDPATH)/src/adminserver
|
||||
GOBUILDPATH_UI=$(GOBUILDPATH)/src/ui
|
||||
GOBUILDPATH_JOBSERVICE=$(GOBUILDPATH)/src/jobservice
|
||||
GOBUILDMAKEPATH=$(GOBUILDPATH)/make
|
||||
GOBUILDMAKEPATH_ADMINSERVER=$(GOBUILDMAKEPATH)/dev/adminserver
|
||||
GOBUILDMAKEPATH_UI=$(GOBUILDMAKEPATH)/dev/ui
|
||||
GOBUILDMAKEPATH_JOBSERVICE=$(GOBUILDMAKEPATH)/dev/jobservice
|
||||
GOLANGDOCKERFILENAME=Dockerfile.golang
|
||||
|
||||
# binary
|
||||
ADMINSERVERSOURCECODE=$(SRCPATH)/adminserver
|
||||
ADMINSERVERBINARYPATH=$(MAKEDEVPATH)/adminserver
|
||||
ADMINSERVERBINARYNAME=harbor_adminserver
|
||||
UISOURCECODE=$(SRCPATH)/ui
|
||||
UIBINARYPATH=$(MAKEDEVPATH)/ui
|
||||
UIBINARYNAME=harbor_ui
|
||||
JOBSERVICESOURCECODE=$(SRCPATH)/jobservice
|
||||
JOBSERVICEBINARYPATH=$(MAKEDEVPATH)/jobservice
|
||||
JOBSERVICEBINARYNAME=harbor_jobservice
|
||||
|
||||
# prepare parameters
|
||||
PREPAREPATH=$(TOOLSPATH)
|
||||
PREPARECMD=prepare
|
||||
|
||||
# configfile
|
||||
CONFIGPATH=$(MAKEPATH)
|
||||
CONFIGFILE=harbor.cfg
|
||||
|
||||
# makefile
|
||||
MAKEFILEPATH_PHOTON=$(MAKEPATH)/photon
|
||||
|
||||
# common dockerfile
|
||||
DOCKERFILEPATH_COMMON=$(MAKEPATH)/common
|
||||
DOCKERFILEPATH_DB=$(DOCKERFILEPATH_COMMON)/db
|
||||
DOCKERFILENAME_DB=Dockerfile
|
||||
|
||||
# docker image name
|
||||
DOCKERIMAGENAME_ADMINSERVER=vmware/harbor-adminserver
|
||||
DOCKERIMAGENAME_UI=vmware/harbor-ui
|
||||
DOCKERIMAGENAME_JOBSERVICE=vmware/harbor-jobservice
|
||||
DOCKERIMAGENAME_LOG=vmware/harbor-log
|
||||
DOCKERIMAGENAME_DB=vmware/harbor-db
|
||||
|
||||
# docker-compose files
|
||||
DOCKERCOMPOSEFILEPATH=$(MAKEPATH)
|
||||
DOCKERCOMPOSETPLFILENAME=docker-compose.tpl
|
||||
DOCKERCOMPOSEFILENAME=docker-compose.yml
|
||||
DOCKERCOMPOSENOTARYFILENAME=docker-compose.notary.yml
|
||||
|
||||
# version prepare
|
||||
VERSIONFILEPATH=$(CURDIR)
|
||||
VERSIONFILENAME=VERSION
|
||||
GITCMD=$(shell which git)
|
||||
GITTAG=$(GITCMD) describe --tags
|
||||
ifeq ($(DEVFLAG), true)
|
||||
VERSIONTAG=dev
|
||||
else
|
||||
VERSIONTAG=$(shell $(GITTAG))
|
||||
endif
|
||||
|
||||
SEDCMD=$(shell which sed)
|
||||
|
||||
# package
|
||||
TARCMD=$(shell which tar)
|
||||
ZIPCMD=$(shell which gzip)
|
||||
DOCKERIMGFILE=harbor
|
||||
HARBORPKG=harbor
|
||||
|
||||
# pushimage
|
||||
PUSHSCRIPTPATH=$(MAKEPATH)
|
||||
PUSHSCRIPTNAME=pushimage.sh
|
||||
REGISTRYUSER=user
|
||||
REGISTRYPASSWORD=default
|
||||
|
||||
version:
|
||||
@printf $(VERSIONTAG) > $(VERSIONFILEPATH)/$(VERSIONFILENAME);
|
||||
|
||||
check_environment:
|
||||
@$(MAKEPATH)/$(CHECKENVCMD)
|
||||
|
||||
compile_adminserver:
|
||||
@echo "compiling binary for adminserver..."
|
||||
@$(GOBUILD) -o $(ADMINSERVERBINARYPATH)/$(ADMINSERVERBINARYNAME) $(ADMINSERVERSOURCECODE)
|
||||
@echo "Done."
|
||||
|
||||
compile_ui:
|
||||
@echo "compiling binary for ui..."
|
||||
@$(GOBUILD) -o $(UIBINARYPATH)/$(UIBINARYNAME) $(UISOURCECODE)
|
||||
@echo "Done."
|
||||
|
||||
compile_jobservice:
|
||||
@echo "compiling binary for jobservice..."
|
||||
@$(GOBUILD) -o $(JOBSERVICEBINARYPATH)/$(JOBSERVICEBINARYNAME) $(JOBSERVICESOURCECODE)
|
||||
@echo "Done."
|
||||
|
||||
compile_clarity:
|
||||
@echo "compiling binary for clarity ui..."
|
||||
@if [ "$(HTTPPROXY)" != "" ] ; then \
|
||||
$(DOCKERCMD) run --rm -v $(UIPATH)/static:$(CLARITYSEEDPATH)/dist -v $(UINGPATH)/src:$(CLARITYSEEDPATH)/src $(CLARITYIMAGE) $(SHELL) $(CLARITYBUILDSCRIPT) -p $(HTTPPROXY); \
|
||||
else \
|
||||
$(DOCKERCMD) run --rm -v $(UIPATH)/static:$(CLARITYSEEDPATH)/dist -v $(UINGPATH)/src:$(CLARITYSEEDPATH)/src $(CLARITYIMAGE) $(SHELL) $(CLARITYBUILDSCRIPT); \
|
||||
fi
|
||||
@echo "Done."
|
||||
|
||||
compile_normal: compile_clarity compile_adminserver compile_ui compile_jobservice
|
||||
|
||||
compile_golangimage: compile_clarity
|
||||
@echo "compiling binary for adminserver (golang image)..."
|
||||
@echo $(GOBASEPATH)
|
||||
@echo $(GOBUILDPATH)
|
||||
@$(DOCKERCMD) run --rm -v $(BUILDPATH):$(GOBUILDPATH) -w $(GOBUILDPATH_ADMINSERVER) $(GOBUILDIMAGE) $(GOIMAGEBUILD) -v -o $(GOBUILDMAKEPATH_ADMINSERVER)/$(ADMINSERVERBINARYNAME)
|
||||
@echo "Done."
|
||||
|
||||
@echo "compiling binary for ui (golang image)..."
|
||||
@echo $(GOBASEPATH)
|
||||
@echo $(GOBUILDPATH)
|
||||
@$(DOCKERCMD) run --rm -v $(BUILDPATH):$(GOBUILDPATH) -w $(GOBUILDPATH_UI) $(GOBUILDIMAGE) $(GOIMAGEBUILD) -v -o $(GOBUILDMAKEPATH_UI)/$(UIBINARYNAME)
|
||||
@echo "Done."
|
||||
|
||||
@echo "compiling binary for jobservice (golang image)..."
|
||||
@$(DOCKERCMD) run --rm -v $(BUILDPATH):$(GOBUILDPATH) -w $(GOBUILDPATH_JOBSERVICE) $(GOBUILDIMAGE) $(GOIMAGEBUILD) -v -o $(GOBUILDMAKEPATH_JOBSERVICE)/$(JOBSERVICEBINARYNAME)
|
||||
@echo "Done."
|
||||
|
||||
compile:check_environment $(COMPILETAG)
|
||||
|
||||
prepare:
|
||||
@echo "preparing..."
|
||||
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
||||
$(MAKEPATH)/$(PREPARECMD) --conf $(CONFIGPATH)/$(CONFIGFILE) --with-notary; \
|
||||
else \
|
||||
$(MAKEPATH)/$(PREPARECMD) --conf $(CONFIGPATH)/$(CONFIGFILE) ; \
|
||||
fi
|
||||
|
||||
build_common: version
|
||||
@echo "buildging db container for photon..."
|
||||
@cd $(DOCKERFILEPATH_DB) && $(DOCKERBUILD) -f $(DOCKERFILENAME_DB) -t $(DOCKERIMAGENAME_DB):$(VERSIONTAG) .
|
||||
@echo "Done."
|
||||
|
||||
build_photon: build_common
|
||||
make -f $(MAKEFILEPATH_PHOTON)/Makefile build -e DEVFLAG=$(DEVFLAG)
|
||||
|
||||
build: build_$(BASEIMAGE)
|
||||
|
||||
modify_composefile:
|
||||
@echo "preparing docker-compose file..."
|
||||
@cp $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSETPLFILENAME) $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME)
|
||||
@$(SEDCMD) -i 's/__version__/$(VERSIONTAG)/g' $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME)
|
||||
|
||||
install: compile build prepare modify_composefile start
|
||||
|
||||
package_online: modify_composefile
|
||||
@echo "packing online package ..."
|
||||
@cp -r make $(HARBORPKG)
|
||||
@if [ -n "$(REGISTRYSERVER)" ] ; then \
|
||||
$(SEDCMD) -i 's/image\: vmware/image\: $(REGISTRYSERVER)\/$(REGISTRYPROJECTNAME)/' \
|
||||
$(HARBORPKG)/docker-compose.yml ; \
|
||||
fi
|
||||
@cp LICENSE $(HARBORPKG)/LICENSE
|
||||
@cp NOTICE $(HARBORPKG)/NOTICE
|
||||
|
||||
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
||||
$(TARCMD) -zcvf harbor-online-installer-$(VERSIONTAG).tgz \
|
||||
$(HARBORPKG)/common/templates $(HARBORPKG)/prepare \
|
||||
$(HARBORPKG)/LICENSE $(HARBORPKG)/NOTICE \
|
||||
$(HARBORPKG)/install.sh $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) \
|
||||
$(HARBORPKG)/harbor.cfg $(HARBORPKG)/$(DOCKERCOMPOSENOTARYFILENAME); \
|
||||
else \
|
||||
$(TARCMD) -zcvf harbor-online-installer-$(VERSIONTAG).tgz \
|
||||
$(HARBORPKG)/common/templates $(HARBORPKG)/prepare \
|
||||
$(HARBORPKG)/LICENSE $(HARBORPKG)/NOTICE \
|
||||
$(HARBORPKG)/install.sh $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) \
|
||||
$(HARBORPKG)/harbor.cfg ; \
|
||||
fi
|
||||
|
||||
@rm -rf $(HARBORPKG)
|
||||
@echo "Done."
|
||||
|
||||
package_offline: compile build modify_composefile
|
||||
@echo "packing offline package ..."
|
||||
@cp -r make $(HARBORPKG)
|
||||
|
||||
@cp LICENSE $(HARBORPKG)/LICENSE
|
||||
@cp NOTICE $(HARBORPKG)/NOTICE
|
||||
|
||||
@echo "pulling nginx and registry..."
|
||||
@$(DOCKERPULL) vmware/registry:$(REGISTRYVERSION)
|
||||
@$(DOCKERPULL) nginx:$(NGINXVERSION)
|
||||
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
||||
echo "pulling notary and harbor-notary-db..."; \
|
||||
$(DOCKERPULL) vmware/notary-photon:$(NOTARYVERSION); \
|
||||
$(DOCKERPULL) vmware/notary-photon:$(NOTARYSIGNERVERSION); \
|
||||
$(DOCKERPULL) vmware/harbor-notary-db:$(MARIADBVERSION); \
|
||||
fi
|
||||
|
||||
@echo "saving harbor docker image"
|
||||
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
||||
$(DOCKERSAVE) -o $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \
|
||||
$(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) \
|
||||
$(DOCKERIMAGENAME_UI):$(VERSIONTAG) \
|
||||
$(DOCKERIMAGENAME_LOG):$(VERSIONTAG) \
|
||||
$(DOCKERIMAGENAME_DB):$(VERSIONTAG) \
|
||||
$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) \
|
||||
nginx:$(NGINXVERSION) vmware/registry:$(REGISTRYVERSION) photon:$(PHOTONVERSION) \
|
||||
vmware/notary-photon:$(NOTARYVERSION) vmware/notary-photon:$(NOTARYSIGNERVERSION) vmware/harbor-notary-db:$(MARIADBVERSION); \
|
||||
else \
|
||||
$(DOCKERSAVE) -o $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \
|
||||
$(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) \
|
||||
$(DOCKERIMAGENAME_UI):$(VERSIONTAG) \
|
||||
$(DOCKERIMAGENAME_LOG):$(VERSIONTAG) \
|
||||
$(DOCKERIMAGENAME_DB):$(VERSIONTAG) \
|
||||
$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) \
|
||||
nginx:$(NGINXVERSION) vmware/registry:$(REGISTRYVERSION) photon:$(PHOTONVERSION) ; \
|
||||
fi
|
||||
|
||||
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
||||
$(TARCMD) -zcvf harbor-offline-installer-$(VERSIONTAG).tgz \
|
||||
$(HARBORPKG)/common/templates $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \
|
||||
$(HARBORPKG)/prepare $(HARBORPKG)/NOTICE \
|
||||
$(HARBORPKG)/LICENSE $(HARBORPKG)/install.sh \
|
||||
$(HARBORPKG)/harbor.cfg $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) \
|
||||
$(HARBORPKG)/$(DOCKERCOMPOSENOTARYFILENAME) ; \
|
||||
else \
|
||||
$(TARCMD) -zcvf harbor-offline-installer-$(VERSIONTAG).tgz \
|
||||
$(HARBORPKG)/common/templates $(HARBORPKG)/$(DOCKERIMGFILE).$(VERSIONTAG).tgz \
|
||||
$(HARBORPKG)/prepare $(HARBORPKG)/NOTICE \
|
||||
$(HARBORPKG)/LICENSE $(HARBORPKG)/install.sh \
|
||||
$(HARBORPKG)/harbor.cfg $(HARBORPKG)/$(DOCKERCOMPOSEFILENAME) ; \
|
||||
fi
|
||||
|
||||
@rm -rf $(HARBORPKG)
|
||||
@echo "Done."
|
||||
|
||||
pushimage:
|
||||
@echo "pushing harbor images ..."
|
||||
@$(DOCKERTAG) $(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG)
|
||||
@$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG) \
|
||||
$(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER)
|
||||
@$(DOCKERRMIMAGE) $(REGISTRYSERVER)$(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG)
|
||||
|
||||
@$(DOCKERTAG) $(DOCKERIMAGENAME_UI):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_UI):$(VERSIONTAG)
|
||||
@$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_UI):$(VERSIONTAG) \
|
||||
$(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER)
|
||||
@$(DOCKERRMIMAGE) $(REGISTRYSERVER)$(DOCKERIMAGENAME_UI):$(VERSIONTAG)
|
||||
|
||||
@$(DOCKERTAG) $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG)
|
||||
@$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG) \
|
||||
$(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER)
|
||||
@$(DOCKERRMIMAGE) $(REGISTRYSERVER)$(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG)
|
||||
|
||||
@$(DOCKERTAG) $(DOCKERIMAGENAME_LOG):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_LOG):$(VERSIONTAG)
|
||||
@$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_LOG):$(VERSIONTAG) \
|
||||
$(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER)
|
||||
@$(DOCKERRMIMAGE) $(REGISTRYSERVER)$(DOCKERIMAGENAME_LOG):$(VERSIONTAG)
|
||||
|
||||
@$(DOCKERTAG) $(DOCKERIMAGENAME_DB):$(VERSIONTAG) $(REGISTRYSERVER)$(DOCKERIMAGENAME_DB):$(VERSIONTAG)
|
||||
@$(PUSHSCRIPTPATH)/$(PUSHSCRIPTNAME) $(REGISTRYSERVER)$(DOCKERIMAGENAME_DB):$(VERSIONTAG) \
|
||||
$(REGISTRYUSER) $(REGISTRYPASSWORD) $(REGISTRYSERVER)
|
||||
@$(DOCKERRMIMAGE) $(REGISTRYSERVER)$(DOCKERIMAGENAME_DB):$(VERSIONTAG)
|
||||
|
||||
start:
|
||||
@echo "loading harbor images..."
|
||||
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
||||
$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSENOTARYFILENAME) up -d ; \
|
||||
else \
|
||||
$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) up -d ; \
|
||||
fi
|
||||
@echo "Start complete. You can visit harbor now."
|
||||
|
||||
down:
|
||||
@echo "Please make sure to set -e NOTARYFLAG=true if you are using Notary in Harbor, otherwise the Notary containers cannot be stop automaticlly."
|
||||
@while [ -z "$$CONTINUE" ]; do \
|
||||
read -r -p "Type anything but Y or y to exit. [Y/N]: " CONTINUE; \
|
||||
done ; \
|
||||
[ $$CONTINUE = "y" ] || [ $$CONTINUE = "Y" ] || (echo "Exiting."; exit 1;)
|
||||
@echo "stoping harbor instance..."
|
||||
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
|
||||
$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSENOTARYFILENAME) down -v ; \
|
||||
else \
|
||||
$(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) down -v ; \
|
||||
fi
|
||||
@echo "Done."
|
||||
|
||||
cleanbinary:
|
||||
@echo "cleaning binary..."
|
||||
@if [ -f $(ADMINSERVERBINARYPATH)/$(ADMINSERVERBINARYNAME) ] ; then rm $(ADMINSERVERBINARYPATH)/$(ADMINSERVERBINARYNAME) ; fi
|
||||
@if [ -f $(UIBINARYPATH)/$(UIBINARYNAME) ] ; then rm $(UIBINARYPATH)/$(UIBINARYNAME) ; fi
|
||||
@if [ -f $(JOBSERVICEBINARYPATH)/$(JOBSERVICEBINARYNAME) ] ; then rm $(JOBSERVICEBINARYPATH)/$(JOBSERVICEBINARYNAME) ; fi
|
||||
|
||||
cleanimage:
|
||||
@echo "cleaning image for photon..."
|
||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_ADMINSERVER):$(VERSIONTAG)
|
||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_UI):$(VERSIONTAG)
|
||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_DB):$(VERSIONTAG)
|
||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_JOBSERVICE):$(VERSIONTAG)
|
||||
- $(DOCKERRMIMAGE) -f $(DOCKERIMAGENAME_LOG):$(VERSIONTAG)
|
||||
# - $(DOCKERRMIMAGE) -f registry:$(REGISTRYVERSION)
|
||||
# - $(DOCKERRMIMAGE) -f nginx:1.11.5
|
||||
|
||||
cleandockercomposefile:
|
||||
@echo "cleaning $(DOCKERCOMPOSEFILEPATH)/docker-compose.yml"
|
||||
@if [ -f $(DOCKERCOMPOSEFILEPATH)/docker-compose.yml ] ; then rm $(DOCKERCOMPOSEFILEPATH)/docker-compose.yml ; fi
|
||||
|
||||
cleanversiontag:
|
||||
@echo "cleaning version TAG"
|
||||
@rm -rf $(VERSIONFILEPATH)/$(VERSIONFILENAME)
|
||||
|
||||
cleanpackage:
|
||||
@echo "cleaning harbor install package"
|
||||
@if [ -d $(BUILDPATH)/harbor ] ; then rm -rf $(BUILDPATH)/harbor ; fi
|
||||
@if [ -f $(BUILDPATH)/harbor-online-installer-$(VERSIONTAG).tgz ] ; \
|
||||
then rm $(BUILDPATH)/harbor-online-installer-$(VERSIONTAG).tgz ; fi
|
||||
@if [ -f $(BUILDPATH)/harbor-offline-installer-$(VERSIONTAG).tgz ] ; \
|
||||
then rm $(BUILDPATH)/harbor-offline-installer-$(VERSIONTAG).tgz ; fi
|
||||
|
||||
.PHONY: cleanall
|
||||
cleanall: cleanbinary cleanimage cleandockercomposefile cleanversiontag cleanpackage
|
||||
|
||||
clean:
|
||||
@echo " make cleanall: remove binary, Harbor images, specific version docker-compose"
|
||||
@echo " file, specific version tag, online and offline install package"
|
||||
@echo " make cleanbinary: remove ui and jobservice binary"
|
||||
@echo " make cleanimage: remove Harbor images"
|
||||
@echo " make cleandockercomposefile: remove specific version docker-compose"
|
||||
@echo " make cleanversiontag: cleanpackageremove specific version tag"
|
||||
@echo " make cleanpackage: remove online and offline install package"
|
||||
|
||||
all: install
|
||||
|
51
docs/use_make.md
Normal file
51
docs/use_make.md
Normal file
@ -0,0 +1,51 @@
|
||||
### Variables
|
||||
Variable | Description
|
||||
-------------------|-------------
|
||||
BASEIMAGE | Container base image, default: photon
|
||||
DEVFLAG | Build model flag, default: dev
|
||||
COMPILETAG | Compile model flag, default: compile_normal (local golang build)
|
||||
GOBUILDIMAGE | Golang image to compile harbor go source code.
|
||||
CLARITYIMAGE | Clarity image that based on Node to compile UI.
|
||||
NOTARYFLAG | Whether to enable notary in harbor, default:false
|
||||
HTTPPROXY | Clarity proxy to build UI.
|
||||
|
||||
|
||||
### Targets
|
||||
Target | Description
|
||||
--------------------|-------------
|
||||
all | prepare env, compile binaries, build images and install images
|
||||
prepare | prepare env
|
||||
compile | compile ui and jobservice code
|
||||
compile_ui | compile ui binary
|
||||
compile_jobservice | compile jobservice binary
|
||||
compile_clarity | compile clarity ui binary
|
||||
compile_adminserver | compile admin server binary
|
||||
build | build Harbor docker images (default: using build_photon)
|
||||
build_photon | build Harbor docker images from Photon OS base image
|
||||
install | compile binaries, build images, prepare specific version of compose file and startup Harbor instance
|
||||
start | startup Harbor instance
|
||||
down | shutdown Harbor instance
|
||||
package_online | prepare online install package
|
||||
package_offline | prepare offline install package
|
||||
pushimage | push Harbor images to specific registry server
|
||||
clean all | remove binary, Harbor images, specific version docker-compose file, specific version tag and online/offline install package
|
||||
cleanbinary | remove ui and jobservice binary
|
||||
cleanimage | remove Harbor images
|
||||
cleandockercomposefile | remove specific version docker-compose
|
||||
cleanversiontag | remove specific version tag
|
||||
cleanpackage | remove online/offline install package
|
||||
version | set harbor version
|
||||
|
||||
#### EXAMPLE:
|
||||
|
||||
#### Build and run harbor from source code.
|
||||
make install GOBUILDIMAGE=golang:1.7.3 COMPILETAG=compile_golangimage CLARITYIMAGE=danieljt/harbor-clarity-base:0.8.4 NOTARYFLAG=true HTTPPROXY=http://proxy.vmware.com:3128
|
||||
|
||||
### Package offline installer
|
||||
make package_offline GOBUILDIMAGE=golang:1.7.3 COMPILETAG=compile_golangimage CLARITYIMAGE=danieljt/harbor-clarity-base:0.8.4 NOTARYFLAG=true HTTPPROXY=http://proxy.vmware.com:3128
|
||||
|
||||
### Start harbor with notary
|
||||
make -e NOTARYFLAG=true start
|
||||
|
||||
### Stop harbor with notary
|
||||
make -e NOTARYFLAG=true down
|
23
docs/use_notary.md
Normal file
23
docs/use_notary.md
Normal file
@ -0,0 +1,23 @@
|
||||
### Setup
|
||||
In harbor.cfg, make sure the attribute ```ui_url_protocol``` is set to ```https```, and the attributes ```ssl_cert``` and ```ssl_cert_key``` are pointed to valid certificates. For more information about generating https certificate please refer to: [Configuring HTTPS for Harbor](configure_https.md)
|
||||
|
||||
### Copy Root Certificate
|
||||
Suppose the Harbor instance is hosted on a machine ```192.168.0.5```
|
||||
If you are using a self-signed cetificate, make sure to copy the CA root cert to ```/etc/docker/certs.d/192.168.0.5/``` and ```~/.docker/tls/192.168.0.5/```
|
||||
|
||||
### Enable Docker Content Trust
|
||||
It can be done via setting envrironment variables:
|
||||
|
||||
```
|
||||
export DOCKER_CONTENT_TRUST=1
|
||||
export DOCKER_CONTENT_TRUST_SERVER=https://192.168.0.5/notary
|
||||
```
|
||||
|
||||
### Set alias for notary (optional)
|
||||
Because by default the local directory for storing meta files for notary client is different from docker client. If you want to use notary client to manipulate the keys/meta files generated by Docker Content Trust, please set the alias to reduce the effort:
|
||||
|
||||
```
|
||||
alias notary="notary -s https//192.168.0.5 -d ~/.docker/trust --tlscacert /
|
||||
etc/docker/certs.d/192.168.0.5/ca.crt"
|
||||
|
||||
```
|
34
make/common/templates/notary/notary-signer-ca.crt
Normal file
34
make/common/templates/notary/notary-signer-ca.crt
Normal file
@ -0,0 +1,34 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIF3TCCA8WgAwIBAgIJANgnJg8tUB+HMA0GCSqGSIb3DQEBCwUAMIGEMQswCQYD
|
||||
VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTESMBAGA1UEBwwJUGFsbyBBbHRv
|
||||
MRUwEwYDVQQKDAxWTXdhcmUsIEluYy4xDzANBgNVBAsMBkhhcmJvcjEkMCIGA1UE
|
||||
AwwbU2VsZi1zaWduZWQgYnkgVk13YXJlLCBJbmMuMB4XDTE3MDMyNDA1MzE1N1oX
|
||||
DTI3MDMyMjA1MzE1N1owgYQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9y
|
||||
bmlhMRIwEAYDVQQHDAlQYWxvIEFsdG8xFTATBgNVBAoMDFZNd2FyZSwgSW5jLjEP
|
||||
MA0GA1UECwwGSGFyYm9yMSQwIgYDVQQDDBtTZWxmLXNpZ25lZCBieSBWTXdhcmUs
|
||||
IEluYy4wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQClgcA3XhXFgaBa
|
||||
iK5G60ym0SB0P4KDyB0aKz1nQwf3svJdzUOLzom3zK8mUDXZ5b0Jnix5KrW6CONs
|
||||
JsjPtZKRXVNkWhUh6362OUt2icmq3BLGqKQ9qTqi4R1NrPr4vug/TmBumxMB+JJI
|
||||
UHRJgLox1dXUEsyxxv5yt/AKPa9nZruI2x8CzdKRVhsiR06B70OJZA8l2UuRv7v8
|
||||
9biGGu4Haavt4CG0goPBXh7PpPNHcoQmgdMAHkawBmrf3qvn2nSrJzfbjsv6iQ9/
|
||||
e3GRAmWmJVsDBvlxwtIJDXLvm3qUN/P/ul6w6zbueAXkAq5UcjIMdDLSnt690DWo
|
||||
B7cO8FWKg4TqvuJ0+qb9Uwty+3x/mONiq9kwbFIKuLnjRJApPO1gevGexotiOyKp
|
||||
ljJMkeabPCuClquqI+LxM+TEmDtxOfJ2OuhisOaAuW2qYl2ZdnaTaVz42kctobwj
|
||||
+DnhvtwItE88mf8tYxDY+Kp+bITlcanmSPASw/YJXMrIbPynzMPCloe2TRSoImGC
|
||||
8uQI6rLSyeUvkpCCxIDnfUTuhmSc2jseqTYyxXrf+qMVNNoTC2VMUwt/nxerjK1a
|
||||
L000KIqk4h0GqUwuAE6I1CPLN9eQE9qlaeSxKPiScPG3M3mkKyIIAKUz3WjR7UnW
|
||||
Aw1Z5fRH28ci8GfbxynTMuWlU/izqwIDAQABo1AwTjAdBgNVHQ4EFgQUZP1uZGYH
|
||||
85c0RIrIJVr/RdC64YcwHwYDVR0jBBgwFoAUZP1uZGYH85c0RIrIJVr/RdC64Ycw
|
||||
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEACYIVf0U2kc849GlpvYCv
|
||||
LDGjbdswjmjAxpZaKFCO3MjAEhDxd8QWb1uCN+asRKV146qU3UL40stjjWUpwx6P
|
||||
YQ48zJi1N+Npc53NWoTQ8JxsmQtATTaIlAgYg1WC1oTg5WTPeNOAY/KuSiwPHIrX
|
||||
yaCJdz0+c1xKRRE1m3m85amrtAJkIigL8WIPsKqnNprP11zLzaebMJNpGwq2lRsI
|
||||
4Sm0SEdJNaOm3fQ8KuTElBAGEmJ3F34FeNajM+hIkd0RnG35nsJQgMQz36E5rMVd
|
||||
P1Djk/wPfXJIk61lGJvS/Rl41c1d+XG8aFjhL0APdYHddB11IZ7+QNslEk11kiVI
|
||||
nNjx5CfFuE6ZSq/TAVrco97TxqKdbMIMkRp/MKoTlxG4O5UlFGOniGvQT4g1A962
|
||||
aobnVvxkIhZ5NbPc8PX18EdfpQcheubDZuQtZMmcdU7ilFI0pP9/bQ2EYKi2oPJv
|
||||
4v6vtCYKU2et2KLJLFt7zUoY4zJGqJcW8BibP5kDkmAT+qxurH6T5X+M2QctdxU/
|
||||
63L3sE/dH3saSAVNqB1hs+9pweEj6E+Uaj6Oyn9UDarri11y+esyVPdBEnHwCEsc
|
||||
o3/KMSc7gXfixQi+WgRoD0DpR/bNatjgbq7KSGi9gZp/Aq+ltx5I49nbf4c+WZ9b
|
||||
l7WOOMS8XTJr7KLDUXkAeic=
|
||||
-----END CERTIFICATE-----
|
@ -1,63 +1,32 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFBDCCAuygAwIBAgIJAMbWdVJcKhXYMA0GCSqGSIb3DQEBCwUAMGwxCzAJBgNV
|
||||
BAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzEPMA0G
|
||||
A1UECgwGRG9ja2VyMScwJQYDVQQDDB5Ob3RhcnkgSW50ZXJtZWRpYXRlIFRlc3Rp
|
||||
bmcgQ0EwHhcNMTcwMTIzMDYwMzM3WhcNMTkwMjEyMDYwMzM3WjBbMQswCQYDVQQG
|
||||
EwJVUzELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDzANBgNV
|
||||
BAoMBkRvY2tlcjEWMBQGA1UEAwwNbm90YXJ5LXNpZ25lcjCCASIwDQYJKoZIhvcN
|
||||
AQEBBQADggEPADCCAQoCggEBANhO8+K9xT6M9dQC90Hxs6bmTXWQzE5oV2kLeVKq
|
||||
OjwAvGt6wBE2XJCAbTS3FORIOyoOVQDVCv2Pk2lZXGWqSrH8SY2umjRJIhPDiqN9
|
||||
V5M/gcmMm2EUgwmp2l4bsDk1MQ6GSbud5kjYGZcp9uXxAVO8tfLVLQF7ohJYqiex
|
||||
JN+fZkQyxTgSqrI7MKK1pUvGX/fa6EXzpKwxTQPJXiG/ZQW0Pn+gdrz+/Cf0PcVy
|
||||
V/Ghc2RR+WjKzqqAiDUJoEtKm/xQVRcSPbagVLCe0KZr7VmtDWnHsUv9ZB9BRNlI
|
||||
lRVDOhVDCCcMu/zEtcxuH8ja7fafi5xNt6vCBmHuCXQtTUsCAwEAAaOBuTCBtjAf
|
||||
BgNVHSMEGDAWgBQjgpNYJjU9Ei7nadpOhHm59FPiKTAMBgNVHRMBAf8EAjAAMB0G
|
||||
A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAOBgNVHQ8BAf8EBAMCBaAwNwYD
|
||||
VR0RBDAwLoINbm90YXJ5LXNpZ25lcoIMbm90YXJ5c2lnbmVygglsb2NhbGhvc3SH
|
||||
BAp1BI4wHQYDVR0OBBYEFLv4/22eN7pe8IzCbL+gKr2i/o6VMA0GCSqGSIb3DQEB
|
||||
CwUAA4ICAQBzBcFgcwtr7oNP7WPyG64mRXHFs1qGCoDZO3D2dZPF/vUKnyPWI6+i
|
||||
Ozu1Lmvd6QUQ5C0m91D6RidKKy3ENz2MgUo8NNj3QY3XzassiLnNOtpo1ed6U3BG
|
||||
2w05gaLTTFywnpOgPy180U6f5uNSHGxY/fq9dN+8YR/MqGOht74q36x0swkPegG/
|
||||
+0SLloKOJw1wBzZ4nCLmED08DWNnuNTAj5IIVjApzqZbTh4+z6H1lmN3b7XwmiWw
|
||||
+y7Jx8k74h5JmqKQnV+3lN0DlCc1BCbtH2fbKOmAKeu4gMniw5FBo75wYrPIet+Z
|
||||
E3G2Zg+T6fjTXAnLGT3S0RVn/CW1lLR6RgkoFgURRZoJyTWrg+1yu4ZOgEz+bot2
|
||||
/hMAr/fjo+Dd6ReFrgGkpTyWYtPhYusori1W8KW138CVrJmSs6p2ss1Ixh8uIOaQ
|
||||
iFmlX/ZXXbvkz3FGQS9LfBdESO3MGjiJTcnXE0DTnXf6RmdlUfNwxsZbIliFa0TQ
|
||||
E/JjIJYQzWmtkJbUdC02GUMjUJAM7SxmP7tU9CmMmjUI28Nno0XtPN2WsAszaiLh
|
||||
JYLJCi7rqaLo0oZuaXVIrgBpQ0qEC1XXS5sCQL+xvMSYvke/rhwIPItWt7Ww/9yj
|
||||
QDIi1nzzX86lbKd095pNX4sUfFx6j4caR8iENgJDfWnqynAzj1Y21A==
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
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
|
||||
XJCKYD/1jYIB
|
||||
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
|
||||
MrTKyOEOBi0Aew==
|
||||
-----END CERTIFICATE-----
|
||||
|
@ -1,28 +1,52 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEA2E7z4r3FPoz11AL3QfGzpuZNdZDMTmhXaQt5Uqo6PAC8a3rA
|
||||
ETZckIBtNLcU5Eg7Kg5VANUK/Y+TaVlcZapKsfxJja6aNEkiE8OKo31Xkz+ByYyb
|
||||
YRSDCanaXhuwOTUxDoZJu53mSNgZlyn25fEBU7y18tUtAXuiEliqJ7Ek359mRDLF
|
||||
OBKqsjsworWlS8Zf99roRfOkrDFNA8leIb9lBbQ+f6B2vP78J/Q9xXJX8aFzZFH5
|
||||
aMrOqoCINQmgS0qb/FBVFxI9tqBUsJ7QpmvtWa0NacexS/1kH0FE2UiVFUM6FUMI
|
||||
Jwy7/MS1zG4fyNrt9p+LnE23q8IGYe4JdC1NSwIDAQABAoIBAHykYhyRxYrZpv3Y
|
||||
B6pUIHVX1+Ka4V98+IFrPynHNW9F7UzxmqNQc95AYq0xojQ4+v6s64ZjPMYHaaYW
|
||||
/AsJKamN+sRNjEX8rko9LzIuE7yhp6QABbjXHPsAiPgZdF5CrFX2Q558yinHfFeC
|
||||
sualDWK3JxEajaiBGU8BEGt2xAymuWACGblrM1aAEZa8B84TW3CzzcdyzAkn8P3e
|
||||
piJCe+DWMc33441r0KlV5GruwF9ewXiWzZtXAOiP/0xEDICFdlFWbO39myMpxDdU
|
||||
Y0uZ+zmn2G3gz2tz25thH0Wl7mDQ3AA0VlHurgPBBEekeZPQmjiKW+F4slCzXvuy
|
||||
kW/urIECgYEA/LhY+OWlZVXzIEly7z1/cU9/WImqTs2uRKDeQHMwZrd7D9BXkJuQ
|
||||
jPN+jZlMYBBrxoaCywbMrgB80Z3MgGHaSx9OIDEZmaxyuQv0zQJCMogysYkbCcaD
|
||||
mHYnyAf7OXa708Z168WAisEhrwa/DXBn3/hPoBkrbMsuPF/J+tEP7lsCgYEA2x2g
|
||||
86SitgPVeNV3iuZ6D/SV0QIbDWOYoST2GQn2LnfALIOrzpXRClOSQZ2pGtg9gYo1
|
||||
owUyyOSv2Fke93p3ufHv3Gqvjl55lzBVV0siHkEXwHcol36DDGQcskVnXJqaL3IF
|
||||
tiOisuJS9A7PW7gEi0miyGzzB/kh/IEWHKqLL9ECgYEAoBOFB+MuqMmQftsHWlLx
|
||||
7qwUVdidb90IjZ/4J4rPFcESyimFzas8HIv/lWGM5yx/l/iL0F42N+FHLt9tMcTJ
|
||||
qNvjeLChLp307RGNtm2/0JJEyf+2iLKdmGz/Nc0YbIWw46vJ9dXcXgeHdn4ndjPF
|
||||
GDEI/rfysa7hUoy6O41BMhECgYBPJsLPgHdufLAOeD44pM0PGnFMERCoo4OtImbr
|
||||
4JdXbdazvdTASYo7yriYj1VY5yhAtSZu/x+7RjDnXDo9d7XsK6NT4g4Mxb/yh3ks
|
||||
kW1/tE/aLLEzGHZKcZeUJlISN57e6Ld7dh/9spf4pajuHuk1T6JH+GNKTAqk5hSQ
|
||||
wmKJIQKBgCGBWGvJrCeT5X9oHdrlHj2YoKvIIG1eibagcjcKemD7sWzi7Q4P7JIo
|
||||
xeX8K1WVxdBpo4/RiQcGFmwSmSUKwwr1dO00xtjxIl7ip4DU+WAM7CdmcOIOMbr4
|
||||
rP9T/wy1ZBkERCIw2ElybTzB8yuOlNLuOMhUeU55xUMFNYYrWEp2
|
||||
-----END RSA PRIVATE KEY-----
|
||||
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
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
|
||||
tajKl+aoc+CDxUYbilzhhKetWWF/cJY=
|
||||
-----END PRIVATE KEY-----
|
||||
|
@ -1,32 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
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
|
||||
lVTA0dsixv5zF3wTuad9inhk9Rmlq1KoaqA=
|
||||
-----END CERTIFICATE-----
|
@ -6,7 +6,7 @@
|
||||
"type": "remote",
|
||||
"hostname": "notarysigner",
|
||||
"port": "7899",
|
||||
"tls_ca_file": "./root-ca.crt",
|
||||
"tls_ca_file": "./notary-signer-ca.crt",
|
||||
"key_algorithm": "ecdsa"
|
||||
},
|
||||
"logging": {
|
||||
|
@ -60,6 +60,8 @@ services:
|
||||
- TERM=dumb
|
||||
- MYSQL_ALLOW_EMPTY_PASSWORD="true"
|
||||
command: mysqld --innodb_file_per_table
|
||||
depends_on:
|
||||
- log
|
||||
logging:
|
||||
driver: "syslog"
|
||||
options:
|
||||
|
@ -1,7 +1,7 @@
|
||||
version: '2'
|
||||
services:
|
||||
log:
|
||||
image: vmware/harbor-log
|
||||
image: vmware/harbor-log:__version__
|
||||
container_name: harbor-log
|
||||
restart: always
|
||||
volumes:
|
||||
@ -11,7 +11,7 @@ services:
|
||||
networks:
|
||||
- harbor
|
||||
registry:
|
||||
image: registry:2.6.0
|
||||
image: vmware/registry:photon-2.6.0
|
||||
container_name: registry
|
||||
restart: always
|
||||
volumes:
|
||||
@ -31,7 +31,7 @@ services:
|
||||
syslog-address: "tcp://127.0.0.1:1514"
|
||||
tag: "registry"
|
||||
mysql:
|
||||
image: vmware/harbor-db
|
||||
image: vmware/harbor-db:__version__
|
||||
container_name: harbor-db
|
||||
restart: always
|
||||
volumes:
|
||||
@ -48,7 +48,7 @@ services:
|
||||
syslog-address: "tcp://127.0.0.1:1514"
|
||||
tag: "mysql"
|
||||
adminserver:
|
||||
image: vmware/harbor-adminserver
|
||||
image: vmware/harbor-adminserver:__version__
|
||||
container_name: harbor-adminserver
|
||||
env_file:
|
||||
- ./common/config/adminserver/env
|
||||
@ -67,7 +67,7 @@ services:
|
||||
syslog-address: "tcp://127.0.0.1:1514"
|
||||
tag: "adminserver"
|
||||
ui:
|
||||
image: vmware/harbor-ui
|
||||
image: vmware/harbor-ui:__version__
|
||||
container_name: harbor-ui
|
||||
env_file:
|
||||
- ./common/config/ui/env
|
||||
@ -88,7 +88,7 @@ services:
|
||||
syslog-address: "tcp://127.0.0.1:1514"
|
||||
tag: "ui"
|
||||
jobservice:
|
||||
image: vmware/harbor-jobservice
|
||||
image: vmware/harbor-jobservice:__version__
|
||||
container_name: harbor-jobservice
|
||||
env_file:
|
||||
- ./common/config/jobservice/env
|
||||
|
@ -20,19 +20,10 @@ max_job_workers = 3
|
||||
|
||||
#Determine whether or not to generate certificate for the registry's token.
|
||||
#If the value is on, the prepare script creates new root cert and private key
|
||||
#for generating token to access the registry. If the value is off, a key/certificate must
|
||||
#be supplied for token generation.
|
||||
#for generating token to access the registry. If the value is off the default key/cert will be used.
|
||||
#This flag also controls the creation of the notary signer's cert.
|
||||
customize_crt = on
|
||||
|
||||
#Information of your organization for certificate
|
||||
crt_country = CN
|
||||
crt_state = State
|
||||
crt_location = CN
|
||||
crt_organization = organization
|
||||
crt_organizationalunit = organizational unit
|
||||
crt_commonname = example.com
|
||||
crt_email = example@example.com
|
||||
|
||||
#The path of cert and key files for nginx, they are applied only the protocol is set to https
|
||||
ssl_cert = /data/cert/server.crt
|
||||
ssl_cert_key = /data/cert/server.key
|
||||
|
@ -166,13 +166,13 @@ then
|
||||
if [ -n "$(docker-compose -f docker-compose.yml -f docker-compose.notary.yml ps -q)" ]
|
||||
then
|
||||
note "stopping existing Harbor instance ..."
|
||||
docker-compose -f docker-compose.yml -f docker-compose.notary.yml down
|
||||
docker-compose -f docker-compose.yml -f docker-compose.notary.yml down -v
|
||||
fi
|
||||
else
|
||||
if [ -n "$(docker-compose -f docker-compose.yml ps -q)" ]
|
||||
then
|
||||
note "stopping existing Harbor instance ..."
|
||||
docker-compose -f docker-compose.yml down
|
||||
docker-compose -f docker-compose.yml down -v
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
|
95
make/prepare
95
make/prepare
@ -135,13 +135,6 @@ if protocol == "https":
|
||||
cert_path = rcp.get("configuration", "ssl_cert")
|
||||
cert_key_path = rcp.get("configuration", "ssl_cert_key")
|
||||
customize_crt = rcp.get("configuration", "customize_crt")
|
||||
crt_country = rcp.get("configuration", "crt_country")
|
||||
crt_state = rcp.get("configuration", "crt_state")
|
||||
crt_location = rcp.get("configuration", "crt_location")
|
||||
crt_organization = rcp.get("configuration", "crt_organization")
|
||||
crt_organizationalunit = rcp.get("configuration", "crt_organizationalunit")
|
||||
crt_commonname = rcp.get("configuration", "crt_commonname")
|
||||
crt_email = rcp.get("configuration", "crt_email")
|
||||
max_job_workers = rcp.get("configuration", "max_job_workers")
|
||||
token_expiration = rcp.get("configuration", "token_expiration")
|
||||
verify_remote_cert = rcp.get("configuration", "verify_remote_cert")
|
||||
@ -262,52 +255,54 @@ FNULL = open(os.devnull, 'w')
|
||||
from functools import wraps
|
||||
def stat_decorator(func):
|
||||
@wraps(func)
|
||||
def check_wrapper(*args, **kwargs):
|
||||
stat = func(*args, **kwargs)
|
||||
message = "Generated configuration file: %s" % kwargs['path'] \
|
||||
if stat == 0 else "Fail to generate %s" % kwargs['path']
|
||||
def check_wrapper(*args, **kw):
|
||||
stat = func(*args, **kw)
|
||||
message = "Generated certificate, key file: %s, cert file: %s" % (kw['key_path'], kw['cert_path']) \
|
||||
if stat == 0 else "Fail to generate key file: %s, cert file: %s" % (kw['key_path'], kw['cert_path'])
|
||||
print(message)
|
||||
if stat != 0:
|
||||
sys.exit(1)
|
||||
return check_wrapper
|
||||
|
||||
@stat_decorator
|
||||
def check_private_key_stat(*args, **kwargs):
|
||||
return subprocess.call(["openssl", "genrsa", "-out", kwargs['path'], "4096"],\
|
||||
stdout=FNULL, stderr=subprocess.STDOUT)
|
||||
def create_root_cert(subj, key_path="./k.key", cert_path="./cert.crt"):
|
||||
rc = subprocess.call(["openssl", "genrsa", "-out", key_path, "4096"], stdout=FNULL, stderr=subprocess.STDOUT)
|
||||
if rc != 0:
|
||||
return rc
|
||||
return subprocess.call(["openssl", "req", "-new", "-x509", "-key", key_path,\
|
||||
"-out", cert_path, "-days", "3650", "-subj", subj], stdout=FNULL, stderr=subprocess.STDOUT)
|
||||
|
||||
@stat_decorator
|
||||
def check_certificate_stat(*args, **kwargs):
|
||||
dirty_subj = "/C={0}/ST={1}/L={2}/O={3}/OU={4}/CN={5}/emailAddress={6}"\
|
||||
.format(crt_country, crt_state, crt_location, crt_organization,\
|
||||
crt_organizationalunit, crt_commonname, crt_email)
|
||||
subj = validate_crt_subj(dirty_subj)
|
||||
return subprocess.call(["openssl", "req", "-new", "-x509", "-key",\
|
||||
private_key_pem, "-out", root_crt, "-days", "3650", "-subj", subj], \
|
||||
stdout=FNULL, stderr=subprocess.STDOUT)
|
||||
def create_cert(subj, ca_key, ca_cert, key_path="./k.key", cert_path="./cert.crt"):
|
||||
cert_dir = os.path.dirname(cert_path)
|
||||
csr_path = os.path.join(cert_dir, "tmp.csr")
|
||||
rc = subprocess.call(["openssl", "req", "-newkey", "rsa:4096", "-nodes","-sha256","-keyout", key_path,\
|
||||
"-out", csr_path, "-subj", subj], stdout=FNULL, stderr=subprocess.STDOUT)
|
||||
if rc != 0:
|
||||
return rc
|
||||
return subprocess.call(["openssl", "x509", "-req", "-days", "3650", "-in", csr_path, "-CA", \
|
||||
ca_cert, "-CAkey", ca_key, "-CAcreateserial", "-out", cert_path], stdout=FNULL, stderr=subprocess.STDOUT)
|
||||
|
||||
def openssl_is_installed(stat):
|
||||
if stat == 0:
|
||||
return True
|
||||
else:
|
||||
def openssl_installed():
|
||||
shell_stat = subprocess.check_call(["which", "openssl"], stdout=FNULL, stderr=subprocess.STDOUT)
|
||||
if shell_stat != 0:
|
||||
print("Cannot find openssl installed in this computer\nUse default SSL certificate file")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
if customize_crt == 'on':
|
||||
if customize_crt == 'on' and openssl_installed():
|
||||
shell_stat = subprocess.check_call(["which", "openssl"], stdout=FNULL, stderr=subprocess.STDOUT)
|
||||
if openssl_is_installed(shell_stat):
|
||||
private_key_pem = os.path.join(config_dir, "ui", "private_key.pem")
|
||||
root_crt = os.path.join(config_dir, "registry", "root.crt")
|
||||
|
||||
check_private_key_stat(path=private_key_pem)
|
||||
check_certificate_stat(path=root_crt)
|
||||
empty_subj = "/C=/ST=/L=/O=/CN=/"
|
||||
private_key_pem = os.path.join(config_dir, "ui", "private_key.pem")
|
||||
root_crt = os.path.join(config_dir, "registry", "root.crt")
|
||||
create_root_cert(empty_subj, key_path=private_key_pem, cert_path=root_crt)
|
||||
else:
|
||||
print("Generated configuration file: %s" % ui_config_dir + "private_key.pem")
|
||||
print("Copied configuration file: %s" % ui_config_dir + "private_key.pem")
|
||||
shutil.copyfile(os.path.join(templates_dir, "ui", "private_key.pem"), os.path.join(ui_config_dir, "private_key.pem"))
|
||||
print("Generated configuration file: %s" % registry_config_dir + "root.crt")
|
||||
print("Copied configuration file: %s" % registry_config_dir + "root.crt")
|
||||
shutil.copyfile(os.path.join(templates_dir, "registry", "root.crt"), os.path.join(registry_config_dir, "root.crt"))
|
||||
|
||||
FNULL.close()
|
||||
if args.notary_mode:
|
||||
notary_config_dir = prep_conf_dir(config_dir, "notary")
|
||||
notary_temp_dir = os.path.join(templates_dir, "notary")
|
||||
@ -315,11 +310,27 @@ if args.notary_mode:
|
||||
if os.path.exists(os.path.join(notary_config_dir, "mysql-initdb.d")):
|
||||
shutil.rmtree(os.path.join(notary_config_dir, "mysql-initdb.d"))
|
||||
shutil.copytree(os.path.join(notary_temp_dir, "mysql-initdb.d"), os.path.join(notary_config_dir, "mysql-initdb.d"))
|
||||
#TODO:generate certs?
|
||||
print("Copying certs for notary signer")
|
||||
shutil.copy2(os.path.join(notary_temp_dir, "notary-signer.crt"), notary_config_dir)
|
||||
shutil.copy2(os.path.join(notary_temp_dir, "notary-signer.key"), notary_config_dir)
|
||||
shutil.copy2(os.path.join(notary_temp_dir, "root-ca.crt"), notary_config_dir)
|
||||
if customize_crt == 'on' and openssl_installed():
|
||||
temp_cert_dir = os.path.join(base_dir, "cert_tmp")
|
||||
if not os.path.exists(temp_cert_dir):
|
||||
os.makedirs(temp_cert_dir)
|
||||
ca_subj = "/C=US/ST=California/L=Palo Alto/O=VMware, Inc./OU=Harbor/CN=Self-signed by VMware, Inc."
|
||||
cert_subj = "/C=US/ST=California/L=Palo Alto/O=VMware, Inc./OU=Harbor/CN=notarysigner"
|
||||
signer_ca_cert = os.path.join(temp_cert_dir, "notary-signer-ca.crt")
|
||||
signer_ca_key = os.path.join(temp_cert_dir, "notary-signer-ca.key")
|
||||
signer_cert_path = os.path.join(temp_cert_dir, "notary-signer.crt")
|
||||
signer_key_path = os.path.join(temp_cert_dir, "notary-signer.key")
|
||||
create_root_cert(ca_subj, key_path=signer_ca_key, cert_path=signer_ca_cert)
|
||||
create_cert(cert_subj, signer_ca_key, signer_ca_cert, key_path=signer_key_path, cert_path=signer_cert_path)
|
||||
print("Copying certs for notary signer")
|
||||
shutil.copy2(signer_cert_path, notary_config_dir)
|
||||
shutil.copy2(signer_key_path, notary_config_dir)
|
||||
shutil.copy2(signer_ca_cert, notary_config_dir)
|
||||
else:
|
||||
print("Copying certs for notary signer")
|
||||
shutil.copy2(os.path.join(notary_temp_dir, "notary-signer.crt"), notary_config_dir)
|
||||
shutil.copy2(os.path.join(notary_temp_dir, "notary-signer.key"), notary_config_dir)
|
||||
shutil.copy2(os.path.join(notary_temp_dir, "notary-signer-ca.crt"), notary_config_dir)
|
||||
|
||||
shutil.copy2(os.path.join(registry_config_dir, "root.crt"), notary_config_dir)
|
||||
print("Copying notary signer configuration file")
|
||||
@ -335,6 +346,6 @@ if args.notary_mode:
|
||||
default_alias = ''.join(random.choice(string.ascii_letters) for i in range(8))
|
||||
render(os.path.join(notary_temp_dir, "signer_env"), os.path.join(notary_config_dir, "signer_env"), alias = default_alias)
|
||||
|
||||
|
||||
FNULL.close()
|
||||
print("The configuration files are ready, please use docker-compose to start the service.")
|
||||
|
||||
|
21
src/ui/views/reset-password-mail.tpl
Normal file
21
src/ui/views/reset-password-mail.tpl
Normal file
@ -0,0 +1,21 @@
|
||||
<!--
|
||||
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<p>Please click this link to reset your password:</p>
|
||||
<a href="{{.URL}}/reset_password?reset_uuid={{.UUID}}">{{.URL}}/reset_password?reset_uuid={{.UUID}}</a>
|
||||
</body>
|
||||
</html>
|
@ -120,15 +120,8 @@ export class HarborShellComponent implements OnInit, OnDestroy {
|
||||
//Handle the global search event and then let the result page to trigger api
|
||||
doSearch(event: string): void {
|
||||
if (event === "") {
|
||||
if (!this.isSearchResultsOpened) {
|
||||
//Will not open search result panel if term is empty
|
||||
return;
|
||||
} else {
|
||||
//If opened, then close the search result panel
|
||||
this.isSearchResultsOpened = false;
|
||||
this.searchResultComponet.close();
|
||||
return;
|
||||
}
|
||||
//Do nothing
|
||||
return;
|
||||
}
|
||||
//Once this method is called
|
||||
//the search results page must be opened
|
||||
|
62
src/ui_ng/src/app/config/config.component.1.html
Normal file
62
src/ui_ng/src/app/config/config.component.1.html
Normal file
@ -0,0 +1,62 @@
|
||||
<div class="config-container">
|
||||
<h2 style="display: inline-block;" class="custom-h2">{{'CONFIG.TITLE' | translate }}</h2>
|
||||
<span class="spinner spinner-inline" [hidden]="inProgress === false"></span>
|
||||
<clr-tabs (clrTabsCurrentTabLinkChanged)="tabLinkChanged($event)">
|
||||
<clr-tab-link [clrTabLinkId]="'config-auth'" [clrTabLinkActive]='isCurrentTabLink("config-auth")'>{{'CONFIG.AUTH' | translate }}</clr-tab-link>
|
||||
<clr-tab-link [clrTabLinkId]="'config-replication'" [clrTabLinkActive]='isCurrentTabLink("config-replication")'>{{'CONFIG.REPLICATION' | translate }}</clr-tab-link>
|
||||
<clr-tab-link [clrTabLinkId]="'config-email'" [clrTabLinkActive]='isCurrentTabLink("config-email")'>{{'CONFIG.EMAIL' | translate }}</clr-tab-link>
|
||||
<clr-tab-link [clrTabLinkId]="'config-system'" [clrTabLinkActive]='isCurrentTabLink("config-system")'>{{'CONFIG.SYSTEM' | translate }}</clr-tab-link>
|
||||
|
||||
<clr-tab-content [clrTabContentId]="'authentication'" [clrTabContentActive]='isCurrentTabContent("authentication")'>
|
||||
<config-auth [ldapConfig]="allConfig"></config-auth>
|
||||
</clr-tab-content>
|
||||
<clr-tab-content [clrTabContentId]="'replication'" [clrTabContentActive]='isCurrentTabContent("replication")'>
|
||||
<form #repoConfigFrom="ngForm" class="form">
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="verifyRemoteCert">{{'CONFIG.VERIFY_REMOTE_CERT' | translate }}</label>
|
||||
<clr-checkbox name="verifyRemoteCert" id="verifyRemoteCert" [(ngModel)]="allConfig.verify_remote_cert.value" [disabled]="disabled(allConfig.verify_remote_cert)">
|
||||
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-lg tooltip-top-right" style="top:-8px;">
|
||||
<clr-icon shape="info-circle" class="is-info" size="24"></clr-icon>
|
||||
<span class="tooltip-content">{{'CONFIG.TOOLTIP.VERIFY_REMOTE_CERT' | translate }}</span>
|
||||
</a>
|
||||
</clr-checkbox>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</clr-tab-content>
|
||||
<clr-tab-content [clrTabContentId]="'email'" [clrTabContentActive]='isCurrentTabContent("email")'>
|
||||
<config-email [mailConfig]="allConfig"></config-email>
|
||||
</clr-tab-content>
|
||||
<clr-tab-content [clrTabContentId]="'system_settings'" [clrTabContentActive]='isCurrentTabContent("system_settings")'>
|
||||
<form #systemConfigFrom="ngForm" class="form">
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="tokenExpiration" class="required">{{'CONFIG.TOKEN_EXPIRATION' | translate}}</label>
|
||||
<label for="tokenExpiration" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-right" [class.invalid]="tokenExpirationInput.invalid && (tokenExpirationInput.dirty || tokenExpirationInput.touched)">
|
||||
<input name="tokenExpiration" type="text" #tokenExpirationInput="ngModel" [(ngModel)]="allConfig.token_expiration.value"
|
||||
required
|
||||
pattern="^[1-9]{1}[\d]*$"
|
||||
id="tokenExpiration"
|
||||
size="40" [disabled]="disabled(allConfig.token_expiration)">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.NUMBER_REQUIRED' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right">
|
||||
<clr-icon shape="info-circle" class="is-info" size="24"></clr-icon>
|
||||
<span class="tooltip-content">{{'CONFIG.TOOLTIP.TOKEN_EXPIRATION' | translate}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</clr-tab-content>
|
||||
</clr-tabs>
|
||||
<div>
|
||||
<button type="button" class="btn btn-primary" (click)="save()" [disabled]="!isValid() || !hasChanges()">{{'BUTTON.SAVE' | translate}}</button>
|
||||
<button type="button" class="btn btn-outline" (click)="cancel()" [disabled]="!isValid() || !hasChanges()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button type="button" class="btn btn-outline" (click)="testMailServer()" *ngIf="showTestServerBtn" [disabled]="!isMailConfigValid()">{{'BUTTON.TEST_MAIL' | translate}}</button>
|
||||
<button type="button" class="btn btn-outline" (click)="testLDAPServer()" *ngIf="showLdapServerBtn" [disabled]="!isLDAPConfigValid()">{{'BUTTON.TEST_LDAP' | translate}}</button>
|
||||
<span class="spinner spinner-inline" [hidden]="!testingInProgress"></span>
|
||||
</div>
|
||||
</div>
|
@ -1,39 +1,47 @@
|
||||
<div class="config-container">
|
||||
<h2 style="display: inline-block;" class="custom-h2">{{'CONFIG.TITLE' | translate }}</h2>
|
||||
<span class="spinner spinner-inline" [hidden]="inProgress === false"></span>
|
||||
<clr-tabs (clrTabsCurrentTabLinkChanged)="tabLinkChanged($event)">
|
||||
<clr-tab-link [clrTabLinkId]="'config-auth'" [clrTabLinkActive]="true">{{'CONFIG.AUTH' | translate }}</clr-tab-link>
|
||||
<clr-tab-link [clrTabLinkId]="'config-replication'">{{'CONFIG.REPLICATION' | translate }}</clr-tab-link>
|
||||
<clr-tab-link [clrTabLinkId]="'config-email'">{{'CONFIG.EMAIL' | translate }}</clr-tab-link>
|
||||
<clr-tab-link [clrTabLinkId]="'config-system'">{{'CONFIG.SYSTEM' | translate }}</clr-tab-link>
|
||||
|
||||
<clr-tab-content [clrTabContentId]="'authentication'" [clrTabContentActive]="true">
|
||||
<config-auth [ldapConfig]="allConfig"></config-auth>
|
||||
</clr-tab-content>
|
||||
<clr-tab-content [clrTabContentId]="'replication'">
|
||||
<form #repoConfigFrom="ngForm" class="form">
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="verifyRemoteCert">{{'CONFIG.VERIFY_REMOTE_CERT' | translate }}</label>
|
||||
<clr-checkbox name="verifyRemoteCert" id="verifyRemoteCert" [(ngModel)]="allConfig.verify_remote_cert.value" [disabled]="disabled(allConfig.verify_remote_cert)">
|
||||
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-lg tooltip-top-right" style="top:-8px;">
|
||||
<clr-icon shape="info-circle" class="is-info" size="24"></clr-icon>
|
||||
<span class="tooltip-content">{{'CONFIG.TOOLTIP.VERIFY_REMOTE_CERT' | translate }}</span>
|
||||
</a>
|
||||
</clr-checkbox>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</clr-tab-content>
|
||||
<clr-tab-content [clrTabContentId]="'email'">
|
||||
<config-email [mailConfig]="allConfig"></config-email>
|
||||
</clr-tab-content>
|
||||
<clr-tab-content [clrTabContentId]="'system_settings'">
|
||||
<form #systemConfigFrom="ngForm" class="form">
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="tokenExpiration" class="required">{{'CONFIG.TOKEN_EXPIRATION' | translate}}</label>
|
||||
<label for="tokenExpiration" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-right" [class.invalid]="tokenExpirationInput.invalid && (tokenExpirationInput.dirty || tokenExpirationInput.touched)">
|
||||
<ul id="configTabs" class="nav" role="tablist">
|
||||
<li role="presentation" class="nav-item">
|
||||
<button id="config-auth" class="btn btn-link nav-link active" aria-controls="authentication" [class.active]='isCurrentTabLink("config-auth")' type="button" (click)='tabLinkClick("config-auth")'>{{'CONFIG.AUTH' | translate }}</button>
|
||||
</li>
|
||||
<li role="presentation" class="nav-item">
|
||||
<button id="config-replication" class="btn btn-link nav-link" aria-controls="replication" [class.active]='isCurrentTabLink("config-replication")' type="button" (click)='tabLinkClick("config-replication")'>{{'CONFIG.REPLICATION' | translate }}</button>
|
||||
</li>
|
||||
<li role="presentation" class="nav-item">
|
||||
<button id="config-email" class="btn btn-link nav-link" aria-controls="email" [class.active]='isCurrentTabLink("config-email")' type="button" (click)='tabLinkClick("config-email")'>{{'CONFIG.EMAIL' | translate }}</button>
|
||||
</li>
|
||||
<li role="presentation" class="nav-item">
|
||||
<button id="config-system" class="btn btn-link nav-link" aria-controls="system_settings" [class.active]='isCurrentTabLink("config-system")' type="button" (click)='tabLinkClick("config-system")'>{{'CONFIG.SYSTEM' | translate }}</button>
|
||||
</li>
|
||||
</ul>
|
||||
<section id="authentication" role="tabpanel" aria-labelledby="config-auth" [hidden]='!isCurrentTabContent("authentication")'>
|
||||
<config-auth [ldapConfig]="allConfig"></config-auth>
|
||||
</section>
|
||||
<section id="replication" role="tabpanel" aria-labelledby="config-replication" [hidden]='!isCurrentTabContent("replication")'>
|
||||
<form #repoConfigFrom="ngForm" class="form">
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="verifyRemoteCert">{{'CONFIG.VERIFY_REMOTE_CERT' | translate }}</label>
|
||||
<clr-checkbox name="verifyRemoteCert" id="verifyRemoteCert" [(ngModel)]="allConfig.verify_remote_cert.value" [disabled]="disabled(allConfig.verify_remote_cert)">
|
||||
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-lg tooltip-top-right" style="top:-8px;">
|
||||
<clr-icon shape="info-circle" class="is-info" size="24"></clr-icon>
|
||||
<span class="tooltip-content">{{'CONFIG.TOOLTIP.VERIFY_REMOTE_CERT' | translate }}</span>
|
||||
</a>
|
||||
</clr-checkbox>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</section>
|
||||
<section id="email" role="tabpanel" aria-labelledby="config-email" [hidden]='!isCurrentTabContent("email")'>
|
||||
<config-email [mailConfig]="allConfig"></config-email>
|
||||
</section>
|
||||
<section id="system_settings" role="tabpanel" aria-labelledby="config-system" [hidden]='!isCurrentTabContent("system_settings")'>
|
||||
<form #systemConfigFrom="ngForm" class="form">
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="tokenExpiration" class="required">{{'CONFIG.TOKEN_EXPIRATION' | translate}}</label>
|
||||
<label for="tokenExpiration" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-right" [class.invalid]="tokenExpirationInput.invalid && (tokenExpirationInput.dirty || tokenExpirationInput.touched)">
|
||||
<input name="tokenExpiration" type="text" #tokenExpirationInput="ngModel" [(ngModel)]="allConfig.token_expiration.value"
|
||||
required
|
||||
pattern="^[1-9]{1}[\d]*$"
|
||||
@ -43,15 +51,14 @@
|
||||
{{'TOOLTIP.NUMBER_REQUIRED' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right">
|
||||
<clr-icon shape="info-circle" class="is-info" size="24"></clr-icon>
|
||||
<span class="tooltip-content">{{'CONFIG.TOOLTIP.TOKEN_EXPIRATION' | translate}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</clr-tab-content>
|
||||
</clr-tabs>
|
||||
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right">
|
||||
<clr-icon shape="info-circle" class="is-info" size="24"></clr-icon>
|
||||
<span class="tooltip-content">{{'CONFIG.TOOLTIP.TOKEN_EXPIRATION' | translate}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</section>
|
||||
<div>
|
||||
<button type="button" class="btn btn-primary" (click)="save()" [disabled]="!isValid() || !hasChanges()">{{'BUTTON.SAVE' | translate}}</button>
|
||||
<button type="button" class="btn btn-outline" (click)="cancel()" [disabled]="!isValid() || !hasChanges()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
|
@ -16,8 +16,15 @@ import { ConfigurationAuthComponent } from './auth/config-auth.component';
|
||||
import { ConfigurationEmailComponent } from './email/config-email.component';
|
||||
|
||||
import { AppConfigService } from '../app-config.service';
|
||||
import { SessionService } from '../shared/session.service';
|
||||
|
||||
const fakePass = "fakepassword";
|
||||
const TabLinkContentMap = {
|
||||
"config-auth": "authentication",
|
||||
"config-replication": "replication",
|
||||
"config-email": "email",
|
||||
"config-system": "system_settings"
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: 'config',
|
||||
@ -27,7 +34,7 @@ const fakePass = "fakepassword";
|
||||
export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||
private onGoing: boolean = false;
|
||||
allConfig: Configuration = new Configuration();
|
||||
private currentTabId: string = "";
|
||||
private currentTabId: string = "config-auth";//default tab
|
||||
private originalCopy: Configuration;
|
||||
private confirmSub: Subscription;
|
||||
private testingOnGoing: boolean = false;
|
||||
@ -41,17 +48,76 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||
private msgService: MessageService,
|
||||
private configService: ConfigurationService,
|
||||
private confirmService: ConfirmationDialogService,
|
||||
private appConfigService: AppConfigService) { }
|
||||
private appConfigService: AppConfigService,
|
||||
private session: SessionService) { }
|
||||
|
||||
private isCurrentTabLink(tabId: string): boolean {
|
||||
return this.currentTabId === tabId;
|
||||
}
|
||||
|
||||
private isCurrentTabContent(contentId: string): boolean {
|
||||
return TabLinkContentMap[this.currentTabId] === contentId;
|
||||
}
|
||||
|
||||
private hasUnsavedChangesOfCurrentTab(): any {
|
||||
let allChanges = this.getChanges();
|
||||
if (this.isEmpty(allChanges)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let properties = [];
|
||||
switch (this.currentTabId) {
|
||||
case "config-auth":
|
||||
for (let prop in allChanges) {
|
||||
if (prop.startsWith("ldap_")) {
|
||||
return allChanges;
|
||||
}
|
||||
}
|
||||
properties = ["auth_mode", "project_creation_restriction", "self_registration"];
|
||||
break;
|
||||
case "config-email":
|
||||
for (let prop in allChanges) {
|
||||
if (prop.startsWith("email_")) {
|
||||
return allChanges;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
case "config-replication":
|
||||
properties = ["verify_remote_cert"];
|
||||
break;
|
||||
case "config-system":
|
||||
properties = ["token_expiration"];
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
for (let prop in allChanges) {
|
||||
if (properties.indexOf(prop) != -1) {
|
||||
return allChanges;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
//First load
|
||||
this.retrieveConfig();
|
||||
//Double confirm the current use has admin role
|
||||
let currentUser = this.session.getCurrentUser();
|
||||
if (currentUser && currentUser.has_admin_role > 0) {
|
||||
this.retrieveConfig();
|
||||
}
|
||||
|
||||
this.confirmSub = this.confirmService.confirmationConfirm$.subscribe(confirmation => {
|
||||
if (confirmation &&
|
||||
confirmation.state === ConfirmationState.CONFIRMED &&
|
||||
confirmation.source === ConfirmationTargets.CONFIG) {
|
||||
this.reset(confirmation.data);
|
||||
confirmation.state === ConfirmationState.CONFIRMED) {
|
||||
if (confirmation.source === ConfirmationTargets.CONFIG) {
|
||||
this.reset(confirmation.data);
|
||||
} else if (confirmation.source === ConfirmationTargets.CONFIG_TAB) {
|
||||
this.reset(confirmation.data["changes"]);
|
||||
this.currentTabId = confirmation.data["tabId"];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -104,8 +170,15 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||
return this.authConfig && this.authConfig.isValid();
|
||||
}
|
||||
|
||||
public tabLinkChanged(tabLink: any) {
|
||||
this.currentTabId = tabLink.id;
|
||||
public tabLinkClick(tabLink: string) {
|
||||
//Whether has unsave changes in current tab
|
||||
let changes = this.hasUnsavedChangesOfCurrentTab();
|
||||
if (!changes) {
|
||||
this.currentTabId = tabLink;
|
||||
return;
|
||||
}
|
||||
|
||||
this.confirmUnsavedTabChanges(changes, tabLink);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -154,14 +227,7 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||
public cancel(): void {
|
||||
let changes = this.getChanges();
|
||||
if (!this.isEmpty(changes)) {
|
||||
let msg = new ConfirmationMessage(
|
||||
"CONFIG.CONFIRM_TITLE",
|
||||
"CONFIG.CONFIRM_SUMMARY",
|
||||
"",
|
||||
changes,
|
||||
ConfirmationTargets.CONFIG
|
||||
);
|
||||
this.confirmService.openComfirmDialog(msg);
|
||||
this.confirmUnsavedChanges(changes);
|
||||
} else {
|
||||
//Inprop situation, should not come here
|
||||
console.error("Nothing changed");
|
||||
@ -218,6 +284,33 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
private confirmUnsavedChanges(changes: any) {
|
||||
let msg = new ConfirmationMessage(
|
||||
"CONFIG.CONFIRM_TITLE",
|
||||
"CONFIG.CONFIRM_SUMMARY",
|
||||
"",
|
||||
changes,
|
||||
ConfirmationTargets.CONFIG
|
||||
);
|
||||
|
||||
this.confirmService.openComfirmDialog(msg);
|
||||
}
|
||||
|
||||
private confirmUnsavedTabChanges(changes: any, tabId: string){
|
||||
let msg = new ConfirmationMessage(
|
||||
"CONFIG.CONFIRM_TITLE",
|
||||
"CONFIG.CONFIRM_SUMMARY",
|
||||
"",
|
||||
{
|
||||
"changes": changes,
|
||||
"tabId": tabId
|
||||
},
|
||||
ConfirmationTargets.CONFIG_TAB
|
||||
);
|
||||
|
||||
this.confirmService.openComfirmDialog(msg);
|
||||
}
|
||||
|
||||
private retrieveConfig(): void {
|
||||
this.onGoing = true;
|
||||
this.configService.getConfiguration()
|
||||
|
@ -18,6 +18,7 @@ export class MessageComponent implements OnInit {
|
||||
globalMessage: Message = new Message();
|
||||
globalMessageOpened: boolean;
|
||||
messageText: string = "";
|
||||
private timer: any = null;
|
||||
|
||||
constructor(
|
||||
private messageService: MessageService,
|
||||
@ -48,7 +49,7 @@ export class MessageComponent implements OnInit {
|
||||
|
||||
// Make the message alert bar dismiss after several intervals.
|
||||
//Only for this case
|
||||
setInterval(() => this.onClose(), dismissInterval);
|
||||
this.timer = setTimeout(() => this.onClose(), dismissInterval);
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -56,15 +57,9 @@ export class MessageComponent implements OnInit {
|
||||
|
||||
//Translate or refactor the message shown to user
|
||||
translateMessage(msg: Message): void {
|
||||
if (!msg) {
|
||||
return;
|
||||
}
|
||||
|
||||
let key = "";
|
||||
if (!msg.message) {
|
||||
key = "UNKNOWN_ERROR";
|
||||
} else {
|
||||
key = typeof msg.message === "string" ? msg.message.trim() : msg.message;
|
||||
let key = "UNKNOWN_ERROR", param = "";
|
||||
if (msg && msg.message) {
|
||||
key = (typeof msg.message === "string" ? msg.message.trim() : msg.message);
|
||||
if (key === "") {
|
||||
key = "UNKNOWN_ERROR";
|
||||
}
|
||||
@ -73,13 +68,11 @@ export class MessageComponent implements OnInit {
|
||||
//Override key for HTTP 401 and 403
|
||||
if (this.globalMessage.statusCode === httpStatusCode.Unauthorized) {
|
||||
key = "UNAUTHORIZED_ERROR";
|
||||
}
|
||||
|
||||
if (this.globalMessage.statusCode === httpStatusCode.Forbidden) {
|
||||
} else if (this.globalMessage.statusCode === httpStatusCode.Forbidden) {
|
||||
key = "FORBIDDEN_ERROR";
|
||||
}
|
||||
}
|
||||
|
||||
this.translate.get(key).subscribe((res: string) => this.messageText = res);
|
||||
this.translate.get(key, { 'param': param }).subscribe((res: string) => this.messageText = res);
|
||||
}
|
||||
|
||||
public get needAuth(): boolean {
|
||||
@ -98,6 +91,9 @@ export class MessageComponent implements OnInit {
|
||||
}
|
||||
|
||||
onClose() {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
this.globalMessageOpened = false;
|
||||
}
|
||||
}
|
@ -33,6 +33,8 @@ import { AuthCheckGuard } from './shared/route/auth-user-activate.service';
|
||||
import { SignInGuard } from './shared/route/sign-in-guard-activate.service';
|
||||
import { LeavingConfigRouteDeactivate } from './shared/route/leaving-config-deactivate.service';
|
||||
|
||||
import { MemberGuard } from './shared/route/member-guard-activate.service';
|
||||
|
||||
const harborRoutes: Routes = [
|
||||
{ path: '', redirectTo: 'harbor', pathMatch: 'full' },
|
||||
{ path: 'password-reset', component: ResetPasswordComponent },
|
||||
@ -74,11 +76,15 @@ const harborRoutes: Routes = [
|
||||
},
|
||||
{
|
||||
path: 'tags/:id/:repo',
|
||||
component: TagRepositoryComponent
|
||||
component: TagRepositoryComponent,
|
||||
resolve: {
|
||||
projectResolver: ProjectRoutingResolver
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'projects/:id',
|
||||
component: ProjectDetailComponent,
|
||||
canActivate: [MemberGuard],
|
||||
resolve: {
|
||||
projectResolver: ProjectRoutingResolver
|
||||
},
|
||||
@ -89,7 +95,8 @@ const harborRoutes: Routes = [
|
||||
},
|
||||
{
|
||||
path: 'replication',
|
||||
component: ReplicationComponent
|
||||
component: ReplicationComponent,
|
||||
canActivate: [SystemAdminGuard]
|
||||
},
|
||||
{
|
||||
path: 'member',
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Http, Headers, RequestOptions } from '@angular/http';
|
||||
|
||||
import { BaseService } from '../service/base.service';
|
||||
|
||||
import { AuditLog } from './audit-log';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
@ -13,7 +11,7 @@ import 'rxjs/add/observable/throw';
|
||||
export const logEndpoint = "/api/logs";
|
||||
|
||||
@Injectable()
|
||||
export class AuditLogService extends BaseService {
|
||||
export class AuditLogService {
|
||||
private httpOptions = new RequestOptions({
|
||||
headers: new Headers({
|
||||
"Content-Type": 'application/json',
|
||||
@ -21,9 +19,7 @@ export class AuditLogService extends BaseService {
|
||||
})
|
||||
});
|
||||
|
||||
constructor(private http: Http) {
|
||||
super();
|
||||
}
|
||||
constructor(private http: Http) {}
|
||||
|
||||
listAuditLogs(queryParam: AuditLog): Observable<any> {
|
||||
return this.http
|
||||
@ -36,13 +32,12 @@ export class AuditLogService extends BaseService {
|
||||
username: queryParam.username
|
||||
})
|
||||
.map(response => response)
|
||||
.catch(error => this.handleError(error));
|
||||
.catch(error => Observable.throw(error));
|
||||
}
|
||||
|
||||
getRecentLogs(lines: number): Observable<AuditLog[]> {
|
||||
return this.http.get(logEndpoint + "?lines=" + lines, this.httpOptions)
|
||||
.map(response => response.json() as AuditLog[])
|
||||
.catch(error => this.handleError(error));
|
||||
.catch(error => Observable.throw(error));
|
||||
}
|
||||
|
||||
}
|
@ -2,25 +2,18 @@
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
.filter-log {
|
||||
float: right;
|
||||
margin-right: 24px;
|
||||
position: relative;
|
||||
top: 8px;
|
||||
}
|
||||
|
||||
.action-head-pos {
|
||||
position: relative;
|
||||
top: 20px;
|
||||
padding-right: 18px;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
position: absolute;
|
||||
right: -4px;
|
||||
top: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.refresh-btn:hover {
|
||||
color: #00bfff;
|
||||
}
|
||||
|
||||
.custom-lines-button {
|
||||
padding: 0px !important;
|
||||
min-width: 25px !important;
|
||||
@ -29,4 +22,21 @@
|
||||
.lines-button-toggole {
|
||||
font-size: 16px;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.log-select {
|
||||
width: 180px;
|
||||
display: inline-block;
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
.item-divider {
|
||||
height: 24px;
|
||||
display: inline-block;
|
||||
width: 1px;
|
||||
background-color: #ccc;
|
||||
opacity: 0.55;
|
||||
margin-left: 12px;
|
||||
top: 8px;
|
||||
position: relative;
|
||||
}
|
@ -1,20 +1,24 @@
|
||||
<div>
|
||||
<h2 class="h2-log-override">{{'SIDE_NAV.LOGS' | translate}}</h2>
|
||||
<div class="action-head-pos">
|
||||
<span>
|
||||
<label>{{'RECENT_LOG.SUB_TITLE' | translate}} </label>
|
||||
<button type="submit" class="btn btn-link custom-lines-button" [class.lines-button-toggole]="lines === 10" (click)="setLines(10)">10</button>
|
||||
<label> | </label>
|
||||
<button type="submit" class="btn btn-link custom-lines-button" [class.lines-button-toggole]="lines === 25" (click)="setLines(25)">25</button>
|
||||
<label> | </label>
|
||||
<button type="submit" class="btn btn-link custom-lines-button" [class.lines-button-toggole]="lines === 50" (click)="setLines(50)">50</button>
|
||||
<label>{{'RECENT_LOG.SUB_TITLE_SUFIX' | translate}}</label>
|
||||
</span>
|
||||
<grid-filter class="filter-log" filterPlaceholder='{{"AUDIT_LOG.FILTER_PLACEHOLDER" | translate}}' (filter)="doFilter($event)"></grid-filter>
|
||||
<span class="refresh-btn" (click)="refresh()">
|
||||
<h2 class="h2-log-override">{{'SIDE_NAV.LOGS' | translate}}
|
||||
<span class="badge badge-info">{{logNumber}}</span>
|
||||
</h2>
|
||||
<div class="row flex-items-xs-between flex-items-xs-bottom">
|
||||
<div></div>
|
||||
<div class="action-head-pos">
|
||||
<div class="select log-select">
|
||||
<select id="log_display_num" (change)="handleOnchange($event)">
|
||||
<option value="10">{{'RECENT_LOG.SUB_TITLE' | translate}} 10 {{'RECENT_LOG.SUB_TITLE_SUFIX' | translate}}</option>
|
||||
<option value="25">{{'RECENT_LOG.SUB_TITLE' | translate}} 25 {{'RECENT_LOG.SUB_TITLE_SUFIX' | translate}}</option>
|
||||
<option value="50">{{'RECENT_LOG.SUB_TITLE' | translate}} 50 {{'RECENT_LOG.SUB_TITLE_SUFIX' | translate}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="item-divider"></div>
|
||||
<grid-filter filterPlaceholder='{{"AUDIT_LOG.FILTER_PLACEHOLDER" | translate}}' (filter)="doFilter($event)"></grid-filter>
|
||||
<span (click)="refresh()" class="refresh-btn">
|
||||
<clr-icon shape="refresh" [hidden]="inProgress" ng-disabled="inProgress"></clr-icon>
|
||||
<span class="spinner spinner-inline" [hidden]="inProgress === false"></span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<clr-datagrid>
|
||||
|
@ -34,17 +34,22 @@ export class RecentLogComponent implements OnInit {
|
||||
this.retrieveLogs();
|
||||
}
|
||||
|
||||
public get inProgress(): boolean {
|
||||
return this.onGoing;
|
||||
private handleOnchange($event: any) {
|
||||
if (event && event.target && event.srcElement["value"]) {
|
||||
this.lines = event.srcElement["value"];
|
||||
if (this.lines < 10) {
|
||||
this.lines = 10;
|
||||
}
|
||||
this.retrieveLogs();
|
||||
}
|
||||
}
|
||||
|
||||
public setLines(lines: number): void {
|
||||
this.lines = lines;
|
||||
if (this.lines < 10) {
|
||||
this.lines = 10;
|
||||
}
|
||||
public get logNumber(): number {
|
||||
return this.recentLogs?this.recentLogs.length:0;
|
||||
}
|
||||
|
||||
this.retrieveLogs();
|
||||
public get inProgress(): boolean {
|
||||
return this.onGoing;
|
||||
}
|
||||
|
||||
public doFilter(terms: string): void {
|
||||
@ -60,7 +65,7 @@ export class RecentLogComponent implements OnInit {
|
||||
this.retrieveLogs();
|
||||
}
|
||||
|
||||
public formatDateTime(dateTime: string){
|
||||
public formatDateTime(dateTime: string) {
|
||||
let dt: Date = new Date(dateTime);
|
||||
return dt.toLocaleString();
|
||||
}
|
||||
|
@ -31,6 +31,6 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline" (click)="onCancel()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button type="button" class="btn btn-primary" [disabled]="!projectForm.form.valid" (click)="onSubmit()">{{'BUTTON.OK' | translate}}</button>
|
||||
<button type="button" class="btn btn-primary" [disabled]="projectForm.form.invalid" (click)="onSubmit()">{{'BUTTON.OK' | translate}}</button>
|
||||
</div>
|
||||
</clr-modal>
|
||||
|
@ -13,7 +13,6 @@ import { InlineAlertComponent } from '../../shared/inline-alert/inline-alert.com
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'create-project',
|
||||
templateUrl: 'create-project.component.html',
|
||||
@ -50,6 +49,7 @@ export class CreateProjectComponent implements AfterViewChecked {
|
||||
.subscribe(
|
||||
status=>{
|
||||
this.create.emit(true);
|
||||
this.messageService.announceMessage(status, 'PROJECT.CREATED_SUCCESS', AlertType.SUCCESS);
|
||||
this.createProjectOpened = false;
|
||||
},
|
||||
error=>{
|
||||
|
@ -1,20 +1,20 @@
|
||||
<clr-datagrid (clrDgRefresh)="refresh($event)">
|
||||
<clr-dg-column>{{'PROJECT.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'PROJECT.PUBLIC_OR_PRIVATE' | translate}}</clr-dg-column>
|
||||
<clr-dg-column *ngIf="showRoleInfo">{{'PROJECT.ROLE' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'PROJECT.REPO_COUNT'| translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'PROJECT.CREATION_TIME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'PROJECT.DESCRIPTION' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *ngFor="let p of projects" [clrDgItem]="p">
|
||||
<clr-dg-action-overflow *ngIf="listFullMode">
|
||||
<button class="action-item" (click)="newReplicationRule(p)">{{'PROJECT.REPLICATION_RULE' | translate}}</button>
|
||||
<clr-dg-action-overflow [hidden]="!listFullMode || p.current_user_role_id !== 1">
|
||||
<button class="action-item" (click)="newReplicationRule(p)" [hidden]="!isSystemAdmin">{{'PROJECT.REPLICATION_RULE' | translate}}</button>
|
||||
<button class="action-item" (click)="toggleProject(p)">{{'PROJECT.MAKE' | translate}} {{(p.public === 0 ? 'PROJECT.PUBLIC' : 'PROJECT.PRIVATE') | translate}} </button>
|
||||
<button class="action-item" (click)="deleteProject(p)">{{'PROJECT.DELETE' | translate}}</button>
|
||||
</clr-dg-action-overflow>
|
||||
<clr-dg-cell><a href="javascript:void(0)" (click)="goToLink(p.project_id)">{{p.name}}</a></clr-dg-cell>
|
||||
<clr-dg-cell>{{ (p.public === 1 ? 'PROJECT.PUBLIC' : 'PROJECT.PRIVATE') | translate}}</clr-dg-cell>
|
||||
<clr-dg-cell *ngIf="showRoleInfo">{{roleInfo[p.current_user_role_id] | translate}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{p.repo_count}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{p.creation_time}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{p.description}}</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
{{totalRecordCount || (projects ? projects.length : 0)}} {{'PROJECT.ITEMS' | translate}}
|
||||
|
@ -5,7 +5,7 @@ import { ProjectService } from '../project.service';
|
||||
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
import { SearchTriggerService } from '../../base/global-search/search-trigger.service';
|
||||
import { ListMode } from '../../shared/shared.const';
|
||||
import { ListMode, ProjectTypes, RoleInfo } from '../../shared/shared.const';
|
||||
|
||||
import { State } from 'clarity-angular';
|
||||
|
||||
@ -24,6 +24,8 @@ export class ListProjectComponent implements OnInit {
|
||||
@Input() totalRecordCount: number;
|
||||
pageOffset: number = 1;
|
||||
|
||||
@Input() filteredType: string;
|
||||
|
||||
@Output() paginate = new EventEmitter<State>();
|
||||
|
||||
@Output() toggle = new EventEmitter<Project>();
|
||||
@ -31,6 +33,8 @@ export class ListProjectComponent implements OnInit {
|
||||
|
||||
@Input() mode: string = ListMode.FULL;
|
||||
|
||||
roleInfo = RoleInfo;
|
||||
|
||||
constructor(
|
||||
private session: SessionService,
|
||||
private router: Router,
|
||||
@ -43,6 +47,15 @@ export class ListProjectComponent implements OnInit {
|
||||
return this.mode === ListMode.FULL && this.session.getCurrentUser() != null;
|
||||
}
|
||||
|
||||
get showRoleInfo(): boolean {
|
||||
return this.listFullMode && this.filteredType === ProjectTypes[0];
|
||||
}
|
||||
|
||||
public get isSystemAdmin(): boolean {
|
||||
let account = this.session.getCurrentUser();
|
||||
return account != null && account.has_admin_role > 0;
|
||||
}
|
||||
|
||||
goToLink(proId: number): void {
|
||||
this.searchTrigger.closeSearch(false);
|
||||
|
||||
|
@ -19,15 +19,15 @@
|
||||
<div class="form-group">
|
||||
<label class="col-md-4 form-group-label-override">{{'MEMBER.ROLE' | translate}}</label>
|
||||
<div class="radio">
|
||||
<input type="radio" name="roleRadios" id="checkrads_project_admin" value="1" [(ngModel)]="member.role_id">
|
||||
<input type="radio" name="roleRadios" id="checkrads_project_admin" [value]="1" [(ngModel)]="member.role_id">
|
||||
<label for="checkrads_project_admin">{{'MEMBER.PROJECT_ADMIN' | translate}}</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<input type="radio" name="roleRadios" id="checkrads_developer" value="2" [(ngModel)]="member.role_id">
|
||||
<input type="radio" name="roleRadios" id="checkrads_developer" [value]="2" [(ngModel)]="member.role_id">
|
||||
<label for="checkrads_developer">{{'MEMBER.DEVELOPER' | translate}}</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<input type="radio" name="roleRadios" id="checkrads_guest" value="3" [(ngModel)]="member.role_id">
|
||||
<input type="radio" name="roleRadios" id="checkrads_guest" [value]="3" [(ngModel)]="member.role_id">
|
||||
<label for="checkrads_guest">{{'MEMBER.GUEST' | translate}}</label>
|
||||
</div>
|
||||
</div>
|
||||
@ -36,6 +36,6 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline" (click)="onCancel()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button type="button" class="btn btn-primary" (click)="onSubmit()">{{'BUTTON.OK' | translate}}</button>
|
||||
<button type="button" class="btn btn-primary" [disabled]="memberForm.form.invalid" (click)="onSubmit()">{{'BUTTON.OK' | translate}}</button>
|
||||
</div>
|
||||
</clr-modal>
|
||||
|
@ -51,6 +51,7 @@ export class AddMemberComponent implements AfterViewChecked {
|
||||
.addMember(this.projectId, this.member.username, +this.member.role_id)
|
||||
.subscribe(
|
||||
response=>{
|
||||
this.messageService.announceMessage(response, 'MEMBER.ADDED_SUCCESS', AlertType.SUCCESS);
|
||||
console.log('Added member successfully.');
|
||||
this.added.emit(true);
|
||||
this.addMemberOpened = false;
|
||||
@ -112,9 +113,11 @@ export class AddMemberComponent implements AfterViewChecked {
|
||||
}
|
||||
|
||||
openAddMemberModal(): void {
|
||||
this.memberForm.reset();
|
||||
this.member = new Member();
|
||||
this.addMemberOpened = true;
|
||||
this.hasChanged = false;
|
||||
this.member.role_id = 1;
|
||||
}
|
||||
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<div class="row flex-items-xs-between">
|
||||
<div class="flex-xs-middle option-left">
|
||||
<button class="btn btn-primary" (click)="openAddMemberModal()"><clr-icon shape="add"></clr-icon> {{'MEMBER.MEMBER' | translate }}</button>
|
||||
<button *ngIf="hasProjectAdminRole" class="btn btn-primary" (click)="openAddMemberModal()"><clr-icon shape="add"></clr-icon> {{'MEMBER.MEMBER' | translate }}</button>
|
||||
<add-member [projectId]="projectId" (added)="addedMember($event)"></add-member>
|
||||
</div>
|
||||
<div class="flex-xs-middle option-right">
|
||||
@ -17,15 +17,15 @@
|
||||
<clr-datagrid>
|
||||
<clr-dg-column>{{'MEMBER.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'MEMBER.ROLE' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *ngFor="let u of members">
|
||||
<clr-dg-action-overflow [hidden]="u.user_id === currentUser.user_id">
|
||||
<button class="action-item" (click)="changeRole(u.user_id, 1)">{{'MEMBER.PROJECT_ADMIN' | translate}}</button>
|
||||
<button class="action-item" (click)="changeRole(u.user_id, 2)">{{'MEMBER.DEVELOPER' | translate}}</button>
|
||||
<button class="action-item" (click)="changeRole(u.user_id, 3)">{{'MEMBER.GUEST' | translate}}</button>
|
||||
<button class="action-item" (click)="deleteMember(u.user_id)">{{'MEMBER.DELETE' | translate}}</button>
|
||||
<clr-dg-row *ngFor="let m of members">
|
||||
<clr-dg-action-overflow [hidden]="m.user_id === currentUser.user_id || !hasProjectAdminRole">
|
||||
<button class="action-item" (click)="changeRole(m, 1)">{{'MEMBER.PROJECT_ADMIN' | translate}}</button>
|
||||
<button class="action-item" (click)="changeRole(m, 2)">{{'MEMBER.DEVELOPER' | translate}}</button>
|
||||
<button class="action-item" (click)="changeRole(m, 3)">{{'MEMBER.GUEST' | translate}}</button>
|
||||
<button class="action-item" (click)="deleteMember(m)">{{'MEMBER.DELETE' | translate}}</button>
|
||||
</clr-dg-action-overflow>
|
||||
<clr-dg-cell>{{u.username}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{roleInfo[u.role_id] | translate}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{m.username}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{roleInfo[m.role_id] | translate}}</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>{{ (members ? members.length : 0) }} {{'MEMBER.ITEMS' | translate}}</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
|
@ -15,6 +15,8 @@ import { ConfirmationDialogService } from '../../shared/confirmation-dialog/conf
|
||||
import { ConfirmationMessage } from '../../shared/confirmation-dialog/confirmation-message';
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
|
||||
import { RoleInfo } from '../../shared/shared.const';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import 'rxjs/add/operator/switchMap';
|
||||
import 'rxjs/add/operator/catch';
|
||||
@ -22,7 +24,7 @@ import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/observable/throw';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
export const roleInfo: {} = { 1: 'MEMBER.PROJECT_ADMIN', 2: 'MEMBER.DEVELOPER', 3: 'MEMBER.GUEST' };
|
||||
import { Project } from '../../project/project';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
@ -31,21 +33,22 @@ export const roleInfo: {} = { 1: 'MEMBER.PROJECT_ADMIN', 2: 'MEMBER.DEVELOPER',
|
||||
})
|
||||
export class MemberComponent implements OnInit, OnDestroy {
|
||||
|
||||
currentUser: SessionUser;
|
||||
members: Member[];
|
||||
projectId: number;
|
||||
roleInfo = roleInfo;
|
||||
roleInfo = RoleInfo;
|
||||
private delSub: Subscription;
|
||||
|
||||
@ViewChild(AddMemberComponent)
|
||||
addMemberComponent: AddMemberComponent;
|
||||
|
||||
currentUser: SessionUser;
|
||||
hasProjectAdminRole: boolean;
|
||||
|
||||
constructor(private route: ActivatedRoute, private router: Router,
|
||||
private memberService: MemberService, private messageService: MessageService,
|
||||
private deletionDialogService: ConfirmationDialogService,
|
||||
session: SessionService) {
|
||||
//Get current user from registered resolver.
|
||||
this.currentUser = session.getCurrentUser();
|
||||
private session: SessionService) {
|
||||
|
||||
this.delSub = deletionDialogService.confirmationConfirm$.subscribe(message => {
|
||||
if (message &&
|
||||
message.state === ConfirmationState.CONFIRMED &&
|
||||
@ -54,7 +57,8 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||
.deleteMember(this.projectId, message.data)
|
||||
.subscribe(
|
||||
response => {
|
||||
console.log('Successful change role with user ' + message.data);
|
||||
this.messageService.announceMessage(response, 'MEMBER.DELETED_SUCCESS', AlertType.SUCCESS);
|
||||
console.log('Successful delete member: ' + message.data);
|
||||
this.retrieve(this.projectId, '');
|
||||
},
|
||||
error => this.messageService.announceMessage(error.status, 'Failed to change role with user ' + message.data, AlertType.DANGER)
|
||||
@ -71,8 +75,7 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||
error => {
|
||||
this.router.navigate(['/harbor', 'projects']);
|
||||
this.messageService.announceMessage(error.status, 'Failed to get project member with project ID:' + projectId, AlertType.DANGER);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@ -85,6 +88,15 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||
//Get projectId from route params snapshot.
|
||||
this.projectId = +this.route.snapshot.parent.params['id'];
|
||||
console.log('Get projectId from route params snapshot:' + this.projectId);
|
||||
|
||||
this.currentUser = this.session.getCurrentUser();
|
||||
//Get current user from registered resolver.
|
||||
let resolverData = this.route.snapshot.parent.data;
|
||||
if(resolverData) {
|
||||
this.hasProjectAdminRole = (<Project>resolverData['projectResolver']).has_project_admin_role;
|
||||
}
|
||||
|
||||
|
||||
|
||||
this.retrieve(this.projectId, '');
|
||||
}
|
||||
@ -97,24 +109,27 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||
this.retrieve(this.projectId, '');
|
||||
}
|
||||
|
||||
changeRole(userId: number, roleId: number) {
|
||||
this.memberService
|
||||
.changeMemberRole(this.projectId, userId, roleId)
|
||||
.subscribe(
|
||||
response => {
|
||||
console.log('Successful change role with user ' + userId + ' to roleId ' + roleId);
|
||||
this.retrieve(this.projectId, '');
|
||||
},
|
||||
error => this.messageService.announceMessage(error.status, 'Failed to change role with user ' + userId + ' to roleId ' + roleId, AlertType.DANGER)
|
||||
);
|
||||
changeRole(m: Member, roleId: number) {
|
||||
if(m) {
|
||||
this.memberService
|
||||
.changeMemberRole(this.projectId, m.user_id, roleId)
|
||||
.subscribe(
|
||||
response => {
|
||||
this.messageService.announceMessage(response, 'MEMBER.SWITCHED_SUCCESS', AlertType.SUCCESS);
|
||||
console.log('Successful change role with user ' + m.user_id + ' to roleId ' + roleId);
|
||||
this.retrieve(this.projectId, '');
|
||||
},
|
||||
error => this.messageService.announceMessage(error.status, 'Failed to change role with user ' + m.user_id + ' to roleId ' + roleId, AlertType.DANGER)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
deleteMember(userId: number) {
|
||||
deleteMember(m: Member) {
|
||||
let deletionMessage: ConfirmationMessage = new ConfirmationMessage(
|
||||
'MEMBER.DELETION_TITLE',
|
||||
'MEMBER.DELETION_SUMMARY',
|
||||
userId + "",
|
||||
userId,
|
||||
m.username,
|
||||
m.user_id,
|
||||
ConfirmationTargets.PROJECT_MEMBER
|
||||
);
|
||||
this.deletionDialogService.openComfirmDialog(deletionMessage);
|
||||
|
@ -6,22 +6,19 @@ import 'rxjs/add/operator/catch';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/observable/throw';
|
||||
|
||||
import { BaseService } from '../../service/base.service';
|
||||
import { Member } from './member';
|
||||
|
||||
@Injectable()
|
||||
export class MemberService extends BaseService {
|
||||
export class MemberService {
|
||||
|
||||
constructor(private http: Http) {
|
||||
super();
|
||||
}
|
||||
constructor(private http: Http) {}
|
||||
|
||||
listMembers(projectId: number, username: string): Observable<Member[]> {
|
||||
console.log('Get member from project_id:' + projectId + ', username:' + username);
|
||||
return this.http
|
||||
.get(`/api/projects/${projectId}/members?username=${username}`)
|
||||
.map(response=>response.json())
|
||||
.catch(error=>this.handleError(error));
|
||||
.map(response=>response.json() as Member[])
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
|
||||
addMember(projectId: number, username: string, roleId: number): Observable<any> {
|
||||
|
@ -5,10 +5,10 @@
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" routerLink="repository" routerLinkActive="active">{{'PROJECT_DETAIL.REPOSITORIES' | translate}}</a>
|
||||
</li>
|
||||
<li class="nav-item" *ngIf="isSessionValid">
|
||||
<li class="nav-item" *ngIf="isSystemAdmin || isMember">
|
||||
<a class="nav-link" routerLink="member" routerLinkActive="active">{{'PROJECT_DETAIL.USERS' | translate}}</a>
|
||||
</li>
|
||||
<li class="nav-item" *ngIf="isSessionValid">
|
||||
<li class="nav-item" *ngIf="isSystemAdmin || isMember">
|
||||
<a class="nav-link" routerLink="log" routerLinkActive="active">{{'PROJECT_DETAIL.LOGS' | translate}}</a>
|
||||
</li>
|
||||
<li class="nav-item" *ngIf="isSessionValid && isSystemAdmin">
|
||||
|
@ -4,6 +4,7 @@ import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { Project } from '../project';
|
||||
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
import { ProjectService } from '../../project/project.service';
|
||||
|
||||
@Component({
|
||||
selector: 'project-detail',
|
||||
@ -13,13 +14,18 @@ import { SessionService } from '../../shared/session.service';
|
||||
export class ProjectDetailComponent {
|
||||
|
||||
currentProject: Project;
|
||||
isMember: boolean;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private sessionService: SessionService) {
|
||||
this.route.data.subscribe(data=>this.currentProject = <Project>data['projectResolver']);
|
||||
private sessionService: SessionService,
|
||||
private projectService: ProjectService) {
|
||||
|
||||
this.route.data.subscribe(data=>{
|
||||
this.currentProject = <Project>data['projectResolver'];
|
||||
this.isMember = this.currentProject.is_member;
|
||||
});
|
||||
}
|
||||
|
||||
public get isSystemAdmin(): boolean {
|
||||
|
@ -3,23 +3,43 @@ import { Router, Resolve, RouterStateSnapshot, ActivatedRouteSnapshot } from '@a
|
||||
|
||||
import { Project } from './project';
|
||||
import { ProjectService } from './project.service';
|
||||
import { SessionService } from '../shared/session.service';
|
||||
import 'rxjs/add/operator/mergeMap';
|
||||
|
||||
@Injectable()
|
||||
export class ProjectRoutingResolver implements Resolve<Project>{
|
||||
|
||||
constructor(private projectService: ProjectService, private router: Router) {}
|
||||
constructor(
|
||||
private sessionService: SessionService,
|
||||
private projectService: ProjectService,
|
||||
private router: Router) {}
|
||||
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<Project> {
|
||||
let projectId = route.params['id'];
|
||||
let projectId = route.params['id'];
|
||||
console.log('Project resolver, projectID:' + projectId);
|
||||
return this.projectService
|
||||
.getProject(projectId)
|
||||
.then(project=> {
|
||||
if(project) {
|
||||
return project;
|
||||
} else {
|
||||
this.router.navigate(['/harbor', 'projects']);
|
||||
return null;
|
||||
}
|
||||
.toPromise()
|
||||
.then((project: Project)=> {
|
||||
if(project) {
|
||||
let currentUser = this.sessionService.getCurrentUser();
|
||||
let projectMembers = this.sessionService.getProjectMembers();
|
||||
if(currentUser && projectMembers) {
|
||||
let currentMember = projectMembers.find(m=>m.user_id === currentUser.user_id);
|
||||
if(currentMember) {
|
||||
project.is_member = true;
|
||||
project.has_project_admin_role = (currentMember.role_name === 'projectAdmin') || currentUser.has_admin_role === 1;
|
||||
}
|
||||
}
|
||||
return project;
|
||||
} else {
|
||||
this.router.navigate(['/harbor', 'projects']);
|
||||
return null;
|
||||
}
|
||||
}).catch(error=>{
|
||||
this.router.navigate(['/harbor', 'projects']);
|
||||
return null;
|
||||
});
|
||||
|
||||
}
|
||||
}
|
@ -1,11 +1,13 @@
|
||||
.header-title {
|
||||
margin-top: 0;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.option-left {
|
||||
padding-left: 12px;
|
||||
margin-top: 12px;
|
||||
padding-left: 12px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.option-right {
|
||||
padding-right: 16px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
padding-right: 16px;
|
||||
margin-top: 18px;
|
||||
}
|
@ -1,9 +1,12 @@
|
||||
<div class="row">
|
||||
<div class="row" style="margin-right: 12px;">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<h2 class="header-title">{{'PROJECT.PROJECTS' | translate}}</h2>
|
||||
<div>
|
||||
<statistics-panel></statistics-panel>
|
||||
</div>
|
||||
<div class="row flex-items-xs-between">
|
||||
<div class="option-left">
|
||||
<button class="btn btn-primary" (click)="openModal()"><clr-icon shape="add"></clr-icon> {{'PROJECT.PROJECT' | translate}}</button>
|
||||
<button *ngIf="projectCreationRestriction" class="btn btn-primary" (click)="openModal()"><clr-icon shape="add"></clr-icon> {{'PROJECT.PROJECT' | translate}}</button>
|
||||
<create-project (create)="createProject($event)"></create-project>
|
||||
</div>
|
||||
<div class="option-right">
|
||||
@ -18,11 +21,13 @@
|
||||
</div>
|
||||
</clr-dropdown>
|
||||
<grid-filter filterPlaceholder='{{"PROJECT.FILTER_PLACEHOLDER" | translate}}' (filter)="doSearchProjects($event)"></grid-filter>
|
||||
<a href="javascript:void(0)" (click)="refresh()"><clr-icon shape="refresh"></clr-icon></a>
|
||||
<a href="javascript:void(0)" (click)="refresh()">
|
||||
<clr-icon shape="refresh"></clr-icon>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<list-project [projects]="changedProjects" (toggle)="toggleProject($event)" (delete)="deleteProject($event)" (paginate)="retrieve($event)" [totalPage]="totalPage" [totalRecordCount]="totalRecordCount"></list-project>
|
||||
</div>
|
||||
<list-project [projects]="changedProjects" [filteredType]="projectTypes[currentFilteredType]" (toggle)="toggleProject($event)" (delete)="deleteProject($event)" (paginate)="retrieve($event)" [totalPage]="totalPage" [totalRecordCount]="totalRecordCount"></list-project>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
@ -23,7 +23,9 @@ import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
import { State } from 'clarity-angular';
|
||||
|
||||
const types: {} = { 0: 'PROJECT.MY_PROJECTS', 1: 'PROJECT.PUBLIC_PROJECTS' };
|
||||
import { AppConfigService } from '../app-config.service';
|
||||
import { SessionService } from '../shared/session.service';
|
||||
import { ProjectTypes } from '../shared/shared.const';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
@ -35,7 +37,7 @@ export class ProjectComponent implements OnInit, OnDestroy {
|
||||
|
||||
selected = [];
|
||||
changedProjects: Project[];
|
||||
projectTypes = types;
|
||||
projectTypes = ProjectTypes;
|
||||
|
||||
@ViewChild(CreateProjectComponent)
|
||||
creationProject: CreateProjectComponent;
|
||||
@ -59,6 +61,8 @@ export class ProjectComponent implements OnInit, OnDestroy {
|
||||
constructor(
|
||||
private projectService: ProjectService,
|
||||
private messageService: MessageService,
|
||||
private appConfigService: AppConfigService,
|
||||
private sessionService: SessionService,
|
||||
private deletionDialogService: ConfirmationDialogService) {
|
||||
this.subscription = deletionDialogService.confirmationConfirm$.subscribe(message => {
|
||||
if (message &&
|
||||
@ -69,6 +73,7 @@ export class ProjectComponent implements OnInit, OnDestroy {
|
||||
.deleteProject(projectId)
|
||||
.subscribe(
|
||||
response => {
|
||||
this.messageService.announceMessage(response, 'PROJECT.DELETED_SUCCESS', AlertType.SUCCESS);
|
||||
console.log('Successful delete project with ID:' + projectId);
|
||||
this.retrieve();
|
||||
},
|
||||
@ -76,11 +81,13 @@ export class ProjectComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.projectName = '';
|
||||
this.isPublic = 0;
|
||||
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
@ -89,6 +96,19 @@ export class ProjectComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
get projectCreationRestriction(): boolean {
|
||||
let account = this.sessionService.getCurrentUser();
|
||||
if(account) {
|
||||
switch(this.appConfigService.getConfig().project_creation_restriction) {
|
||||
case 'adminonly':
|
||||
return (account.has_admin_role === 1);
|
||||
case 'everyone':
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
retrieve(state?: State): void {
|
||||
if (state) {
|
||||
this.page = state.page.to + 1;
|
||||
@ -123,7 +143,7 @@ export class ProjectComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
doFilterProjects(filteredType: number): void {
|
||||
console.log('Filter projects with type:' + types[filteredType]);
|
||||
console.log('Filter projects with type:' + this.projectTypes[filteredType]);
|
||||
this.isPublic = filteredType;
|
||||
this.currentFilteredType = filteredType;
|
||||
this.retrieve();
|
||||
@ -135,7 +155,10 @@ export class ProjectComponent implements OnInit, OnDestroy {
|
||||
this.projectService
|
||||
.toggleProjectPublic(p.project_id, p.public)
|
||||
.subscribe(
|
||||
response => console.log('Successful toggled project_id:' + p.project_id),
|
||||
response => {
|
||||
this.messageService.announceMessage(response, 'PROJECT.TOGGLED_SUCCESS', AlertType.SUCCESS);
|
||||
console.log('Successful toggled project_id:' + p.project_id);
|
||||
},
|
||||
error => this.messageService.announceMessage(error.status, error, AlertType.WARNING)
|
||||
);
|
||||
}
|
||||
|
@ -3,8 +3,6 @@ import { Injectable } from '@angular/core';
|
||||
import { Http, Headers, RequestOptions, Response, URLSearchParams } from '@angular/http';
|
||||
import { Project } from './project';
|
||||
|
||||
import { BaseService } from '../service/base.service';
|
||||
|
||||
import { Message } from '../global-message/message';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
@ -22,11 +20,10 @@ export class ProjectService {
|
||||
|
||||
constructor(private http: Http) {}
|
||||
|
||||
getProject(projectId: number): Promise<Project> {
|
||||
getProject(projectId: number): Observable<any> {
|
||||
return this.http
|
||||
.get(`/api/projects/${projectId}`)
|
||||
.toPromise()
|
||||
.then(response=>response.json() as Project)
|
||||
.map(response=>response.json())
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
|
||||
@ -70,4 +67,11 @@ export class ProjectService {
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
|
||||
checkProjectMember(projectId: number): Observable<any> {
|
||||
return this.http
|
||||
.get(`/api/projects/${projectId}/members`)
|
||||
.map(response=>response.json())
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
|
||||
}
|
@ -29,4 +29,6 @@ export class Project {
|
||||
update_time: Date;
|
||||
current_user_role_id: number;
|
||||
repo_count: number;
|
||||
has_project_admin_role: boolean;
|
||||
is_member: boolean;
|
||||
}
|
@ -108,6 +108,7 @@ export class CreateEditDestinationComponent implements AfterViewChecked {
|
||||
.createTarget(this.target)
|
||||
.subscribe(
|
||||
response=>{
|
||||
this.messageService.announceMessage(response, 'DESTINATION.CREATED_SUCCESS', AlertType.SUCCESS);
|
||||
console.log('Successful added target.');
|
||||
this.createEditDestinationOpened = false;
|
||||
this.reload.emit(true);
|
||||
@ -129,7 +130,7 @@ export class CreateEditDestinationComponent implements AfterViewChecked {
|
||||
.get(errorMessageKey)
|
||||
.subscribe(res=>{
|
||||
this.messageService.announceMessage(error.status, errorMessageKey, AlertType.DANGER);
|
||||
this.inlineAlert.showInlineError(errorMessageKey);
|
||||
this.inlineAlert.showInlineError(res);
|
||||
});
|
||||
}
|
||||
);
|
||||
@ -139,6 +140,7 @@ export class CreateEditDestinationComponent implements AfterViewChecked {
|
||||
.updateTarget(this.target)
|
||||
.subscribe(
|
||||
response=>{
|
||||
this.messageService.announceMessage(response, 'DESTINATION.UPDATED_SUCCESS', AlertType.SUCCESS);
|
||||
console.log('Successful updated target.');
|
||||
this.createEditDestinationOpened = false;
|
||||
this.reload.emit(true);
|
||||
@ -158,7 +160,7 @@ export class CreateEditDestinationComponent implements AfterViewChecked {
|
||||
this.translateService
|
||||
.get(errorMessageKey)
|
||||
.subscribe(res=>{
|
||||
this.inlineAlert.showInlineError(errorMessageKey);
|
||||
this.inlineAlert.showInlineError(res);
|
||||
this.messageService.announceMessage(error.status, errorMessageKey, AlertType.DANGER);
|
||||
});
|
||||
}
|
||||
|
@ -43,14 +43,15 @@ export class DestinationComponent implements OnInit {
|
||||
.deleteTarget(targetId)
|
||||
.subscribe(
|
||||
response => {
|
||||
this.messageService.announceMessage(response, 'DESTINATION.DELETED_SUCCESS', AlertType.SUCCESS);
|
||||
console.log('Successful deleted target with ID:' + targetId);
|
||||
this.reload();
|
||||
},
|
||||
error => this.messageService
|
||||
.announceMessage(error.status,
|
||||
'Failed to delete target with ID:' + targetId + ', error:' + error,
|
||||
AlertType.DANGER)
|
||||
);
|
||||
error => {
|
||||
this.messageService
|
||||
.announceMessage(error.status,'DESTINATION.DELETED_FAILED', AlertType.DANGER);
|
||||
console.log('Failed to delete target with ID:' + targetId + ', error:' + error);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Http, URLSearchParams, Response } from '@angular/http';
|
||||
|
||||
import { BaseService } from '../service/base.service';
|
||||
|
||||
import { Policy } from './policy';
|
||||
import { Job } from './job';
|
||||
import { Target } from './target';
|
||||
@ -14,10 +12,8 @@ import 'rxjs/add/observable/throw';
|
||||
import 'rxjs/add/operator/mergeMap';
|
||||
|
||||
@Injectable()
|
||||
export class ReplicationService extends BaseService {
|
||||
constructor(private http: Http) {
|
||||
super();
|
||||
}
|
||||
export class ReplicationService {
|
||||
constructor(private http: Http) {}
|
||||
|
||||
listPolicies(policyName: string, projectId?: any): Observable<Policy[]> {
|
||||
if(!projectId) {
|
||||
|
@ -3,7 +3,7 @@
|
||||
<clr-dg-column>{{'REPOSITORY.TAGS_COUNT' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.PULL_COUNT' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *ngFor="let r of repositories" [clrDgItem]='r'>
|
||||
<clr-dg-action-overflow *ngIf="listFullMode">
|
||||
<clr-dg-action-overflow *ngIf="listFullMode && hasProjectAdminRole">
|
||||
<button class="action-item">{{'REPOSITORY.COPY_ID' | translate}}</button>
|
||||
<button class="action-item">{{'REPOSITORY.COPY_PARENT_ID' | translate}}</button>
|
||||
<button class="action-item" (click)="deleteRepo(r.name)">{{'REPOSITORY.DELETE' | translate}}</button>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
|
||||
import { Router, NavigationExtras } from '@angular/router';
|
||||
import { Repository } from '../repository';
|
||||
import { State } from 'clarity-angular';
|
||||
@ -7,14 +7,18 @@ import { SearchTriggerService } from '../../base/global-search/search-trigger.se
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
import { ListMode } from '../../shared/shared.const';
|
||||
|
||||
import { SessionUser } from '../../shared/session-user';
|
||||
|
||||
@Component({
|
||||
selector: 'list-repository',
|
||||
templateUrl: 'list-repository.component.html'
|
||||
})
|
||||
export class ListRepositoryComponent {
|
||||
export class ListRepositoryComponent implements OnInit {
|
||||
|
||||
@Input() projectId: number;
|
||||
@Input() repositories: Repository[];
|
||||
|
||||
|
||||
@Output() delete = new EventEmitter<string>();
|
||||
|
||||
@Input() totalPage: number;
|
||||
@ -22,6 +26,7 @@ export class ListRepositoryComponent {
|
||||
@Output() paginate = new EventEmitter<State>();
|
||||
|
||||
@Input() mode: string = ListMode.FULL;
|
||||
@Input() hasProjectAdminRole: boolean;
|
||||
|
||||
pageOffset: number = 1;
|
||||
|
||||
@ -30,6 +35,8 @@ export class ListRepositoryComponent {
|
||||
private searchTrigger: SearchTriggerService,
|
||||
private session: SessionService) { }
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
deleteRepo(repoName: string) {
|
||||
this.delete.emit(repoName);
|
||||
}
|
||||
|
@ -8,6 +8,6 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<list-repository [projectId]="projectId" [repositories]="changedRepositories" (delete)="deleteRepo($event)" [totalPage]="totalPage" [totalRecordCount]="totalRecordCount" (paginate)="retrieve($event)"></list-repository>
|
||||
<list-repository [projectId]="projectId" [repositories]="changedRepositories" (delete)="deleteRepo($event)" [totalPage]="totalPage" [totalRecordCount]="totalRecordCount" [hasProjectAdminRole]="hasProjectAdminRole" (paginate)="retrieve($event)"></list-repository>
|
||||
</div>
|
||||
</div>
|
@ -14,6 +14,8 @@ import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
import { State } from 'clarity-angular';
|
||||
|
||||
import { Project } from '../project/project';
|
||||
|
||||
const repositoryTypes = [
|
||||
{ key: '0', description: 'REPOSITORY.MY_REPOSITORY' },
|
||||
{ key: '1', description: 'REPOSITORY.PUBLIC_REPOSITORY' }
|
||||
@ -39,6 +41,8 @@ export class RepositoryComponent implements OnInit {
|
||||
totalPage: number;
|
||||
totalRecordCount: number;
|
||||
|
||||
hasProjectAdminRole: boolean;
|
||||
|
||||
subscription: Subscription;
|
||||
|
||||
constructor(
|
||||
@ -60,17 +64,22 @@ export class RepositoryComponent implements OnInit {
|
||||
.subscribe(
|
||||
response => {
|
||||
this.refresh();
|
||||
this.messageService.announceMessage(response, 'REPOSITORY.DELETED_REPO_SUCCESS', AlertType.SUCCESS);
|
||||
console.log('Successful deleted repo:' + repoName);
|
||||
},
|
||||
error => this.messageService.announceMessage(error.status, 'Failed to delete repo:' + repoName, AlertType.DANGER)
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.projectId = this.route.snapshot.parent.params['id'];
|
||||
let resolverData = this.route.snapshot.parent.data;
|
||||
if(resolverData) {
|
||||
this.hasProjectAdminRole = (<Project>resolverData['projectResolver']).has_project_admin_role;
|
||||
}
|
||||
this.currentRepositoryType = this.repositoryTypes[0];
|
||||
this.lastFilteredRepoName = '';
|
||||
this.retrieve();
|
||||
|
@ -3,19 +3,19 @@
|
||||
<clr-datagrid>
|
||||
<clr-dg-column>{{'REPOSITORY.TAG' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.PULL_COMMAND' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.SIGNED' | translate}}</clr-dg-column>
|
||||
<clr-dg-column *ngIf="withNotary">{{'REPOSITORY.SIGNED' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.AUTHOR' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.CREATED' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.DOCKER_VERSION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.ARCHITECTURE' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.OS' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *ngFor="let t of tags" [clrDgItem]='t'>
|
||||
<clr-dg-action-overflow>
|
||||
<clr-dg-action-overflow *ngIf="hasProjectAdminRole">
|
||||
<button class="action-item" (click)="deleteTag(t)">{{'REPOSITORY.DELETE' | translate}}</button>
|
||||
</clr-dg-action-overflow>
|
||||
<clr-dg-cell>{{t.tag}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.pullCommand}}</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<clr-dg-cell *ngIf="withNotary">
|
||||
<clr-icon shape="check" *ngIf="t.signed" style="color: #1D5100;"></clr-icon>
|
||||
<clr-icon shape="close" *ngIf="!t.signed" style="color: #C92100;"></clr-icon>
|
||||
</clr-dg-cell>
|
||||
|
@ -10,10 +10,15 @@ import { ConfirmationMessage } from '../../shared/confirmation-dialog/confirmati
|
||||
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
import { Tag } from '../tag';
|
||||
import { TagView } from '../tag-view';
|
||||
|
||||
import { AppConfigService } from '../../app-config.service';
|
||||
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
|
||||
import { Project } from '../../project/project';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'tag-repository',
|
||||
@ -25,8 +30,11 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
|
||||
projectId: number;
|
||||
repoName: string;
|
||||
|
||||
hasProjectAdminRole: boolean = false;
|
||||
|
||||
tags: TagView[];
|
||||
registryUrl: string;
|
||||
withNotary: boolean;
|
||||
|
||||
private subscription: Subscription;
|
||||
|
||||
@ -35,7 +43,9 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
|
||||
private messageService: MessageService,
|
||||
private deletionDialogService: ConfirmationDialogService,
|
||||
private repositoryService: RepositoryService,
|
||||
private appConfigService: AppConfigService) {
|
||||
private appConfigService: AppConfigService,
|
||||
private session: SessionService){
|
||||
|
||||
this.subscription = this.deletionDialogService.confirmationConfirm$.subscribe(
|
||||
message => {
|
||||
if (message &&
|
||||
@ -52,6 +62,7 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
|
||||
.subscribe(
|
||||
response => {
|
||||
this.retrieve();
|
||||
this.messageService.announceMessage(response, 'REPOSITORY.DELETED_TAG_SUCCESS', AlertType.SUCCESS);
|
||||
console.log('Deleted repo:' + this.repoName + ' with tag:' + tagName);
|
||||
},
|
||||
error => this.messageService.announceMessage(error.status, 'Failed to delete tag:' + tagName + ' under repo:' + this.repoName, AlertType.DANGER)
|
||||
@ -59,15 +70,20 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
let resolverData = this.route.snapshot.data;
|
||||
console.log(JSON.stringify(resolverData));
|
||||
if(resolverData) {
|
||||
this.hasProjectAdminRole = (<Project>resolverData['projectResolver']).has_project_admin_role;
|
||||
}
|
||||
this.projectId = this.route.snapshot.params['id'];
|
||||
this.repoName = this.route.snapshot.params['repo'];
|
||||
this.tags = [];
|
||||
this.registryUrl = this.appConfigService.getConfig().registry_url;
|
||||
this.withNotary = this.appConfigService.getConfig().with_notary;
|
||||
this.retrieve();
|
||||
}
|
||||
|
||||
@ -79,25 +95,35 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
|
||||
|
||||
retrieve() {
|
||||
this.tags = [];
|
||||
this.repositoryService
|
||||
.listTagsWithVerifiedSignatures(this.repoName)
|
||||
.subscribe(
|
||||
items => {
|
||||
items.forEach(t => {
|
||||
let tag = new TagView();
|
||||
tag.tag = t.tag;
|
||||
let data = JSON.parse(t.manifest.history[0].v1Compatibility);
|
||||
tag.architecture = data['architecture'];
|
||||
tag.author = data['author'];
|
||||
tag.signed = t.signed;
|
||||
tag.created = data['created'];
|
||||
tag.dockerVersion = data['docker_version'];
|
||||
tag.pullCommand = 'docker pull ' + this.registryUrl + '/' + t.manifest.name + ':' + t.tag;
|
||||
tag.os = data['os'];
|
||||
this.tags.push(tag);
|
||||
});
|
||||
},
|
||||
error => this.messageService.announceMessage(error.status, 'Failed to list tags with repo:' + this.repoName, AlertType.DANGER));
|
||||
if(this.withNotary) {
|
||||
this.repositoryService
|
||||
.listTagsWithVerifiedSignatures(this.repoName)
|
||||
.subscribe(
|
||||
items => this.listTags(items),
|
||||
error => this.messageService.announceMessage(error.status, 'Failed to list tags with repo:' + this.repoName, AlertType.DANGER));
|
||||
} else {
|
||||
this.repositoryService
|
||||
.listTags(this.repoName)
|
||||
.subscribe(
|
||||
items => this.listTags(items),
|
||||
error => this.messageService.announceMessage(error.status, 'Failed to list tags with repo:' + this.repoName, AlertType.DANGER));
|
||||
}
|
||||
}
|
||||
|
||||
private listTags(tags: Tag[]): void {
|
||||
tags.forEach(t => {
|
||||
let tag = new TagView();
|
||||
tag.tag = t.tag;
|
||||
let data = JSON.parse(t.manifest.history[0].v1Compatibility);
|
||||
tag.architecture = data['architecture'];
|
||||
tag.author = data['author'];
|
||||
tag.signed = t.signed;
|
||||
tag.created = data['created'];
|
||||
tag.dockerVersion = data['docker_version'];
|
||||
tag.pullCommand = 'docker pull ' + this.registryUrl + '/' + t.manifest.name + ':' + t.tag;
|
||||
tag.os = data['os'];
|
||||
this.tags.push(tag);
|
||||
});
|
||||
}
|
||||
|
||||
deleteTag(tag: TagView) {
|
||||
|
@ -1,8 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CanActivate } from '@angular/router';
|
||||
|
||||
export class AuthGuard implements CanActivate {
|
||||
canActivate() {
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
import { Http, Response,} from '@angular/http';
|
||||
|
||||
export class BaseService {
|
||||
|
||||
protected handleError(error: Response | any): Promise<any> {
|
||||
// In a real world app, we might use a remote logging infrastructure
|
||||
let errMsg: string;
|
||||
console.log(typeof error);
|
||||
if (error instanceof Response) {
|
||||
const body = error.json() || '';
|
||||
const err = body.error || JSON.stringify(body);
|
||||
errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
|
||||
} else {
|
||||
errMsg = error.message ? error.message : error.toString();
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
@ -183,6 +183,7 @@ export class CreateEditPolicyComponent implements OnInit, AfterViewChecked {
|
||||
.createPolicy(this.getPolicyByForm())
|
||||
.subscribe(
|
||||
response=>{
|
||||
this.messageService.announceMessage(response, 'REPLICATION.CREATED_SUCCESS', AlertType.SUCCESS);
|
||||
console.log('Successful created policy: ' + response);
|
||||
this.createEditPolicyOpened = false;
|
||||
this.reload.emit(true);
|
||||
@ -199,6 +200,7 @@ export class CreateEditPolicyComponent implements OnInit, AfterViewChecked {
|
||||
.createOrUpdatePolicyWithNewTarget(this.getPolicyByForm(), this.getTargetByForm())
|
||||
.subscribe(
|
||||
response=>{
|
||||
this.messageService.announceMessage(response, 'REPLICATION.UPDATED_SUCCESS', AlertType.SUCCESS);
|
||||
console.log('Successful created policy and target:' + response);
|
||||
this.createEditPolicyOpened = false;
|
||||
this.reload.emit(true);
|
||||
|
@ -50,7 +50,10 @@ export class ListPolicyComponent implements OnDestroy {
|
||||
this.replicationService
|
||||
.enablePolicy(policy.id, policy.enabled)
|
||||
.subscribe(
|
||||
res => console.log('Successful toggled policy status'),
|
||||
response => {
|
||||
this.messageService.announceMessage(response, 'REPLICATION.TOGGLED_SUCCESS', AlertType.SUCCESS);
|
||||
console.log('Successful toggled policy status')
|
||||
},
|
||||
error => this.messageService.announceMessage(error.status, "Failed to toggle policy status.", AlertType.DANGER)
|
||||
);
|
||||
}
|
||||
@ -67,10 +70,11 @@ export class ListPolicyComponent implements OnDestroy {
|
||||
.deletePolicy(message.data)
|
||||
.subscribe(
|
||||
response => {
|
||||
this.messageService.announceMessage(response, 'REPLICATION.DELETED_SUCCESS', AlertType.SUCCESS);
|
||||
console.log('Successful delete policy with ID:' + message.data);
|
||||
this.reload.emit(true);
|
||||
},
|
||||
error => this.messageService.announceMessage(error.status, 'Failed to delete policy with ID:' + message.data, AlertType.DANGER)
|
||||
error => this.messageService.announceMessage(error.status, 'REPLICATION.DELETED_FAILED', AlertType.DANGER)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,43 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
CanActivate, Router,
|
||||
ActivatedRouteSnapshot,
|
||||
RouterStateSnapshot,
|
||||
CanActivateChild
|
||||
} from '@angular/router';
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
import { ProjectService } from '../../project/project.service';
|
||||
import { CommonRoutes } from '../../shared/shared.const';
|
||||
|
||||
@Injectable()
|
||||
export class MemberGuard implements CanActivate, CanActivateChild {
|
||||
constructor(
|
||||
private sessionService: SessionService,
|
||||
private projectService: ProjectService,
|
||||
private router: Router) {}
|
||||
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> | boolean {
|
||||
let projectId = route.params['id'];
|
||||
this.sessionService.setProjectMembers([]);
|
||||
return new Promise((resolve, reject) => {
|
||||
this.projectService.checkProjectMember(projectId)
|
||||
.subscribe(
|
||||
res=>{
|
||||
this.sessionService.setProjectMembers(res);
|
||||
return resolve(true)
|
||||
},
|
||||
error => {
|
||||
//Add exception for repository in project detail router activation.
|
||||
if(state.url.endsWith('repository')) {
|
||||
return resolve(true);
|
||||
}
|
||||
this.router.navigate([CommonRoutes.HARBOR_DEFAULT]);
|
||||
return resolve(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> | boolean {
|
||||
return this.canActivate(route, state);
|
||||
}
|
||||
}
|
@ -3,6 +3,8 @@ import { Headers, Http, URLSearchParams } from '@angular/http';
|
||||
import 'rxjs/add/operator/toPromise';
|
||||
|
||||
import { SessionUser } from './session-user';
|
||||
import { Member } from '../project/member/member';
|
||||
|
||||
import { SignInCredential } from './sign-in-credential';
|
||||
import { enLang } from '../shared/shared.const'
|
||||
|
||||
@ -27,6 +29,8 @@ const langMap = {
|
||||
export class SessionService {
|
||||
currentUser: SessionUser = null;
|
||||
|
||||
projectMembers: Member[];
|
||||
|
||||
private headers = new Headers({
|
||||
"Content-Type": 'application/json'
|
||||
});
|
||||
@ -143,4 +147,12 @@ export class SessionService {
|
||||
})
|
||||
.catch(error => this.handleError(error));
|
||||
}
|
||||
|
||||
setProjectMembers(projectMembers: Member[]): void {
|
||||
this.projectMembers = projectMembers;
|
||||
}
|
||||
|
||||
getProjectMembers(): Member[] {
|
||||
return this.projectMembers;
|
||||
}
|
||||
}
|
@ -24,7 +24,8 @@ export const enum ConfirmationTargets {
|
||||
REPOSITORY,
|
||||
TAG,
|
||||
CONFIG,
|
||||
CONFIG_ROUTE
|
||||
CONFIG_ROUTE,
|
||||
CONFIG_TAB
|
||||
};
|
||||
|
||||
export const enum ActionType {
|
||||
@ -51,4 +52,7 @@ export const CookieKeyOfAdmiral = "admiral.endpoint.latest";
|
||||
|
||||
export const enum ConfirmationState {
|
||||
NA, CONFIRMED, CANCEL
|
||||
}
|
||||
}
|
||||
|
||||
export const ProjectTypes = { 0: 'PROJECT.MY_PROJECTS', 1: 'PROJECT.PUBLIC_PROJECTS' };
|
||||
export const RoleInfo = { 1: 'MEMBER.PROJECT_ADMIN', 2: 'MEMBER.DEVELOPER', 3: 'MEMBER.GUEST' };
|
||||
|
@ -32,6 +32,7 @@ import { StatisticsComponent } from './statictics/statistics.component';
|
||||
import { StatisticsPanelComponent } from './statictics/statistics-panel.component';
|
||||
import { SignInGuard } from './route/sign-in-guard-activate.service';
|
||||
import { LeavingConfigRouteDeactivate } from './route/leaving-config-deactivate.service';
|
||||
import { MemberGuard } from './route/member-guard-activate.service';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@ -79,7 +80,8 @@ import { LeavingConfigRouteDeactivate } from './route/leaving-config-deactivate.
|
||||
SystemAdminGuard,
|
||||
AuthCheckGuard,
|
||||
SignInGuard,
|
||||
LeavingConfigRouteDeactivate
|
||||
LeavingConfigRouteDeactivate,
|
||||
MemberGuard
|
||||
]
|
||||
})
|
||||
export class SharedModule {
|
||||
|
@ -1,25 +1,41 @@
|
||||
<div class="card card-block">
|
||||
<h3 class="card-title">{{'STATISTICS.TITLE' | translate }}</h3>
|
||||
<span class="card-text">
|
||||
<div class="row">
|
||||
<div class="col-xs-2 col-sm-2 col-md-2 col-lg-2 col-xl-2">
|
||||
<span class="statistic-column-title">{{'STATISTICS.PRO_ITEM' | translate }}</span>
|
||||
</div>
|
||||
<div class="col-xs-10 col-sm-10 col-md-10 col-lg-10 col-xl-10">
|
||||
<statistics [data]='{number: originalCopy.my_project_count, label: "my"}'></statistics>
|
||||
<statistics [data]='{number: originalCopy.public_project_count, label: "pub"}'></statistics>
|
||||
<statistics [data]='{number: originalCopy.total_project_count, label: "total"}' *ngIf="isValidSession"></statistics>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-2 col-sm-2 col-md-2 col-lg-2 col-xl-2">
|
||||
<span class="statistic-column-title">{{'STATISTICS.REPO_ITEM' | translate }}</span>
|
||||
<div class="row flex-items-xs-between flex-items-xs-middle">
|
||||
<div></div>
|
||||
<div id="right_statistic_panel" style="margin-right: 18px;">
|
||||
<div class="statistic-block">
|
||||
<div class="statistic-column-block">
|
||||
<div>
|
||||
<span class="statistic-column-title statistic-column-title-pro">{{'STATISTICS.PRO_ITEM' | translate }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="statistic-column-title statistic-column-title-repo">{{'STATISTICS.REPO_ITEM' | translate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="statistic-column-block" style="margin-left: 16px;">
|
||||
<div>
|
||||
<statistics [data]='originalCopy.my_project_count' [label]='"STATISTICS.INDEX_MY" | translate'></statistics>
|
||||
</div>
|
||||
<div>
|
||||
<statistics [data]='originalCopy.my_repo_count' [label]='"STATISTICS.INDEX_MY" | translate'></statistics>
|
||||
</div>
|
||||
</div>
|
||||
<div class="statistic-column-block" style="margin-left: 28px;">
|
||||
<div>
|
||||
<statistics [data]='originalCopy.public_project_count' [label]='"STATISTICS.INDEX_PUB" | translate'></statistics>
|
||||
</div>
|
||||
<div>
|
||||
<statistics [data]='originalCopy.public_repo_count' [label]='"STATISTICS.INDEX_PUB" | translate'></statistics>
|
||||
</div>
|
||||
</div>
|
||||
<div class="statistic-column-block" style="margin-left: 28px;">
|
||||
<div>
|
||||
<statistics [data]='originalCopy.total_project_count' [label]='"STATISTICS.INDEX_TOTAL" | translate' *ngIf="isValidSession"></statistics>
|
||||
</div>
|
||||
<div>
|
||||
<statistics [data]='originalCopy.total_repo_count' [label]='"STATISTICS.INDEX_TOTAL" | translate' *ngIf="isValidSession"></statistics>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="statistic-item-divider"></div>
|
||||
<div class="statistic-block">Storage</div>
|
||||
</div>
|
||||
<div class="col-xs-10 col-sm-10 col-md-10 col-lg-10 col-xl-10">
|
||||
<statistics [data]='{number: originalCopy.my_repo_count, label: "my"}'></statistics>
|
||||
<statistics [data]='{number: originalCopy.public_repo_count, label: "pub"}'></statistics>
|
||||
<statistics [data]='{number: originalCopy.total_repo_count, label: "total"}' *ngIf="isValidSession"></statistics>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
@ -1,30 +1,57 @@
|
||||
.statistic-wrapper {
|
||||
padding: 12px;
|
||||
margin: 12px;
|
||||
text-align: center;
|
||||
padding: 4px;
|
||||
margin: 4px;
|
||||
text-align: right;
|
||||
vertical-align: middle;
|
||||
height: 72px;
|
||||
min-width: 108px;
|
||||
max-width: 216px;
|
||||
height: 30px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.statistic-data {
|
||||
font-size: 48px;
|
||||
font-weight: bolder;
|
||||
font-family: "Metropolis";
|
||||
line-height: 48px;
|
||||
font-size: 16px;
|
||||
font-weight: 900;
|
||||
font-family: "semibold";
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.statistic-text {
|
||||
font-size: 24px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
font-size: 10px;
|
||||
line-height: 10px;
|
||||
text-transform: uppercase;
|
||||
font-family: "Metropolis";
|
||||
font-family: "semibold";
|
||||
}
|
||||
|
||||
.statistic-column-block {
|
||||
display: inline-block;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.statistic-column-title {
|
||||
position: relative;
|
||||
top: 40%;
|
||||
text-transform: uppercase;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.statistic-column-title-pro {
|
||||
top: -10px;
|
||||
}
|
||||
|
||||
.statistic-column-title-repo {
|
||||
top: 3px;
|
||||
}
|
||||
|
||||
.statistic-item-divider {
|
||||
height: 54px;
|
||||
display: inline-block;
|
||||
width: 1px;
|
||||
background-color: #ccc;
|
||||
opacity: 0.55;
|
||||
margin-left: 4px;
|
||||
margin-right: 12px;
|
||||
position: relative;
|
||||
top: 3px;
|
||||
}
|
||||
|
||||
.statistic-block {
|
||||
display: inline-block;
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
<div class="statistic-wrapper">
|
||||
<span class="statistic-data">{{data.number}}</span>
|
||||
<span class="statistic-text">{{data.label}}</span>
|
||||
<span class="statistic-data">{{data}}</span>
|
||||
<span class="statistic-text">{{label}}</span>
|
||||
</div>
|
@ -7,5 +7,6 @@ import { Component, Input } from '@angular/core';
|
||||
})
|
||||
|
||||
export class StatisticsComponent {
|
||||
@Input() data: any;
|
||||
@Input() label: string;
|
||||
@Input() data: number = 0;
|
||||
}
|
@ -110,10 +110,10 @@
|
||||
"PROJECT": {
|
||||
"PROJECTS": "Projects",
|
||||
"NAME": "Project Name",
|
||||
"ROLE": "Role",
|
||||
"PUBLIC_OR_PRIVATE": "Public",
|
||||
"REPO_COUNT": "Repositories Count",
|
||||
"CREATION_TIME": "Creation Time",
|
||||
"DESCRIPTION": "Description",
|
||||
"PUBLIC": "Public",
|
||||
"PRIVATE": "Private",
|
||||
"MAKE": "Make",
|
||||
@ -132,7 +132,10 @@
|
||||
"DELETION_TITLE": "Confirm project deletion",
|
||||
"DELETION_SUMMARY": "Do you want to delete project {{param}}?",
|
||||
"FILTER_PLACEHOLDER": "Filter Projects",
|
||||
"REPLICATION_RULE": "Replication Rule"
|
||||
"REPLICATION_RULE": "Replication Rule",
|
||||
"CREATED_SUCCESS": "Created project successfully.",
|
||||
"DELETED_SUCCESS": "Deleted project successfully.",
|
||||
"TOGGLED_SUCCESS": "Toggled project successfully."
|
||||
},
|
||||
"PROJECT_DETAIL": {
|
||||
"REPOSITORIES": "Repositories",
|
||||
@ -159,7 +162,10 @@
|
||||
"UNKNOWN_ERROR": "Unknown error occurred while adding member.",
|
||||
"FILTER_PLACEHOLDER": "Filter Members",
|
||||
"DELETION_TITLE": "Confirm project member deletion",
|
||||
"DELETION_SUMMARY": "Do you want to delete project member {{param}}?"
|
||||
"DELETION_SUMMARY": "Do you want to delete project member {{param}}?",
|
||||
"ADDED_SUCCESS": "Added member successfully.",
|
||||
"DELETED_SUCCESS": "Deleted member successfully.",
|
||||
"SWITCHED_SUCCESS": "Switched member role successfully."
|
||||
},
|
||||
"AUDIT_LOG": {
|
||||
"USERNAME": "Username",
|
||||
@ -235,7 +241,12 @@
|
||||
"TOGGLE_ENABLE_TITLE": "Enable Policy",
|
||||
"CONFIRM_TOGGLE_ENABLE_POLICY": "After enabling the replication policy, all repositories under the project will be replicated to the destination registry. Please confirm to continue.",
|
||||
"TOGGLE_DISABLE_TITLE": "Disable Policy",
|
||||
"CONFIRM_TOGGLE_DISABLE_POLICY": "After disabling the policy, all unfinished replication jobs of this policy will be stopped and canceled. Please confirm to continue."
|
||||
"CONFIRM_TOGGLE_DISABLE_POLICY": "After disabling the policy, all unfinished replication jobs of this policy will be stopped and canceled. Please confirm to continue.",
|
||||
"CREATED_SUCCESS": "Created policy successfully.",
|
||||
"UPDATED_SUCCESS": "Updated policy successfully.",
|
||||
"DELETED_SUCCESS": "Deleted policy successfully.",
|
||||
"DELETED_FAILED": "Deleted policy failed.",
|
||||
"TOGGLED_SUCCESS": "Toggled policy status successfully."
|
||||
},
|
||||
"DESTINATION": {
|
||||
"NEW_ENDPOINT": "New Endpoint",
|
||||
@ -257,7 +268,11 @@
|
||||
"INVALID_NAME": "Invalid destination name.",
|
||||
"FAILED_TO_GET_TARGET": "Failed to get endpoint.",
|
||||
"CREATION_TIME": "Creation Time",
|
||||
"ITEMS": "item(s)"
|
||||
"ITEMS": "item(s)",
|
||||
"CREATED_SUCCESS": "Created destination successfully.",
|
||||
"UPDATED_SUCCESS": "Updated destination successfully.",
|
||||
"DELETED_SUCCESS": "Deleted destination successfully.",
|
||||
"DELETED_FAILED": "Deleted destination failed."
|
||||
},
|
||||
"REPOSITORY": {
|
||||
"COPY_ID": "Copy ID",
|
||||
@ -275,7 +290,7 @@
|
||||
"DELETION_SUMMARY_TAG": "Do you want to delete tag {{param}}?",
|
||||
"DELETION_TITLE_TAG_DENIED": "Signed Tag can't be deleted",
|
||||
"DELETION_SUMMARY_TAG_DENIED": "The tag must be removed from the Notary before it can be deleted. {{param}}",
|
||||
"FILTER_FOR_REPOSITORIES": "Filter for repositories",
|
||||
"FILTER_FOR_REPOSITORIES": "Filter Repositories",
|
||||
"TAG": "Tag",
|
||||
"SIGNED": "Signed",
|
||||
"AUTHOR": "Author",
|
||||
@ -286,7 +301,9 @@
|
||||
"SHOW_DETAILS": "Show Details",
|
||||
"REPOSITORIES": "Repositories",
|
||||
"ITEMS": "item(s)",
|
||||
"POP_REPOS": "Popular Repositories"
|
||||
"POP_REPOS": "Popular Repositories",
|
||||
"DELETED_REPO_SUCCESS": "Deleted repository successfully.",
|
||||
"DELETED_TAG_SUCCESS": "Deleted tag successfully."
|
||||
},
|
||||
"ALERT": {
|
||||
"FORM_CHANGE_CONFIRMATION": "Some changes are not saved yet, do you really want to cancel?"
|
||||
@ -310,7 +327,7 @@
|
||||
"EMAIL": "Email",
|
||||
"SYSTEM": "System Settings",
|
||||
"CONFIRM_TITLE": "Confirm to cancel",
|
||||
"CONFIRM_SUMMARY": "Some changes are not saved yet, do you really want to leave?",
|
||||
"CONFIRM_SUMMARY": "Some changes are not saved yet, do you really want to discard?",
|
||||
"SAVE_SUCCESS": "Configurations have been successfully saved",
|
||||
"MAIL_SERVER": "Email Server",
|
||||
"MAIL_SERVER_PORT": "Email Server Port",
|
||||
@ -386,7 +403,8 @@
|
||||
"IN_PROGRESS": "Search...",
|
||||
"BACK": "Back"
|
||||
},
|
||||
"UNKNOWN_ERROR": "Some unknown errors HAVE occurred. Please try again later",
|
||||
"UNKNOWN_ERROR": "Unknown errors have occurred. Please try again later",
|
||||
"UNAUTHORIZED_ERROR": "Your session is invalid or has expired. You need to sign in to continue the operation",
|
||||
"FORBIDDEN_ERROR": "You are not allowed to perform this operation"
|
||||
"FORBIDDEN_ERROR": "You are not allowed to perform this operation",
|
||||
"GENERAL_ERROR": "Errors have occurred when performing service call: {{param}}"
|
||||
}
|
@ -110,10 +110,10 @@
|
||||
"PROJECT": {
|
||||
"PROJECTS": "项目",
|
||||
"NAME": "项目名称",
|
||||
"ROLE": "角色",
|
||||
"PUBLIC_OR_PRIVATE": "公开",
|
||||
"REPO_COUNT": "镜像仓库数",
|
||||
"CREATION_TIME": "创建时间",
|
||||
"DESCRIPTION": "描述",
|
||||
"PUBLIC": "公开",
|
||||
"PRIVATE": "私有",
|
||||
"MAKE": "设为",
|
||||
@ -132,7 +132,10 @@
|
||||
"DELETION_TITLE": "删除项目确认",
|
||||
"DELETION_SUMMARY": "你确认删除项目 {{param}}?",
|
||||
"FILTER_PLACEHOLDER": "过滤项目",
|
||||
"REPLICATION_RULE": "复制策略"
|
||||
"REPLICATION_RULE": "复制策略",
|
||||
"CREATED_SUCCESS": "创建项目成功。",
|
||||
"DELETED_SUCCESS": "删除项目成功。",
|
||||
"TOGGLED_SUCCESS": "切换状态成功。"
|
||||
},
|
||||
"PROJECT_DETAIL": {
|
||||
"REPOSITORIES": "镜像仓库",
|
||||
@ -159,7 +162,10 @@
|
||||
"UNKNOWN_ERROR": "添加成员时发生未知错误。",
|
||||
"FILTER_PLACEHOLDER": "过滤成员",
|
||||
"DELETION_TITLE": "删除项目成员确认",
|
||||
"DELETION_SUMMARY": "你确认删除项目成员 {{param}}?"
|
||||
"DELETION_SUMMARY": "你确认删除项目成员 {{param}}?",
|
||||
"ADDED_SUCCESS": "新增成员成功。",
|
||||
"DELETED_SUCCESS": "删除成员成功",
|
||||
"SWITCHED_SUCCESS": "切换角色成功"
|
||||
},
|
||||
"AUDIT_LOG": {
|
||||
"USERNAME": "用户名",
|
||||
@ -235,7 +241,12 @@
|
||||
"TOGGLE_ENABLE_TITLE": "启用策略",
|
||||
"CONFIRM_TOGGLE_ENABLE_POLICY": "启用策略后,该项目下的所有镜像仓库将复制到目标实例。请确认继续。",
|
||||
"TOGGLE_DISABLE_TITLE": "停用策略",
|
||||
"CONFIRM_TOGGLE_DISABLE_POLICY": "停用策略后,所有未完成的复制任务将被终止和取消。请确认继续。"
|
||||
"CONFIRM_TOGGLE_DISABLE_POLICY": "停用策略后,所有未完成的复制任务将被终止和取消。请确认继续。",
|
||||
"CREATED_SUCCESS": "创建复制策略成功。",
|
||||
"UPDATED_SUCCESS": "更新复制策略成功。",
|
||||
"DELETED_SUCCESS": "删除复制策略成功。",
|
||||
"DELETED_FAILED": "删除复制策略失败。",
|
||||
"TOGGLED_SUCCESS": "切换复制策略状态成功。"
|
||||
},
|
||||
"DESTINATION": {
|
||||
"NEW_ENDPOINT": "新建目标",
|
||||
@ -257,7 +268,11 @@
|
||||
"INVALID_NAME": "无效的目标名称。",
|
||||
"FAILED_TO_GET_TARGET": "获取目标失败。",
|
||||
"CREATION_TIME": "创建时间",
|
||||
"ITEMS": "条记录"
|
||||
"ITEMS": "条记录",
|
||||
"CREATED_SUCCESS": "创建目标成功。",
|
||||
"UPDATED_SUCCESS": "更新目标成功。",
|
||||
"DELETED_SUCCESS": "删除目标成功。",
|
||||
"DELETED_FAILED": "删除目标失败。"
|
||||
},
|
||||
"REPOSITORY": {
|
||||
"COPY_ID": "复制ID",
|
||||
@ -286,7 +301,9 @@
|
||||
"SHOW_DETAILS": "显示详细",
|
||||
"REPOSITORIES": "镜像仓库",
|
||||
"ITEMS": "条记录",
|
||||
"POP_REPOS": "受欢迎的镜像库"
|
||||
"POP_REPOS": "受欢迎的镜像库",
|
||||
"DELETED_REPO_SUCCESS": "删除镜像仓库成功。",
|
||||
"DELETED_TAG_SUCCESS": "删除镜像标签成功。"
|
||||
},
|
||||
"ALERT": {
|
||||
"FORM_CHANGE_CONFIRMATION": "表单内容改变,确认取消?"
|
||||
@ -378,8 +395,8 @@
|
||||
"TITLE": "统计",
|
||||
"PRO_ITEM": "项目",
|
||||
"REPO_ITEM": "镜像库",
|
||||
"INDEX_MY": "私有的",
|
||||
"INDEX_PUB": "公开的",
|
||||
"INDEX_MY": "私有",
|
||||
"INDEX_PUB": "公开",
|
||||
"INDEX_TOTAL": "总计"
|
||||
},
|
||||
"SEARCH": {
|
||||
@ -388,5 +405,6 @@
|
||||
},
|
||||
"UNKNOWN_ERROR": "发生未知错误,请稍后再试",
|
||||
"UNAUTHORIZED_ERROR": "会话无效或者已经过期, 请重新登录以继续",
|
||||
"FORBIDDEN_ERROR": "当前操作被禁止,请确认你有合法的权限"
|
||||
"FORBIDDEN_ERROR": "当前操作被禁止,请确认你有合法的权限",
|
||||
"GENERAL_ERROR": "调用后台服务时出现错误: {{param}}"
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
// Fetch prints the content found at a URL.
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
time.Sleep(60 * time.Second)
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
var client = &http.Client{
|
||||
Timeout: time.Second * 30,
|
||||
Transport: tr,
|
||||
}
|
||||
|
||||
for _, url := range os.Args[1:] {
|
||||
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "fetch: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "fetch: reading %s: %v\n", url, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
// fmt.Printf("%s", b)
|
||||
|
||||
if strings.Contains(string(b), "Harbor") {
|
||||
fmt.Printf("sucess!\n")
|
||||
} else {
|
||||
fmt.Println("the response does not contain \"Harbor\"!")
|
||||
|
||||
fmt.Println(string(b))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
28
tests/startuptest.sh
Executable file
28
tests/startuptest.sh
Executable file
@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
|
||||
set +e
|
||||
|
||||
TIMEOUT=12
|
||||
while [ $TIMEOUT -gt 0 ]; do
|
||||
STATUS=$(curl --insecure -s -o /dev/null -w '%{http_code}' https://localhost/)
|
||||
if [ $STATUS -eq 200 ]; then
|
||||
break
|
||||
fi
|
||||
TIMEOUT=$(($TIMEOUT - 1))
|
||||
sleep 5
|
||||
done
|
||||
|
||||
if [ $TIMEOUT -eq 0 ]; then
|
||||
echo "Harbor cannot reach within one minute."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
curl --insecure -s -L -H "Accept: application/json" https://localhost/ | grep "Harbor" > /dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Harbor is running success."
|
||||
else
|
||||
echo "Harbor is running fail."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user