diff --git a/.travis.yml b/.travis.yml
index b0dd4c556..80a1df4a7 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -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
diff --git a/Makefile b/Makefile
index b126a2316..719c23d05 100644
--- a/Makefile
+++ b/Makefile
@@ -390,9 +390,9 @@ down:
[ $$CONTINUE = "y" ] || [ $$CONTINUE = "Y" ] || (echo "Exiting."; exit 1;)
@echo "stoping harbor instance..."
@if [ "$(NOTARYFLAG)" = "true" ] ; then \
- $(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSENOTARYFILENAME) down ; \
+ $(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSENOTARYFILENAME) down -v ; \
else \
- $(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) down ; \
+ $(DOCKERCOMPOSECMD) -f $(DOCKERCOMPOSEFILEPATH)/$(DOCKERCOMPOSEFILENAME) down -v ; \
fi
@echo "Done."
diff --git a/make/common/templates/notary/notary-signer.crt b/make/common/templates/notary/notary-signer.crt
index 9e9478998..1189dfd7b 100644
--- a/make/common/templates/notary/notary-signer.crt
+++ b/make/common/templates/notary/notary-signer.crt
@@ -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
+MIIFdjCCA14CCQCeVwANSZmmiDANBgkqhkiG9w0BAQsFADCBhDELMAkGA1UEBhMC
+VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAcMCVBhbG8gQWx0bzEVMBMG
+A1UECgwMVk13YXJlLCBJbmMuMQ8wDQYDVQQLDAZIYXJib3IxJDAiBgNVBAMMG1Nl
+bGYtc2lnbmVkIGJ5IFZNd2FyZSwgSW5jLjAeFw0xNzAzMjQwNTMyMDBaFw0yNzAz
+MjIwNTMyMDBaMHUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRIw
+EAYDVQQHDAlQYWxvIEFsdG8xFTATBgNVBAoMDFZNd2FyZSwgSW5jLjEPMA0GA1UE
+CwwGSGFyYm9yMRUwEwYDVQQDDAxub3RhcnlzaWduZXIwggIiMA0GCSqGSIb3DQEB
+AQUAA4ICDwAwggIKAoICAQC6TV2RCoH8d1g6xFvDo4FL9v+pGLe5+bu9ryjTaLbN
+dH/Cmf5/8WrmgJ3vG2Ksk796J7qsVddwvQkZn6NwDm2Tm+ETMCG85yEA3jl4Kr9R
+XfWHYWEavv0vsq6M+bUSSq7VJAhgk4wfx6qJBnFX2qKpODeYLHaHxU1EnIXrStNf
+IqR4Eu0Xre8jAkzrDdaFy/KnX4HGgNdz413CXzBCKEuu3VJj07ZvonnTzOgoLvh8
++PCoQ2M4OBPT9gHqUov1I8nWnrjc+HuM1BW3YIGCB5TV9x0Y7hjvkr4E38gbJURj
+uDwg8jof4lMRmU/FHXFLt1ucGwNFUJdPwI7dyEKRA03Lr7htfP5sa9tmv3L93dKD
+po1gW1LsfiM3Cur5jARM/hBA+eYJr12Laf9oL59r8JmweqF3zRSwGSY336XoR/Fv
+/PAFs9vfKKWZp0uiRtuY9JZNRTF8trnfNf1957bND+DS2HWPmWkw4yK6CGa0s55X
+adiDt4gDFvKjl68dBWZoHutY+cZy/hK1D5uqagcX1kzbr/Pzy1gsq9FBBwaTJqBu
+YIAsSuzP+7NNZXoPd3rg13V93pbZr8eQN5VOQIBZK83xZEtHSJBEdUSuBOo3JS7j
+/rjEnspRqOI4soFnx1vaK0TrRyzJ5KBOuGpW4u8/ZUdIq8KIE30Mj/XI/sgAPr5j
+UQIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQBjqYBm/FRqyMH2hnHA0TMXY/WPufJ8
+TX10daELCAYJCEETXmUt1i7dnFxdAZXTnHENHdNYiS4nGBfqMLmODtcAamcv6Dcl
+JnyQPt3QlCDPKkcHgz3y4tvDDx6M5rFWYzN9QLiWAYrunIk1R4Jj7FODrM6/NODE
+0Mz1czWfsmLfX/jF80SsxnY1DCLKGgo6/RID3xTp4eIMboxCfeH2/yDA+6YPyYbV
+Si4ccwo9Foq0IYU8bimPNTyBQ0N+8ajcn328ql6aazmr894Ch5pWA3Qxaa98FcKS
+zokBvmmCuvCJ9HOmxKWdFEhSRS9GWxn7wg78UIlLP/8RfUrsecBJHgyhWRA7Qs3K
+keiG68Zrhn456IdMxjCZXgJ7gAAe77n4Cz8sFEHAvnAg9JLNEHuEBV5H1Hb7TzET
+k0lPiEY78QjutOpqHsWiagqSjlGEMqKI9c8WxXHh9030T/6NnWkdXFo+4HaEZEpp
+0JryASS53B5SwLIPrn0Y2/io/kRgbglGktPt6Ex0DwW3f96lcz3me34Nw+HOYYnz
+b0cz7JqJZgFXfEnykic3IwZs7m7Xrl9B/vvaVub9Fb5LQ7rIzrO7VkoILov/G41B
+Pd4/kagjXDTWd+UBMvZF6YGjr+TUZi5ooi7bvQ3X6N9WNYKW4a1DOokz9janStiL
+MrTKyOEOBi0Aew==
-----END CERTIFICATE-----
diff --git a/make/common/templates/notary/notary-signer.key b/make/common/templates/notary/notary-signer.key
index 2db6e2ce9..3973cec7b 100644
--- a/make/common/templates/notary/notary-signer.key
+++ b/make/common/templates/notary/notary-signer.key
@@ -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-----
diff --git a/make/docker-compose.tpl b/make/docker-compose.tpl
index decf381ee..302c15dd1 100644
--- a/make/docker-compose.tpl
+++ b/make/docker-compose.tpl
@@ -118,6 +118,7 @@ services:
ports:
- 80:80
- 443:443
+ - 4443:4443
depends_on:
- mysql
- registry
diff --git a/make/harbor.cfg b/make/harbor.cfg
index 844daa49e..8c9575585 100644
--- a/make/harbor.cfg
+++ b/make/harbor.cfg
@@ -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
diff --git a/make/install.sh b/make/install.sh
index 54c2b10be..def0189f0 100755
--- a/make/install.sh
+++ b/make/install.sh
@@ -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 ""
diff --git a/make/prepare b/make/prepare
index df52b6662..9d26ed54c 100755
--- a/make/prepare
+++ b/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")
@@ -273,44 +266,43 @@ def stat_decorator(func):
@stat_decorator
def create_root_cert(subj, key_path="./k.key", cert_path="./cert.crt"):
- rc = subprocess.call(["openssl", "genrsa", "-out", key_path, "4096"])
+ 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])
+ "-out", cert_path, "-days", "3650", "-subj", subj], stdout=FNULL, stderr=subprocess.STDOUT)
@stat_decorator
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])
+ "-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])
+ 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):
- 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)
+ 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("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("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")
@@ -318,13 +310,12 @@ 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?
- if customize_crt == 'on':
+ 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/CN=Self Signed CA/"
- cert_subj = "/C=US/ST=California/L=Palo Alto/O=Vmware/CN=notarysigner/"
+ 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")
@@ -355,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.")
diff --git a/src/ui/views/reset-password-mail.tpl b/src/ui/views/reset-password-mail.tpl
new file mode 100644
index 000000000..31fb438ef
--- /dev/null
+++ b/src/ui/views/reset-password-mail.tpl
@@ -0,0 +1,21 @@
+
+
+
+
+ Please click this link to reset your password:
+ {{.URL}}/reset_password?reset_uuid={{.UUID}}
+
+
diff --git a/src/ui_ng/src/app/harbor-routing.module.ts b/src/ui_ng/src/app/harbor-routing.module.ts
index 967a3c402..583071bfb 100644
--- a/src/ui_ng/src/app/harbor-routing.module.ts
+++ b/src/ui_ng/src/app/harbor-routing.module.ts
@@ -76,7 +76,10 @@ const harborRoutes: Routes = [
},
{
path: 'tags/:id/:repo',
- component: TagRepositoryComponent
+ component: TagRepositoryComponent,
+ resolve: {
+ projectResolver: ProjectRoutingResolver
+ }
},
{
path: 'projects/:id',
diff --git a/src/ui_ng/src/app/log/audit-log.service.ts b/src/ui_ng/src/app/log/audit-log.service.ts
index 320d589b5..28be16021 100644
--- a/src/ui_ng/src/app/log/audit-log.service.ts
+++ b/src/ui_ng/src/app/log/audit-log.service.ts
@@ -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 {
return this.http
@@ -36,12 +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 {
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));
}
}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/project/create-project/create-project.component.html b/src/ui_ng/src/app/project/create-project/create-project.component.html
index 725f538a6..785ba7fe9 100644
--- a/src/ui_ng/src/app/project/create-project/create-project.component.html
+++ b/src/ui_ng/src/app/project/create-project/create-project.component.html
@@ -31,6 +31,6 @@
diff --git a/src/ui_ng/src/app/project/list-project/list-project.component.html b/src/ui_ng/src/app/project/list-project/list-project.component.html
index fa6074706..c04437c00 100644
--- a/src/ui_ng/src/app/project/list-project/list-project.component.html
+++ b/src/ui_ng/src/app/project/list-project/list-project.component.html
@@ -1,9 +1,9 @@
{{'PROJECT.NAME' | translate}}
{{'PROJECT.PUBLIC_OR_PRIVATE' | translate}}
+ {{'PROJECT.ROLE' | translate}}
{{'PROJECT.REPO_COUNT'| translate}}
{{'PROJECT.CREATION_TIME' | translate}}
- {{'PROJECT.DESCRIPTION' | translate}}
@@ -12,9 +12,9 @@
{{p.name}}
{{ (p.public === 1 ? 'PROJECT.PUBLIC' : 'PROJECT.PRIVATE') | translate}}
+ {{roleInfo[p.current_user_role_id] | translate}}
{{p.repo_count}}
{{p.creation_time}}
- {{p.description}}
{{totalRecordCount || (projects ? projects.length : 0)}} {{'PROJECT.ITEMS' | translate}}
diff --git a/src/ui_ng/src/app/project/list-project/list-project.component.ts b/src/ui_ng/src/app/project/list-project/list-project.component.ts
index c4a5895cb..1a4c8b5ea 100644
--- a/src/ui_ng/src/app/project/list-project/list-project.component.ts
+++ b/src/ui_ng/src/app/project/list-project/list-project.component.ts
@@ -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();
@Output() toggle = new EventEmitter();
@@ -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,10 @@ 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;
diff --git a/src/ui_ng/src/app/project/member/add-member/add-member.component.html b/src/ui_ng/src/app/project/member/add-member/add-member.component.html
index 844ddd209..55764ae35 100644
--- a/src/ui_ng/src/app/project/member/add-member/add-member.component.html
+++ b/src/ui_ng/src/app/project/member/add-member/add-member.component.html
@@ -36,6 +36,6 @@
diff --git a/src/ui_ng/src/app/project/member/member.component.html b/src/ui_ng/src/app/project/member/member.component.html
index a0563db81..ac4a5ea5f 100644
--- a/src/ui_ng/src/app/project/member/member.component.html
+++ b/src/ui_ng/src/app/project/member/member.component.html
@@ -17,15 +17,15 @@
{{'MEMBER.NAME' | translate}}
{{'MEMBER.ROLE' | translate}}
-
-
-
-
-
-
+
+
+
+
+
+
- {{u.username}}
- {{roleInfo[u.role_id] | translate}}
+ {{m.username}}
+ {{roleInfo[m.role_id] | translate}}
{{ (members ? members.length : 0) }} {{'MEMBER.ITEMS' | translate}}
diff --git a/src/ui_ng/src/app/project/member/member.component.ts b/src/ui_ng/src/app/project/member/member.component.ts
index b13b04c3b..765f67ee6 100644
--- a/src/ui_ng/src/app/project/member/member.component.ts
+++ b/src/ui_ng/src/app/project/member/member.component.ts
@@ -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,31 +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();
- let projectMembers: Member[] = session.getProjectMembers();
- if(this.currentUser && projectMembers) {
- let currentMember = projectMembers.find(m=>m.user_id === this.currentUser.user_id);
- if(currentMember) {
- this.hasProjectAdminRole = (currentMember.role_name === 'projectAdmin');
- }
- }
-
+ private session: SessionService) {
+
this.delSub = deletionDialogService.confirmationConfirm$.subscribe(message => {
if (message &&
message.state === ConfirmationState.CONFIRMED &&
@@ -82,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() {
@@ -97,6 +89,15 @@ export class MemberComponent implements OnInit, OnDestroy {
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 = (resolverData['projectResolver']).has_project_admin_role;
+ }
+
+
+
this.retrieve(this.projectId, '');
}
@@ -108,25 +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 => {
- this.messageService.announceMessage(response, 'MEMBER.SWITCHED_SUCCESS', AlertType.SUCCESS);
- 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);
diff --git a/src/ui_ng/src/app/project/member/member.service.ts b/src/ui_ng/src/app/project/member/member.service.ts
index 5578b1083..28cb0c895 100644
--- a/src/ui_ng/src/app/project/member/member.service.ts
+++ b/src/ui_ng/src/app/project/member/member.service.ts
@@ -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 {
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 {
diff --git a/src/ui_ng/src/app/project/project-detail/project-detail.component.html b/src/ui_ng/src/app/project/project-detail/project-detail.component.html
index ccfd09356..b33359980 100644
--- a/src/ui_ng/src/app/project/project-detail/project-detail.component.html
+++ b/src/ui_ng/src/app/project/project-detail/project-detail.component.html
@@ -5,10 +5,10 @@
{{'PROJECT_DETAIL.REPOSITORIES' | translate}}
-
+
{{'PROJECT_DETAIL.USERS' | translate}}
-
+
{{'PROJECT_DETAIL.LOGS' | translate}}
diff --git a/src/ui_ng/src/app/project/project-detail/project-detail.component.ts b/src/ui_ng/src/app/project/project-detail/project-detail.component.ts
index 9914eb253..b5128b252 100644
--- a/src/ui_ng/src/app/project/project-detail/project-detail.component.ts
+++ b/src/ui_ng/src/app/project/project-detail/project-detail.component.ts
@@ -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 = data['projectResolver']);
+ private sessionService: SessionService,
+ private projectService: ProjectService) {
+ this.route.data.subscribe(data=>{
+ this.currentProject = data['projectResolver'];
+ this.isMember = this.currentProject.is_member;
+ });
}
public get isSystemAdmin(): boolean {
diff --git a/src/ui_ng/src/app/project/project-routing-resolver.service.ts b/src/ui_ng/src/app/project/project-routing-resolver.service.ts
index 92e80906f..d479b5669 100644
--- a/src/ui_ng/src/app/project/project-routing-resolver.service.ts
+++ b/src/ui_ng/src/app/project/project-routing-resolver.service.ts
@@ -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{
- constructor(private projectService: ProjectService, private router: Router) {}
+ constructor(
+ private sessionService: SessionService,
+ private projectService: ProjectService,
+ private router: Router) {}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise {
- 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;
});
+
}
}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/project/project.component.css b/src/ui_ng/src/app/project/project.component.css
index d7166ed6c..d230c21d8 100644
--- a/src/ui_ng/src/app/project/project.component.css
+++ b/src/ui_ng/src/app/project/project.component.css
@@ -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;
+}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/project/project.component.html b/src/ui_ng/src/app/project/project.component.html
index 1649cd740..008069e04 100644
--- a/src/ui_ng/src/app/project/project.component.html
+++ b/src/ui_ng/src/app/project/project.component.html
@@ -1,6 +1,9 @@
-
+
+
+
+
@@ -18,11 +21,13 @@
-
+
+
+
-
-
-
-
+
+
+
+
diff --git a/src/ui_ng/src/app/project/project.component.ts b/src/ui_ng/src/app/project/project.component.ts
index fb82dd380..f87525334 100644
--- a/src/ui_ng/src/app/project/project.component.ts
+++ b/src/ui_ng/src/app/project/project.component.ts
@@ -25,9 +25,7 @@ import { State } from 'clarity-angular';
import { AppConfigService } from '../app-config.service';
import { SessionService } from '../shared/session.service';
-
-
-const types: {} = { 0: 'PROJECT.MY_PROJECTS', 1: 'PROJECT.PUBLIC_PROJECTS' };
+import { ProjectTypes } from '../shared/shared.const';
@Component({
moduleId: module.id,
@@ -39,7 +37,7 @@ export class ProjectComponent implements OnInit, OnDestroy {
selected = [];
changedProjects: Project[];
- projectTypes = types;
+ projectTypes = ProjectTypes;
@ViewChild(CreateProjectComponent)
creationProject: CreateProjectComponent;
@@ -145,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();
diff --git a/src/ui_ng/src/app/project/project.service.ts b/src/ui_ng/src/app/project/project.service.ts
index 187d791c1..50c637fe7 100644
--- a/src/ui_ng/src/app/project/project.service.ts
+++ b/src/ui_ng/src/app/project/project.service.ts
@@ -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 {
+ getProject(projectId: number): Observable {
return this.http
.get(`/api/projects/${projectId}`)
- .toPromise()
- .then(response=>response.json() as Project)
+ .map(response=>response.json())
.catch(error=>Observable.throw(error));
}
diff --git a/src/ui_ng/src/app/project/project.ts b/src/ui_ng/src/app/project/project.ts
index 1c6d73014..3304c6d5f 100644
--- a/src/ui_ng/src/app/project/project.ts
+++ b/src/ui_ng/src/app/project/project.ts
@@ -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;
}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/replication/replication.service.ts b/src/ui_ng/src/app/replication/replication.service.ts
index bec392cfd..a8526fc19 100644
--- a/src/ui_ng/src/app/replication/replication.service.ts
+++ b/src/ui_ng/src/app/replication/replication.service.ts
@@ -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 {
if(!projectId) {
diff --git a/src/ui_ng/src/app/repository/list-repository/list-repository.component.ts b/src/ui_ng/src/app/repository/list-repository/list-repository.component.ts
index df2490d55..b33a1b06f 100644
--- a/src/ui_ng/src/app/repository/list-repository/list-repository.component.ts
+++ b/src/ui_ng/src/app/repository/list-repository/list-repository.component.ts
@@ -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';
@@ -8,16 +8,17 @@ import { SessionService } from '../../shared/session.service';
import { ListMode } from '../../shared/shared.const';
import { SessionUser } from '../../shared/session-user';
-import { Member } from '../../project/member/member';
@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();
@Input() totalPage: number;
@@ -25,25 +26,16 @@ export class ListRepositoryComponent {
@Output() paginate = new EventEmitter();
@Input() mode: string = ListMode.FULL;
+ @Input() hasProjectAdminRole: boolean;
pageOffset: number = 1;
- hasProjectAdminRole: boolean;
-
constructor(
private router: Router,
private searchTrigger: SearchTriggerService,
- private session: SessionService) {
- //Get current user from registered resolver.
- let currentUser = session.getCurrentUser();
- let projectMembers: Member[] = session.getProjectMembers();
- if(currentUser && projectMembers) {
- let currentMember = projectMembers.find(m=>m.user_id === currentUser.user_id);
- if(currentMember) {
- this.hasProjectAdminRole = (currentMember.role_name === 'projectAdmin');
- }
- }
- }
+ private session: SessionService) { }
+
+ ngOnInit() {}
deleteRepo(repoName: string) {
this.delete.emit(repoName);
diff --git a/src/ui_ng/src/app/repository/repository.component.html b/src/ui_ng/src/app/repository/repository.component.html
index 5e506a53a..bafef0007 100644
--- a/src/ui_ng/src/app/repository/repository.component.html
+++ b/src/ui_ng/src/app/repository/repository.component.html
@@ -8,6 +8,6 @@
-
+
\ No newline at end of file
diff --git a/src/ui_ng/src/app/repository/repository.component.ts b/src/ui_ng/src/app/repository/repository.component.ts
index 616cb1209..d1869c6be 100644
--- a/src/ui_ng/src/app/repository/repository.component.ts
+++ b/src/ui_ng/src/app/repository/repository.component.ts
@@ -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(
@@ -66,12 +70,16 @@ export class RepositoryComponent implements OnInit {
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 = (resolverData['projectResolver']).has_project_admin_role;
+ }
this.currentRepositoryType = this.repositoryTypes[0];
this.lastFilteredRepoName = '';
this.retrieve();
diff --git a/src/ui_ng/src/app/repository/tag-repository/tag-repository.component.ts b/src/ui_ng/src/app/repository/tag-repository/tag-repository.component.ts
index 6b7211c29..4a91096b3 100644
--- a/src/ui_ng/src/app/repository/tag-repository/tag-repository.component.ts
+++ b/src/ui_ng/src/app/repository/tag-repository/tag-repository.component.ts
@@ -16,7 +16,8 @@ import { TagView } from '../tag-view';
import { AppConfigService } from '../../app-config.service';
import { SessionService } from '../../shared/session.service';
-import { Member } from '../../project/member/member';
+
+import { Project } from '../../project/project';
@Component({
moduleId: module.id,
@@ -29,7 +30,7 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
projectId: number;
repoName: string;
- hasProjectAdminRole: boolean;
+ hasProjectAdminRole: boolean = false;
tags: TagView[];
registryUrl: string;
@@ -45,15 +46,6 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
private appConfigService: AppConfigService,
private session: SessionService){
- let currentUser = session.getCurrentUser();
- let projectMembers: Member[] = session.getProjectMembers();
- if(currentUser && projectMembers) {
- let currentMember = projectMembers.find(m=>m.user_id === currentUser.user_id);
- if(currentMember) {
- this.hasProjectAdminRole = (currentMember.role_name === 'projectAdmin');
- }
- }
-
this.subscription = this.deletionDialogService.confirmationConfirm$.subscribe(
message => {
if (message &&
@@ -78,11 +70,15 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
}
}
}
- }
- )
+ });
}
ngOnInit() {
+ let resolverData = this.route.snapshot.data;
+ console.log(JSON.stringify(resolverData));
+ if(resolverData) {
+ this.hasProjectAdminRole = (resolverData['projectResolver']).has_project_admin_role;
+ }
this.projectId = this.route.snapshot.params['id'];
this.repoName = this.route.snapshot.params['repo'];
this.tags = [];
@@ -100,17 +96,17 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
retrieve() {
this.tags = [];
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));
+ 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));
+ .listTags(this.repoName)
+ .subscribe(
+ items => this.listTags(items),
+ error => this.messageService.announceMessage(error.status, 'Failed to list tags with repo:' + this.repoName, AlertType.DANGER));
}
}
diff --git a/src/ui_ng/src/app/service/auth-guard.service.ts b/src/ui_ng/src/app/service/auth-guard.service.ts
deleted file mode 100644
index 4c1fbcea4..000000000
--- a/src/ui_ng/src/app/service/auth-guard.service.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { Injectable } from '@angular/core';
-import { CanActivate } from '@angular/router';
-
-export class AuthGuard implements CanActivate {
- canActivate() {
- return true;
- }
-}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/service/base.service.ts b/src/ui_ng/src/app/service/base.service.ts
deleted file mode 100644
index 7e79b2049..000000000
--- a/src/ui_ng/src/app/service/base.service.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { Http, Response,} from '@angular/http';
-
-export class BaseService {
-
- protected handleError(error: Response | any): Promise {
- // 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);
- }
-}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/shared/route/member-guard-activate.service.ts b/src/ui_ng/src/app/shared/route/member-guard-activate.service.ts
index a1d4fc74f..18632bbab 100644
--- a/src/ui_ng/src/app/shared/route/member-guard-activate.service.ts
+++ b/src/ui_ng/src/app/shared/route/member-guard-activate.service.ts
@@ -17,7 +17,8 @@ export class MemberGuard implements CanActivate, CanActivateChild {
private router: Router) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise | boolean {
- let projectId: number = route.params['id'];
+ let projectId = route.params['id'];
+ this.sessionService.setProjectMembers([]);
return new Promise((resolve, reject) => {
this.projectService.checkProjectMember(projectId)
.subscribe(
@@ -26,6 +27,10 @@ export class MemberGuard implements CanActivate, CanActivateChild {
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);
});
diff --git a/src/ui_ng/src/app/shared/shared.const.ts b/src/ui_ng/src/app/shared/shared.const.ts
index 536ed59bd..830b00baf 100644
--- a/src/ui_ng/src/app/shared/shared.const.ts
+++ b/src/ui_ng/src/app/shared/shared.const.ts
@@ -52,4 +52,7 @@ export const CookieKeyOfAdmiral = "admiral.endpoint.latest";
export const enum ConfirmationState {
NA, CONFIRMED, CANCEL
-}
\ No newline at end of file
+}
+
+export const ProjectTypes = { 0: 'PROJECT.MY_PROJECTS', 1: 'PROJECT.PUBLIC_PROJECTS' };
+export const RoleInfo = { 1: 'MEMBER.PROJECT_ADMIN', 2: 'MEMBER.DEVELOPER', 3: 'MEMBER.GUEST' };
diff --git a/src/ui_ng/src/app/shared/statictics/statistics-panel.component.html b/src/ui_ng/src/app/shared/statictics/statistics-panel.component.html
index e7a860ed4..df9828631 100644
--- a/src/ui_ng/src/app/shared/statictics/statistics-panel.component.html
+++ b/src/ui_ng/src/app/shared/statictics/statistics-panel.component.html
@@ -1,25 +1,41 @@
-
-
{{'STATISTICS.TITLE' | translate }}
-
-
-
-{{'STATISTICS.PRO_ITEM' | translate }}
-
-
-
-
-
-
-
-
-
-
{{'STATISTICS.REPO_ITEM' | translate }}
+
+
+
+
+
+
+ {{'STATISTICS.PRO_ITEM' | translate }}
+
+
+ {{'STATISTICS.REPO_ITEM' | translate }}
+
+
+
+
+
+
+
+
Storage
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/ui_ng/src/app/shared/statictics/statistics.component.css b/src/ui_ng/src/app/shared/statictics/statistics.component.css
index 51b62a853..f308d3377 100644
--- a/src/ui_ng/src/app/shared/statictics/statistics.component.css
+++ b/src/ui_ng/src/app/shared/statictics/statistics.component.css
@@ -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;
}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/shared/statictics/statistics.component.html b/src/ui_ng/src/app/shared/statictics/statistics.component.html
index 642ed916a..2d98c617e 100644
--- a/src/ui_ng/src/app/shared/statictics/statistics.component.html
+++ b/src/ui_ng/src/app/shared/statictics/statistics.component.html
@@ -1,4 +1,4 @@
- {{data.number}}
- {{data.label}}
+ {{data}}
+ {{label}}
\ No newline at end of file
diff --git a/src/ui_ng/src/app/shared/statictics/statistics.component.ts b/src/ui_ng/src/app/shared/statictics/statistics.component.ts
index f1e5563fb..bcb028b61 100644
--- a/src/ui_ng/src/app/shared/statictics/statistics.component.ts
+++ b/src/ui_ng/src/app/shared/statictics/statistics.component.ts
@@ -7,5 +7,6 @@ import { Component, Input } from '@angular/core';
})
export class StatisticsComponent {
- @Input() data: any;
+ @Input() label: string;
+ @Input() data: number = 0;
}
\ No newline at end of file
diff --git a/src/ui_ng/src/i18n/lang/en-lang.json b/src/ui_ng/src/i18n/lang/en-lang.json
index 7faa7e7df..d1efbb68b 100644
--- a/src/ui_ng/src/i18n/lang/en-lang.json
+++ b/src/ui_ng/src/i18n/lang/en-lang.json
@@ -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",
@@ -290,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",
diff --git a/src/ui_ng/src/i18n/lang/zh-lang.json b/src/ui_ng/src/i18n/lang/zh-lang.json
index efac765dc..3250b2369 100644
--- a/src/ui_ng/src/i18n/lang/zh-lang.json
+++ b/src/ui_ng/src/i18n/lang/zh-lang.json
@@ -110,10 +110,10 @@
"PROJECT": {
"PROJECTS": "项目",
"NAME": "项目名称",
+ "ROLE": "角色",
"PUBLIC_OR_PRIVATE": "公开",
"REPO_COUNT": "镜像仓库数",
"CREATION_TIME": "创建时间",
- "DESCRIPTION": "描述",
"PUBLIC": "公开",
"PRIVATE": "私有",
"MAKE": "设为",
@@ -395,8 +395,8 @@
"TITLE": "统计",
"PRO_ITEM": "项目",
"REPO_ITEM": "镜像库",
- "INDEX_MY": "私有的",
- "INDEX_PUB": "公开的",
+ "INDEX_MY": "私有",
+ "INDEX_PUB": "公开",
"INDEX_TOTAL": "总计"
},
"SEARCH": {
diff --git a/tests/startuptest.go b/tests/startuptest.go
deleted file mode 100644
index 5330b45b5..000000000
--- a/tests/startuptest.go
+++ /dev/null
@@ -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)
- }
-
- }
-}
diff --git a/tests/startuptest.sh b/tests/startuptest.sh
new file mode 100755
index 000000000..7a41cd768
--- /dev/null
+++ b/tests/startuptest.sh
@@ -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
+
+