diff --git a/.gitignore b/.gitignore index b53274b48..09be8ccc0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ Deploy/config/registry/config.yml Deploy/config/ui/env Deploy/config/ui/app.conf Deploy/config/db/env -Deploy/harbor.cfg +Deploy/config/jobservice/env ui/ui *.pyc +jobservice/test diff --git a/.travis.yml b/.travis.yml index b7c4c8fd5..b86e1adae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,13 +29,25 @@ env: HARBOR_ADMIN_PASSWD: Harbor12345 before_install: - - ./tests/hostcfg.sh + - sudo ./tests/hostcfg.sh - cd Deploy - - ./prepare + - sudo ./prepare - cd .. install: - - sudo apt-get update && sudo apt-get install -y libldap2-dev + - sudo apt-get update && sudo apt-get install -y libldap2-dev +# - sudo apt-get remove -y mysql-common mysql-server-5.5 mysql-server-core-5.5 mysql-client-5.5 mysql-client-core-5.5 +# - sudo apt-get autoremove -y +# - sudo apt-get install -y libaio1 +# - wget -O mysql-5.6.14.deb http://dev.mysql.com/get/Downloads/MySQL-5.6/mysql-5.6.14-debian6.0-x86_64.deb/from/http://cdn.mysql.com/ +# - sudo dpkg -i mysql-5.6.14.deb +# - sudo cp /opt/mysql/server-5.6/support-files/mysql.server /etc/init.d/mysql.server +# - sudo ln -s /opt/mysql/server-5.6/bin/* /usr/bin/ +# - sudo sed -i'' 's/table_cache/table_open_cache/' /etc/mysql/my.cnf +# - sudo sed -i'' 's/log_slow_queries/slow_query_log/' /etc/mysql/my.cnf +# - sudo sed -i'' 's/basedir[^=]\+=.*$/basedir = \/opt\/mysql\/server-5.6/' /etc/mysql/my.cnf +# - sudo /etc/init.d/mysql.server start +# - mysql --version - go get -d github.com/docker/distribution - go get -d github.com/docker/libtrust - go get -d github.com/go-sql-driver/mysql @@ -46,6 +58,8 @@ install: - curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose - chmod +x docker-compose - sudo mv docker-compose /usr/local/bin + - sudo sed -i '$a DOCKER_OPTS=\"$DOCKER_OPTS --insecure-registry 127.0.0.1\"' /etc/default/docker + - sudo service docker restart - go get github.com/dghubble/sling - go get github.com/stretchr/testify @@ -66,4 +80,5 @@ script: # test for API + - sudo ./tests/testprepare.sh - go test -v ./tests/apitests diff --git a/Deploy/config/jobservice/app.conf b/Deploy/config/jobservice/app.conf new file mode 100644 index 000000000..21439a8e1 --- /dev/null +++ b/Deploy/config/jobservice/app.conf @@ -0,0 +1,5 @@ +appname = jobservice +runmode = dev + +[dev] +httpport = 80 diff --git a/Deploy/config/registry/root.crt b/Deploy/config/registry/root.crt index 326d8080a..c31b27de6 100644 --- a/Deploy/config/registry/root.crt +++ b/Deploy/config/registry/root.crt @@ -1,15 +1,35 @@ -----BEGIN CERTIFICATE----- -MIICWDCCAcGgAwIBAgIJAN1nLuloDeHNMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV -BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX -aWRnaXRzIFB0eSBMdGQwHhcNMTYwMTI3MDQyMDM1WhcNNDMwNjE0MDQyMDM1WjBF -MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 -ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB -gQClak/4HO7EeLU0w/BhtVENPLOqU0AP2QjVUdg1qhNiDWVrbWx9KYHqz5Kn0n2+ -fxdZo3o7ZY5/2+hhgkKh1z6Kge9XGgune6z4fx2J/X2Se8WsGeQUTiND8ngSnsCA -NtYFwW50SbUZPtyf5XjAfKRofZem51OxbxzN3217L/ubKwIDAQABo1AwTjAdBgNV -HQ4EFgQU5EG2VrB3I6G/TudUpz+kBgQXSvYwHwYDVR0jBBgwFoAU5EG2VrB3I6G/ -TudUpz+kBgQXSvYwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQAx+2eo -oOm0YNy9KQ81+7GQkKVWoPQXjAGGgZuZj8WCFepYqUSJ4q5qbuVCY8WbGcHVk2Rx -Jg1XDCmMjBgYP6S0ikezBRqSmNA3G6oFiydTKBfPs6RNalsB0C78Xk5l5+PIyd2R -jFKOKoMpkjwfeJv2j64WNGoBgqj7XRBoJ11a4g== +MIIGBzCCA++gAwIBAgIJAKB8CNqCxhr7MA0GCSqGSIb3DQEBCwUAMIGZMQswCQYD +VQQGEwJDTjEOMAwGA1UECAwFU3RhdGUxCzAJBgNVBAcMAkNOMRUwEwYDVQQKDAxv +cmdhbml6YXRpb24xHDAaBgNVBAsME29yZ2FuaXphdGlvbmFsIHVuaXQxFDASBgNV +BAMMC2V4YW1wbGUuY29tMSIwIAYJKoZIhvcNAQkBFhNleGFtcGxlQGV4YW1wbGUu +Y29tMB4XDTE2MDUxNjAyNDY1NVoXDTI2MDUxNDAyNDY1NVowgZkxCzAJBgNVBAYT +AkNOMQ4wDAYDVQQIDAVTdGF0ZTELMAkGA1UEBwwCQ04xFTATBgNVBAoMDG9yZ2Fu +aXphdGlvbjEcMBoGA1UECwwTb3JnYW5pemF0aW9uYWwgdW5pdDEUMBIGA1UEAwwL +ZXhhbXBsZS5jb20xIjAgBgkqhkiG9w0BCQEWE2V4YW1wbGVAZXhhbXBsZS5jb20w +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2ky/K/XneJKbCbpOsWlQ7 +OwgYEQNsa044RkwSbTwPwgLafUZ3r9c5nkXE8APqAikTQQBwyiNjk7QeXgIOjJXd +7+IpwGoU6Bi2miA21qfvJPknyDAqw9tT/ycGQrvkY6rnqd++ri30ZUByUgO0du6+ +aWHo7af5/G1HQz0tu6i1tIF1dhSHNeqJKwxyUG8vIiT/PfbtU/mXSdQ07M+4ojBC +O7FgoOS+rWgbL3yhWUTrCXSV2HZlhksYBhtWGoFVRPVSf89iqL02h9rZEjmfVY6R +QlCnzu9v49Q8WFU528f+gDNXr9v13PKEDmloMzTqWPaCyD2FBbEKBsWHXHf1zqlI +jyGZV7rHZ3i0C1LI6bdDDP7M7aVs8O+RjxK+HmfFRg5us2t6g7zAevwwLpMZRAud +S39F91Up7l9g8WXpViok/8vcsOdePvvWcWro8qJhuEHAnDdMzj2Cko1L85/vRM/a +budWXK7Ix0TlPWPfHJc2SLFeqqcm5Iypf/cGabQ6f0oRt6bCfspFgX9upznT5FwZ +R0o1w6Q3q+4xVl6LgZvEAudWppyz79RACJA/jbXZQ7uJkXAxoI0nev9vgY6XJqUj +XIQDih2hmi/uTnNU7Me7w7pCYKPdHlNU652kaJSH6W6ZFGk2rEOCOeAuWO9pZTq2 +3IhuOcDAKOcmimlkzaWRGQIDAQABo1AwTjAdBgNVHQ4EFgQUPJF++WMsv1OJvf7F +oCew37JTnfQwHwYDVR0jBBgwFoAUPJF++WMsv1OJvf7FoCew37JTnfQwDAYDVR0T +BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAb5LvqukMxWd5Zajbh3orfYsXmhWn +UWiwG176+bd3b5xMlG9iLd4vQ11lTZoIhFOfprRQzbizQ8BzR2JBQckpLcy+5hyA +D3M9vLL37OwA0wT6kxFnd6LtlFaH5gG++huw2ts2PDXFz0jqw+0YE/R8ov2+YdaZ +aPSEMunmAuEY1TbYWzz4u6PxycxhQzDQ34ZmJZ34Elvw1NYMfPMGTKp34PsxIcgT +ao5jqb9RMU6JAumfXrOvXRjjl573vX2hgMZzEU6OF2/+uyg95chn6nO1GUQrT2+F +/1xIqfHfFCm8+jujSDgqfBtGI+2C7No+Dq8LEyEINZe6wSQ81+ryt5jy5SZmAsnj +V4OsSIwlpR5fLUwrFStVoUWHEKl1DflkYki/cAC1TL0Om+ldJ219kcOnaXDNaq66 +3I75BvRY7/88MYLl4Fgt7sn05Mn3uNPrCrci8d0R1tlXIcwMdCowIHeZdWHX43f7 +NsVk/7VSOxJ343csgaQc+3WxEFK0tBxGO6GP+Xj0XmdVGLhalVBsEhPjnmx+Yyrn +oMsTA1Yrs88C8ItQn7zuO/30eKNGTnby0gptHiS6sa/c3O083Mpi8y33GPVZDvBl +l9PfSZT8LG7SvpjsdgdNZlyFvTY4vsB+Vd5Howh7gXYPVXdCs4k7HMyo7zvzliZS +ekCw9NGLoNqQqnA= -----END CERTIFICATE----- diff --git a/Deploy/config/ui/private_key.pem b/Deploy/config/ui/private_key.pem index 6c68cacb3..d2dc85dd1 100644 --- a/Deploy/config/ui/private_key.pem +++ b/Deploy/config/ui/private_key.pem @@ -1,15 +1,51 @@ -----BEGIN RSA PRIVATE KEY----- -MIICXQIBAAKBgQClak/4HO7EeLU0w/BhtVENPLOqU0AP2QjVUdg1qhNiDWVrbWx9 -KYHqz5Kn0n2+fxdZo3o7ZY5/2+hhgkKh1z6Kge9XGgune6z4fx2J/X2Se8WsGeQU -TiND8ngSnsCANtYFwW50SbUZPtyf5XjAfKRofZem51OxbxzN3217L/ubKwIDAQAB -AoGBAITMMuNYJwAogCGaZHOs4yMjZoIJT9bpQMQxbsi2f9UqOA/ky0I4foqKloyQ -2k6DLbXTHqBsydgwLgGKWAAiE5xIR2bPMUNSLgjbA2eLly3aOR/0FJ5n09k2EmGg -Am7tLP+6yneXWKVi3HI3NzXriVjWK94WHGGC1b9F+n5CY/2RAkEA1d62OJUNve2k -IY6/b6T0BdssFo3VFcm22vnayEL/wcYrnRfF9Pb5wM4HUUqwVelKTouivXg60GNK -ZKYAx5CtHwJBAMYAEf5u0CQ/8URcwBuMkm0LzK4AM2x1nGs7gIxAEFhu1Z4xPjVe -MtIxuHhDhlLvD760uccmo5yE72QJ1ZrYBHUCQQCAxLZMPRpoB4QyHEOREe1G9V6H -OeBZXPk2wQcEWqqo3gt2a1DqHCXl+2aWgHTJVUxDHHngwFoRDCdHkFeZ0LcbAkAj -T8/luI2WaXD16DS6tQ9IM1qFjbOeHDuRRENgv+wqWVnvpIibq/kUU5m6mRBTqh78 -u+6F/fYf6/VluftGalAhAkAukdMtt+sksq2e7Qw2dRr5GXtXjt+Otjj0NaJENmWk -a7SgAs34EOWtbd0XGYpZFrg134MzQGbweFeEUTj++e8p +MIIJKAIBAAKCAgEAtpMvyv153iSmwm6TrFpUOzsIGBEDbGtOOEZMEm08D8IC2n1G +d6/XOZ5FxPAD6gIpE0EAcMojY5O0Hl4CDoyV3e/iKcBqFOgYtpogNtan7yT5J8gw +KsPbU/8nBkK75GOq56nfvq4t9GVAclIDtHbuvmlh6O2n+fxtR0M9LbuotbSBdXYU +hzXqiSsMclBvLyIk/z327VP5l0nUNOzPuKIwQjuxYKDkvq1oGy98oVlE6wl0ldh2 +ZYZLGAYbVhqBVUT1Un/PYqi9Nofa2RI5n1WOkUJQp87vb+PUPFhVOdvH/oAzV6/b +9dzyhA5paDM06lj2gsg9hQWxCgbFh1x39c6pSI8hmVe6x2d4tAtSyOm3Qwz+zO2l +bPDvkY8Svh5nxUYObrNreoO8wHr8MC6TGUQLnUt/RfdVKe5fYPFl6VYqJP/L3LDn +Xj771nFq6PKiYbhBwJw3TM49gpKNS/Of70TP2m7nVlyuyMdE5T1j3xyXNkixXqqn +JuSMqX/3Bmm0On9KEbemwn7KRYF/bqc50+RcGUdKNcOkN6vuMVZei4GbxALnVqac +s+/UQAiQP4212UO7iZFwMaCNJ3r/b4GOlyalI1yEA4odoZov7k5zVOzHu8O6QmCj +3R5TVOudpGiUh+lumRRpNqxDgjngLljvaWU6ttyIbjnAwCjnJoppZM2lkRkCAwEA +AQKCAgAvsvCPlf2a3fR7Y6xNISRUfS22K+u7DaXX6fXB8qv4afWY45Xfex89vG35 +78L2Bi55C0h0LztjrpkmPeVHq88TtrJduhl88M5UFpxH93jUb9JwZErBQX4xyb2G +UzUHjEqAT89W3+a9rR5TP74cDd59/MZJtp1mIF7keVqochi3sDsKVxkx4hIuWALe +csk5hTApRyUWCBRzRCSe1yfF0wnMpA/JcP+SGXfTcmqbNNlelo/Q/kaga59+3UmT +C0Wy41s8fIvP+MnGT2QLxkkrqYyfwrWTweqoTtuKEIHjpdnwUcoYJKfQ6jKp8aH0 +STyP5UIyFOKNuFjyh6ZfoPbuT1nGW+YKlUnK4hQ9N/GE0oMoecTaHTbqM+psQvbj +6+CG/1ukA5ZTQyogNyuOApArFBQ+RRmVudPKA3JYygIhwctuB2oItsVEOEZMELCn +g2aVFAVXGfGRDXvpa8oxs3Pc6RJEp/3tON6+w7cMCx0lwN/Jk2Ie6RgTzUycT3k6 +MoTQJRoO6/ZHcx3hTut/CfnrWiltyAUZOsefLuLg+Pwf9GHhOycLRI6gHfgSwdIV +S77UbbELWdscVr1EoPIasUm1uYWBBcFRTturRW+GHJ8TZX+mcWSBcWwBhp15LjEl +tJf+9U6lWMOSB2LvT+vFmR0M9q56fo7UeKFIR7mo7/GpiVu5AQKCAQEA6Qs7G9mw +N/JZOSeQO6xIQakC+sKApPyXO58fa7WQzri+l2UrLNp0DEQfZCujqDgwys6OOzR/ +xg8ZKQWVoad08Ind3ZwoJgnLn6QLENOcE6PpWxA/JjnVGP4JrXCYR98cP0sf9jEI +xkR1qT50GbeqU3RDFliI4kGRvbZ8cekzuWppfQcjstSBPdvuxqAcUVmTnTw83nvD +FmBbhlLiEgI3iKtJ97UB7480ivnWnOuusduk7FO4jF3hkrOa+YRidinTCi8JBo0Y +jx4Ci3Y5x6nvwkXhKzXapd7YmPNisUc5xA7/a+W71cyC0IKUwRc/8pYWLL3R3CpR +YiV8gf6gwzOckQKCAQEAyI9CSNoAQH4zpS8B9PF8zILqEEuun8m1f5JB3hQnfWzm +7uz/zg6I0TkcCE0AJVSKPHQm1V9+TRbF9+DiOWHEYYzPmK8h63SIufaWxZPqai4E +PUj6eQWykBUVJ96n6/AW0JHRZ+WrJ5RXBqCLuY7NP6wDhORrCJjBwaGMohNpbKPS +H3QewsoxCh+CEXKdKyy+/yU/f4E89PlHapkW1/bDJ5u7puSD+KvmiDDIXSBncdOO +uFT8n+XH5IwgjdXFSDim15rQ8jD2l2xLcwKboTpx5GeRl8oB1VGm0fUbBn1dvGPG +4WfHGyrp9VNZtP160WoHr+vRVPqvHNkoeAlCfEwQCQKCAQBN1dtzLN0HgqE8TrOE +ysEDdTCykj4nXNoiJr522hi4gsndhQPLolb6NdKKQW0S5Vmekyi8K4e1nhtYMS5N +5MFRCasZtmtOcR0af87WWucZRDjPmniNCunaxBZ1YFLsRl+H4E6Xir8UgY8O7PYY +FNkFsKIrl3x4nU/RHl8oKKyG9Dyxbq4Er6dPAuMYYiezIAkGjjUCVjHNindnQM2T +GDx2IEe/PSydV6ZD+LguhyU88FCAQmI0N7L8rZJIXmgIcWW0VAterceTHYHaFK2t +u1uB9pcDOKSDnA+Z3kiLT2/CxQOYhQ2clgbnH4YRi/Nm0awsW2X5dATklAKm5GXL +bLSRAoIBAQClaNnPQdTBXBR2IN3pSZ2XAkXPKMwdxvtk+phOc6raHA4eceLL7FrU +y9gd1HvRTfcwws8gXcDKDYU62gNaNhMELWEt2QsNqS/2x7Qzwbms1sTyUpUZaSSL +BohLOKyfv4ThgdIGcXoGi6Z2tcRnRqpq4BCK8uR/05TBgN5+8amaS0ZKYLfaCW4G +nlPk1fVgHWhtAChtnYZLuKg494fKmB7+NMfAbmmVlxjrq+gkPkxyqXvk9Vrg+V8y +VIuozu0Fkouv+GRpyw4ldtCHS1hV0eEK8ow2dwmqCMygDxm58X10mYn2b2PcOTl5 +9sNerUw1GNC8O66K+rGgBk4FKgXmg8kZAoIBABBcuisK250fXAfjAWXGqIMs2+Di +vqAdT041SNZEOJSGNFsLJbhd/3TtCLf29PN/YXtnvBmC37rqryTsqjSbx/YT2Jbr +Bk3jOr9JVbmcoSubXl8d/uzf7IGs91qaCgBwPZHgeH+kK13FCLexz+U9zYMZ78fF +/yO82CpoekT+rcl1jzYn43b6gIklHABQU1uCD6MMyMhJ9Op2WmbDk3X+py359jMc ++Cr2zfzdHAIVff2dOV3OL+ZHEWbwtnn3htKUdOmjoTJrciFx0xNZJS5Q7QYHMONj +yPqbajyhopiN01aBQpCSGF1F1uRpWeIjTrAZPbrwLl9YSYXz0AT05QeFEFk= -----END RSA PRIVATE KEY----- diff --git a/Deploy/db/registry.sql b/Deploy/db/registry.sql index 870d2d032..e5f13f7d4 100644 --- a/Deploy/db/registry.sql +++ b/Deploy/db/registry.sql @@ -93,8 +93,8 @@ create table access_log ( log_id int NOT NULL AUTO_INCREMENT, user_id int NOT NULL, project_id int NOT NULL, - repo_name varchar (40), - repo_tag varchar (20), + repo_name varchar (256), + repo_tag varchar (128), GUID varchar(64), operation varchar(20) NOT NULL, op_time timestamp, @@ -103,17 +103,58 @@ create table access_log ( FOREIGN KEY (project_id) REFERENCES project (project_id) ); +create table replication_policy ( + id int NOT NULL AUTO_INCREMENT, + name varchar(256), + project_id int NOT NULL, + target_id int NOT NULL, + enabled tinyint(1) NOT NULL DEFAULT 1, + description text, + cron_str varchar(256), + start_time timestamp NULL, + creation_time timestamp default CURRENT_TIMESTAMP, + update_time timestamp default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, + PRIMARY KEY (id) + ); + +create table replication_target ( + id int NOT NULL AUTO_INCREMENT, + name varchar(64), + url varchar(64), + username varchar(40), + password varchar(40), + /* + target_type indicates the type of target registry, + 0 means it's a harbor instance, + 1 means it's a regulart registry + */ + target_type tinyint(1) NOT NULL DEFAULT 0, + creation_time timestamp default CURRENT_TIMESTAMP, + update_time timestamp default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, + PRIMARY KEY (id) + ); + +create table replication_job ( + id int NOT NULL AUTO_INCREMENT, + status varchar(64) NOT NULL, + policy_id int NOT NULL, + repository varchar(256) NOT NULL, + operation varchar(64) NOT NULL, + tags varchar(16384), + creation_time timestamp default CURRENT_TIMESTAMP, + update_time timestamp default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP, + PRIMARY KEY (id), + INDEX policy (policy_id) + ); + create table properties ( k varchar(64) NOT NULL, v varchar(128) NOT NULL, primary key (k) ); -insert into properties (k, v) values -('schema_version', '0.1.1'); - CREATE TABLE IF NOT EXISTS `alembic_version` ( `version_num` varchar(32) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -insert into alembic_version values ('0.1.1'); +insert into alembic_version values ('0.3.0'); diff --git a/Deploy/docker-compose.yml b/Deploy/docker-compose.yml index 2dc09b620..e6308669f 100644 --- a/Deploy/docker-compose.yml +++ b/Deploy/docker-compose.yml @@ -46,6 +46,8 @@ services: volumes: - ./config/ui/app.conf:/etc/ui/app.conf - ./config/ui/private_key.pem:/etc/ui/private_key.pem + - ../static:/go/bin/static + - ../views:/go/bin/views depends_on: - log logging: @@ -53,6 +55,22 @@ services: options: syslog-address: "tcp://127.0.0.1:1514" syslog-tag: "ui" + jobservice: + build: + context: ../ + dockerfile: Dockerfile.job + env_file: + - ./config/jobservice/env + volumes: + - /data/job_logs:/var/log/jobs + - ./config/jobservice/app.conf:/etc/jobservice/app.conf + depends_on: + - ui + logging: + driver: "syslog" + options: + syslog-address: "tcp://127.0.0.1:1514" + syslog-tag: "jobservice" proxy: image: library/nginx:1.9 volumes: diff --git a/Deploy/harbor.cfg b/Deploy/harbor.cfg index bd949cea5..e1acfa6aa 100644 --- a/Deploy/harbor.cfg +++ b/Deploy/harbor.cfg @@ -2,7 +2,7 @@ #The IP address or hostname to access admin UI and registry service. #DO NOT use localhost or 127.0.0.1, because Harbor needs to be accessed by external clients. -hostname = reg.mydomain.com +hostname = reg.mydomain.org #The protocol for accessing the UI and token/notification service, by default it is http. #It can be set to https if ssl is enabled on nginx. @@ -35,7 +35,16 @@ db_password = root123 #Turn on or off the self-registration feature self_registration = on -#Turn on or off the customize your certicate +#Number of job workers in job service, default is 3 +max_job_workers = 3 + +#Toggle on and off to tell job service wheter or not verify the ssl cert +#when it tries to access a remote registry +verify_remote_cert = on + +#Turn on or off the customize your certificate for registry's token. +#If the value is on, the prepare script will generate new root cert and private key +#for generating token to access the image in registry. customize_crt = on #fill in your certicate message diff --git a/Deploy/prepare b/Deploy/prepare index edd31ea95..5a301ce61 100755 --- a/Deploy/prepare +++ b/Deploy/prepare @@ -2,6 +2,8 @@ # -*- coding: utf-8 -*- from __future__ import print_function, unicode_literals # We require Python 2.6 or later from string import Template +import random +import string import os import sys from io import open @@ -44,13 +46,16 @@ 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") +verify_remote_cert = rcp.get("configuration", "verify_remote_cert") ######## +ui_secret = ''.join(random.choice(string.ascii_letters+string.digits) for i in range(16)) + base_dir = os.path.dirname(__file__) config_dir = os.path.join(base_dir, "config") templates_dir = os.path.join(base_dir, "templates") - ui_config_dir = os.path.join(config_dir,"ui") if not os.path.exists(ui_config_dir): os.makedirs(os.path.join(config_dir, "ui")) @@ -59,6 +64,10 @@ db_config_dir = os.path.join(config_dir, "db") if not os.path.exists(db_config_dir): os.makedirs(os.path.join(config_dir, "db")) +job_config_dir = os.path.join(config_dir, "jobservice") +if not os.path.exists(job_config_dir): + os.makedirs(job_config_dir) + def render(src, dest, **kw): t = Template(open(src, 'r').read()) with open(dest, 'w') as f: @@ -69,8 +78,9 @@ ui_conf_env = os.path.join(config_dir, "ui", "env") ui_conf = os.path.join(config_dir, "ui", "app.conf") registry_conf = os.path.join(config_dir, "registry", "config.yml") db_conf_env = os.path.join(config_dir, "db", "env") +job_conf_env = os.path.join(config_dir, "jobservice", "env") -conf_files = [ ui_conf, ui_conf_env, registry_conf, db_conf_env ] +conf_files = [ ui_conf, ui_conf_env, registry_conf, db_conf_env, job_conf_env ] def rmdir(cf): for f in cf: if os.path.exists(f): @@ -87,7 +97,9 @@ render(os.path.join(templates_dir, "ui", "env"), harbor_admin_password=harbor_admin_password, ldap_url=ldap_url, ldap_basedn=ldap_basedn, - self_registration=self_registration) + self_registration=self_registration, + ui_secret=ui_secret, + verify_remote_cert=verify_remote_cert) render(os.path.join(templates_dir, "ui", "app.conf"), ui_conf, @@ -107,6 +119,14 @@ render(os.path.join(templates_dir, "db", "env"), db_conf_env, db_password=db_password) +render(os.path.join(templates_dir, "jobservice", "env"), + job_conf_env, + db_password=db_password, + ui_secret=ui_secret, + max_job_workers=max_job_workers, + ui_url=ui_url, + verify_remote_cert=verify_remote_cert) + def validate_crt_subj(dirty_subj): subj_list = [item for item in dirty_subj.strip().split("/") \ if len(item.split("=")) == 2 and len(item.split("=")[1]) > 0] diff --git a/Deploy/templates/jobservice/env b/Deploy/templates/jobservice/env new file mode 100644 index 000000000..5359e8512 --- /dev/null +++ b/Deploy/templates/jobservice/env @@ -0,0 +1,14 @@ +MYSQL_HOST=mysql +MYSQL_PORT=3306 +MYSQL_USR=root +MYSQL_PWD=$db_password +UI_SECRET=$ui_secret +CONFIG_PATH=/etc/jobservice/app.conf +REGISTRY_URL=http://registry:5000 +VERIFY_REMOTE_CERT=$verify_remote_cert +MAX_JOB_WORKERS=$max_job_workers +LOG_LEVEL=debug +LOG_DIR=/var/log/jobs +GODEBUG=netdns=cgo +EXT_ENDPOINT=$ui_url +TOKEN_URL=http://ui diff --git a/Deploy/templates/ui/app.conf b/Deploy/templates/ui/app.conf index f75b673c8..70dddbc89 100644 --- a/Deploy/templates/ui/app.conf +++ b/Deploy/templates/ui/app.conf @@ -2,8 +2,8 @@ appname = registry runmode = dev [lang] -types = en-US|zh-CN|de-DE|ru-RU|ja-JP -names = en-US|zh-CN|de-DE|ru-RU|ja-JP +types = en-US|zh-CN +names = en-US|zh-CN [dev] httpport = 80 diff --git a/Deploy/templates/ui/env b/Deploy/templates/ui/env index d21e8fafc..a77452f41 100644 --- a/Deploy/templates/ui/env +++ b/Deploy/templates/ui/env @@ -3,13 +3,18 @@ MYSQL_PORT=3306 MYSQL_USR=root MYSQL_PWD=$db_password REGISTRY_URL=http://registry:5000 +UI_URL=http://ui CONFIG_PATH=/etc/ui/app.conf HARBOR_REG_URL=$hostname HARBOR_ADMIN_PASSWORD=$harbor_admin_password -HARBOR_URL=$hostname +HARBOR_URL=$ui_url AUTH_MODE=$auth_mode LDAP_URL=$ldap_url LDAP_BASE_DN=$ldap_basedn +UI_SECRET=$ui_secret SELF_REGISTRATION=$self_registration LOG_LEVEL=debug GODEBUG=netdns=cgo +EXT_ENDPOINT=$ui_url +TOKEN_URL=http://ui +VERIFY_REMOTE_CERT=$verify_remote_cert diff --git a/Dockerfile.job b/Dockerfile.job new file mode 100644 index 000000000..5f80a04e2 --- /dev/null +++ b/Dockerfile.job @@ -0,0 +1,18 @@ +FROM golang:1.6.2 + +MAINTAINER jiangd@vmware.com + +RUN apt-get update \ + && apt-get install -y libldap2-dev \ + && rm -r /var/lib/apt/lists/* +COPY . /go/src/github.com/vmware/harbor + +WORKDIR /go/src/github.com/vmware/harbor/jobservice + +RUN go get -d github.com/docker/distribution \ + && go get -d github.com/docker/libtrust \ + && go get -d github.com/go-sql-driver/mysql \ + && go build -v -a -o /go/bin/harbor_jobservice \ + && chmod u+x /go/bin/harbor_jobservice +WORKDIR /go/bin/ +ENTRYPOINT ["/go/bin/harbor_jobservice"] diff --git a/api/base.go b/api/base.go index f2529b61e..72f9da50b 100644 --- a/api/base.go +++ b/api/base.go @@ -17,8 +17,12 @@ package api import ( "encoding/json" + "fmt" "net/http" + "os" + "strconv" + "github.com/astaxie/beego/validation" "github.com/vmware/harbor/auth" "github.com/vmware/harbor/dao" "github.com/vmware/harbor/models" @@ -51,6 +55,30 @@ func (b *BaseAPI) DecodeJSONReq(v interface{}) { } } +// Validate validates v if it implements interface validation.ValidFormer +func (b *BaseAPI) Validate(v interface{}) { + validator := validation.Validation{} + isValid, err := validator.Valid(v) + if err != nil { + log.Errorf("failed to validate: %v", err) + b.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + if !isValid { + message := "" + for _, e := range validator.Errors { + message += fmt.Sprintf("%s %s \n", e.Field, e.Message) + } + b.CustomAbort(http.StatusBadRequest, message) + } +} + +// DecodeJSONReqAndValidate does both decoding and validation +func (b *BaseAPI) DecodeJSONReqAndValidate(v interface{}) { + b.DecodeJSONReq(v) + b.Validate(v) +} + // ValidateUser checks if the request triggered by a valid user func (b *BaseAPI) ValidateUser() int { @@ -94,3 +122,29 @@ func (b *BaseAPI) Redirect(statusCode int, resouceID string) { b.Ctx.Redirect(statusCode, resoucreURI) } + +// GetIDFromURL checks the ID in request URL +func (b *BaseAPI) GetIDFromURL() int64 { + idStr := b.Ctx.Input.Param(":id") + if len(idStr) == 0 { + b.CustomAbort(http.StatusBadRequest, "invalid ID in URL") + } + + id, err := strconv.ParseInt(idStr, 10, 64) + if err != nil || id <= 0 { + b.CustomAbort(http.StatusBadRequest, "invalid ID in URL") + } + + return id +} + +func getIsInsecure() bool { + insecure := false + + verifyRemoteCert := os.Getenv("VERIFY_REMOTE_CERT") + if verifyRemoteCert == "off" { + insecure = true + } + + return insecure +} diff --git a/api/jobs/replication.go b/api/jobs/replication.go new file mode 100644 index 000000000..0dee3765b --- /dev/null +++ b/api/jobs/replication.go @@ -0,0 +1,199 @@ +/* + 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. +*/ + +package api + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/http/httputil" + "strconv" + + "github.com/vmware/harbor/api" + "github.com/vmware/harbor/dao" + "github.com/vmware/harbor/job" + "github.com/vmware/harbor/job/config" + "github.com/vmware/harbor/job/utils" + "github.com/vmware/harbor/models" + "github.com/vmware/harbor/utils/log" +) + +// ReplicationJob handles /api/replicationJobs /api/replicationJobs/:id/log +// /api/replicationJobs/actions +type ReplicationJob struct { + api.BaseAPI +} + +// ReplicationReq holds informations of request for /api/replicationJobs +type ReplicationReq struct { + PolicyID int64 `json:"policy_id"` + Repo string `json:"repository"` + Operation string `json:"operation"` + TagList []string `json:"tags"` +} + +// Post creates replication jobs according to the policy. +func (rj *ReplicationJob) Post() { + var data ReplicationReq + rj.DecodeJSONReq(&data) + log.Debugf("data: %+v", data) + p, err := dao.GetRepPolicy(data.PolicyID) + if err != nil { + log.Errorf("Failed to get policy, error: %v", err) + rj.RenderError(http.StatusInternalServerError, fmt.Sprintf("Failed to get policy, id: %d", data.PolicyID)) + return + } + if p == nil { + log.Errorf("Policy not found, id: %d", data.PolicyID) + rj.RenderError(http.StatusNotFound, fmt.Sprintf("Policy not found, id: %d", data.PolicyID)) + return + } + if len(data.Repo) == 0 { // sync all repositories + repoList, err := getRepoList(p.ProjectID) + if err != nil { + log.Errorf("Failed to get repository list, project id: %d, error: %v", p.ProjectID, err) + rj.RenderError(http.StatusInternalServerError, err.Error()) + return + } + log.Debugf("repo list: %v", repoList) + for _, repo := range repoList { + err := rj.addJob(repo, data.PolicyID, models.RepOpTransfer) + if err != nil { + log.Errorf("Failed to insert job record, error: %v", err) + rj.RenderError(http.StatusInternalServerError, err.Error()) + return + } + } + } else { // sync a single repository + var op string + if len(data.Operation) > 0 { + op = data.Operation + } else { + op = models.RepOpTransfer + } + err := rj.addJob(data.Repo, data.PolicyID, op, data.TagList...) + if err != nil { + log.Errorf("Failed to insert job record, error: %v", err) + rj.RenderError(http.StatusInternalServerError, err.Error()) + return + } + } +} + +func (rj *ReplicationJob) addJob(repo string, policyID int64, operation string, tags ...string) error { + j := models.RepJob{ + Repository: repo, + PolicyID: policyID, + Operation: operation, + TagList: tags, + } + log.Debugf("Creating job for repo: %s, policy: %d", repo, policyID) + id, err := dao.AddRepJob(j) + if err != nil { + return err + } + log.Debugf("Send job to scheduler, job id: %d", id) + job.Schedule(id) + return nil +} + +// RepActionReq holds informations of request for /api/replicationJobs/actions +type RepActionReq struct { + PolicyID int64 `json:"policy_id"` + Action string `json:"action"` +} + +// HandleAction supports some operations to all the jobs of one policy +func (rj *ReplicationJob) HandleAction() { + var data RepActionReq + rj.DecodeJSONReq(&data) + //Currently only support stop action + if data.Action != "stop" { + log.Errorf("Unrecognized action: %s", data.Action) + rj.RenderError(http.StatusBadRequest, fmt.Sprintf("Unrecongized action: %s", data.Action)) + return + } + jobs, err := dao.GetRepJobToStop(data.PolicyID) + if err != nil { + log.Errorf("Failed to get jobs to stop, error: %v", err) + rj.RenderError(http.StatusInternalServerError, "Faild to get jobs to stop") + return + } + var jobIDList []int64 + for _, j := range jobs { + jobIDList = append(jobIDList, j.ID) + } + job.WorkerPool.StopJobs(jobIDList) +} + +// GetLog gets logs of the job +func (rj *ReplicationJob) GetLog() { + idStr := rj.Ctx.Input.Param(":id") + jid, err := strconv.ParseInt(idStr, 10, 64) + if err != nil { + log.Errorf("Error parsing job id: %s, error: %v", idStr, err) + rj.RenderError(http.StatusBadRequest, "Invalid job id") + return + } + logFile := utils.GetJobLogPath(jid) + rj.Ctx.Output.Download(logFile) +} + +// calls the api from UI to get repo list +func getRepoList(projectID int64) ([]string, error) { + /* + uiUser := os.Getenv("UI_USR") + if len(uiUser) == 0 { + uiUser = "admin" + } + uiPwd := os.Getenv("UI_PWD") + if len(uiPwd) == 0 { + uiPwd = "Harbor12345" + } + */ + uiURL := config.LocalUIURL() + client := &http.Client{} + req, err := http.NewRequest("GET", uiURL+"/api/repositories?project_id="+strconv.Itoa(int(projectID)), nil) + if err != nil { + log.Errorf("Error when creating request: %v", err) + return nil, err + } + //req.SetBasicAuth(uiUser, uiPwd) + req.AddCookie(&http.Cookie{Name: models.UISecretCookie, Value: config.UISecret()}) + //dump, err := httputil.DumpRequest(req, true) + //log.Debugf("req: %q", dump) + resp, err := client.Do(req) + if err != nil { + log.Errorf("Error when calling UI api to get repositories, error: %v", err) + return nil, err + } + if resp.StatusCode != http.StatusOK { + log.Errorf("Unexpected status code: %d", resp.StatusCode) + dump, _ := httputil.DumpResponse(resp, true) + log.Debugf("response: %q", dump) + return nil, fmt.Errorf("Unexpected status code when getting repository list: %d", resp.StatusCode) + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Errorf("Failed to read the response body, error: %v", err) + return nil, err + } + var repoList []string + err = json.Unmarshal(body, &repoList) + return repoList, err +} diff --git a/api/log.go b/api/log.go new file mode 100644 index 000000000..bc8b05f74 --- /dev/null +++ b/api/log.go @@ -0,0 +1,86 @@ +/* + 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. +*/ + +package api + +import ( + "net/http" + "strconv" + "time" + + "github.com/vmware/harbor/dao" + "github.com/vmware/harbor/models" + "github.com/vmware/harbor/utils/log" +) + +//LogAPI handles request api/logs +type LogAPI struct { + BaseAPI + userID int +} + +//Prepare validates the URL and the user +func (l *LogAPI) Prepare() { + l.userID = l.ValidateUser() +} + +//Get returns the recent logs according to parameters +func (l *LogAPI) Get() { + var err error + startTime := l.GetString("start_time") + if len(startTime) != 0 { + i, err := strconv.ParseInt(startTime, 10, 64) + if err != nil { + log.Errorf("Parse startTime to int error, err: %v", err) + l.CustomAbort(http.StatusBadRequest, "startTime is not a valid integer") + } + startTime = time.Unix(i, 0).String() + } + + endTime := l.GetString("end_time") + if len(endTime) != 0 { + j, err := strconv.ParseInt(endTime, 10, 64) + if err != nil { + log.Errorf("Parse endTime to int error, err: %v", err) + l.CustomAbort(http.StatusBadRequest, "endTime is not a valid integer") + } + endTime = time.Unix(j, 0).String() + } + + var linesNum int + lines := l.GetString("lines") + if len(lines) != 0 { + linesNum, err = strconv.Atoi(lines) + if err != nil { + log.Errorf("Get parameters error--lines, err: %v", err) + l.CustomAbort(http.StatusBadRequest, "bad request of lines") + } + if linesNum <= 0 { + log.Warning("lines must be a positive integer") + l.CustomAbort(http.StatusBadRequest, "lines is 0 or negative") + } + } else if len(startTime) == 0 && len(endTime) == 0 { + linesNum = 10 + } + + var logList []models.AccessLog + logList, err = dao.GetRecentLogs(l.userID, linesNum, startTime, endTime) + if err != nil { + log.Errorf("Get recent logs error, err: %v", err) + l.CustomAbort(http.StatusInternalServerError, "Internal error") + } + l.Data["json"] = logList + l.ServeJSON() +} diff --git a/api/project.go b/api/project.go index 5d2a3cf1f..fdbce7591 100644 --- a/api/project.go +++ b/api/project.go @@ -18,6 +18,7 @@ package api import ( "fmt" "net/http" + "regexp" "github.com/vmware/harbor/dao" "github.com/vmware/harbor/models" @@ -40,6 +41,7 @@ type projectReq struct { } const projectNameMaxLen int = 30 +const projectNameMinLen int = 4 // Prepare validates the URL and the user func (p *ProjectAPI) Prepare() { @@ -75,7 +77,7 @@ func (p *ProjectAPI) Post() { err := validateProjectReq(req) if err != nil { log.Errorf("Invalid project request, error: %v", err) - p.RenderError(http.StatusBadRequest, "Invalid request for creating project") + p.RenderError(http.StatusBadRequest, fmt.Sprintf("invalid request: %v", err)) return } projectName := req.ProjectName @@ -157,7 +159,7 @@ func (p *ProjectAPI) List() { if len(isPublic) > 0 { public, err = strconv.Atoi(isPublic) if err != nil { - log.Errorf("Error parsing public property: %d, error: %v", isPublic, err) + log.Errorf("Error parsing public property: %v, error: %v", isPublic, err) p.CustomAbort(http.StatusBadRequest, "invalid project Id") } } @@ -197,10 +199,9 @@ func (p *ProjectAPI) List() { p.ServeJSON() } -// Put ... -func (p *ProjectAPI) Put() { +// ToggleProjectPublic ... +func (p *ProjectAPI) ToggleProjectPublic() { p.userID = p.ValidateUser() - var req projectReq var public int @@ -284,11 +285,13 @@ func isProjectAdmin(userID int, pid int64) bool { func validateProjectReq(req projectReq) error { pn := req.ProjectName - if len(pn) == 0 { - return fmt.Errorf("Project name can not be empty") + if isIllegalLength(req.ProjectName, projectNameMinLen, projectNameMaxLen) { + return fmt.Errorf("Project name is illegal in length. (greater than 4 or less than 30)") } - if len(pn) > projectNameMaxLen { - return fmt.Errorf("Project name is too long") + validProjectName := regexp.MustCompile(`^[a-z0-9](?:-*[a-z0-9])*(?:[._][a-z0-9](?:-*[a-z0-9])*)*$`) + legal := validProjectName.MatchString(pn) + if !legal { + return fmt.Errorf("Project name is not in lower case or contains illegal characters!") } return nil } diff --git a/api/replication_job.go b/api/replication_job.go new file mode 100644 index 000000000..93ce92183 --- /dev/null +++ b/api/replication_job.go @@ -0,0 +1,180 @@ +/* + 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. +*/ + +package api + +import ( + "fmt" + "io" + "io/ioutil" + "net/http" + "strconv" + "time" + + "github.com/vmware/harbor/dao" + "github.com/vmware/harbor/models" + "github.com/vmware/harbor/utils/log" +) + +// RepJobAPI handles request to /api/replicationJobs /api/replicationJobs/:id/log +type RepJobAPI struct { + BaseAPI + jobID int64 +} + +// Prepare validates that whether user has system admin role +func (ra *RepJobAPI) Prepare() { + uid := ra.ValidateUser() + isAdmin, err := dao.IsAdminRole(uid) + if err != nil { + log.Errorf("Failed to Check if the user is admin, error: %v, uid: %d", err, uid) + } + if !isAdmin { + ra.CustomAbort(http.StatusForbidden, "") + } + + idStr := ra.Ctx.Input.Param(":id") + if len(idStr) != 0 { + id, err := strconv.ParseInt(idStr, 10, 64) + if err != nil { + ra.CustomAbort(http.StatusBadRequest, "ID is invalid") + } + ra.jobID = id + } + +} + +// List filters jobs according to the policy and repository +func (ra *RepJobAPI) List() { + var policyID int64 + var repository, status string + var startTime, endTime *time.Time + var num int + var err error + + policyIDStr := ra.GetString("policy_id") + if len(policyIDStr) != 0 { + policyID, err = strconv.ParseInt(policyIDStr, 10, 64) + if err != nil || policyID <= 0 { + ra.CustomAbort(http.StatusBadRequest, fmt.Sprintf("invalid policy ID: %s", policyIDStr)) + } + } + + numStr := ra.GetString("num") + if len(numStr) != 0 { + num, err = strconv.Atoi(numStr) + if err != nil { + ra.CustomAbort(http.StatusBadRequest, fmt.Sprintf("invalid num: %s", numStr)) + } + } + if num <= 0 { + num = 200 + } + + endTimeStr := ra.GetString("end_time") + if len(endTimeStr) != 0 { + i, err := strconv.ParseInt(endTimeStr, 10, 64) + if err != nil { + ra.CustomAbort(http.StatusBadRequest, "invalid end_time") + } + t := time.Unix(i, 0) + endTime = &t + } + + startTimeStr := ra.GetString("start_time") + if len(startTimeStr) != 0 { + i, err := strconv.ParseInt(startTimeStr, 10, 64) + if err != nil { + ra.CustomAbort(http.StatusBadRequest, "invalid start_time") + } + t := time.Unix(i, 0) + startTime = &t + } + + if startTime == nil && endTime == nil { + // if start_time and end_time are both null, list jobs of last 10 days + t := time.Now().UTC().AddDate(0, 0, -10) + startTime = &t + } + + repository = ra.GetString("repository") + status = ra.GetString("status") + + jobs, err := dao.FilterRepJobs(policyID, repository, status, startTime, endTime, num) + if err != nil { + log.Errorf("failed to filter jobs according policy ID %d, repository %s, status %s: %v", policyID, repository, status, err) + ra.RenderError(http.StatusInternalServerError, "Failed to query job") + return + } + ra.Data["json"] = jobs + ra.ServeJSON() +} + +// Delete ... +func (ra *RepJobAPI) Delete() { + if ra.jobID == 0 { + ra.CustomAbort(http.StatusBadRequest, "id is nil") + } + + job, err := dao.GetRepJob(ra.jobID) + if err != nil { + log.Errorf("failed to get job %d: %v", ra.jobID, err) + ra.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + if job.Status == models.JobPending || job.Status == models.JobRunning { + ra.CustomAbort(http.StatusBadRequest, fmt.Sprintf("job is %s, can not be deleted", job.Status)) + } + + if err = dao.DeleteRepJob(ra.jobID); err != nil { + log.Errorf("failed to deleted job %d: %v", ra.jobID, err) + ra.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } +} + +// GetLog ... +func (ra *RepJobAPI) GetLog() { + if ra.jobID == 0 { + ra.CustomAbort(http.StatusBadRequest, "id is nil") + } + + resp, err := http.Get(buildJobLogURL(strconv.FormatInt(ra.jobID, 10))) + if err != nil { + log.Errorf("failed to get log for job %d: %v", ra.jobID, err) + ra.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + if resp.StatusCode == http.StatusOK { + ra.Ctx.ResponseWriter.Header().Set(http.CanonicalHeaderKey("Content-Length"), resp.Header.Get(http.CanonicalHeaderKey("Content-Length"))) + ra.Ctx.ResponseWriter.Header().Set(http.CanonicalHeaderKey("Content-Type"), "text/plain") + + if _, err = io.Copy(ra.Ctx.ResponseWriter, resp.Body); err != nil { + log.Errorf("failed to write log to response; %v", err) + ra.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + return + } + + defer resp.Body.Close() + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Errorf("failed to read reponse body: %v", err) + ra.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + ra.CustomAbort(resp.StatusCode, string(b)) +} + +//TODO:add Post handler to call job service API to submit jobs by policy diff --git a/api/replication_policy.go b/api/replication_policy.go new file mode 100644 index 000000000..be24aa0e9 --- /dev/null +++ b/api/replication_policy.go @@ -0,0 +1,351 @@ +/* + 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. +*/ + +package api + +import ( + "fmt" + + "net/http" + "strconv" + + "github.com/vmware/harbor/dao" + "github.com/vmware/harbor/models" + "github.com/vmware/harbor/utils/log" +) + +// RepPolicyAPI handles /api/replicationPolicies /api/replicationPolicies/:id/enablement +type RepPolicyAPI struct { + BaseAPI +} + +// Prepare validates whether the user has system admin role +func (pa *RepPolicyAPI) Prepare() { + uid := pa.ValidateUser() + var err error + isAdmin, err := dao.IsAdminRole(uid) + if err != nil { + log.Errorf("Failed to Check if the user is admin, error: %v, uid: %d", err, uid) + } + if !isAdmin { + pa.CustomAbort(http.StatusForbidden, "") + } +} + +// Get ... +func (pa *RepPolicyAPI) Get() { + id := pa.GetIDFromURL() + policy, err := dao.GetRepPolicy(id) + if err != nil { + log.Errorf("failed to get policy %d: %v", id, err) + pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + if policy == nil { + pa.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound)) + } + + pa.Data["json"] = policy + pa.ServeJSON() +} + +// List filters policies by name and project_id, if name and project_id +// are nil, List returns all policies +func (pa *RepPolicyAPI) List() { + name := pa.GetString("name") + projectIDStr := pa.GetString("project_id") + + var projectID int64 + var err error + + if len(projectIDStr) != 0 { + projectID, err = strconv.ParseInt(projectIDStr, 10, 64) + if err != nil || projectID <= 0 { + pa.CustomAbort(http.StatusBadRequest, "invalid project ID") + } + } + + policies, err := dao.FilterRepPolicies(name, projectID) + if err != nil { + log.Errorf("failed to filter policies %s project ID %d: %v", name, projectID, err) + pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + pa.Data["json"] = policies + pa.ServeJSON() +} + +// Post creates a policy, and if it is enbled, the replication will be triggered right now. +func (pa *RepPolicyAPI) Post() { + policy := &models.RepPolicy{} + pa.DecodeJSONReqAndValidate(policy) + + po, err := dao.GetRepPolicyByName(policy.Name) + if err != nil { + log.Errorf("failed to get policy %s: %v", policy.Name, err) + pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + if po != nil { + pa.CustomAbort(http.StatusConflict, "name is already used") + } + + project, err := dao.GetProjectByID(policy.ProjectID) + if err != nil { + log.Errorf("failed to get project %d: %v", policy.ProjectID, err) + pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + if project == nil { + pa.CustomAbort(http.StatusBadRequest, fmt.Sprintf("project %d does not exist", policy.ProjectID)) + } + + target, err := dao.GetRepTarget(policy.TargetID) + if err != nil { + log.Errorf("failed to get target %d: %v", policy.TargetID, err) + pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + if target == nil { + pa.CustomAbort(http.StatusBadRequest, fmt.Sprintf("target %d does not exist", policy.TargetID)) + } + + policies, err := dao.GetRepPolicyByProjectAndTarget(policy.ProjectID, policy.TargetID) + if err != nil { + log.Errorf("failed to get policy [project ID: %d,targetID: %d]: %v", policy.ProjectID, policy.TargetID, err) + pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + if len(policies) > 0 { + pa.CustomAbort(http.StatusConflict, "policy already exists with the same project and target") + } + + pid, err := dao.AddRepPolicy(*policy) + if err != nil { + log.Errorf("Failed to add policy to DB, error: %v", err) + pa.RenderError(http.StatusInternalServerError, "Internal Error") + return + } + + if policy.Enabled == 1 { + go func() { + if err := TriggerReplication(pid, "", nil, models.RepOpTransfer); err != nil { + log.Errorf("failed to trigger replication of %d: %v", pid, err) + } else { + log.Infof("replication of %d triggered", pid) + } + }() + } + + pa.Redirect(http.StatusCreated, strconv.FormatInt(pid, 10)) +} + +// Put modifies name, description, target and enablement of policy +func (pa *RepPolicyAPI) Put() { + id := pa.GetIDFromURL() + originalPolicy, err := dao.GetRepPolicy(id) + if err != nil { + log.Errorf("failed to get policy %d: %v", id, err) + pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + if originalPolicy == nil { + pa.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound)) + } + + policy := &models.RepPolicy{} + pa.DecodeJSONReq(policy) + policy.ProjectID = originalPolicy.ProjectID + pa.Validate(policy) + + // check duplicate name + if policy.Name != originalPolicy.Name { + po, err := dao.GetRepPolicyByName(policy.Name) + if err != nil { + log.Errorf("failed to get policy %s: %v", policy.Name, err) + pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + if po != nil { + pa.CustomAbort(http.StatusConflict, "name is already used") + } + } + + if policy.TargetID != originalPolicy.TargetID { + //target of policy can not be modified when the policy is enabled + if originalPolicy.Enabled == 1 { + pa.CustomAbort(http.StatusBadRequest, "target of policy can not be modified when the policy is enabled") + } + + // check the existance of target + target, err := dao.GetRepTarget(policy.TargetID) + if err != nil { + log.Errorf("failed to get target %d: %v", policy.TargetID, err) + pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + if target == nil { + pa.CustomAbort(http.StatusBadRequest, fmt.Sprintf("target %d does not exist", policy.TargetID)) + } + + // check duplicate policy with the same project and target + policies, err := dao.GetRepPolicyByProjectAndTarget(policy.ProjectID, policy.TargetID) + if err != nil { + log.Errorf("failed to get policy [project ID: %d,targetID: %d]: %v", policy.ProjectID, policy.TargetID, err) + pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + if len(policies) > 0 { + pa.CustomAbort(http.StatusConflict, "policy already exists with the same project and target") + } + } + + policy.ID = id + + /* + isTargetChanged := !(policy.TargetID == originalPolicy.TargetID) + isEnablementChanged := !(policy.Enabled == policy.Enabled) + + var shouldStop, shouldTrigger bool + + // if target and enablement are not changed, do nothing + if !isTargetChanged && !isEnablementChanged { + shouldStop = false + shouldTrigger = false + } else if !isTargetChanged && isEnablementChanged { + // target is not changed, but enablement is changed + if policy.Enabled == 0 { + shouldStop = true + shouldTrigger = false + } else { + shouldStop = false + shouldTrigger = true + } + } else if isTargetChanged && !isEnablementChanged { + // target is changed, but enablement is not changed + if policy.Enabled == 0 { + // enablement is 0, do nothing + shouldStop = false + shouldTrigger = false + } else { + // enablement is 1, so stop original target's jobs + // and trigger new target's jobs + shouldStop = true + shouldTrigger = true + } + } else { + // both target and enablement are changed + + // enablement: 1 -> 0 + if policy.Enabled == 0 { + shouldStop = true + shouldTrigger = false + } else { + shouldStop = false + shouldTrigger = true + } + } + + if shouldStop { + if err := postReplicationAction(id, "stop"); err != nil { + log.Errorf("failed to stop replication of %d: %v", id, err) + pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + log.Infof("replication of %d has been stopped", id) + } + + if err = dao.UpdateRepPolicy(policy); err != nil { + log.Errorf("failed to update policy %d: %v", id, err) + pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + if shouldTrigger { + go func() { + if err := TriggerReplication(id, "", nil, models.RepOpTransfer); err != nil { + log.Errorf("failed to trigger replication of %d: %v", id, err) + } else { + log.Infof("replication of %d triggered", id) + } + }() + } + */ + + if err = dao.UpdateRepPolicy(policy); err != nil { + log.Errorf("failed to update policy %d: %v", id, err) + pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + if policy.Enabled != originalPolicy.Enabled && policy.Enabled == 1 { + go func() { + if err := TriggerReplication(id, "", nil, models.RepOpTransfer); err != nil { + log.Errorf("failed to trigger replication of %d: %v", id, err) + } else { + log.Infof("replication of %d triggered", id) + } + }() + } +} + +type enablementReq struct { + Enabled int `json:"enabled"` +} + +// UpdateEnablement changes the enablement of the policy +func (pa *RepPolicyAPI) UpdateEnablement() { + id := pa.GetIDFromURL() + policy, err := dao.GetRepPolicy(id) + if err != nil { + log.Errorf("failed to get policy %d: %v", id, err) + pa.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + if policy == nil { + pa.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound)) + } + + e := enablementReq{} + pa.DecodeJSONReq(&e) + if e.Enabled != 0 && e.Enabled != 1 { + pa.RenderError(http.StatusBadRequest, "invalid enabled value") + return + } + + if policy.Enabled == e.Enabled { + return + } + + if err := dao.UpdateRepPolicyEnablement(id, e.Enabled); err != nil { + log.Errorf("Failed to update policy enablement in DB, error: %v", err) + pa.RenderError(http.StatusInternalServerError, "Internal Error") + return + } + + if e.Enabled == 1 { + go func() { + if err := TriggerReplication(id, "", nil, models.RepOpTransfer); err != nil { + log.Errorf("failed to trigger replication of %d: %v", id, err) + } else { + log.Infof("replication of %d triggered", id) + } + }() + } else { + go func() { + if err := postReplicationAction(id, "stop"); err != nil { + log.Errorf("failed to stop replication of %d: %v", id, err) + } else { + log.Infof("try to stop replication of %d", id) + } + }() + } +} diff --git a/api/repository.go b/api/repository.go index a355d73dc..a962c9dbc 100644 --- a/api/repository.go +++ b/api/repository.go @@ -27,17 +27,18 @@ import ( "github.com/docker/distribution/manifest/schema1" "github.com/vmware/harbor/dao" "github.com/vmware/harbor/models" + "github.com/vmware/harbor/service/cache" svc_utils "github.com/vmware/harbor/service/utils" "github.com/vmware/harbor/utils/log" "github.com/vmware/harbor/utils/registry" + + registry_error "github.com/vmware/harbor/utils/registry/error" + "github.com/vmware/harbor/utils/registry/auth" - "github.com/vmware/harbor/utils/registry/errors" ) // RepositoryAPI handles request to /api/repositories /api/repositories/tags /api/repositories/manifests, the parm has to be put // in the query string as the web framework can not parse the URL if it contains veriadic sectors. -// For repostiories, we won't check the session in this API due to search functionality, querying manifest will be contorlled by -// the security of registry type RepositoryAPI struct { BaseAPI } @@ -62,7 +63,13 @@ func (ra *RepositoryAPI) Get() { } if p.Public == 0 { - userID := ra.ValidateUser() + var userID int + + if svc_utils.VerifySecret(ra.Ctx.Request) { + userID = 1 + } else { + userID = ra.ValidateUser() + } if !checkProjectPermission(userID, projectID) { ra.RenderError(http.StatusForbidden, "") @@ -70,7 +77,7 @@ func (ra *RepositoryAPI) Get() { } } - repoList, err := svc_utils.GetRepoFromCache() + repoList, err := cache.GetRepoFromCache() if err != nil { log.Errorf("Failed to get repo from cache, error: %v", err) ra.RenderError(http.StatusInternalServerError, "internal sever error") @@ -106,6 +113,20 @@ func (ra *RepositoryAPI) Delete() { ra.CustomAbort(http.StatusBadRequest, "repo_name is nil") } + projectName := getProjectName(repoName) + project, err := dao.GetProjectByName(projectName) + if err != nil { + log.Errorf("failed to get project %s: %v", projectName, err) + ra.CustomAbort(http.StatusInternalServerError, "") + } + + if project.Public == 0 { + userID := ra.ValidateUser() + if !hasProjectAdminRole(userID, project.ProjectID) { + ra.CustomAbort(http.StatusForbidden, "") + } + } + rc, err := ra.initRepositoryClient(repoName) if err != nil { log.Errorf("error occurred while initializing repository client for %s: %v", repoName, err) @@ -117,40 +138,57 @@ func (ra *RepositoryAPI) Delete() { if len(tag) == 0 { tagList, err := rc.ListTag() if err != nil { - e, ok := errors.ParseError(err) - if ok { - log.Info(e) - ra.CustomAbort(e.StatusCode, e.Message) - } else { - log.Error(err) - ra.CustomAbort(http.StatusInternalServerError, "internal error") + if regErr, ok := err.(*registry_error.Error); ok { + ra.CustomAbort(regErr.StatusCode, regErr.Detail) } + + log.Errorf("error occurred while listing tags of %s: %v", repoName, err) + ra.CustomAbort(http.StatusInternalServerError, "internal error") } + + // TODO remove the logic if the bug of registry is fixed + if len(tagList) == 0 { + ra.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound)) + } + tags = append(tags, tagList...) } else { tags = append(tags, tag) } + user, _, ok := ra.Ctx.Request.BasicAuth() + if !ok { + user, err = ra.getUsername() + if err != nil { + log.Errorf("failed to get user: %v", err) + } + } + for _, t := range tags { if err := rc.DeleteTag(t); err != nil { - e, ok := errors.ParseError(err) - if ok { - ra.CustomAbort(e.StatusCode, e.Message) - } else { - log.Error(err) - ra.CustomAbort(http.StatusInternalServerError, "internal error") + if regErr, ok := err.(*registry_error.Error); ok { + ra.CustomAbort(regErr.StatusCode, regErr.Detail) } + + log.Errorf("error occurred while deleting tags of %s: %v", repoName, err) + ra.CustomAbort(http.StatusInternalServerError, "internal error") } log.Infof("delete tag: %s %s", repoName, t) + go TriggerReplicationByRepository(repoName, []string{t}, models.RepOpDelete) + + go func(tag string) { + if err := dao.AccessLog(user, projectName, repoName, tag, "delete"); err != nil { + log.Errorf("failed to add access log: %v", err) + } + }(t) } go func() { log.Debug("refreshing catalog cache") - if err := svc_utils.RefreshCatalogCache(); err != nil { + if err := cache.RefreshCatalogCache(); err != nil { log.Errorf("error occurred while refresh catalog cache: %v", err) } }() - } type tag struct { @@ -165,6 +203,20 @@ func (ra *RepositoryAPI) GetTags() { ra.CustomAbort(http.StatusBadRequest, "repo_name is nil") } + projectName := getProjectName(repoName) + project, err := dao.GetProjectByName(projectName) + if err != nil { + log.Errorf("failed to get project %s: %v", projectName, err) + ra.CustomAbort(http.StatusInternalServerError, "") + } + + if project.Public == 0 { + userID := ra.ValidateUser() + if !checkProjectPermission(userID, project.ProjectID) { + ra.CustomAbort(http.StatusForbidden, "") + } + } + rc, err := ra.initRepositoryClient(repoName) if err != nil { log.Errorf("error occurred while initializing repository client for %s: %v", repoName, err) @@ -175,13 +227,12 @@ func (ra *RepositoryAPI) GetTags() { ts, err := rc.ListTag() if err != nil { - e, ok := errors.ParseError(err) - if ok { - ra.CustomAbort(e.StatusCode, e.Message) - } else { - log.Error(err) - ra.CustomAbort(http.StatusInternalServerError, "internal error") + if regErr, ok := err.(*registry_error.Error); ok { + ra.CustomAbort(regErr.StatusCode, regErr.Detail) } + + log.Errorf("error occurred while listing tags of %s: %v", repoName, err) + ra.CustomAbort(http.StatusInternalServerError, "internal error") } tags = append(tags, ts...) @@ -201,6 +252,20 @@ func (ra *RepositoryAPI) GetManifests() { ra.CustomAbort(http.StatusBadRequest, "repo_name or tag is nil") } + projectName := getProjectName(repoName) + project, err := dao.GetProjectByName(projectName) + if err != nil { + log.Errorf("failed to get project %s: %v", projectName, err) + ra.CustomAbort(http.StatusInternalServerError, "") + } + + if project.Public == 0 { + userID := ra.ValidateUser() + if !checkProjectPermission(userID, project.ProjectID) { + ra.CustomAbort(http.StatusForbidden, "") + } + } + rc, err := ra.initRepositoryClient(repoName) if err != nil { log.Errorf("error occurred while initializing repository client for %s: %v", repoName, err) @@ -212,13 +277,12 @@ func (ra *RepositoryAPI) GetManifests() { mediaTypes := []string{schema1.MediaTypeManifest} _, _, payload, err := rc.PullManifest(tag, mediaTypes) if err != nil { - e, ok := errors.ParseError(err) - if ok { - ra.CustomAbort(e.StatusCode, e.Message) - } else { - log.Error(err) - ra.CustomAbort(http.StatusInternalServerError, "internal error") + if regErr, ok := err.(*registry_error.Error); ok { + ra.CustomAbort(regErr.StatusCode, regErr.Detail) } + + log.Errorf("error occurred while getting manifest of %s:%s: %v", repoName, tag, err) + ra.CustomAbort(http.StatusInternalServerError, "internal error") } mani := models.Manifest{} err = json.Unmarshal(payload, &mani) @@ -246,8 +310,8 @@ func (ra *RepositoryAPI) initRepositoryClient(repoName string) (r *registry.Repo username, password, ok := ra.Ctx.Request.BasicAuth() if ok { - credential := auth.NewBasicAuthCredential(username, password) - return registry.NewRepositoryWithCredential(repoName, endpoint, credential) + return newRepositoryClient(endpoint, getIsInsecure(), username, password, + repoName, "repository", repoName, "pull", "push", "*") } username, err = ra.getUsername() @@ -255,7 +319,8 @@ func (ra *RepositoryAPI) initRepositoryClient(repoName string) (r *registry.Repo return nil, err } - return registry.NewRepositoryWithUsername(repoName, endpoint, username) + return cache.NewRepositoryClient(endpoint, getIsInsecure(), username, repoName, + "repository", repoName, "pull", "push", "*") } func (ra *RepositoryAPI) getUsername() (string, error) { @@ -288,3 +353,56 @@ func (ra *RepositoryAPI) getUsername() (string, error) { return "", nil } + +//GetTopRepos handles request GET /api/repositories/top +func (ra *RepositoryAPI) GetTopRepos() { + var err error + var countNum int + count := ra.GetString("count") + if len(count) == 0 { + countNum = 10 + } else { + countNum, err = strconv.Atoi(count) + if err != nil { + log.Errorf("Get parameters error--count, err: %v", err) + ra.CustomAbort(http.StatusBadRequest, "bad request of count") + } + if countNum <= 0 { + log.Warning("count must be a positive integer") + ra.CustomAbort(http.StatusBadRequest, "count is 0 or negative") + } + } + repos, err := dao.GetTopRepos(countNum) + if err != nil { + log.Errorf("error occured in get top 10 repos: %v", err) + ra.CustomAbort(http.StatusInternalServerError, "internal server error") + } + ra.Data["json"] = repos + ra.ServeJSON() +} + +func newRepositoryClient(endpoint string, insecure bool, username, password, repository, scopeType, scopeName string, + scopeActions ...string) (*registry.Repository, error) { + + credential := auth.NewBasicAuthCredential(username, password) + authorizer := auth.NewStandardTokenAuthorizer(credential, insecure, scopeType, scopeName, scopeActions...) + + store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer) + if err != nil { + return nil, err + } + + client, err := registry.NewRepositoryWithModifiers(repository, endpoint, insecure, store) + if err != nil { + return nil, err + } + return client, nil +} + +func getProjectName(repository string) string { + project := "" + if strings.Contains(repository, "/") { + project = repository[0:strings.LastIndex(repository, "/")] + } + return project +} diff --git a/api/search.go b/api/search.go index 3acdc7ca8..4da0068c0 100644 --- a/api/search.go +++ b/api/search.go @@ -22,7 +22,7 @@ import ( "github.com/vmware/harbor/dao" "github.com/vmware/harbor/models" - svc_utils "github.com/vmware/harbor/service/utils" + "github.com/vmware/harbor/service/cache" "github.com/vmware/harbor/utils" "github.com/vmware/harbor/utils/log" ) @@ -68,7 +68,7 @@ func (s *SearchAPI) Get() { } } - projectSorter := &utils.ProjectSorter{Projects: projects} + projectSorter := &models.ProjectSorter{Projects: projects} sort.Sort(projectSorter) projectResult := []map[string]interface{}{} for _, p := range projects { @@ -85,7 +85,7 @@ func (s *SearchAPI) Get() { } } - repositories, err2 := svc_utils.GetRepoFromCache() + repositories, err2 := cache.GetRepoFromCache() if err2 != nil { log.Errorf("Failed to get repos from cache, error: %v", err2) s.CustomAbort(http.StatusInternalServerError, "Failed to get repositories search result") diff --git a/api/statistic.go b/api/statistic.go index 67a7cc388..862889463 100644 --- a/api/statistic.go +++ b/api/statistic.go @@ -21,7 +21,7 @@ import ( "github.com/vmware/harbor/dao" "github.com/vmware/harbor/models" - svc_utils "github.com/vmware/harbor/service/utils" + "github.com/vmware/harbor/service/cache" "github.com/vmware/harbor/utils/log" ) @@ -88,7 +88,7 @@ func (s *StatisticAPI) Get() { //getReposByProject returns repo numbers of specified project func getRepoCountByProject(projectName string) int { - repoList, err := svc_utils.GetRepoFromCache() + repoList, err := cache.GetRepoFromCache() if err != nil { log.Errorf("Failed to get repo from cache, error: %v", err) return 0 @@ -107,7 +107,7 @@ func getRepoCountByProject(projectName string) int { //getTotalRepoCount returns total repo count func getTotalRepoCount() int { - repoList, err := svc_utils.GetRepoFromCache() + repoList, err := cache.GetRepoFromCache() if err != nil { log.Errorf("Failed to get repo from cache, error: %v", err) return 0 diff --git a/api/target.go b/api/target.go new file mode 100644 index 000000000..c89f1a3fd --- /dev/null +++ b/api/target.go @@ -0,0 +1,356 @@ +/* + 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. +*/ + +package api + +import ( + "fmt" + "net" + "net/http" + "net/url" + "strconv" + + "github.com/vmware/harbor/dao" + "github.com/vmware/harbor/models" + "github.com/vmware/harbor/utils" + "github.com/vmware/harbor/utils/log" + "github.com/vmware/harbor/utils/registry" + "github.com/vmware/harbor/utils/registry/auth" + registry_error "github.com/vmware/harbor/utils/registry/error" +) + +// TargetAPI handles request to /api/targets/ping /api/targets/{} +type TargetAPI struct { + BaseAPI +} + +// Prepare validates the user +func (t *TargetAPI) Prepare() { + userID := t.ValidateUser() + isSysAdmin, err := dao.IsAdminRole(userID) + if err != nil { + log.Errorf("error occurred in IsAdminRole: %v", err) + t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + if !isSysAdmin { + t.CustomAbort(http.StatusForbidden, http.StatusText(http.StatusForbidden)) + } +} + +// Ping validates whether the target is reachable and whether the credential is valid +func (t *TargetAPI) Ping() { + var endpoint, username, password string + + idStr := t.GetString("id") + if len(idStr) != 0 { + id, err := strconv.ParseInt(idStr, 10, 64) + if err != nil { + t.CustomAbort(http.StatusBadRequest, fmt.Sprintf("id %s is invalid", idStr)) + } + + target, err := dao.GetRepTarget(id) + if err != nil { + log.Errorf("failed to get target %d: %v", id, err) + t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + if target == nil { + t.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound)) + } + + endpoint = target.URL + username = target.Username + password = target.Password + + if len(password) != 0 { + password, err = utils.ReversibleDecrypt(password) + if err != nil { + log.Errorf("failed to decrypt password: %v", err) + t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + } + } else { + endpoint = t.GetString("endpoint") + if len(endpoint) == 0 { + t.CustomAbort(http.StatusBadRequest, "id or endpoint is needed") + } + + username = t.GetString("username") + password = t.GetString("password") + } + + registry, err := newRegistryClient(endpoint, getIsInsecure(), username, password, + "", "", "") + if err != nil { + // timeout, dns resolve error, connection refused, etc. + if urlErr, ok := err.(*url.Error); ok { + if netErr, ok := urlErr.Err.(net.Error); ok { + t.CustomAbort(http.StatusBadRequest, netErr.Error()) + } + + t.CustomAbort(http.StatusBadRequest, urlErr.Error()) + } + + log.Errorf("failed to create registry client: %#v", err) + t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + if err = registry.Ping(); err != nil { + if regErr, ok := err.(*registry_error.Error); ok { + t.CustomAbort(regErr.StatusCode, regErr.Detail) + } + + log.Errorf("failed to ping registry %s: %v", registry.Endpoint.String(), err) + t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } +} + +// Get ... +func (t *TargetAPI) Get() { + id := t.GetIDFromURL() + + target, err := dao.GetRepTarget(id) + if err != nil { + log.Errorf("failed to get target %d: %v", id, err) + t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + if target == nil { + t.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound)) + } + + // The reason why the password is returned is that when user just wants to + // modify other fields of target he does not need to input the password again. + // The security issue can be fixed by enable https. + if len(target.Password) != 0 { + pwd, err := utils.ReversibleDecrypt(target.Password) + if err != nil { + log.Errorf("failed to decrypt password: %v", err) + t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + target.Password = pwd + } + + t.Data["json"] = target + t.ServeJSON() +} + +// List ... +func (t *TargetAPI) List() { + name := t.GetString("name") + targets, err := dao.FilterRepTargets(name) + if err != nil { + log.Errorf("failed to filter targets %s: %v", name, err) + t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + for _, target := range targets { + if len(target.Password) == 0 { + continue + } + + str, err := utils.ReversibleDecrypt(target.Password) + if err != nil { + log.Errorf("failed to decrypt password: %v", err) + t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + target.Password = str + } + + t.Data["json"] = targets + t.ServeJSON() + return +} + +// Post ... +func (t *TargetAPI) Post() { + target := &models.RepTarget{} + t.DecodeJSONReqAndValidate(target) + + ta, err := dao.GetRepTargetByName(target.Name) + if err != nil { + log.Errorf("failed to get target %s: %v", target.Name, err) + t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + if ta != nil { + t.CustomAbort(http.StatusConflict, "name is already used") + } + + ta, err = dao.GetRepTargetByConnInfo(target.URL, target.Username) + if err != nil { + log.Errorf("failed to get target [ %s %s ]: %v", target.URL, target.Username, err) + t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + if ta != nil { + t.CustomAbort(http.StatusConflict, "the connection information[ endpoint, username ] is conflict with other target") + } + + if len(target.Password) != 0 { + target.Password = utils.ReversibleEncrypt(target.Password) + } + + id, err := dao.AddRepTarget(*target) + if err != nil { + log.Errorf("failed to add target: %v", err) + t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + t.Redirect(http.StatusCreated, strconv.FormatInt(id, 10)) +} + +// Put ... +func (t *TargetAPI) Put() { + id := t.GetIDFromURL() + + originalTarget, err := dao.GetRepTarget(id) + if err != nil { + log.Errorf("failed to get target %d: %v", id, err) + t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + if originalTarget == nil { + t.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound)) + } + + policies, err := dao.GetRepPolicyByTarget(id) + if err != nil { + log.Errorf("failed to get policies according target %d: %v", id, err) + t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + hasEnabledPolicy := false + for _, policy := range policies { + if policy.Enabled == 1 { + hasEnabledPolicy = true + break + } + } + + if hasEnabledPolicy { + t.CustomAbort(http.StatusBadRequest, "the target is associated with policy which is enabled") + } + + target := &models.RepTarget{} + t.DecodeJSONReqAndValidate(target) + + if target.Name != originalTarget.Name { + ta, err := dao.GetRepTargetByName(target.Name) + if err != nil { + log.Errorf("failed to get target %s: %v", target.Name, err) + t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + if ta != nil { + t.CustomAbort(http.StatusConflict, "name is already used") + } + } + + if target.URL != originalTarget.URL || target.Username != originalTarget.Username { + ta, err := dao.GetRepTargetByConnInfo(target.URL, target.Username) + if err != nil { + log.Errorf("failed to get target [ %s %s ]: %v", target.URL, target.Username, err) + t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + if ta != nil { + t.CustomAbort(http.StatusConflict, "the connection information[ endpoint, username ] is conflict with other target") + } + } + + target.ID = id + + if len(target.Password) != 0 { + target.Password = utils.ReversibleEncrypt(target.Password) + } + + if err := dao.UpdateRepTarget(*target); err != nil { + log.Errorf("failed to update target %d: %v", id, err) + t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } +} + +// Delete ... +func (t *TargetAPI) Delete() { + id := t.GetIDFromURL() + + target, err := dao.GetRepTarget(id) + if err != nil { + log.Errorf("failed to get target %d: %v", id, err) + t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + if target == nil { + t.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound)) + } + + policies, err := dao.GetRepPolicyByTarget(id) + if err != nil { + log.Errorf("failed to get policies according target %d: %v", id, err) + t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + if len(policies) > 0 { + t.CustomAbort(http.StatusBadRequest, "the target is used by policies, can not be deleted") + } + + if err = dao.DeleteRepTarget(id); err != nil { + log.Errorf("failed to delete target %d: %v", id, err) + t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } +} + +func newRegistryClient(endpoint string, insecure bool, username, password, scopeType, scopeName string, + scopeActions ...string) (*registry.Registry, error) { + credential := auth.NewBasicAuthCredential(username, password) + authorizer := auth.NewStandardTokenAuthorizer(credential, insecure, scopeType, scopeName, scopeActions...) + + store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer) + if err != nil { + return nil, err + } + + client, err := registry.NewRegistryWithModifiers(endpoint, insecure, store) + if err != nil { + return nil, err + } + return client, nil +} + +// ListPolicies ... +func (t *TargetAPI) ListPolicies() { + id := t.GetIDFromURL() + + target, err := dao.GetRepTarget(id) + if err != nil { + log.Errorf("failed to get target %d: %v", id, err) + t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + if target == nil { + t.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound)) + } + + policies, err := dao.GetRepPolicyByTarget(id) + if err != nil { + log.Errorf("failed to get policies according target %d: %v", id, err) + t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + t.Data["json"] = policies + t.ServeJSON() +} diff --git a/api/user.go b/api/user.go index 45869fb6c..54b85e140 100644 --- a/api/user.go +++ b/api/user.go @@ -16,8 +16,10 @@ package api import ( + "fmt" "net/http" "os" + "regexp" "strconv" "strings" @@ -133,14 +135,52 @@ func (ua *UserAPI) Get() { } // Put ... -func (ua *UserAPI) Put() { //currently only for toggle admin, so no request body +func (ua *UserAPI) Put() { + ldapAdminUser := (ua.AuthMode == "ldap_auth" && ua.userID == 1 && ua.userID == ua.currentUserID) + + if !(ua.AuthMode == "db_auth" || ldapAdminUser) { + ua.CustomAbort(http.StatusForbidden, "") + } if !ua.IsAdmin { - log.Warningf("current user, id: %d does not have admin role, can not update other user's role", ua.currentUserID) - ua.RenderError(http.StatusForbidden, "User does not have admin role") + if ua.userID != ua.currentUserID { + log.Warning("Guests can only change their own account.") + ua.CustomAbort(http.StatusForbidden, "Guests can only change their own account.") + } + } + user := models.User{UserID: ua.userID} + ua.DecodeJSONReq(&user) + err := commonValidate(user) + if err != nil { + log.Warning("Bad request in change user profile: %v", err) + ua.RenderError(http.StatusBadRequest, "change user profile error:"+err.Error()) return } userQuery := models.User{UserID: ua.userID} - dao.ToggleUserAdminRole(userQuery) + u, err := dao.GetUser(userQuery) + if err != nil { + log.Errorf("Error occurred in GetUser, error: %v", err) + ua.CustomAbort(http.StatusInternalServerError, "Internal error.") + } + if u == nil { + log.Errorf("User with Id: %d does not exist", ua.userID) + ua.CustomAbort(http.StatusNotFound, "") + } + if u.Email != user.Email { + emailExist, err := dao.UserExists(user, "email") + if err != nil { + log.Errorf("Error occurred in change user profile: %v", err) + ua.CustomAbort(http.StatusInternalServerError, "Internal error.") + } + if emailExist { + log.Warning("email has already been used!") + ua.RenderError(http.StatusConflict, "email has already been used!") + return + } + } + if err := dao.ChangeUserProfile(user); err != nil { + log.Errorf("Failed to update user profile, error: %v", err) + ua.CustomAbort(http.StatusInternalServerError, err.Error()) + } } // Post ... @@ -157,12 +197,36 @@ func (ua *UserAPI) Post() { user := models.User{} ua.DecodeJSONReq(&user) - + err := validate(user) + if err != nil { + log.Warning("Bad request in Register: %v", err) + ua.RenderError(http.StatusBadRequest, "register error:"+err.Error()) + return + } + userExist, err := dao.UserExists(user, "username") + if err != nil { + log.Errorf("Error occurred in Register: %v", err) + ua.CustomAbort(http.StatusInternalServerError, "Internal error.") + } + if userExist { + log.Warning("username has already been used!") + ua.RenderError(http.StatusConflict, "username has already been used!") + return + } + emailExist, err := dao.UserExists(user, "email") + if err != nil { + log.Errorf("Error occurred in change user profile: %v", err) + ua.CustomAbort(http.StatusInternalServerError, "Internal error.") + } + if emailExist { + log.Warning("email has already been used!") + ua.RenderError(http.StatusConflict, "email has already been used!") + return + } userID, err := dao.Register(user) if err != nil { log.Errorf("Error occurred in Register: %v", err) - ua.RenderError(http.StatusInternalServerError, "Internal error.") - return + ua.CustomAbort(http.StatusInternalServerError, "Internal error.") } ua.Redirect(http.StatusCreated, strconv.FormatInt(userID, 10)) @@ -186,9 +250,8 @@ func (ua *UserAPI) Delete() { // ChangePassword handles PUT to /api/users/{}/password func (ua *UserAPI) ChangePassword() { - ldapAdminUser := (ua.AuthMode == "ldap_auth" && ua.userID == 1 && ua.userID == ua.currentUserID) - + if !(ua.AuthMode == "db_auth" || ldapAdminUser) { ua.CustomAbort(http.StatusForbidden, "") } @@ -228,3 +291,80 @@ func (ua *UserAPI) ChangePassword() { ua.CustomAbort(http.StatusInternalServerError, "Internal error.") } } + +// ToggleUserAdminRole handles PUT api/users/{}/sysadmin +func (ua *UserAPI) ToggleUserAdminRole() { + if !ua.IsAdmin { + log.Warningf("current user, id: %d does not have admin role, can not update other user's role", ua.currentUserID) + ua.RenderError(http.StatusForbidden, "User does not have admin role") + return + } + userQuery := models.User{UserID: ua.userID} + ua.DecodeJSONReq(&userQuery) + if err := dao.ToggleUserAdminRole(userQuery.UserID, userQuery.HasAdminRole); err != nil { + log.Errorf("Error occurred in ToggleUserAdminRole: %v", err) + ua.CustomAbort(http.StatusInternalServerError, "Internal error.") + } +} + +// validate only validate when user register +func validate(user models.User) error { + + if isIllegalLength(user.Username, 0, 20) { + return fmt.Errorf("Username with illegal length.") + } + if isContainIllegalChar(user.Username, []string{",", "~", "#", "$", "%"}) { + return fmt.Errorf("Username contains illegal characters.") + } + if isIllegalLength(user.Password, 0, 20) { + return fmt.Errorf("Password with illegal length.") + } + if err := commonValidate(user); err != nil { + return err + } + return nil +} + +//commonValidate validates email, realname, comment information when user register or change their profile +func commonValidate(user models.User) error { + + if len(user.Email) > 0 { + if m, _ := regexp.MatchString(`^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$`, user.Email); !m { + return fmt.Errorf("Email with illegal format.") + } + } else { + return fmt.Errorf("Email can't be empty") + } + + if isIllegalLength(user.Realname, 0, 20) { + return fmt.Errorf("Realname with illegal length.") + } + + if isContainIllegalChar(user.Realname, []string{",", "~", "#", "$", "%"}) { + return fmt.Errorf("Realname contains illegal characters.") + } + if isIllegalLength(user.Comment, -1, 30) { + return fmt.Errorf("Comment with illegal length.") + } + return nil + +} + +func isIllegalLength(s string, min int, max int) bool { + if min == -1 { + return (len(s) > max) + } + if max == -1 { + return (len(s) <= min) + } + return (len(s) < min || len(s) > max) +} + +func isContainIllegalChar(s string, illegalChar []string) bool { + for _, c := range illegalChar { + if strings.Index(s, c) >= 0 { + return true + } + } + return false +} diff --git a/api/utils.go b/api/utils.go index e63fae2be..e0f5d13cc 100644 --- a/api/utils.go +++ b/api/utils.go @@ -16,6 +16,14 @@ package api import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "os" + "strings" + "github.com/vmware/harbor/dao" "github.com/vmware/harbor/models" "github.com/vmware/harbor/utils/log" @@ -51,11 +59,13 @@ func listRoles(userID int, projectID int64) ([]models.Role, error) { roles := make([]models.Role, 0, 1) isSysAdmin, err := dao.IsAdminRole(userID) if err != nil { + log.Errorf("failed to determine whether the user %d is system admin: %v", userID, err) return roles, err } if isSysAdmin { role, err := dao.GetRoleByID(models.PROJECTADMIN) if err != nil { + log.Errorf("failed to get role %d: %v", models.PROJECTADMIN, err) return roles, err } roles = append(roles, *role) @@ -64,6 +74,7 @@ func listRoles(userID int, projectID int64) ([]models.Role, error) { rs, err := dao.GetUserProjectRoles(userID, projectID) if err != nil { + log.Errorf("failed to get user %d 's roles for project %d: %v", userID, projectID, err) return roles, err } roles = append(roles, rs...) @@ -81,3 +92,142 @@ func checkUserExists(name string) int { } return 0 } + +// TriggerReplication triggers the replication according to the policy +func TriggerReplication(policyID int64, repository string, + tags []string, operation string) error { + data := struct { + PolicyID int64 `json:"policy_id"` + Repo string `json:"repository"` + Operation string `json:"operation"` + TagList []string `json:"tags"` + }{ + PolicyID: policyID, + Repo: repository, + TagList: tags, + Operation: operation, + } + + b, err := json.Marshal(&data) + if err != nil { + return err + } + + url := buildReplicationURL() + + resp, err := http.DefaultClient.Post(url, "application/json", bytes.NewBuffer(b)) + if err != nil { + return err + } + + if resp.StatusCode == http.StatusOK { + return nil + } + + defer resp.Body.Close() + + b, err = ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + + return fmt.Errorf("%d %s", resp.StatusCode, string(b)) +} + +// GetPoliciesByRepository returns policies according the repository +func GetPoliciesByRepository(repository string) ([]*models.RepPolicy, error) { + repository = strings.TrimSpace(repository) + repository = strings.TrimRight(repository, "/") + projectName := repository[:strings.LastIndex(repository, "/")] + + project, err := dao.GetProjectByName(projectName) + if err != nil { + return nil, err + } + + policies, err := dao.GetRepPolicyByProject(project.ProjectID) + if err != nil { + return nil, err + } + + return policies, nil +} + +// TriggerReplicationByRepository triggers the replication according to the repository +func TriggerReplicationByRepository(repository string, tags []string, operation string) { + policies, err := GetPoliciesByRepository(repository) + if err != nil { + log.Errorf("failed to get policies for repository %s: %v", repository, err) + return + } + + for _, policy := range policies { + if err := TriggerReplication(policy.ID, repository, tags, operation); err != nil { + log.Errorf("failed to trigger replication of policy %d for %s: %v", policy.ID, repository, err) + } else { + log.Infof("replication of policy %d for %s triggered", policy.ID, repository) + } + } +} + +func postReplicationAction(policyID int64, acton string) error { + data := struct { + PolicyID int64 `json:"policy_id"` + Action string `json:"action"` + }{ + PolicyID: policyID, + Action: acton, + } + + b, err := json.Marshal(&data) + if err != nil { + return err + } + + url := buildReplicationActionURL() + + resp, err := http.DefaultClient.Post(url, "application/json", bytes.NewBuffer(b)) + if err != nil { + return err + } + + if resp.StatusCode == http.StatusOK { + return nil + } + + defer resp.Body.Close() + + b, err = ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + + return fmt.Errorf("%d %s", resp.StatusCode, string(b)) +} + +func buildReplicationURL() string { + url := getJobServiceURL() + return fmt.Sprintf("%s/api/jobs/replication", url) +} + +func buildJobLogURL(jobID string) string { + url := getJobServiceURL() + return fmt.Sprintf("%s/api/jobs/replication/%s/log", url, jobID) +} + +func buildReplicationActionURL() string { + url := getJobServiceURL() + return fmt.Sprintf("%s/api/jobs/replication/actions", url) +} + +func getJobServiceURL() string { + url := os.Getenv("JOB_SERVICE_URL") + url = strings.TrimSpace(url) + url = strings.TrimRight(url, "/") + + if len(url) == 0 { + url = "http://jobservice" + } + + return url +} diff --git a/auth/ldap/ldap.go b/auth/ldap/ldap.go index 8de4d47fb..6929147d1 100644 --- a/auth/ldap/ldap.go +++ b/auth/ldap/ldap.go @@ -111,6 +111,9 @@ func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) { u.Realname = m.Principal u.Password = "12345678AbC" u.Comment = "registered from LDAP." + if u.Email == "" { + u.Email = u.Username + "@placeholder.com" + } userID, err := dao.Register(u) if err != nil { return nil, err diff --git a/controllers/accountsetting.go b/controllers/accountsetting.go new file mode 100644 index 000000000..5d49e60d7 --- /dev/null +++ b/controllers/accountsetting.go @@ -0,0 +1,11 @@ +package controllers + +// AccountSettingController handles request to /account_setting +type AccountSettingController struct { + BaseController +} + +// Get renders the account settings page +func (asc *AccountSettingController) Get() { + asc.Forward("page_title_account_setting", "account-settings.htm") +} diff --git a/controllers/addnew.go b/controllers/addnew.go new file mode 100644 index 000000000..c3ec9c571 --- /dev/null +++ b/controllers/addnew.go @@ -0,0 +1,32 @@ +package controllers + +import ( + "net/http" + + "github.com/vmware/harbor/dao" + "github.com/vmware/harbor/utils/log" +) + +// AddNewController handles requests to /add_new +type AddNewController struct { + BaseController +} + +// Get renders the add new page +func (anc *AddNewController) Get() { + sessionUserID := anc.GetSession("userId") + anc.Data["AddNew"] = false + if sessionUserID != nil { + isAdmin, err := dao.IsAdminRole(sessionUserID.(int)) + if err != nil { + log.Errorf("Error occurred in IsAdminRole: %v", err) + anc.CustomAbort(http.StatusInternalServerError, "") + } + if isAdmin && anc.AuthMode == "db_auth" { + anc.Data["AddNew"] = true + anc.Forward("page_title_add_new", "sign-up.htm") + return + } + } + anc.CustomAbort(http.StatusUnauthorized, "Status Unauthorized.") +} diff --git a/controllers/adminoption.go b/controllers/adminoption.go new file mode 100644 index 000000000..d0a10c1fa --- /dev/null +++ b/controllers/adminoption.go @@ -0,0 +1,11 @@ +package controllers + +// AdminOptionController handles requests to /admin_option +type AdminOptionController struct { + BaseController +} + +// Get renders the admin options page +func (aoc *AdminOptionController) Get() { + aoc.Forward("page_title_admin_option", "admin-options.htm") +} diff --git a/controllers/base.go b/controllers/base.go index 7d4b3e535..4ce094eab 100644 --- a/controllers/base.go +++ b/controllers/base.go @@ -1,47 +1,24 @@ -/* - 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. -*/ - package controllers import ( "net/http" "os" + "path/filepath" "strings" "github.com/astaxie/beego" "github.com/beego/i18n" + "github.com/vmware/harbor/auth" "github.com/vmware/harbor/dao" + "github.com/vmware/harbor/models" "github.com/vmware/harbor/utils/log" ) -// CommonController handles request from UI that doesn't expect a page, such as /login /logout ... -type CommonController struct { - BaseController -} - -// Render returns nil. -func (c *CommonController) Render() error { - return nil -} - // BaseController wraps common methods such as i18n support, forward, which can be leveraged by other UI render controllers. type BaseController struct { beego.Controller i18n.Locale SelfRegistration bool - IsLdapAdminUser bool IsAdmin bool AuthMode string } @@ -52,33 +29,48 @@ type langType struct { } const ( + viewPath = "sections" + prefixNg = "" defaultLang = "en-US" ) var supportLanguages map[string]langType +var mappingLangNames map[string]string // Prepare extracts the language information from request and populate data for rendering templates. func (b *BaseController) Prepare() { var lang string - al := b.Ctx.Request.Header.Get("Accept-Language") - if len(al) > 4 { - al = al[:5] // Only compare first 5 letters. - if i18n.IsExist(al) { - lang = al + langCookie, err := b.Ctx.Request.Cookie("language") + if err != nil { + log.Errorf("Error occurred in Request.Cookie: %v", err) + } + if langCookie != nil { + lang = langCookie.Value + } + if len(lang) == 0 { + sessionLang := b.GetSession("lang") + if sessionLang != nil { + b.SetSession("Lang", lang) + lang = sessionLang.(string) + } else { + al := b.Ctx.Request.Header.Get("Accept-Language") + if len(al) > 4 { + al = al[:5] // Only compare first 5 letters. + if i18n.IsExist(al) { + lang = al + } + } } } - if _, exist := supportLanguages[lang]; exist == false { //Check if support the request language. + if _, exist := supportLanguages[lang]; !exist { //Check if support the request language. lang = defaultLang //Set default language if not supported. } - sessionLang := b.GetSession("lang") - if sessionLang != nil { - b.SetSession("Lang", lang) - lang = sessionLang.(string) - } + b.Ctx.SetCookie("language", lang, 0, "/") + b.SetSession("Lang", lang) curLang := langType{ Lang: lang, @@ -106,60 +98,101 @@ func (b *BaseController) Prepare() { b.AuthMode = authMode b.Data["AuthMode"] = b.AuthMode - selfRegistration := strings.ToLower(os.Getenv("SELF_REGISTRATION")) - - if selfRegistration == "on" { - b.SelfRegistration = true - } - - sessionUserID := b.GetSession("userId") - if sessionUserID != nil { - b.Data["Username"] = b.GetSession("username") - b.Data["UserId"] = sessionUserID.(int) - - if (sessionUserID == 1 && b.AuthMode == "ldap_auth") { - b.IsLdapAdminUser = true - } - - var err error - b.IsAdmin, err = dao.IsAdminRole(sessionUserID.(int)) - if err != nil { - log.Errorf("Error occurred in IsAdminRole:%v", err) - b.CustomAbort(http.StatusInternalServerError, "Internal error.") - } - } - - b.Data["IsAdmin"] = b.IsAdmin - b.Data["SelfRegistration"] = b.SelfRegistration - b.Data["IsLdapAdminUser"] = b.IsLdapAdminUser - } -// ForwardTo setup layout and template for content for a page. -func (b *BaseController) ForwardTo(pageTitle string, pageName string) { - b.Layout = "segment/base-layout.tpl" - b.TplName = "segment/base-layout.tpl" - b.Data["PageTitle"] = b.Tr(pageTitle) +// Forward to setup layout and template for content for a page. +func (b *BaseController) Forward(title, templateName string) { + b.Layout = filepath.Join(prefixNg, "layout.htm") + b.TplName = filepath.Join(prefixNg, templateName) + b.Data["Title"] = b.Tr(title) b.LayoutSections = make(map[string]string) - b.LayoutSections["HeaderInc"] = "segment/header-include.tpl" - b.LayoutSections["HeaderContent"] = "segment/header-content.tpl" - b.LayoutSections["BodyContent"] = pageName + ".tpl" - b.LayoutSections["ModalDialog"] = "segment/modal-dialog.tpl" - b.LayoutSections["FootContent"] = "segment/foot-content.tpl" + b.LayoutSections["HeaderInclude"] = filepath.Join(prefixNg, viewPath, "header-include.htm") + b.LayoutSections["FooterInclude"] = filepath.Join(prefixNg, viewPath, "footer-include.htm") + b.LayoutSections["HeaderContent"] = filepath.Join(prefixNg, viewPath, "header-content.htm") + b.LayoutSections["FooterContent"] = filepath.Join(prefixNg, viewPath, "footer-content.htm") + } var langTypes []*langType +// CommonController handles request from UI that doesn't expect a page, such as /SwitchLanguage /logout ... +type CommonController struct { + BaseController +} + +// Render returns nil. +func (cc *CommonController) Render() error { + return nil +} + +// Login handles login request from UI. +func (cc *CommonController) Login() { + principal := cc.GetString("principal") + password := cc.GetString("password") + + user, err := auth.Login(models.AuthModel{ + Principal: principal, + Password: password, + }) + if err != nil { + log.Errorf("Error occurred in UserLogin: %v", err) + cc.CustomAbort(http.StatusUnauthorized, "") + } + + if user == nil { + cc.CustomAbort(http.StatusUnauthorized, "") + } + + cc.SetSession("userId", user.UserID) + cc.SetSession("username", user.Username) +} + +// LogOut Habor UI +func (cc *CommonController) LogOut() { + cc.DestroySession() +} + +// SwitchLanguage User can swith to prefered language +func (cc *CommonController) SwitchLanguage() { + lang := cc.GetString("lang") + hash := cc.GetString("hash") + if _, exist := supportLanguages[lang]; !exist { + lang = defaultLang + } + cc.SetSession("lang", lang) + cc.Data["Lang"] = lang + cc.Redirect(cc.Ctx.Request.Header.Get("Referer")+hash, http.StatusFound) +} + +// UserExists checks if user exists when user input value in sign in form. +func (cc *CommonController) UserExists() { + target := cc.GetString("target") + value := cc.GetString("value") + + user := models.User{} + switch target { + case "username": + user.Username = value + case "email": + user.Email = value + } + + exist, err := dao.UserExists(user, target) + if err != nil { + log.Errorf("Error occurred in UserExists: %v", err) + cc.CustomAbort(http.StatusInternalServerError, "Internal error.") + } + cc.Data["json"] = exist + cc.ServeJSON() +} + func init() { //conf/app.conf -> os.Getenv("config_path") configPath := os.Getenv("CONFIG_PATH") if len(configPath) != 0 { log.Infof("Config path: %s", configPath) - beego.AppConfigPath = configPath - if err := beego.ParseConfig(); err != nil { - log.Warningf("Failed to parse config file: %s, error: %v", configPath, err) - } + beego.LoadAppConfig("ini", configPath) } beego.AddFuncMap("i18n", i18n.Tr) @@ -170,18 +203,17 @@ func init() { supportLanguages = make(map[string]langType) langTypes = make([]*langType, 0, len(langs)) - for i, v := range langs { + + for i, lang := range langs { t := langType{ - Lang: v, + Lang: lang, Name: names[i], } langTypes = append(langTypes, &t) - supportLanguages[v] = t - } - - for _, lang := range langs { + supportLanguages[lang] = t if err := i18n.SetMessage(lang, "static/i18n/"+"locale_"+lang+".ini"); err != nil { log.Errorf("Fail to set message file: %s", err.Error()) } } + } diff --git a/controllers/changepassword.go b/controllers/changepassword.go new file mode 100644 index 000000000..0ecddb29c --- /dev/null +++ b/controllers/changepassword.go @@ -0,0 +1,11 @@ +package controllers + +// ChangePasswordController handles request to /change_password +type ChangePasswordController struct { + BaseController +} + +// Get renders the change password page +func (asc *ChangePasswordController) Get() { + asc.Forward("page_title_change_password", "change-password.htm") +} diff --git a/controllers/dashboard.go b/controllers/dashboard.go new file mode 100644 index 000000000..bca215461 --- /dev/null +++ b/controllers/dashboard.go @@ -0,0 +1,11 @@ +package controllers + +// DashboardController handles requests to /dashboard +type DashboardController struct { + BaseController +} + +// Get renders the dashboard page +func (dc *DashboardController) Get() { + dc.Forward("page_title_dashboard", "dashboard.htm") +} diff --git a/controllers/index.go b/controllers/index.go new file mode 100644 index 000000000..12339a64c --- /dev/null +++ b/controllers/index.go @@ -0,0 +1,11 @@ +package controllers + +// IndexController handles request to / +type IndexController struct { + BaseController +} + +// Get renders the index page +func (ic *IndexController) Get() { + ic.Forward("page_title_index", "index.htm") +} diff --git a/controllers/itemdetail.go b/controllers/itemdetail.go deleted file mode 100644 index 895083469..000000000 --- a/controllers/itemdetail.go +++ /dev/null @@ -1,102 +0,0 @@ -/* - 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. -*/ - -package controllers - -import ( - "net/http" - "net/url" - "os" - - "github.com/vmware/harbor/dao" - "github.com/vmware/harbor/utils/log" -) - -// ItemDetailController handles requet to /registry/detail, which shows the detail of a project. -type ItemDetailController struct { - BaseController -} - -// Get will check if user has permission to view a certain project, if not user will be redirected to signin or his homepage. -// If the check is passed it renders the project detail page. -func (idc *ItemDetailController) Get() { - - projectID, _ := idc.GetInt64("project_id") - - if projectID <= 0 { - log.Errorf("Invalid project id: %d", projectID) - idc.Redirect("/signIn", http.StatusFound) - return - } - - project, err := dao.GetProjectByID(projectID) - - if err != nil { - log.Errorf("Error occurred in GetProjectById: %v", err) - idc.CustomAbort(http.StatusInternalServerError, "Internal error.") - } - - if project == nil { - idc.Redirect("/signIn", http.StatusFound) - return - } - - sessionUserID := idc.GetSession("userId") - - if project.Public != 1 && sessionUserID == nil { - idc.Redirect("/signIn?uri="+url.QueryEscape(idc.Ctx.Input.URI()), http.StatusFound) - return - } - - if sessionUserID != nil { - - userID := sessionUserID.(int) - - idc.Data["Username"] = idc.GetSession("username") - idc.Data["UserId"] = userID - - roleList, err := dao.GetUserProjectRoles(userID, projectID) - if err != nil { - log.Errorf("Error occurred in GetUserProjectRoles: %v", err) - idc.CustomAbort(http.StatusInternalServerError, "Internal error.") - } - - isAdmin, err := dao.IsAdminRole(userID) - if err != nil { - log.Errorf("Error occurred in IsAdminRole: %v", err) - idc.CustomAbort(http.StatusInternalServerError, "Internal error.") - } - - if !isAdmin && (project.Public == 0 && len(roleList) == 0) { - idc.Redirect("/registry/project", http.StatusFound) - return - } - - if len(roleList) > 0 { - idc.Data["RoleId"] = roleList[0].RoleID - } - } - - idc.Data["ProjectId"] = project.ProjectID - idc.Data["ProjectName"] = project.Name - idc.Data["OwnerName"] = project.OwnerName - idc.Data["OwnerId"] = project.OwnerID - - idc.Data["HarborRegUrl"] = os.Getenv("HARBOR_REG_URL") - idc.Data["RepoName"] = idc.GetString("repo_name") - - idc.ForwardTo("page_title_item_details", "item-detail") - -} diff --git a/controllers/login.go b/controllers/login.go deleted file mode 100644 index d608f8fbb..000000000 --- a/controllers/login.go +++ /dev/null @@ -1,82 +0,0 @@ -/* - 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. -*/ - -package controllers - -import ( - "net/http" - - "github.com/vmware/harbor/auth" - "github.com/vmware/harbor/models" - "github.com/vmware/harbor/utils/log" -) - -// IndexController handles request to / -type IndexController struct { - BaseController -} - -// Get renders the index page. -func (c *IndexController) Get() { - c.Data["Username"] = c.GetSession("username") - c.ForwardTo("page_title_index", "index") -} - -// SignInController handles request to /signIn -type SignInController struct { - BaseController -} - -// Get renders Sign In page. -func (sic *SignInController) Get() { - sic.ForwardTo("page_title_sign_in", "sign-in") -} - -// Login handles login request from UI. -func (c *CommonController) Login() { - principal := c.GetString("principal") - password := c.GetString("password") - - user, err := auth.Login(models.AuthModel{ - Principal: principal, - Password: password, - }) - if err != nil { - log.Errorf("Error occurred in UserLogin: %v", err) - c.CustomAbort(http.StatusUnauthorized, "") - } - - if user == nil { - c.CustomAbort(http.StatusUnauthorized, "") - } - - c.SetSession("userId", user.UserID) - c.SetSession("username", user.Username) -} - -// SwitchLanguage handles UI request to switch between different languages and re-render template based on language. -func (c *CommonController) SwitchLanguage() { - lang := c.GetString("lang") - if lang == "en-US" || lang == "zh-CN" || lang == "de-DE" || lang == "ru-RU" || lang == "ja-JP" { - c.SetSession("lang", lang) - c.Data["Lang"] = lang - } - c.Redirect(c.Ctx.Request.Header.Get("Referer"), http.StatusFound) -} - -// Logout handles UI request to logout. -func (c *CommonController) Logout() { - c.DestroySession() -} diff --git a/controllers/navigationdetail.go b/controllers/navigationdetail.go new file mode 100644 index 000000000..2de56ef41 --- /dev/null +++ b/controllers/navigationdetail.go @@ -0,0 +1,36 @@ +package controllers + +import ( + "net/http" + + "github.com/vmware/harbor/dao" + "github.com/vmware/harbor/models" + "github.com/vmware/harbor/utils/log" +) + +// NavigationDetailController handles requests to /navigation_detail +type NavigationDetailController struct { + BaseController +} + +// Get renders user's navigation details header +func (ndc *NavigationDetailController) Get() { + sessionUserID := ndc.GetSession("userId") + var isAdmin int + if sessionUserID != nil { + userID := sessionUserID.(int) + u, err := dao.GetUser(models.User{UserID: userID}) + if err != nil { + log.Errorf("Error occurred in GetUser, error: %v", err) + ndc.CustomAbort(http.StatusInternalServerError, "Internal error.") + } + if u == nil { + log.Warningf("User was deleted already, user id: %d, canceling request.", userID) + ndc.CustomAbort(http.StatusUnauthorized, "") + } + isAdmin = u.HasAdminRole + } + ndc.Data["IsAdmin"] = isAdmin + ndc.TplName = "navigation-detail.htm" + ndc.Render() +} diff --git a/controllers/navigationheader.go b/controllers/navigationheader.go new file mode 100644 index 000000000..36513764c --- /dev/null +++ b/controllers/navigationheader.go @@ -0,0 +1,39 @@ +package controllers + +import ( + "net/http" + + "github.com/vmware/harbor/dao" + "github.com/vmware/harbor/models" + "github.com/vmware/harbor/utils/log" +) + +// NavigationHeaderController handles requests to /navigation_header +type NavigationHeaderController struct { + BaseController +} + +// Get renders user's navigation header +func (nhc *NavigationHeaderController) Get() { + sessionUserID := nhc.GetSession("userId") + var hasLoggedIn bool + var isAdmin int + if sessionUserID != nil { + hasLoggedIn = true + userID := sessionUserID.(int) + u, err := dao.GetUser(models.User{UserID: userID}) + if err != nil { + log.Errorf("Error occurred in GetUser, error: %v", err) + nhc.CustomAbort(http.StatusInternalServerError, "Internal error.") + } + if u == nil { + log.Warningf("User was deleted already, user id: %d, canceling request.", userID) + nhc.CustomAbort(http.StatusUnauthorized, "") + } + isAdmin = u.HasAdminRole + } + nhc.Data["HasLoggedIn"] = hasLoggedIn + nhc.Data["IsAdmin"] = isAdmin + nhc.TplName = "navigation-header.htm" + nhc.Render() +} diff --git a/controllers/optionalmenu.go b/controllers/optionalmenu.go new file mode 100644 index 000000000..ac4cc3098 --- /dev/null +++ b/controllers/optionalmenu.go @@ -0,0 +1,52 @@ +package controllers + +import ( + "net/http" + + "github.com/vmware/harbor/dao" + "github.com/vmware/harbor/models" + "github.com/vmware/harbor/utils/log" +) + +// OptionalMenuController handles request to /optional_menu +type OptionalMenuController struct { + BaseController +} + +// Get renders optional menu, Admin user has "Add User" menu +func (omc *OptionalMenuController) Get() { + sessionUserID := omc.GetSession("userId") + + var hasLoggedIn bool + var allowAddNew bool + + if sessionUserID != nil { + hasLoggedIn = true + userID := sessionUserID.(int) + u, err := dao.GetUser(models.User{UserID: userID}) + if err != nil { + log.Errorf("Error occurred in GetUser, error: %v", err) + omc.CustomAbort(http.StatusInternalServerError, "Internal error.") + } + if u == nil { + log.Warningf("User was deleted already, user id: %d, canceling request.", userID) + omc.CustomAbort(http.StatusUnauthorized, "") + } + omc.Data["Username"] = u.Username + + isAdmin, err := dao.IsAdminRole(sessionUserID.(int)) + if err != nil { + log.Errorf("Error occurred in IsAdminRole: %v", err) + omc.CustomAbort(http.StatusInternalServerError, "") + } + + if isAdmin && omc.AuthMode == "db_auth" { + allowAddNew = true + } + } + omc.Data["AddNew"] = allowAddNew + omc.Data["HasLoggedIn"] = hasLoggedIn + omc.TplName = "optional-menu.htm" + omc.Render() + +} diff --git a/controllers/password.go b/controllers/password.go index 210e5cf9f..1054473e0 100644 --- a/controllers/password.go +++ b/controllers/password.go @@ -1,18 +1,3 @@ -/* - 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. -*/ - package controllers import ( @@ -22,40 +7,13 @@ import ( "regexp" "text/template" + "github.com/astaxie/beego" "github.com/vmware/harbor/dao" "github.com/vmware/harbor/models" "github.com/vmware/harbor/utils" "github.com/vmware/harbor/utils/log" - - "github.com/astaxie/beego" ) -// ChangePasswordController handles request to /changePassword -type ChangePasswordController struct { - BaseController -} - -// Get renders the page for user to change password. -func (cpc *ChangePasswordController) Get() { - sessionUserID := cpc.GetSession("userId") - if sessionUserID == nil { - cpc.Redirect("/signIn", http.StatusFound) - return - } - cpc.Data["Username"] = cpc.GetSession("username") - cpc.ForwardTo("page_title_change_password", "change-password") -} - -// ForgotPasswordController handles request to /forgotPassword -type ForgotPasswordController struct { - BaseController -} - -// Get Renders the page for user to input Email to reset password. -func (fpc *ForgotPasswordController) Get() { - fpc.ForwardTo("page_title_forgot_password", "forgot-password") -} - type messageDetail struct { Hint string URL string @@ -137,6 +95,16 @@ func (cc *CommonController) SendEmail() { } +// ForgotPasswordController handles requests to /forgot_password +type ForgotPasswordController struct { + BaseController +} + +// Get renders forgot password page +func (fpc *ForgotPasswordController) Get() { + fpc.Forward("page_title_forgot_password", "forgot-password.htm") +} + // ResetPasswordController handles request to /resetPassword type ResetPasswordController struct { BaseController @@ -161,7 +129,7 @@ func (rpc *ResetPasswordController) Get() { if user != nil { rpc.Data["ResetUuid"] = user.ResetUUID - rpc.ForwardTo("page_title_reset_password", "reset-password") + rpc.Forward("page_title_reset_password", "reset-password.htm") } else { rpc.Redirect("/", http.StatusFound) } diff --git a/controllers/project.go b/controllers/project.go index 1125bcaa1..86bf299df 100644 --- a/controllers/project.go +++ b/controllers/project.go @@ -1,27 +1,11 @@ -/* - 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. -*/ - package controllers -// ProjectController handles request to /registry/project +// ProjectController handles requests to /project type ProjectController struct { BaseController } -// Get renders project page. -func (p *ProjectController) Get() { - p.Data["Username"] = p.GetSession("username") - p.ForwardTo("page_title_project", "project") +// Get renders project page +func (pc *ProjectController) Get() { + pc.Forward("page_title_project", "project.htm") } diff --git a/controllers/register.go b/controllers/register.go deleted file mode 100644 index d8ed05715..000000000 --- a/controllers/register.go +++ /dev/null @@ -1,87 +0,0 @@ -/* - 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. -*/ - -package controllers - -import ( - "net/http" - - "github.com/vmware/harbor/dao" - "github.com/vmware/harbor/models" - - "github.com/vmware/harbor/utils/log" -) - -// RegisterController handles request to /register -type RegisterController struct { - BaseController -} - -// Get renders the Sign In page, it only works if the auth mode is set to db_auth -func (rc *RegisterController) Get() { - - if !rc.SelfRegistration { - log.Warning("Registration is disabled when self-registration is off.") - rc.Redirect("/signIn", http.StatusFound) - } - - if rc.AuthMode == "db_auth" { - rc.ForwardTo("page_title_registration", "register") - } else { - rc.Redirect("/signIn", http.StatusFound) - } -} - -// AddUserController handles request for adding user with an admin role user -type AddUserController struct { - BaseController -} - -// Get renders the Sign In page, it only works if the auth mode is set to db_auth -func (ac *AddUserController) Get() { - - if !ac.IsAdmin { - log.Warning("Add user can only be used by admin role user.") - ac.Redirect("/signIn", http.StatusFound) - } - - if ac.AuthMode == "db_auth" { - ac.ForwardTo("page_title_add_user", "register") - } else { - ac.Redirect("/signIn", http.StatusFound) - } -} - -// UserExists checks if user exists when user input value in sign in form. -func (cc *CommonController) UserExists() { - target := cc.GetString("target") - value := cc.GetString("value") - - user := models.User{} - switch target { - case "username": - user.Username = value - case "email": - user.Email = value - } - - exist, err := dao.UserExists(user, target) - if err != nil { - log.Errorf("Error occurred in UserExists: %v", err) - cc.CustomAbort(http.StatusInternalServerError, "Internal error.") - } - cc.Data["json"] = exist - cc.ServeJSON() -} diff --git a/controllers/repository.go b/controllers/repository.go new file mode 100644 index 000000000..58ad4dc7e --- /dev/null +++ b/controllers/repository.go @@ -0,0 +1,14 @@ +package controllers + +import "os" + +// RepositoryController handles request to /repository +type RepositoryController struct { + BaseController +} + +// Get renders repository page +func (rc *RepositoryController) Get() { + rc.Data["HarborRegUrl"] = os.Getenv("HARBOR_REG_URL") + rc.Forward("page_title_repository", "repository.htm") +} diff --git a/controllers/search.go b/controllers/search.go index 8762a2975..e9c573751 100644 --- a/controllers/search.go +++ b/controllers/search.go @@ -1,18 +1,3 @@ -/* - 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. -*/ - package controllers // SearchController handles request to /search @@ -20,9 +5,7 @@ type SearchController struct { BaseController } -// Get renders page for displaying search result. +// Get rendlers search bar func (sc *SearchController) Get() { - sc.Data["Username"] = sc.GetSession("username") - sc.Data["QueryParam"] = sc.GetString("q") - sc.ForwardTo("page_title_search", "search") + sc.Forward("page_title_search", "search.htm") } diff --git a/controllers/signin.go b/controllers/signin.go new file mode 100644 index 000000000..8f70acf32 --- /dev/null +++ b/controllers/signin.go @@ -0,0 +1,40 @@ +package controllers + +import ( + "net/http" + + "github.com/vmware/harbor/dao" + "github.com/vmware/harbor/models" + "github.com/vmware/harbor/utils/log" +) + +// SignInController handles requests to /sign_in +type SignInController struct { + BaseController +} + +//Get renders sign_in page +func (sic *SignInController) Get() { + sessionUserID := sic.GetSession("userId") + var hasLoggedIn bool + var username string + if sessionUserID != nil { + hasLoggedIn = true + userID := sessionUserID.(int) + u, err := dao.GetUser(models.User{UserID: userID}) + if err != nil { + log.Errorf("Error occurred in GetUser, error: %v", err) + sic.CustomAbort(http.StatusInternalServerError, "Internal error.") + } + if u == nil { + log.Warningf("User was deleted already, user id: %d, canceling request.", userID) + sic.CustomAbort(http.StatusUnauthorized, "") + } + username = u.Username + } + sic.Data["AuthMode"] = sic.AuthMode + sic.Data["Username"] = username + sic.Data["HasLoggedIn"] = hasLoggedIn + sic.TplName = "sign-in.htm" + sic.Render() +} diff --git a/controllers/signup.go b/controllers/signup.go new file mode 100644 index 000000000..9d46a368b --- /dev/null +++ b/controllers/signup.go @@ -0,0 +1,19 @@ +package controllers + +import ( + "net/http" +) + +// SignUpController handles requests to /sign_up +type SignUpController struct { + BaseController +} + +// Get renders sign up page +func (suc *SignUpController) Get() { + if suc.AuthMode != "db_auth" { + suc.CustomAbort(http.StatusUnauthorized, "Status unauthorized.") + } + suc.Data["AddNew"] = false + suc.Forward("page_title_sign_up", "sign-up.htm") +} diff --git a/dao/accesslog.go b/dao/accesslog.go index dc18e9d66..930607701 100644 --- a/dao/accesslog.go +++ b/dao/accesslog.go @@ -115,3 +115,79 @@ func AccessLog(username, projectName, repoName, repoTag, action string) error { } return err } + +//GetRecentLogs returns recent logs according to parameters +func GetRecentLogs(userID, linesNum int, startTime, endTime string) ([]models.AccessLog, error) { + var recentLogList []models.AccessLog + queryParam := make([]interface{}, 1) + + sql := "select log_id, access_log.user_id, project_id, repo_name, repo_tag, GUID, operation, op_time, username from access_log left join user on access_log.user_id=user.user_id where project_id in (select distinct project_id from project_member where user_id = ?)" + queryParam = append(queryParam, userID) + if startTime != "" { + sql += " and op_time >= ?" + queryParam = append(queryParam, startTime) + } + + if endTime != "" { + sql += " and op_time <= ?" + queryParam = append(queryParam, endTime) + } + + sql += " order by op_time desc" + if linesNum != 0 { + sql += " limit ?" + queryParam = append(queryParam, linesNum) + } + o := GetOrmer() + _, err := o.Raw(sql, queryParam).QueryRows(&recentLogList) + if err != nil { + return nil, err + } + return recentLogList, nil +} + +//GetTopRepos return top accessed public repos +func GetTopRepos(countNum int) ([]models.TopRepo, error) { + + o := GetOrmer() + // hide the where condition: project.public = 1, Can add to the sql when necessary. + sql := "select repo_name, COUNT(repo_name) as access_count from access_log left join project on access_log.project_id=project.project_id where access_log.operation = 'pull' group by repo_name order by access_count desc limit ? " + queryParam := []interface{}{} + queryParam = append(queryParam, countNum) + var list []models.TopRepo + _, err := o.Raw(sql, queryParam).QueryRows(&list) + if err != nil { + return nil, err + } + if len(list) == 0 { + return list, nil + } + placeHolder := make([]string, len(list)) + repos := make([]string, len(list)) + for i, v := range list { + repos[i] = v.RepoName + placeHolder[i] = "?" + } + placeHolderStr := strings.Join(placeHolder, ",") + queryParam = nil + queryParam = append(queryParam, repos) + var usrnameList []models.TopRepo + sql = `select a.username as creator, a.repo_name from (select access_log.repo_name, user.username, + access_log.op_time from user left join access_log on user.user_id = access_log.user_id where + access_log.operation = 'push' and access_log.repo_name in (######) order by access_log.repo_name, + access_log.op_time ASC) a group by a.repo_name` + sql = strings.Replace(sql, "######", placeHolderStr, 1) + _, err = o.Raw(sql, queryParam).QueryRows(&usrnameList) + if err != nil { + return nil, err + } + for i := 0; i < len(list); i++ { + for _, v := range usrnameList { + if v.RepoName == list[i].RepoName { + list[i].Creator = v.Creator + break + } + } + } + return list, nil +} diff --git a/dao/base.go b/dao/base.go index 5a74252c8..97bc55ad0 100644 --- a/dao/base.go +++ b/dao/base.go @@ -18,39 +18,18 @@ package dao import ( "net" - "github.com/vmware/harbor/utils/log" - "os" - "strings" "sync" "time" "github.com/astaxie/beego/orm" _ "github.com/go-sql-driver/mysql" //register mysql driver + "github.com/vmware/harbor/utils/log" ) // NonExistUserID : if a user does not exist, the ID of the user will be 0. const NonExistUserID = 0 -func isIllegalLength(s string, min int, max int) bool { - if min == -1 { - return (len(s) > max) - } - if max == -1 { - return (len(s) <= min) - } - return (len(s) < min || len(s) > max) -} - -func isContainIllegalChar(s string, illegalChar []string) bool { - for _, c := range illegalChar { - if strings.Index(s, c) >= 0 { - return true - } - } - return false -} - // GenerateRandomString generates a random string func GenerateRandomString() (string, error) { o := orm.NewOrm() @@ -65,6 +44,7 @@ func GenerateRandomString() (string, error) { //InitDB initializes the database func InitDB() { + // orm.Debug = true orm.RegisterDriver("mysql", orm.DRMySQL) addr := os.Getenv("MYSQL_HOST") port := os.Getenv("MYSQL_PORT") diff --git a/dao/dao_test.go b/dao/dao_test.go index e281a8d2c..699224700 100644 --- a/dao/dao_test.go +++ b/dao/dao_test.go @@ -20,20 +20,18 @@ import ( "testing" "time" - "github.com/vmware/harbor/utils/log" - - "github.com/vmware/harbor/models" - "github.com/astaxie/beego/orm" + "github.com/vmware/harbor/models" + "github.com/vmware/harbor/utils/log" ) -func execUpdate(o orm.Ormer, sql string, params interface{}) error { +func execUpdate(o orm.Ormer, sql string, params ...interface{}) error { p, err := o.Raw(sql).Prepare() if err != nil { return err } defer p.Close() - _, err = p.Exec(params) + _, err = p.Exec(params...) if err != nil { return err } @@ -97,6 +95,19 @@ func clearUp(username string) { o.Rollback() log.Error(err) } + + err = execUpdate(o, `delete from replication_job where id < 99`) + if err != nil { + log.Error(err) + } + err = execUpdate(o, `delete from replication_policy where id < 99`) + if err != nil { + log.Error(err) + } + err = execUpdate(o, `delete from replication_target where id < 99`) + if err != nil { + log.Error(err) + } o.Commit() } @@ -678,7 +689,7 @@ func TestDeleteProjectMember(t *testing.T) { } func TestToggleAdminRole(t *testing.T) { - err := ToggleUserAdminRole(*currentUser) + err := ToggleUserAdminRole(currentUser.UserID, 1) if err != nil { t.Errorf("Error in toggle ToggleUserAdmin role: %v, user: %+v", err, currentUser) } @@ -689,7 +700,7 @@ func TestToggleAdminRole(t *testing.T) { if !isAdmin { t.Errorf("User is not admin after toggled, user id: %d", currentUser.UserID) } - err = ToggleUserAdminRole(*currentUser) + err = ToggleUserAdminRole(currentUser.UserID, 0) if err != nil { t.Errorf("Error in toggle ToggleUserAdmin role: %v, user: %+v", err, currentUser) } @@ -702,6 +713,78 @@ func TestToggleAdminRole(t *testing.T) { } } +func TestChangeUserProfile(t *testing.T) { + user := models.User{UserID: currentUser.UserID, Email: username + "@163.com", Realname: "test", Comment: "Unit Test"} + err := ChangeUserProfile(user) + if err != nil { + t.Errorf("Error occurred in ChangeUserProfile: %v", err) + } + loginedUser, err := GetUser(models.User{UserID: currentUser.UserID}) + if err != nil { + t.Errorf("Error occurred in GetUser: %v", err) + } + if loginedUser != nil { + if loginedUser.Email != username+"@163.com" { + t.Errorf("user email does not update, expected: %s, acutal: %s", username+"@163.com", loginedUser.Email) + } + if loginedUser.Realname != "test" { + t.Errorf("user realname does not update, expected: %s, acutal: %s", "test", loginedUser.Realname) + } + if loginedUser.Comment != "Unit Test" { + t.Errorf("user email does not update, expected: %s, acutal: %s", "Unit Test", loginedUser.Comment) + } + } +} + +func TestGetRecentLogs(t *testing.T) { + logs, err := GetRecentLogs(currentUser.UserID, 10, "2016-05-13 00:00:00", time.Now().String()) + if err != nil { + t.Errorf("error occured in getting recent logs, error: %v", err) + } + if len(logs) <= 0 { + t.Errorf("get logs error, expected: %d, actual: %d", 1, len(logs)) + } +} + +func TestGetTopRepos(t *testing.T) { + + err := ToggleProjectPublicity(currentProject.ProjectID, publicityOn) + if err != nil { + t.Errorf("Error occurred in ToggleProjectPublicity: %v", err) + } + err = AccessLog(currentUser.Username, currentProject.Name, currentProject.Name+"/ubuntu", repoTag2, "push") + if err != nil { + t.Errorf("Error occurred in AccessLog: %v", err) + } + err = AccessLog(currentUser.Username, currentProject.Name, currentProject.Name+"/ubuntu", repoTag2, "pull") + if err != nil { + t.Errorf("Error occurred in AccessLog: %v", err) + } + topRepos, err := GetTopRepos(10) + if err != nil { + t.Errorf("error occured in getting top repos, error: %v", err) + } + if topRepos[0].RepoName != currentProject.Name+"/ubuntu" { + t.Errorf("error occured in get top reop's name, expected: %v, actual: %v", currentProject.Name+"/ubuntu", topRepos[0].RepoName) + } + if topRepos[0].AccessCount != 1 { + t.Errorf("error occured in get top reop's access count, expected: %v, actual: %v", 1, topRepos[0].AccessCount) + } + if topRepos[0].Creator != currentUser.Username { + t.Errorf("error occured in get top reop's creator, expected: %v, actual: %v", currentUser.Username, topRepos[0].Creator) + } + err = ToggleProjectPublicity(currentProject.ProjectID, publicityOff) + if err != nil { + t.Errorf("Error occurred in ToggleProjectPublicity: %v", err) + } + o := GetOrmer() + _, err = o.QueryTable("access_log").Filter("operation__in", "push,pull").Delete() + if err != nil { + t.Errorf("error occurred in deleting access logs, %v", err) + } + +} + func TestDeleteUser(t *testing.T) { err := DeleteUser(currentUser.UserID) if err != nil { @@ -716,6 +799,573 @@ func TestDeleteUser(t *testing.T) { } } +var targetID, policyID, policyID2, policyID3, jobID, jobID2, jobID3 int64 + +func TestAddRepTarget(t *testing.T) { + target := models.RepTarget{ + Name: "test", + URL: "127.0.0.1:5000", + Username: "admin", + Password: "admin", + } + //_, err := AddRepTarget(target) + id, err := AddRepTarget(target) + t.Logf("added target, id: %d", id) + if err != nil { + t.Errorf("Error occurred in AddRepTarget: %v", err) + } else { + targetID = id + } + id2 := id + 99 + tgt, err := GetRepTarget(id2) + if err != nil { + t.Errorf("Error occurred in GetTarget: %v, id: %d", err, id2) + } + if tgt != nil { + t.Errorf("There should not be a target with id: %d", id2) + } + tgt, err = GetRepTarget(id) + if err != nil { + t.Errorf("Error occurred in GetTarget: %v, id: %d", err, id) + } + if tgt == nil { + t.Errorf("Unable to find a target with id: %d", id) + } + if tgt.URL != "127.0.0.1:5000" { + t.Errorf("Unexpected url in target: %s, expected 127.0.0.1:5000", tgt.URL) + } + if tgt.Username != "admin" { + t.Errorf("Unexpected username in target: %s, expected admin", tgt.Username) + } +} + +func TestGetRepTargetByName(t *testing.T) { + target, err := GetRepTarget(targetID) + if err != nil { + t.Fatalf("failed to get target %d: %v", targetID, err) + } + + target2, err := GetRepTargetByName(target.Name) + if err != nil { + t.Fatalf("failed to get target %s: %v", target.Name, err) + } + + if target.Name != target2.Name { + t.Errorf("unexpected target name: %s, expected: %s", target2.Name, target.Name) + } +} + +func TestUpdateRepTarget(t *testing.T) { + target := &models.RepTarget{ + Name: "name", + URL: "http://url", + Username: "username", + Password: "password", + } + + id, err := AddRepTarget(*target) + if err != nil { + t.Fatalf("failed to add target: %v", err) + } + defer func() { + if err := DeleteRepTarget(id); err != nil { + t.Logf("failed to delete target %d: %v", id, err) + } + }() + + target.ID = id + target.Name = "new_name" + target.URL = "http://new_url" + target.Username = "new_username" + target.Password = "new_password" + + if err = UpdateRepTarget(*target); err != nil { + t.Fatalf("failed to update target: %v", err) + } + + target, err = GetRepTarget(id) + if err != nil { + t.Fatalf("failed to get target %d: %v", id, err) + } + + if target.Name != "new_name" { + t.Errorf("unexpected name: %s, expected: %s", target.Name, "new_name") + } + + if target.URL != "http://new_url" { + t.Errorf("unexpected url: %s, expected: %s", target.URL, "http://new_url") + } + + if target.Username != "new_username" { + t.Errorf("unexpected username: %s, expected: %s", target.Username, "new_username") + } + + if target.Password != "new_password" { + t.Errorf("unexpected password: %s, expected: %s", target.Password, "new_password") + } +} + +func TestFilterRepTargets(t *testing.T) { + targets, err := FilterRepTargets("test") + if err != nil { + t.Fatalf("failed to get all targets: %v", err) + } + + if len(targets) == 0 { + t.Errorf("unexpected num of targets: %d, expected: %d", len(targets), 1) + } +} + +func TestAddRepPolicy(t *testing.T) { + policy := models.RepPolicy{ + ProjectID: 1, + Enabled: 1, + TargetID: targetID, + Description: "whatever", + Name: "mypolicy", + } + id, err := AddRepPolicy(policy) + t.Logf("added policy, id: %d", id) + if err != nil { + t.Errorf("Error occurred in AddRepPolicy: %v", err) + } else { + policyID = id + } + p, err := GetRepPolicy(id) + if err != nil { + t.Errorf("Error occurred in GetPolicy: %v, id: %d", err, id) + } + if p == nil { + t.Errorf("Unable to find a policy with id: %d", id) + } + + if p.Name != "mypolicy" || p.TargetID != targetID || p.Enabled != 1 || p.Description != "whatever" { + t.Errorf("The data does not match, expected: Name: mypolicy, TargetID: %d, Enabled: 1, Description: whatever;\n result: Name: %s, TargetID: %d, Enabled: %d, Description: %s", + targetID, p.Name, p.TargetID, p.Enabled, p.Description) + } + var tm = time.Now().AddDate(0, 0, -1) + if !p.StartTime.After(tm) { + t.Errorf("Unexpected start_time: %v", p.StartTime) + } + +} + +func TestGetRepPolicyByTarget(t *testing.T) { + policies, err := GetRepPolicyByTarget(targetID) + if err != nil { + t.Fatalf("failed to get policy according target %d: %v", targetID, err) + } + + if len(policies) == 0 { + t.Fatal("unexpected length of policies 0, expected is >0") + } + + if policies[0].ID != policyID { + t.Fatalf("unexpected policy: %d, expected: %d", policies[0].ID, policyID) + } +} + +func TestGetRepPolicyByProjectAndTarget(t *testing.T) { + policies, err := GetRepPolicyByProjectAndTarget(1, targetID) + if err != nil { + t.Fatalf("failed to get policy according project %d and target %d: %v", 1, targetID, err) + } + + if len(policies) == 0 { + t.Fatal("unexpected length of policies 0, expected is >0") + } + + if policies[0].ID != policyID { + t.Fatalf("unexpected policy: %d, expected: %d", policies[0].ID, policyID) + } +} + +func TestGetRepPolicyByName(t *testing.T) { + policy, err := GetRepPolicy(policyID) + if err != nil { + t.Fatalf("failed to get policy %d: %v", policyID, err) + } + + policy2, err := GetRepPolicyByName(policy.Name) + if err != nil { + t.Fatalf("failed to get policy %s: %v", policy.Name, err) + } + + if policy.Name != policy2.Name { + t.Errorf("unexpected name: %s, expected: %s", policy2.Name, policy.Name) + } + +} + +func TestDisableRepPolicy(t *testing.T) { + err := DisableRepPolicy(policyID) + if err != nil { + t.Errorf("Failed to disable policy, id: %d", policyID) + } + p, err := GetRepPolicy(policyID) + if err != nil { + t.Errorf("Error occurred in GetPolicy: %v, id: %d", err, policyID) + } + if p == nil { + t.Errorf("Unable to find a policy with id: %d", policyID) + } + if p.Enabled == 1 { + t.Errorf("The Enabled value of replication policy is still 1 after disabled, id: %d", policyID) + } +} + +func TestEnableRepPolicy(t *testing.T) { + err := EnableRepPolicy(policyID) + if err != nil { + t.Errorf("Failed to disable policy, id: %d", policyID) + } + p, err := GetRepPolicy(policyID) + if err != nil { + t.Errorf("Error occurred in GetPolicy: %v, id: %d", err, policyID) + } + if p == nil { + t.Errorf("Unable to find a policy with id: %d", policyID) + } + if p.Enabled == 0 { + t.Errorf("The Enabled value of replication policy is still 0 after disabled, id: %d", policyID) + } +} + +func TestAddRepPolicy2(t *testing.T) { + policy2 := models.RepPolicy{ + ProjectID: 3, + Enabled: 0, + TargetID: 3, + Description: "whatever", + Name: "mypolicy", + } + policyID2, err := AddRepPolicy(policy2) + t.Logf("added policy, id: %d", policyID2) + if err != nil { + t.Errorf("Error occurred in AddRepPolicy: %v", err) + } + p, err := GetRepPolicy(policyID2) + if err != nil { + t.Errorf("Error occurred in GetPolicy: %v, id: %d", err, policyID2) + } + if p == nil { + t.Errorf("Unable to find a policy with id: %d", policyID2) + } + var tm time.Time + if p.StartTime.After(tm) { + t.Errorf("Unexpected start_time: %v", p.StartTime) + } +} + +func TestAddRepJob(t *testing.T) { + job := models.RepJob{ + Repository: "library/ubuntu", + PolicyID: policyID, + Operation: "transfer", + TagList: []string{"12.01", "14.04", "latest"}, + } + id, err := AddRepJob(job) + if err != nil { + t.Errorf("Error occurred in AddRepJob: %v", err) + return + } + jobID = id + + j, err := GetRepJob(id) + if err != nil { + t.Errorf("Error occurred in GetRepJob: %v, id: %d", err, id) + return + } + if j == nil { + t.Errorf("Unable to find a job with id: %d", id) + return + } + if j.Status != models.JobPending || j.Repository != "library/ubuntu" || j.PolicyID != policyID || j.Operation != "transfer" || len(j.TagList) != 3 { + t.Errorf("Expected data of job, id: %d, Status: %s, Repository: library/ubuntu, PolicyID: %d, Operation: transfer, taglist length 3"+ + "but in returned data:, Status: %s, Repository: %s, Operation: %s, PolicyID: %d, TagList: %v", id, models.JobPending, policyID, j.Status, j.Repository, j.Operation, j.PolicyID, j.TagList) + return + } +} + +func TestUpdateRepJobStatus(t *testing.T) { + err := UpdateRepJobStatus(jobID, models.JobFinished) + if err != nil { + t.Errorf("Error occured in UpdateRepJobStatus, error: %v, id: %d", err, jobID) + return + } + j, err := GetRepJob(jobID) + if err != nil { + t.Errorf("Error occurred in GetRepJob: %v, id: %d", err, jobID) + } + if j == nil { + t.Errorf("Unable to find a job with id: %d", jobID) + } + if j.Status != models.JobFinished { + t.Errorf("Job's status: %s, expected: %s, id: %d", j.Status, models.JobFinished, jobID) + } + err = UpdateRepJobStatus(jobID, models.JobPending) + if err != nil { + t.Errorf("Error occured in UpdateRepJobStatus when update it back to status pending, error: %v, id: %d", err, jobID) + return + } +} + +func TestGetRepPolicyByProject(t *testing.T) { + p1, err := GetRepPolicyByProject(99) + if err != nil { + t.Errorf("Error occured in GetRepPolicyByProject:%v, project ID: %d", err, 99) + return + } + if len(p1) > 0 { + t.Errorf("Unexpected length of policy list, expected: 0, in fact: %d, project id: %d", len(p1), 99) + return + } + + p2, err := GetRepPolicyByProject(1) + if err != nil { + t.Errorf("Error occuered in GetRepPolicyByProject:%v, project ID: %d", err, 2) + return + } + if len(p2) != 1 { + t.Errorf("Unexpected length of policy list, expected: 1, in fact: %d, project id: %d", len(p2), 1) + return + } + if p2[0].ID != policyID { + t.Errorf("Unexpecred policy id in result, expected: %d, in fact: %d", policyID, p2[0].ID) + return + } +} + +func TestGetRepJobByPolicy(t *testing.T) { + jobs, err := GetRepJobByPolicy(999) + if err != nil { + t.Errorf("Error occured in GetRepJobByPolicy: %v, policy ID: %d", err, 999) + return + } + if len(jobs) > 0 { + t.Errorf("Unexpected length of jobs, expected: 0, in fact: %d", len(jobs)) + return + } + jobs, err = GetRepJobByPolicy(policyID) + if err != nil { + t.Errorf("Error occured in GetRepJobByPolicy: %v, policy ID: %d", err, policyID) + return + } + if len(jobs) != 1 { + t.Errorf("Unexpected length of jobs, expected: 1, in fact: %d", len(jobs)) + return + } + if jobs[0].ID != jobID { + t.Errorf("Unexpected job ID in the result, expected: %d, in fact: %d", jobID, jobs[0].ID) + return + } +} + +func TestFilterRepJobs(t *testing.T) { + jobs, err := FilterRepJobs(policyID, "", "", nil, nil, 1000) + if err != nil { + t.Errorf("Error occured in FilterRepJobs: %v, policy ID: %d", err, policyID) + return + } + if len(jobs) != 1 { + t.Errorf("Unexpected length of jobs, expected: 1, in fact: %d", len(jobs)) + return + } + if jobs[0].ID != jobID { + t.Errorf("Unexpected job ID in the result, expected: %d, in fact: %d", jobID, jobs[0].ID) + return + } +} + +func TestDeleteRepJob(t *testing.T) { + err := DeleteRepJob(jobID) + if err != nil { + t.Errorf("Error occured in DeleteRepJob: %v, id: %d", err, jobID) + return + } + t.Logf("deleted rep job, id: %d", jobID) + j, err := GetRepJob(jobID) + if err != nil { + t.Errorf("Error occured in GetRepJob:%v", err) + return + } + if j != nil { + t.Errorf("Able to find rep job after deletion, id: %d", jobID) + return + } +} + +func TestGetRepoJobToStop(t *testing.T) { + jobs := [...]models.RepJob{ + models.RepJob{ + Repository: "library/ubuntu", + PolicyID: policyID, + Operation: "transfer", + Status: models.JobRunning, + }, + models.RepJob{ + Repository: "library/ubuntu", + PolicyID: policyID, + Operation: "transfer", + Status: models.JobFinished, + }, + models.RepJob{ + Repository: "library/ubuntu", + PolicyID: policyID, + Operation: "transfer", + Status: models.JobCanceled, + }, + } + var err error + var i int64 + var ids []int64 + for _, j := range jobs { + i, err = AddRepJob(j) + ids = append(ids, i) + if err != nil { + log.Errorf("Failed to add Job: %+v, error: %v", j, err) + return + } + } + res, err := GetRepJobToStop(policyID) + if err != nil { + log.Errorf("Failed to Get Jobs, error: %v", err) + return + } + //time.Sleep(15 * time.Second) + if len(res) != 1 { + log.Errorf("Expected length of stoppable jobs, expected:1, in fact: %d", len(res)) + return + } + for _, id := range ids { + err = DeleteRepJob(id) + if err != nil { + log.Errorf("Failed to delete job, id: %d, error: %v", id, err) + return + } + } +} + +func TestDeleteRepTarget(t *testing.T) { + err := DeleteRepTarget(targetID) + if err != nil { + t.Errorf("Error occured in DeleteRepTarget: %v, id: %d", err, targetID) + return + } + t.Logf("deleted target, id: %d", targetID) + tgt, err := GetRepTarget(targetID) + if err != nil { + t.Errorf("Error occurred in GetTarget: %v, id: %d", err, targetID) + } + if tgt != nil { + t.Errorf("Able to find target after deletion, id: %d", targetID) + } +} + +func TestFilterRepPolicies(t *testing.T) { + _, err := FilterRepPolicies("name", 0) + if err != nil { + t.Fatalf("failed to filter policy: %v", err) + } +} + +func TestUpdateRepPolicy(t *testing.T) { + policy := &models.RepPolicy{ + ID: policyID, + Name: "new_policy_name", + } + if err := UpdateRepPolicy(policy); err != nil { + t.Fatalf("failed to update policy") + } +} + +func TestDeleteRepPolicy(t *testing.T) { + err := DeleteRepPolicy(policyID) + if err != nil { + t.Errorf("Error occured in DeleteRepPolicy: %v, id: %d", err, policyID) + return + } + t.Logf("delete rep policy, id: %d", policyID) + p, err := GetRepPolicy(policyID) + if err != nil && err != orm.ErrNoRows { + t.Errorf("Error occured in GetRepPolicy:%v", err) + } + if p != nil { + t.Errorf("Able to find rep policy after deletion, id: %d", policyID) + } +} + +func TestResetRepJobs(t *testing.T) { + + job1 := models.RepJob{ + Repository: "library/ubuntua", + PolicyID: policyID, + Operation: "transfer", + Status: models.JobRunning, + } + job2 := models.RepJob{ + Repository: "library/ubuntub", + PolicyID: policyID, + Operation: "transfer", + Status: models.JobCanceled, + } + id1, err := AddRepJob(job1) + if err != nil { + t.Errorf("Failed to add job: %+v, error: %v", job1, err) + return + } + id2, err := AddRepJob(job2) + if err != nil { + t.Errorf("Failed to add job: %+v, error: %v", job2, err) + return + } + err = ResetRunningJobs() + if err != nil { + t.Errorf("Failed to reset running jobs, error: %v", err) + } + j1, err := GetRepJob(id1) + if err != nil { + t.Errorf("Failed to get rep job, id: %d, error: %v", id1, err) + return + } + if j1.Status != models.JobPending { + t.Errorf("The rep job: %d, status should be Pending, but infact: %s", id1, j1.Status) + return + } + j2, err := GetRepJob(id2) + if err != nil { + t.Errorf("Failed to get rep job, id: %d, error: %v", id2, err) + return + } + if j2.Status == models.JobPending { + t.Errorf("The rep job: %d, status should be Canceled, but infact: %s", id2, j2.Status) + return + } +} + +func TestGetJobByStatus(t *testing.T) { + r1, err := GetRepJobByStatus(models.JobPending, models.JobRunning) + if err != nil { + t.Errorf("Failed to run GetRepJobByStatus, error: %v", err) + } + if len(r1) != 1 { + t.Errorf("Unexpected length of result, expected 1, but in fact:%d", len(r1)) + return + } + + r2, err := GetRepJobByStatus(models.JobPending, models.JobCanceled) + if err != nil { + t.Errorf("Failed to run GetRepJobByStatus, error: %v", err) + } + if len(r2) != 2 { + t.Errorf("Unexpected length of result, expected 2, but in fact:%d", len(r2)) + return + } + for _, j := range r2 { + DeleteRepJob(j.ID) + } +} + func TestGetOrmer(t *testing.T) { o := GetOrmer() if o == nil { diff --git a/dao/project.go b/dao/project.go index 60f32e367..b8a158692 100644 --- a/dao/project.go +++ b/dao/project.go @@ -18,7 +18,6 @@ package dao import ( "github.com/vmware/harbor/models" - "errors" "fmt" "time" @@ -30,15 +29,7 @@ import ( // AddProject adds a project to the database along with project roles information and access log records. func AddProject(project models.Project) (int64, error) { - if isIllegalLength(project.Name, 4, 30) { - return 0, errors.New("project name is illegal in length. (greater than 4 or less than 30)") - } - if isContainIllegalChar(project.Name, []string{"~", "-", "$", "\\", "[", "]", "{", "}", "(", ")", "&", "^", "%", "*", "<", ">", "\"", "'", "/", "?", "@"}) { - return 0, errors.New("project name contains illegal characters") - } - o := GetOrmer() - p, err := o.Raw("insert into project (owner_id, name, creation_time, update_time, deleted, public) values (?, ?, ?, ?, ?, ?)").Prepare() if err != nil { return 0, err diff --git a/dao/projectmember.go b/dao/projectmember.go index 52947f869..4c165aa82 100644 --- a/dao/projectmember.go +++ b/dao/projectmember.go @@ -58,7 +58,7 @@ func DeleteProjectMember(projectID int64, userID int) error { func GetUserByProject(projectID int64, queryUser models.User) ([]models.User, error) { o := GetOrmer() u := []models.User{} - sql := `select u.user_id, u.username, r.name rolename, r.role_id + sql := `select u.user_id, u.username, r.name rolename, r.role_id as role from user u join project_member pm on pm.project_id = ? and u.user_id = pm.user_id diff --git a/dao/register.go b/dao/register.go index 3dc388c16..c7135df62 100644 --- a/dao/register.go +++ b/dao/register.go @@ -17,7 +17,6 @@ package dao import ( "errors" - "regexp" "time" "github.com/vmware/harbor/models" @@ -26,14 +25,7 @@ import ( // Register is used for user to register, the password is encrypted before the record is inserted into database. func Register(user models.User) (int64, error) { - - err := validate(user) - if err != nil { - return 0, err - } - o := GetOrmer() - p, err := o.Raw("insert into user (username, password, realname, email, comment, salt, sysadmin_flag, creation_time, update_time) values (?, ?, ?, ?, ?, ?, ?, ?, ?)").Prepare() if err != nil { return 0, err @@ -59,46 +51,6 @@ func Register(user models.User) (int64, error) { return userID, nil } -func validate(user models.User) error { - - if isIllegalLength(user.Username, 0, 20) { - return errors.New("Username with illegal length.") - } - if isContainIllegalChar(user.Username, []string{",", "~", "#", "$", "%"}) { - return errors.New("Username contains illegal characters.") - } - - if exist, _ := UserExists(models.User{Username: user.Username}, "username"); exist { - return errors.New("Username already exists.") - } - - if len(user.Email) > 0 { - if m, _ := regexp.MatchString(`^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$`, user.Email); !m { - return errors.New("Email with illegal format.") - } - if exist, _ := UserExists(models.User{Email: user.Email}, "email"); exist { - return errors.New("Email already exists.") - } - } - - if isIllegalLength(user.Realname, 0, 20) { - return errors.New("Realname with illegal length.") - } - - if isContainIllegalChar(user.Realname, []string{",", "~", "#", "$", "%"}) { - return errors.New("Realname contains illegal characters.") - } - - if isIllegalLength(user.Password, 0, 20) { - return errors.New("Password with illegal length.") - } - - if isIllegalLength(user.Comment, -1, 30) { - return errors.New("Comment with illegal length.") - } - return nil -} - // UserExists returns whether a user exists according username or Email. func UserExists(user models.User, target string) (bool, error) { diff --git a/dao/replication_job.go b/dao/replication_job.go new file mode 100644 index 000000000..adc47391c --- /dev/null +++ b/dao/replication_job.go @@ -0,0 +1,423 @@ +/* + 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. +*/ + +package dao + +import ( + "fmt" + "time" + + "strings" + + "github.com/astaxie/beego/orm" + "github.com/vmware/harbor/models" +) + +// AddRepTarget ... +func AddRepTarget(target models.RepTarget) (int64, error) { + o := GetOrmer() + return o.Insert(&target) +} + +// GetRepTarget ... +func GetRepTarget(id int64) (*models.RepTarget, error) { + o := GetOrmer() + t := models.RepTarget{ID: id} + err := o.Read(&t) + if err == orm.ErrNoRows { + return nil, nil + } + return &t, err +} + +// GetRepTargetByName ... +func GetRepTargetByName(name string) (*models.RepTarget, error) { + o := GetOrmer() + t := models.RepTarget{Name: name} + err := o.Read(&t, "Name") + if err == orm.ErrNoRows { + return nil, nil + } + return &t, err +} + +// GetRepTargetByConnInfo ... +func GetRepTargetByConnInfo(endpoint, username string) (*models.RepTarget, error) { + o := GetOrmer() + t := models.RepTarget{ + URL: endpoint, + Username: username, + } + err := o.Read(&t, "URL", "Username") + if err == orm.ErrNoRows { + return nil, nil + } + return &t, err +} + +// DeleteRepTarget ... +func DeleteRepTarget(id int64) error { + o := GetOrmer() + _, err := o.Delete(&models.RepTarget{ID: id}) + return err +} + +// UpdateRepTarget ... +func UpdateRepTarget(target models.RepTarget) error { + o := GetOrmer() + _, err := o.Update(&target, "URL", "Name", "Username", "Password") + return err +} + +// FilterRepTargets filters targets by name +func FilterRepTargets(name string) ([]*models.RepTarget, error) { + o := GetOrmer() + + var args []interface{} + + sql := `select * from replication_target ` + if len(name) != 0 { + sql += `where name like ? ` + args = append(args, "%"+name+"%") + } + sql += `order by creation_time` + + var targets []*models.RepTarget + + if _, err := o.Raw(sql, args).QueryRows(&targets); err != nil { + return nil, err + } + + return targets, nil +} + +// AddRepPolicy ... +func AddRepPolicy(policy models.RepPolicy) (int64, error) { + o := GetOrmer() + sqlTpl := `insert into replication_policy (name, project_id, target_id, enabled, description, cron_str, start_time, creation_time, update_time ) values (?, ?, ?, ?, ?, ?, %s, NOW(), NOW())` + var sql string + if policy.Enabled == 1 { + sql = fmt.Sprintf(sqlTpl, "NOW()") + } else { + sql = fmt.Sprintf(sqlTpl, "NULL") + } + p, err := o.Raw(sql).Prepare() + if err != nil { + return 0, err + } + r, err := p.Exec(policy.Name, policy.ProjectID, policy.TargetID, policy.Enabled, policy.Description, policy.CronStr) + if err != nil { + return 0, err + } + id, err := r.LastInsertId() + return id, err +} + +// GetRepPolicy ... +func GetRepPolicy(id int64) (*models.RepPolicy, error) { + o := GetOrmer() + sql := `select * from replication_policy where id = ?` + + var policy models.RepPolicy + + if err := o.Raw(sql, id).QueryRow(&policy); err != nil { + if err == orm.ErrNoRows { + return nil, nil + } + return nil, err + } + + return &policy, nil +} + +// FilterRepPolicies filters policies by name and project ID +func FilterRepPolicies(name string, projectID int64) ([]*models.RepPolicy, error) { + o := GetOrmer() + + var args []interface{} + + sql := `select rp.id, rp.project_id, p.name as project_name, rp.target_id, + rt.name as target_name, rp.name, rp.enabled, rp.description, + rp.cron_str, rp.start_time, rp.creation_time, rp.update_time, + count(rj.status) as error_job_count + from replication_policy rp + left join project p on rp.project_id=p.project_id + left join replication_target rt on rp.target_id=rt.id + left join replication_job rj on rp.id=rj.policy_id and (rj.status="error" + or rj.status="retrying") ` + + if len(name) != 0 && projectID != 0 { + sql += `where rp.name like ? and rp.project_id = ? ` + args = append(args, "%"+name+"%") + args = append(args, projectID) + } else if len(name) != 0 { + sql += `where rp.name like ? ` + args = append(args, "%"+name+"%") + } else if projectID != 0 { + sql += `where rp.project_id = ? ` + args = append(args, projectID) + } + + sql += `group by rp.id order by rp.creation_time` + + var policies []*models.RepPolicy + if _, err := o.Raw(sql, args).QueryRows(&policies); err != nil { + return nil, err + } + return policies, nil +} + +// GetRepPolicyByName ... +func GetRepPolicyByName(name string) (*models.RepPolicy, error) { + o := GetOrmer() + sql := `select * from replication_policy where name = ?` + + var policy models.RepPolicy + + if err := o.Raw(sql, name).QueryRow(&policy); err != nil { + if err == orm.ErrNoRows { + return nil, nil + } + return nil, err + } + + return &policy, nil +} + +// GetRepPolicyByProject ... +func GetRepPolicyByProject(projectID int64) ([]*models.RepPolicy, error) { + o := GetOrmer() + sql := `select * from replication_policy where project_id = ?` + + var policies []*models.RepPolicy + + if _, err := o.Raw(sql, projectID).QueryRows(&policies); err != nil { + return nil, err + } + + return policies, nil +} + +// GetRepPolicyByTarget ... +func GetRepPolicyByTarget(targetID int64) ([]*models.RepPolicy, error) { + o := GetOrmer() + sql := `select * from replication_policy where target_id = ?` + + var policies []*models.RepPolicy + + if _, err := o.Raw(sql, targetID).QueryRows(&policies); err != nil { + return nil, err + } + + return policies, nil +} + +// GetRepPolicyByProjectAndTarget ... +func GetRepPolicyByProjectAndTarget(projectID, targetID int64) ([]*models.RepPolicy, error) { + o := GetOrmer() + sql := `select * from replication_policy where project_id = ? and target_id = ?` + + var policies []*models.RepPolicy + + if _, err := o.Raw(sql, projectID, targetID).QueryRows(&policies); err != nil { + return nil, err + } + + return policies, nil +} + +// UpdateRepPolicy ... +func UpdateRepPolicy(policy *models.RepPolicy) error { + o := GetOrmer() + _, err := o.Update(policy, "TargetID", "Name", "Enabled", "Description", "CronStr") + return err +} + +// DeleteRepPolicy ... +func DeleteRepPolicy(id int64) error { + o := GetOrmer() + _, err := o.Delete(&models.RepPolicy{ID: id}) + return err +} + +// UpdateRepPolicyEnablement ... +func UpdateRepPolicyEnablement(id int64, enabled int) error { + o := GetOrmer() + p := models.RepPolicy{ + ID: id, + Enabled: enabled, + } + + var err error + if enabled == 1 { + p.StartTime = time.Now() + _, err = o.Update(&p, "Enabled", "StartTime") + } else { + _, err = o.Update(&p, "Enabled") + } + + return err +} + +// EnableRepPolicy ... +func EnableRepPolicy(id int64) error { + return UpdateRepPolicyEnablement(id, 1) +} + +// DisableRepPolicy ... +func DisableRepPolicy(id int64) error { + return UpdateRepPolicyEnablement(id, 0) +} + +// AddRepJob ... +func AddRepJob(job models.RepJob) (int64, error) { + o := GetOrmer() + if len(job.Status) == 0 { + job.Status = models.JobPending + } + if len(job.TagList) > 0 { + job.Tags = strings.Join(job.TagList, ",") + } + return o.Insert(&job) +} + +// GetRepJob ... +func GetRepJob(id int64) (*models.RepJob, error) { + o := GetOrmer() + j := models.RepJob{ID: id} + err := o.Read(&j) + if err == orm.ErrNoRows { + return nil, nil + } + genTagListForJob(&j) + return &j, nil +} + +// GetRepJobByPolicy ... +func GetRepJobByPolicy(policyID int64) ([]*models.RepJob, error) { + var res []*models.RepJob + _, err := repJobPolicyIDQs(policyID).All(&res) + genTagListForJob(res...) + return res, err +} + +// FilterRepJobs filters jobs by repo and policy ID +func FilterRepJobs(policyID int64, repository, status string, startTime, + endTime *time.Time, limit int) ([]*models.RepJob, error) { + o := GetOrmer() + + qs := o.QueryTable(new(models.RepJob)) + if policyID != 0 { + qs = qs.Filter("PolicyID", policyID) + } + if len(repository) != 0 { + qs = qs.Filter("Repository__icontains", repository) + } + if len(status) != 0 { + qs = qs.Filter("Status__icontains", status) + } + + if startTime != nil { + fmt.Printf("%v\n", startTime) + qs = qs.Filter("CreationTime__gte", startTime) + } + + if endTime != nil { + fmt.Printf("%v\n", endTime) + qs = qs.Filter("CreationTime__lte", endTime) + } + + if limit != 0 { + qs = qs.Limit(limit) + } + + qs = qs.OrderBy("-CreationTime") + + var jobs []*models.RepJob + _, err := qs.All(&jobs) + if err != nil { + return nil, err + } + + genTagListForJob(jobs...) + + return jobs, nil +} + +// GetRepJobToStop get jobs that are possibly being handled by workers of a certain policy. +func GetRepJobToStop(policyID int64) ([]*models.RepJob, error) { + var res []*models.RepJob + _, err := repJobPolicyIDQs(policyID).Filter("status__in", models.JobPending, models.JobRunning).All(&res) + genTagListForJob(res...) + return res, err +} + +func repJobQs() orm.QuerySeter { + o := GetOrmer() + return o.QueryTable("replication_job") +} + +func repJobPolicyIDQs(policyID int64) orm.QuerySeter { + return repJobQs().Filter("policy_id", policyID) +} + +// DeleteRepJob ... +func DeleteRepJob(id int64) error { + o := GetOrmer() + _, err := o.Delete(&models.RepJob{ID: id}) + return err +} + +// UpdateRepJobStatus ... +func UpdateRepJobStatus(id int64, status string) error { + o := GetOrmer() + j := models.RepJob{ + ID: id, + Status: status, + } + num, err := o.Update(&j, "Status") + if num == 0 { + err = fmt.Errorf("Failed to update replication job with id: %d %s", id, err.Error()) + } + return err +} + +// ResetRunningJobs update all running jobs status to pending +func ResetRunningJobs() error { + o := GetOrmer() + sql := fmt.Sprintf("update replication_job set status = '%s' where status = '%s'", models.JobPending, models.JobRunning) + _, err := o.Raw(sql).Exec() + return err +} + +// GetRepJobByStatus get jobs of certain statuses +func GetRepJobByStatus(status ...string) ([]*models.RepJob, error) { + var res []*models.RepJob + var t []interface{} + for _, s := range status { + t = append(t, interface{}(s)) + } + _, err := repJobQs().Filter("status__in", t...).All(&res) + genTagListForJob(res...) + return res, err +} + +func genTagListForJob(jobs ...*models.RepJob) { + for _, j := range jobs { + if len(j.Tags) > 0 { + j.TagList = strings.Split(j.Tags, ",") + } + } +} diff --git a/dao/role.go b/dao/role.go index 00281f40d..317057777 100644 --- a/dao/role.go +++ b/dao/role.go @@ -18,6 +18,7 @@ package dao import ( "fmt" + "github.com/astaxie/beego/orm" "github.com/vmware/harbor/models" ) @@ -83,6 +84,9 @@ func GetRoleByID(id int) (*models.Role, error) { var role models.Role if err := o.Raw(sql, id).QueryRow(&role); err != nil { + if err == orm.ErrNoRows { + return nil, nil + } return nil, err } return &role, nil diff --git a/dao/user.go b/dao/user.go index d337a55e2..673f684b0 100644 --- a/dao/user.go +++ b/dao/user.go @@ -109,12 +109,13 @@ func ListUsers(query models.User) ([]models.User, error) { } // ToggleUserAdminRole gives a user admin role. -func ToggleUserAdminRole(u models.User) error { +func ToggleUserAdminRole(userID, hasAdmin int) error { o := GetOrmer() - - sql := `update user set sysadmin_flag =not sysadmin_flag where user_id = ?` - - r, err := o.Raw(sql, u.UserID).Exec() + queryParams := make([]interface{}, 1) + sql := `update user set sysadmin_flag = ? where user_id = ?` + queryParams = append(queryParams, hasAdmin) + queryParams = append(queryParams, userID) + r, err := o.Raw(sql, queryParams).Exec() if err != nil { return err } @@ -229,3 +230,13 @@ func DeleteUser(userID int) error { _, err := o.Raw(`update user set deleted = 1 where user_id = ?`, userID).Exec() return err } + +// ChangeUserProfile ... +func ChangeUserProfile(user models.User) error { + o := GetOrmer() + if _, err := o.Update(&user, "Email", "Realname", "Comment"); err != nil { + log.Errorf("update user failed, error: %v", err) + return err + } + return nil +} diff --git a/docs/img/new_add_member.png b/docs/img/new_add_member.png new file mode 100644 index 000000000..675e1839c Binary files /dev/null and b/docs/img/new_add_member.png differ diff --git a/docs/img/new_browse_project.png b/docs/img/new_browse_project.png new file mode 100644 index 000000000..d374b45bf Binary files /dev/null and b/docs/img/new_browse_project.png differ diff --git a/docs/img/new_create_policy.png b/docs/img/new_create_policy.png new file mode 100644 index 000000000..d9dc603be Binary files /dev/null and b/docs/img/new_create_policy.png differ diff --git a/docs/img/new_create_project.png b/docs/img/new_create_project.png new file mode 100644 index 000000000..036de4e52 Binary files /dev/null and b/docs/img/new_create_project.png differ diff --git a/docs/img/new_delete_repository.png b/docs/img/new_delete_repository.png new file mode 100644 index 000000000..0c5ca5781 Binary files /dev/null and b/docs/img/new_delete_repository.png differ diff --git a/docs/img/new_manage_destination.png b/docs/img/new_manage_destination.png new file mode 100644 index 000000000..7b8778672 Binary files /dev/null and b/docs/img/new_manage_destination.png differ diff --git a/docs/img/new_manage_replication.png b/docs/img/new_manage_replication.png new file mode 100644 index 000000000..89864ca82 Binary files /dev/null and b/docs/img/new_manage_replication.png differ diff --git a/docs/img/new_policy_list.png b/docs/img/new_policy_list.png new file mode 100644 index 000000000..608bbd7fe Binary files /dev/null and b/docs/img/new_policy_list.png differ diff --git a/docs/img/new_project_log.png b/docs/img/new_project_log.png new file mode 100644 index 000000000..0207b8fdb Binary files /dev/null and b/docs/img/new_project_log.png differ diff --git a/docs/img/new_remove_update_member.png b/docs/img/new_remove_update_member.png new file mode 100644 index 000000000..313ed19a0 Binary files /dev/null and b/docs/img/new_remove_update_member.png differ diff --git a/docs/img/new_search.png b/docs/img/new_search.png new file mode 100644 index 000000000..cee714551 Binary files /dev/null and b/docs/img/new_search.png differ diff --git a/docs/img/new_set_admin_remove_user.png b/docs/img/new_set_admin_remove_user.png new file mode 100644 index 000000000..7d4aafe94 Binary files /dev/null and b/docs/img/new_set_admin_remove_user.png differ diff --git a/docs/swagger.yaml b/docs/swagger.yaml index c05963ca3..6c7f660de 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -119,7 +119,7 @@ paths: description: Project name already exists. 500: description: Unexpected internal errors. - /projects/{project_id}: + /projects/{project_id}/publicity: put: summary: Update properties for a selected project. description: | @@ -353,7 +353,24 @@ paths: 404: description: Project ID does not exist. 500: - description: Unexpected internal errors. + description: Unexpected internal errors. + /statistics: + get: + summary: Get projects number and repositories number relevant to the user + description: | + This endpoint is aimed to statistic all of the projects number and repositories number relevant to the logined user, also the public projects number and repositories number. If the user is admin, he can also get total projects number and total repositories number. + tags: + - Products + responses: + 200: + description: Get the projects number and repositories number relevant to the user successfully. + schema: + $ref: '#/definitions/StatisticMap' + 401: + description: User need to log in first. + 500: + description: Unexpected internal errors. + /users: get: summary: Get registered users of Harbor. @@ -407,10 +424,9 @@ paths: description: Unexpected internal errors. /users/{user_id}: put: - summary: Update a registered user to change to be an administrator of Harbor. + summary: Update a registered user to change his profile. description: | - This endpoint let a registered user change to be an administrator - of Harbor. + This endpoint let a registered user change his profile. parameters: - name: user_id in: path @@ -418,6 +434,12 @@ paths: format: int32 required: true description: Registered user ID + - name: profile + in: body + description: Only email, realname and comment can be modified. + required: true + schema: + $ref: '#/definitions/User' tags: - Products responses: @@ -490,7 +512,35 @@ paths: 403: description: Guests can only change their own account. 500: - description: Unexpected internal errors. + description: Unexpected internal errors. + /users/{user_id}/sysadmin: + put: + summary: Update a registered user to change to be an administrator of Harbor. + description: | + This endpoint let a registered user change to be an administrator + of Harbor. + parameters: + - name: user_id + in: path + type: integer + format: int32 + required: true + description: Registered user ID + tags: + - Products + responses: + 200: + description: Updated user's admin role successfully. + 400: + description: Invalid user ID. + 401: + description: User need to log in first. + 403: + description: User does not have permission of admin role. + 404: + description: User ID does not exist. + 500: + description: Unexpected internal errors. /repositories: get: summary: Get repositories accompany with relevant project and repo name. @@ -597,6 +647,70 @@ paths: description: Retrieved manifests from a relevant repository successfully. 500: description: Unexpected internal errors. + /repositories/top: + get: + summary: Get public repositories which are accessed most. + description: | + This endpoint aims to let users see the most popular public repositories + parameters: + - name: count + in: query + type: integer + format: int32 + required: false + description: The number of the requested public repositories, default is 10 if not provided. + tags: + - Products + responses: + 200: + description: Retrieved top repositories successfully. + schema: + type: array + items: + $ref: '#/definitions/TopRepo' + 400: + description: Bad request because of invalid count. + 500: + description: Unexpected internal errors. + /logs: + get: + summary: Get recent logs of the projects which the user is a member of + description: | + This endpoint let user see the recent operation logs of the projects which he is member of + parameters: + - name: lines + in: query + type: integer + format: int32 + required: false + description: The number of logs to be shown, default is 10 if lines, start_time, end_time are not provided. + - name: start_time + in: query + type: integer + format: int64 + required: false + description: The start time of logs to be shown in unix timestap + - name: end_time + in: query + type: integer + format: int64 + required: false + description: The end time of logs to be shown in unix timestap + tags: + - Products + responses: + 200: + description: Get the required logs successfully. + schema: + type: array + items: + $ref: '#/definitions/AccessLog' + 400: + description: Bad request because of invalid parameter of lines or start_time or end_time. + 401: + description: User need to login first. + 500: + description: Unexpected internal errors. definitions: Search: type: object @@ -801,3 +915,45 @@ definitions: username: type: string description: Username relevant to a project role member. + TopRepo: + type: object + properties: + repo_name: + type: string + description: The name of the repo + access_count: + type: integer + format: int + description: The access count of the repo + StatisticMap: + type: object + properties: + my_project_count: + type: integer + format: int32 + description: The count of the projects which the user is a member of. + my_repo_count: + type: integer + format: int32 + description: The count of the repositories belonging to the projects which the user is a member of. + public_project_count: + type: integer + format: int32 + description: The count of the public projects. + public_repo_count: + type: integer + format: int32 + description: The count of the public repositories belonging to the public projects which the user is a member of. + total_project_count: + type: integer + format: int32 + description: The count of the total projects, only be seen when the is admin. + total_repo_count: + type: integer + format: int32 + description: The count of the total repositories, only be seen when the user is admin. + + + + + diff --git a/docs/user_guide.md b/docs/user_guide.md index f1b8cc1bb..084da17c3 100644 --- a/docs/user_guide.md +++ b/docs/user_guide.md @@ -4,9 +4,14 @@ This guide takes you through the fundamentals of using Harbor. You'll learn how * Manage your projects. * Manage members of a project. +* Replicate projects to a remote registry. * Search projects and repositories. -* Manage Harbor system if you are the system administrator. +* Manage Harbor system if you are the system administrator: + + Manage users. + + Manage destinations. + + Manage replication policies. * Pull and push images using Docker client. +* Delete repositories. ##Role Based Access Control @@ -37,39 +42,60 @@ A project in Harbor contains all repositories of an application. RBAC is applied * **Public**: All users have the read privilege to a public project, it's convenient for you to share some repositories with others in this way. * **Private**: A private project can only be accessed by users with proper privileges. -You can create a project after you signed in. Enabling the "Public project" checkbox will make this project public. +You can create a project after you signed in. Enabling the "Public" checkbox will make this project public. -![create project](img/create_project.png) +![create project](img/new_create_project.png) -After the project is created, you can browse repositories, users and access logs using the navigation column on the left. +After the project is created, you can browse repositories, users and logs using the navigation tab. -![browse project](img/browse_project.png) +![browse project](img/new_browse_project.png) -All access logs can be listed by clicking "Logs". You can apply a filter by username, or operations and dates under "Advanced Search". +All logs can be listed by clicking "Logs". You can apply a filter by username, or operations and dates under "Advanced Search". -![browse project](img/project_log.png) +![browse project](img/new_project_log.png) ##Managing members of a project ###Adding members You can add members with different roles to an existing project. -![browse project](img/add_member.png) +![browse project](img/new_add_member.png) ###Updating and removing members You can update or remove a member by clicking the icon on the right. -![browse project](img/remove_update_member.png) +![browse project](img/new_remove_update_member.png) + +##Replicating images +If you are a system administrator, you can replicate images to a remote registry, which is called destination in Harbor. Only Harbor instance is supported as a destination for now. +Click "Add New Policy" on the "Replication" tab, fill the necessary fields and click "OK", a policy for this project will be created. If "Enable" is chosen, the project will be replicated to the remote immediately, and when a new repository is pushed to this project or an existing repository is deleted from this project, the same operation will also be replicated to the destination. + +![browse project](img/new_create_policy.png) + +You can enable or disable a policy in the policy list view, and only the policies which are disbled can be edited. +Click a policy, jobs which belong to this policy will be listed. A job represents the progress which will replicate a repository of one project to the remote. + +![browse project](img/new_policy_list.png) ##Searching projects and repositories -Entering a keyword in the search field at the top lists all matching projects and repos. The search result includes public repos and private repos you have access privilege to. +Entering a keyword in the search field at the top lists all matching projects and repositories. The search result includes both public and private repositories you have access privilege to. -![browse project](img/search.png) +![browse project](img/new_search.png) ##Administrator options -###Setting administrator and deleting user -Administrator can add "SysAdmin" role to an ordinary user by toggling the switch under "System Admin". To delete a user, click on the recycle bin icon. +###Managing user +Administrator can add "administrator" role to an ordinary user by toggling the switch under "Administrator". To delete a user, click on the recycle bin icon. -![browse project](img/set_admin_remove_user.png) +![browse project](img/new_set_admin_remove_user.png) + +###Managing destination +You can list, add, edit and delete destinations in the "Destination" tab. Only destinations which are not referenced by any policies can be edited. + +![browse project](img/new_manage_destination.png) + +###Managing replication +You can list, edit, enable and disable policies in the "Replication" tab. Make sure the policy is disabled before you edit it. + +![browse project](img/new_manage_replication.png) ##Pulling and pushing images using Docker client @@ -119,4 +145,28 @@ Push the image: $ docker push 10.117.169.182/demo/ubuntu:14.04 ``` -**Note: Replace "10.117.169.182" with the IP address or domain name of your Harbor node.** \ No newline at end of file +**Note: Replace "10.117.169.182" with the IP address or domain name of your Harbor node.** + +##Deleting repositories + +Repositories deletion runs in two steps. +First, delete repositories in Harbor's UI. This is soft deletion. You can delete the entire repository or just a tag of it. + +![browse project](img/new_delete_repository.png) + +**Note: If both tag A and tag B reference the same image, after deleting tag A, B will also disappear.** + +Second, delete the real data using registry's garbage colliection(GC). +Make sure that no one is pushing images or Harbor is not running at all before you do GC. If someone were to push an image while GC is running, there is the risk that the image's layers will be mistakenly deleted, leading to a corrupted image. So before running GC, a preferred approach is to stop Harbor first. + +Run the command on the host which harbor is deployed on. + +```sh +$ docker-compose stop +$ docker run -it --name gc --rm --volumes-from deploy_registry_1 registry:2.4.0 garbage-collect [--dry-run] /etc/registry/config.yml +$ docker-compose start +``` + +Option "--dry-run" will print the progress without removing any data. + +About the details of GC, please see [GC](https://github.com/docker/distribution/blob/master/docs/garbage-collection.md). \ No newline at end of file diff --git a/job/config/config.go b/job/config/config.go new file mode 100644 index 000000000..b5477e2f3 --- /dev/null +++ b/job/config/config.go @@ -0,0 +1,125 @@ +/* + 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. +*/ + +package config + +import ( + "fmt" + "os" + "strconv" + + "github.com/astaxie/beego" + "github.com/vmware/harbor/utils/log" +) + +const defaultMaxWorkers int = 10 + +var maxJobWorkers int +var localUIURL string +var localRegURL string +var logDir string +var uiSecret string +var verifyRemoteCert string + +func init() { + maxWorkersEnv := os.Getenv("MAX_JOB_WORKERS") + maxWorkers64, err := strconv.ParseInt(maxWorkersEnv, 10, 32) + maxJobWorkers = int(maxWorkers64) + if err != nil { + log.Warningf("Failed to parse max works setting, error: %v, the default value: %d will be used", err, defaultMaxWorkers) + maxJobWorkers = defaultMaxWorkers + } + + localRegURL = os.Getenv("REGISTRY_URL") + if len(localRegURL) == 0 { + localRegURL = "http://registry:5000" + } + + localUIURL = os.Getenv("UI_URL") + if len(localUIURL) == 0 { + localUIURL = "http://ui" + } + + logDir = os.Getenv("LOG_DIR") + if len(logDir) == 0 { + logDir = "/var/log" + } + + f, err := os.Open(logDir) + defer f.Close() + if err != nil { + panic(err) + } + finfo, err := f.Stat() + if err != nil { + panic(err) + } + if !finfo.IsDir() { + panic(fmt.Sprintf("%s is not a direcotry", logDir)) + } + + uiSecret = os.Getenv("UI_SECRET") + if len(uiSecret) == 0 { + panic("UI Secret is not set") + } + + verifyRemoteCert = os.Getenv("VERIFY_REMOTE_CERT") + if len(verifyRemoteCert) == 0 { + verifyRemoteCert = "on" + } + + configPath := os.Getenv("CONFIG_PATH") + if len(configPath) != 0 { + log.Infof("Config path: %s", configPath) + beego.LoadAppConfig("ini", configPath) + } + + log.Debugf("config: maxJobWorkers: %d", maxJobWorkers) + log.Debugf("config: localUIURL: %s", localUIURL) + log.Debugf("config: localRegURL: %s", localRegURL) + log.Debugf("config: verifyRemoteCert: %s", verifyRemoteCert) + log.Debugf("config: logDir: %s", logDir) + log.Debugf("config: uiSecret: ******") +} + +// MaxJobWorkers ... +func MaxJobWorkers() int { + return maxJobWorkers +} + +// LocalUIURL returns the local ui url, job service will use this URL to call API hosted on ui process +func LocalUIURL() string { + return localUIURL +} + +// LocalRegURL returns the local registry url, job service will use this URL to pull image from the registry +func LocalRegURL() string { + return localRegURL +} + +// LogDir returns the absolute path to which the log file will be written +func LogDir() string { + return logDir +} + +// UISecret will return the value of secret cookie for jobsevice to call UI API. +func UISecret() string { + return uiSecret +} + +// VerifyRemoteCert return the flag to tell jobservice whether or not verify the cert of remote registry +func VerifyRemoteCert() bool { + return verifyRemoteCert != "off" +} diff --git a/job/replication/delete.go b/job/replication/delete.go new file mode 100644 index 000000000..fbb764b47 --- /dev/null +++ b/job/replication/delete.go @@ -0,0 +1,112 @@ +/* + 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. +*/ + +package replication + +import ( + "github.com/vmware/harbor/models" + "github.com/vmware/harbor/utils/log" + "github.com/vmware/harbor/utils/registry" + "github.com/vmware/harbor/utils/registry/auth" +) + +const ( + // StateDelete ... + StateDelete = "delete" +) + +// Deleter deletes repository or tags +type Deleter struct { + repository string // prject_name/repo_name + tags []string + + dstURL string // url of target registry + dstUsr string // username ... + dstPwd string // username ... + + insecure bool + + dstClient *registry.Repository + + logger *log.Logger +} + +// NewDeleter returns a Deleter +func NewDeleter(repository string, tags []string, dstURL, dstUsr, dstPwd string, insecure bool, logger *log.Logger) *Deleter { + deleter := &Deleter{ + repository: repository, + tags: tags, + dstURL: dstURL, + dstUsr: dstUsr, + dstPwd: dstPwd, + insecure: insecure, + logger: logger, + } + deleter.logger.Infof("initialization completed: repository: %s, tags: %v, destination URL: %s, insecure: %v, destination user: %s", + deleter.repository, deleter.tags, deleter.dstURL, deleter.insecure, deleter.dstUsr) + return deleter +} + +// Exit ... +func (d *Deleter) Exit() error { + return nil +} + +// Enter deletes repository or tags +func (d *Deleter) Enter() (string, error) { + state, err := d.enter() + if err != nil && retry(err) { + d.logger.Info("waiting for retrying...") + return models.JobRetrying, nil + } + + return state, err +} + +func (d *Deleter) enter() (string, error) { + dstCred := auth.NewBasicAuthCredential(d.dstUsr, d.dstPwd) + dstClient, err := newRepositoryClient(d.dstURL, d.insecure, dstCred, + d.repository, "repository", d.repository, "pull", "push", "*") + if err != nil { + d.logger.Errorf("an error occurred while creating destination repository client: %v", err) + return "", err + } + + d.dstClient = dstClient + + if len(d.tags) == 0 { + tags, err := d.dstClient.ListTag() + if err != nil { + d.logger.Errorf("an error occurred while listing tags of repository %s on %s with user %s: %v", d.repository, d.dstURL, d.dstUsr, err) + return "", err + } + + d.tags = append(d.tags, tags...) + } + + d.logger.Infof("tags %v will be deleted", d.tags) + + for _, tag := range d.tags { + + if err := d.dstClient.DeleteTag(tag); err != nil { + d.logger.Errorf("an error occurred while deleting repository %s:%s on %s with user %s: %v", d.repository, tag, d.dstURL, d.dstUsr, err) + return "", err + } + + d.logger.Infof("repository %s:%s on %s has been deleted", d.repository, tag, d.dstURL) + } + + return models.JobFinished, nil +} diff --git a/job/replication/error.go b/job/replication/error.go new file mode 100644 index 000000000..19eedd129 --- /dev/null +++ b/job/replication/error.go @@ -0,0 +1,39 @@ +/* + 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. +*/ + +package replication + +import ( + "net" +) + +func retry(err error) bool { + if err == nil { + return false + } + return isNetworkErr(err) +} + +func isTemporary(err error) bool { + if netErr, ok := err.(net.Error); ok { + return netErr.Temporary() + } + return false +} + +func isNetworkErr(err error) bool { + _, ok := err.(net.Error) + return ok +} diff --git a/job/replication/transfer.go b/job/replication/transfer.go new file mode 100644 index 000000000..8ac3015bd --- /dev/null +++ b/job/replication/transfer.go @@ -0,0 +1,560 @@ +/* + 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. +*/ + +package replication + +import ( + "bytes" + "crypto/tls" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "strings" + + "github.com/docker/distribution" + "github.com/docker/distribution/manifest/schema1" + "github.com/docker/distribution/manifest/schema2" + "github.com/vmware/harbor/models" + "github.com/vmware/harbor/utils/log" + "github.com/vmware/harbor/utils/registry" + "github.com/vmware/harbor/utils/registry/auth" +) + +const ( + // StateInitialize ... + StateInitialize = "initialize" + // StateCheck ... + StateCheck = "check" + // StatePullManifest ... + StatePullManifest = "pull_manifest" + // StateTransferBlob ... + StateTransferBlob = "transfer_blob" + // StatePushManifest ... + StatePushManifest = "push_manifest" +) + +var ( + // ErrConflict represents http 409 error + ErrConflict = errors.New("conflict") +) + +// BaseHandler holds informations shared by other state handlers +type BaseHandler struct { + project string // project_name + repository string // prject_name/repo_name + tags []string + + srcURL string // url of source registry + srcSecret string + + dstURL string // url of target registry + dstUsr string // username ... + dstPwd string // password ... + + insecure bool // whether skip secure check when using https + + srcClient *registry.Repository + dstClient *registry.Repository + + manifest distribution.Manifest // manifest of tags[0] + digest string //digest of tags[0]'s manifest + blobs []string // blobs need to be transferred for tags[0] + + blobsExistence map[string]bool //key: digest of blob, value: existence + + logger *log.Logger +} + +// InitBaseHandler initializes a BaseHandler. +func InitBaseHandler(repository, srcURL, srcSecret, + dstURL, dstUsr, dstPwd string, insecure bool, tags []string, logger *log.Logger) *BaseHandler { + + base := &BaseHandler{ + repository: repository, + tags: tags, + srcURL: srcURL, + srcSecret: srcSecret, + dstURL: dstURL, + dstUsr: dstUsr, + dstPwd: dstPwd, + insecure: insecure, + blobsExistence: make(map[string]bool, 10), + logger: logger, + } + + base.project = getProjectName(base.repository) + + return base +} + +// Exit ... +func (b *BaseHandler) Exit() error { + return nil +} + +func getProjectName(repository string) string { + repository = strings.TrimSpace(repository) + repository = strings.TrimRight(repository, "/") + return repository[:strings.LastIndex(repository, "/")] +} + +// Initializer creates clients for source and destination registry, +// lists tags of the repository if parameter tags is nil. +type Initializer struct { + *BaseHandler +} + +// Enter ... +func (i *Initializer) Enter() (string, error) { + i.logger.Infof("initializing: repository: %s, tags: %v, source URL: %s, destination URL: %s, insecure: %v, destination user: %s", + i.repository, i.tags, i.srcURL, i.dstURL, i.insecure, i.dstUsr) + + state, err := i.enter() + if err != nil && retry(err) { + i.logger.Info("waiting for retrying...") + return models.JobRetrying, nil + } + + return state, err +} + +func (i *Initializer) enter() (string, error) { + c := &http.Cookie{Name: models.UISecretCookie, Value: i.srcSecret} + srcCred := auth.NewCookieCredential(c) + srcClient, err := newRepositoryClient(i.srcURL, i.insecure, srcCred, + i.repository, "repository", i.repository, "pull", "push", "*") + if err != nil { + i.logger.Errorf("an error occurred while creating source repository client: %v", err) + return "", err + } + i.srcClient = srcClient + + dstCred := auth.NewBasicAuthCredential(i.dstUsr, i.dstPwd) + dstClient, err := newRepositoryClient(i.dstURL, i.insecure, dstCred, + i.repository, "repository", i.repository, "pull", "push", "*") + if err != nil { + i.logger.Errorf("an error occurred while creating destination repository client: %v", err) + return "", err + } + i.dstClient = dstClient + + if len(i.tags) == 0 { + tags, err := i.srcClient.ListTag() + if err != nil { + i.logger.Errorf("an error occurred while listing tags for source repository: %v", err) + return "", err + } + i.tags = tags + } + + i.logger.Infof("initialization completed: project: %s, repository: %s, tags: %v, source URL: %s, destination URL: %s, insecure: %v, destination user: %s", + i.project, i.repository, i.tags, i.srcURL, i.dstURL, i.insecure, i.dstUsr) + + return StateCheck, nil +} + +// Checker checks the existence of project and the user's privlege to the project +type Checker struct { + *BaseHandler +} + +// Enter check existence of project, if it does not exist, create it, +// if it exists, check whether the user has write privilege to it. +func (c *Checker) Enter() (string, error) { + state, err := c.enter() + if err != nil && retry(err) { + c.logger.Info("waiting for retrying...") + return models.JobRetrying, nil + } + + return state, err +} + +func (c *Checker) enter() (string, error) { +enter: + exist, canWrite, err := c.projectExist() + if err != nil { + c.logger.Errorf("an error occurred while checking existence of project %s on %s with user %s : %v", c.project, c.dstURL, c.dstUsr, err) + return "", err + } + if !exist { + err := c.createProject() + if err != nil { + // other job may be also doing the same thing when the current job + // is creating project, so when the response code is 409, re-check + // the existence of project + if err == ErrConflict { + goto enter + } else { + c.logger.Errorf("an error occurred while creating project %s on %s with user %s : %v", c.project, c.dstURL, c.dstUsr, err) + return "", err + } + } + c.logger.Infof("project %s is created on %s with user %s", c.project, c.dstURL, c.dstUsr) + return StatePullManifest, nil + } + + c.logger.Infof("project %s already exists on %s", c.project, c.dstURL) + + if !canWrite { + err = fmt.Errorf("the user %s is unauthorized to write to project %s on %s", c.dstUsr, c.project, c.dstURL) + c.logger.Errorf("%v", err) + return "", err + } + c.logger.Infof("the user %s has write privilege to project %s on %s", c.dstUsr, c.project, c.dstURL) + + return StatePullManifest, nil +} + +// check the existence of project, if it exists, returning whether the user has write privilege to it +func (c *Checker) projectExist() (exist, canWrite bool, err error) { + url := strings.TrimRight(c.dstURL, "/") + "/api/projects/?project_name=" + c.project + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return + } + + req.SetBasicAuth(c.dstUsr, c.dstPwd) + + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: c.insecure, + }, + }, + } + + resp, err := client.Do(req) + if err != nil { + return + } + + if resp.StatusCode == http.StatusNotFound { + return + } + + if resp.StatusCode == http.StatusUnauthorized { + exist = true + return + } + + defer resp.Body.Close() + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return + } + + if resp.StatusCode == http.StatusOK { + var projects []models.Project + if err = json.Unmarshal(data, &projects); err != nil { + return + } + + if len(projects) == 0 { + return + } + + for _, project := range projects { + if project.Name == c.project { + exist = true + canWrite = (project.Role == models.PROJECTADMIN || + project.Role == models.DEVELOPER) + break + } + } + + return + } + + err = fmt.Errorf("an error occurred while checking existen of project %s on %s with user %s: %d %s", + c.project, c.dstURL, c.dstUsr, resp.StatusCode, string(data)) + + return +} + +func (c *Checker) createProject() error { + // TODO handle publicity of project + project := struct { + ProjectName string `json:"project_name"` + Public bool `json:"public"` + }{ + ProjectName: c.project, + } + + data, err := json.Marshal(project) + if err != nil { + return err + } + + url := strings.TrimRight(c.dstURL, "/") + "/api/projects/" + req, err := http.NewRequest("POST", url, bytes.NewReader(data)) + if err != nil { + return err + } + + req.SetBasicAuth(c.dstUsr, c.dstPwd) + + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: c.insecure, + }, + }, + } + + resp, err := client.Do(req) + if err != nil { + return err + } + + // version 0.1.1's reponse code is 200 + if resp.StatusCode == http.StatusCreated || + resp.StatusCode == http.StatusOK { + return nil + } + + if resp.StatusCode == http.StatusConflict { + return ErrConflict + } + + defer resp.Body.Close() + message, err := ioutil.ReadAll(resp.Body) + if err != nil { + c.logger.Errorf("an error occurred while reading message from response: %v", err) + } + + return fmt.Errorf("failed to create project %s on %s with user %s: %d %s", + c.project, c.dstURL, c.dstUsr, resp.StatusCode, string(message)) +} + +// ManifestPuller pulls the manifest of a tag. And if no tag needs to be pulled, +// the next state that state machine should enter is "finished". +type ManifestPuller struct { + *BaseHandler +} + +// Enter pulls manifest of a tag and checks if all blobs exist in the destination registry +func (m *ManifestPuller) Enter() (string, error) { + state, err := m.enter() + if err != nil && retry(err) { + m.logger.Info("waiting for retrying...") + return models.JobRetrying, nil + } + + return state, err + +} + +func (m *ManifestPuller) enter() (string, error) { + if len(m.tags) == 0 { + m.logger.Infof("no tag needs to be replicated, next state is \"finished\"") + return models.JobFinished, nil + } + + name := m.repository + tag := m.tags[0] + + acceptMediaTypes := []string{schema1.MediaTypeManifest, schema2.MediaTypeManifest} + digest, mediaType, payload, err := m.srcClient.PullManifest(tag, acceptMediaTypes) + if err != nil { + m.logger.Errorf("an error occurred while pulling manifest of %s:%s from %s: %v", name, tag, m.srcURL, err) + return "", err + } + m.digest = digest + m.logger.Infof("manifest of %s:%s pulled successfully from %s: %s", name, tag, m.srcURL, digest) + + if strings.Contains(mediaType, "application/json") { + mediaType = schema1.MediaTypeManifest + } + + manifest, _, err := registry.UnMarshal(mediaType, payload) + if err != nil { + m.logger.Errorf("an error occurred while parsing manifest of %s:%s from %s: %v", name, tag, m.srcURL, err) + return "", err + } + + m.manifest = manifest + + // all blobs(layers and config) + var blobs []string + + for _, discriptor := range manifest.References() { + blobs = append(blobs, discriptor.Digest.String()) + } + + // config is also need to be transferred if the schema of manifest is v2 + manifest2, ok := manifest.(*schema2.DeserializedManifest) + if ok { + blobs = append(blobs, manifest2.Target().Digest.String()) + } + + m.logger.Infof("all blobs of %s:%s from %s: %v", name, tag, m.srcURL, blobs) + + for _, blob := range blobs { + exist, ok := m.blobsExistence[blob] + if !ok { + exist, err = m.dstClient.BlobExist(blob) + if err != nil { + m.logger.Errorf("an error occurred while checking existence of blob %s of %s:%s on %s: %v", blob, name, tag, m.dstURL, err) + return "", err + } + m.blobsExistence[blob] = exist + } + + if !exist { + m.blobs = append(m.blobs, blob) + } else { + m.logger.Infof("blob %s of %s:%s already exists in %s", blob, name, tag, m.dstURL) + } + } + m.logger.Infof("blobs of %s:%s need to be transferred to %s: %v", name, tag, m.dstURL, m.blobs) + + return StateTransferBlob, nil +} + +// BlobTransfer transfers blobs of a tag +type BlobTransfer struct { + *BaseHandler +} + +// Enter pulls blobs and then pushs them to destination registry. +func (b *BlobTransfer) Enter() (string, error) { + state, err := b.enter() + if err != nil && retry(err) { + b.logger.Info("waiting for retrying...") + return models.JobRetrying, nil + } + + return state, err + +} + +func (b *BlobTransfer) enter() (string, error) { + name := b.repository + tag := b.tags[0] + for _, blob := range b.blobs { + b.logger.Infof("transferring blob %s of %s:%s to %s ...", blob, name, tag, b.dstURL) + size, data, err := b.srcClient.PullBlob(blob) + if err != nil { + b.logger.Errorf("an error occurred while pulling blob %s of %s:%s from %s: %v", blob, name, tag, b.srcURL, err) + return "", err + } + if err = b.dstClient.PushBlob(blob, size, data); err != nil { + b.logger.Errorf("an error occurred while pushing blob %s of %s:%s to %s : %v", blob, name, tag, b.dstURL, err) + return "", err + } + b.logger.Infof("blob %s of %s:%s transferred to %s completed", blob, name, tag, b.dstURL) + } + + return StatePushManifest, nil +} + +// ManifestPusher pushs the manifest to destination registry +type ManifestPusher struct { + *BaseHandler +} + +// Enter checks the existence of manifest in the source registry first, and if it +// exists, pushs it to destination registry. The checking operation is to avoid +// the situation that the tag is deleted during the blobs transfering +func (m *ManifestPusher) Enter() (string, error) { + state, err := m.enter() + if err != nil && retry(err) { + m.logger.Info("waiting for retrying...") + return models.JobRetrying, nil + } + + return state, err + +} + +func (m *ManifestPusher) enter() (string, error) { + name := m.repository + tag := m.tags[0] + _, exist, err := m.srcClient.ManifestExist(tag) + if err != nil { + m.logger.Infof("an error occurred while checking the existence of manifest of %s:%s on %s: %v", name, tag, m.srcURL, err) + return "", err + } + if !exist { + m.logger.Infof("manifest of %s:%s does not exist on source registry %s, cancel manifest pushing", name, tag, m.srcURL) + } else { + m.logger.Infof("manifest of %s:%s exists on source registry %s, continue manifest pushing", name, tag, m.srcURL) + + _, manifestExist, err := m.dstClient.ManifestExist(m.digest) + if manifestExist { + m.logger.Infof("manifest of %s:%s exists on destination registry %s, skip manifest pushing", name, tag, m.dstURL) + + m.tags = m.tags[1:] + m.manifest = nil + m.digest = "" + m.blobs = nil + + return StatePullManifest, nil + } + + mediaType, data, err := m.manifest.Payload() + if err != nil { + m.logger.Errorf("an error occurred while getting payload of manifest for %s:%s : %v", name, tag, err) + return "", err + } + + if _, err = m.dstClient.PushManifest(tag, mediaType, data); err != nil { + m.logger.Errorf("an error occurred while pushing manifest of %s:%s to %s : %v", name, tag, m.dstURL, err) + return "", err + } + m.logger.Infof("manifest of %s:%s has been pushed to %s", name, tag, m.dstURL) + } + + m.tags = m.tags[1:] + m.manifest = nil + m.digest = "" + m.blobs = nil + + return StatePullManifest, nil +} + +func newRepositoryClient(endpoint string, insecure bool, credential auth.Credential, repository, scopeType, scopeName string, + scopeActions ...string) (*registry.Repository, error) { + + authorizer := auth.NewStandardTokenAuthorizer(credential, insecure, scopeType, scopeName, scopeActions...) + + store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer) + if err != nil { + return nil, err + } + + uam := &userAgentModifier{ + userAgent: "harbor-registry-client", + } + + client, err := registry.NewRepositoryWithModifiers(repository, endpoint, insecure, store, uam) + if err != nil { + return nil, err + } + return client, nil +} + +type userAgentModifier struct { + userAgent string +} + +// Modify adds user-agent header to the request +func (u *userAgentModifier) Modify(req *http.Request) error { + req.Header.Set(http.CanonicalHeaderKey("User-Agent"), u.userAgent) + return nil +} diff --git a/job/scheduler.go b/job/scheduler.go new file mode 100644 index 000000000..a721f235e --- /dev/null +++ b/job/scheduler.go @@ -0,0 +1,36 @@ +/* + 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. +*/ + +package job + +import ( + "github.com/vmware/harbor/utils/log" + "time" +) + +var jobQueue = make(chan int64) + +// Schedule put a job id into job queue. +func Schedule(jobID int64) { + jobQueue <- jobID +} + +// Reschedule is called by statemachine to retry a job +func Reschedule(jobID int64) { + log.Debugf("Job %d will be rescheduled in 5 minutes", jobID) + time.Sleep(5 * time.Minute) + log.Debugf("Rescheduling job %d", jobID) + Schedule(jobID) +} diff --git a/job/statehandlers.go b/job/statehandlers.go new file mode 100644 index 000000000..d4a855e77 --- /dev/null +++ b/job/statehandlers.go @@ -0,0 +1,119 @@ +/* + 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. +*/ + +package job + +import ( + "time" + + "github.com/vmware/harbor/dao" + "github.com/vmware/harbor/models" + "github.com/vmware/harbor/utils/log" +) + +// StateHandler handles transition, it associates with each state, will be called when +// SM enters and exits a state during a transition. +type StateHandler interface { + // Enter returns the next state, if it returns empty string the SM will hold the current state or + // or decide the next state. + Enter() (string, error) + //Exit should be idempotent + Exit() error +} + +// StatusUpdater implements the StateHandler interface which updates the status of a job in DB when the job enters +// a status. +type StatusUpdater struct { + JobID int64 + State string +} + +// Enter updates the status of a job and returns "_continue" status to tell state machine to move on. +// If the status is a final status it returns empty string and the state machine will be stopped. +func (su StatusUpdater) Enter() (string, error) { + err := dao.UpdateRepJobStatus(su.JobID, su.State) + if err != nil { + log.Warningf("Failed to update state of job: %d, state: %s, error: %v", su.JobID, su.State, err) + } + var next = models.JobContinue + if su.State == models.JobStopped || su.State == models.JobError || su.State == models.JobFinished { + next = "" + } + return next, err +} + +// Exit ... +func (su StatusUpdater) Exit() error { + return nil +} + +// Retry handles a special "retrying" in which case it will update the status in DB and reschedule the job +// via scheduler +type Retry struct { + JobID int64 +} + +// Enter ... +func (jr Retry) Enter() (string, error) { + err := dao.UpdateRepJobStatus(jr.JobID, models.JobRetrying) + if err != nil { + log.Errorf("Failed to update state of job :%d to Retrying, error: %v", jr.JobID, err) + } + go Reschedule(jr.JobID) + return "", err +} + +// Exit ... +func (jr Retry) Exit() error { + return nil +} + +// ImgPuller was for testing +type ImgPuller struct { + img string + logger *log.Logger +} + +// Enter ... +func (ip ImgPuller) Enter() (string, error) { + ip.logger.Infof("I'm pretending to pull img:%s, then sleep 30s", ip.img) + time.Sleep(30 * time.Second) + ip.logger.Infof("wake up from sleep.... testing retry") + return models.JobRetrying, nil +} + +// Exit ... +func (ip ImgPuller) Exit() error { + return nil +} + +// ImgPusher is a statehandler for testing +type ImgPusher struct { + targetURL string + logger *log.Logger +} + +// Enter ... +func (ip ImgPusher) Enter() (string, error) { + ip.logger.Infof("I'm pretending to push img to:%s, then sleep 30s", ip.targetURL) + time.Sleep(30 * time.Second) + ip.logger.Infof("wake up from sleep.... testing retry") + return models.JobRetrying, nil +} + +// Exit ... +func (ip ImgPusher) Exit() error { + return nil +} diff --git a/job/statemachine.go b/job/statemachine.go new file mode 100644 index 000000000..95b306033 --- /dev/null +++ b/job/statemachine.go @@ -0,0 +1,291 @@ +/* + 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. +*/ + +package job + +import ( + "fmt" + "sync" + + "github.com/vmware/harbor/dao" + "github.com/vmware/harbor/job/config" + "github.com/vmware/harbor/job/replication" + "github.com/vmware/harbor/job/utils" + "github.com/vmware/harbor/models" + uti "github.com/vmware/harbor/utils" + "github.com/vmware/harbor/utils/log" +) + +// RepJobParm wraps the parm of a job +type RepJobParm struct { + LocalRegURL string + TargetURL string + TargetUsername string + TargetPassword string + Repository string + Tags []string + Enabled int + Operation string + Insecure bool +} + +// SM is the state machine to handle job, it handles one job at a time. +type SM struct { + JobID int64 + CurrentState string + PreviousState string + //The states that don't have to exist in transition map, such as "Error", "Canceled" + ForcedStates map[string]struct{} + Transitions map[string]map[string]struct{} + Handlers map[string]StateHandler + desiredState string + Logger *log.Logger + Parms *RepJobParm + lock *sync.Mutex +} + +// EnterState transit the statemachine from the current state to the state in parameter. +// It returns the next state the statemachine should tranit to. +func (sm *SM) EnterState(s string) (string, error) { + log.Debugf("Job id: %d, transiting from State: %s, to State: %s", sm.JobID, sm.CurrentState, s) + targets, ok := sm.Transitions[sm.CurrentState] + _, exist := targets[s] + _, isForced := sm.ForcedStates[s] + if !exist && !isForced { + return "", fmt.Errorf("Job id: %d, transition from %s to %s does not exist!", sm.JobID, sm.CurrentState, s) + } + exitHandler, ok := sm.Handlers[sm.CurrentState] + if ok { + if err := exitHandler.Exit(); err != nil { + return "", err + } + } else { + log.Debugf("Job id: %d, no handler found for state:%s, skip", sm.JobID, sm.CurrentState) + } + enterHandler, ok := sm.Handlers[s] + var next = models.JobContinue + var err error + if ok { + if next, err = enterHandler.Enter(); err != nil { + return "", err + } + } else { + log.Debugf("Job id: %d, no handler found for state:%s, skip", sm.JobID, s) + } + sm.PreviousState = sm.CurrentState + sm.CurrentState = s + log.Debugf("Job id: %d, transition succeeded, current state: %s", sm.JobID, s) + return next, nil +} + +// Start kicks off the statemachine to transit from current state to s, and moves on +// It will search the transit map if the next state is "_continue", and +// will enter error state if there's more than one possible path when next state is "_continue" +func (sm *SM) Start(s string) { + n, err := sm.EnterState(s) + log.Debugf("Job id: %d, next state from handler: %s", sm.JobID, n) + for len(n) > 0 && err == nil { + if d := sm.getDesiredState(); len(d) > 0 { + log.Debugf("Job id: %d. Desired state: %s, will ignore the next state from handler", sm.JobID, d) + n = d + sm.setDesiredState("") + continue + } + if n == models.JobContinue && len(sm.Transitions[sm.CurrentState]) == 1 { + for n = range sm.Transitions[sm.CurrentState] { + break + } + log.Debugf("Job id: %d, Continue to state: %s", sm.JobID, n) + continue + } + if n == models.JobContinue && len(sm.Transitions[sm.CurrentState]) != 1 { + log.Errorf("Job id: %d, next state is continue but there are %d possible next states in transition table", sm.JobID, len(sm.Transitions[sm.CurrentState])) + err = fmt.Errorf("Unable to continue") + break + } + n, err = sm.EnterState(n) + log.Debugf("Job id: %d, next state from handler: %s", sm.JobID, n) + } + if err != nil { + log.Warningf("Job id: %d, the statemachin will enter error state due to error: %v", sm.JobID, err) + sm.EnterState(models.JobError) + } +} + +// AddTransition add a transition to the transition table of state machine, the handler is the handler of target state "to" +func (sm *SM) AddTransition(from string, to string, h StateHandler) { + _, ok := sm.Transitions[from] + if !ok { + sm.Transitions[from] = make(map[string]struct{}) + } + sm.Transitions[from][to] = struct{}{} + sm.Handlers[to] = h +} + +// RemoveTransition removes a transition from transition table of the state machine +func (sm *SM) RemoveTransition(from string, to string) { + _, ok := sm.Transitions[from] + if !ok { + return + } + delete(sm.Transitions[from], to) +} + +// Stop will set the desired state as "stopped" such that when next tranisition happen the state machine will stop handling the current job +// and the worker can release itself to the workerpool. +func (sm *SM) Stop(id int64) { + log.Debugf("Trying to stop the job: %d", id) + sm.lock.Lock() + defer sm.lock.Unlock() + //need to check if the sm switched to other job + if id == sm.JobID { + sm.desiredState = models.JobStopped + log.Debugf("Desired state of job %d is set to stopped", id) + } else { + log.Debugf("State machine has switched to job %d, so the action to stop job %d will be ignored", sm.JobID, id) + } +} + +func (sm *SM) getDesiredState() string { + sm.lock.Lock() + defer sm.lock.Unlock() + return sm.desiredState +} + +func (sm *SM) setDesiredState(s string) { + sm.lock.Lock() + defer sm.lock.Unlock() + sm.desiredState = s +} + +// Init initialzie the state machine, it will be called once in the lifecycle of state machine. +func (sm *SM) Init() { + sm.lock = &sync.Mutex{} + sm.Handlers = make(map[string]StateHandler) + sm.Transitions = make(map[string]map[string]struct{}) + sm.ForcedStates = map[string]struct{}{ + models.JobError: struct{}{}, + models.JobStopped: struct{}{}, + models.JobCanceled: struct{}{}, + models.JobRetrying: struct{}{}, + } +} + +// Reset resets the state machine so it will start handling another job. +func (sm *SM) Reset(jid int64) error { + //To ensure the new jobID is visible to the thread to stop the SM + sm.lock.Lock() + sm.JobID = jid + sm.desiredState = "" + sm.lock.Unlock() + + //init parms + job, err := dao.GetRepJob(sm.JobID) + if err != nil { + return fmt.Errorf("Failed to get job, error: %v", err) + } + if job == nil { + return fmt.Errorf("The job doesn't exist in DB, job id: %d", sm.JobID) + } + policy, err := dao.GetRepPolicy(job.PolicyID) + if err != nil { + return fmt.Errorf("Failed to get policy, error: %v", err) + } + if policy == nil { + return fmt.Errorf("The policy doesn't exist in DB, policy id:%d", job.PolicyID) + } + sm.Parms = &RepJobParm{ + LocalRegURL: config.LocalRegURL(), + Repository: job.Repository, + Tags: job.TagList, + Enabled: policy.Enabled, + Operation: job.Operation, + Insecure: !config.VerifyRemoteCert(), + } + if policy.Enabled == 0 { + //worker will cancel this job + return nil + } + target, err := dao.GetRepTarget(policy.TargetID) + if err != nil { + return fmt.Errorf("Failed to get target, error: %v", err) + } + if target == nil { + return fmt.Errorf("The target doesn't exist in DB, target id: %d", policy.TargetID) + } + sm.Parms.TargetURL = target.URL + sm.Parms.TargetUsername = target.Username + pwd := target.Password + + if len(pwd) != 0 { + pwd, err = uti.ReversibleDecrypt(pwd) + if err != nil { + return fmt.Errorf("failed to decrypt password: %v", err) + } + } + + sm.Parms.TargetPassword = pwd + + //init states handlers + sm.Logger = utils.NewLogger(sm.JobID) + sm.Handlers = make(map[string]StateHandler) + sm.Transitions = make(map[string]map[string]struct{}) + sm.CurrentState = models.JobPending + + sm.AddTransition(models.JobPending, models.JobRunning, StatusUpdater{sm.JobID, models.JobRunning}) + sm.AddTransition(models.JobRetrying, models.JobRunning, StatusUpdater{sm.JobID, models.JobRunning}) + sm.Handlers[models.JobError] = StatusUpdater{sm.JobID, models.JobError} + sm.Handlers[models.JobStopped] = StatusUpdater{sm.JobID, models.JobStopped} + sm.Handlers[models.JobRetrying] = Retry{sm.JobID} + + switch sm.Parms.Operation { + case models.RepOpTransfer: + addImgTransferTransition(sm) + case models.RepOpDelete: + addImgDeleteTransition(sm) + default: + err = fmt.Errorf("unsupported operation: %s", sm.Parms.Operation) + } + + return err +} + +//for testing onlly +func addTestTransition(sm *SM) error { + sm.AddTransition(models.JobRunning, "pull-img", ImgPuller{img: sm.Parms.Repository, logger: sm.Logger}) + return nil +} + +func addImgTransferTransition(sm *SM) { + base := replication.InitBaseHandler(sm.Parms.Repository, sm.Parms.LocalRegURL, config.UISecret(), + sm.Parms.TargetURL, sm.Parms.TargetUsername, sm.Parms.TargetPassword, + sm.Parms.Insecure, sm.Parms.Tags, sm.Logger) + + sm.AddTransition(models.JobRunning, replication.StateInitialize, &replication.Initializer{BaseHandler: base}) + sm.AddTransition(replication.StateInitialize, replication.StateCheck, &replication.Checker{BaseHandler: base}) + sm.AddTransition(replication.StateCheck, replication.StatePullManifest, &replication.ManifestPuller{BaseHandler: base}) + sm.AddTransition(replication.StatePullManifest, replication.StateTransferBlob, &replication.BlobTransfer{BaseHandler: base}) + sm.AddTransition(replication.StatePullManifest, models.JobFinished, &StatusUpdater{sm.JobID, models.JobFinished}) + sm.AddTransition(replication.StateTransferBlob, replication.StatePushManifest, &replication.ManifestPusher{BaseHandler: base}) + sm.AddTransition(replication.StatePushManifest, replication.StatePullManifest, &replication.ManifestPuller{BaseHandler: base}) +} + +func addImgDeleteTransition(sm *SM) { + deleter := replication.NewDeleter(sm.Parms.Repository, sm.Parms.Tags, sm.Parms.TargetURL, + sm.Parms.TargetUsername, sm.Parms.TargetPassword, sm.Parms.Insecure, sm.Logger) + + sm.AddTransition(models.JobRunning, replication.StateDelete, deleter) + sm.AddTransition(replication.StateDelete, models.JobFinished, &StatusUpdater{sm.JobID, models.JobFinished}) +} diff --git a/job/utils/logger.go b/job/utils/logger.go new file mode 100644 index 000000000..7d347310f --- /dev/null +++ b/job/utils/logger.go @@ -0,0 +1,66 @@ +/* + 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. +*/ + +package utils + +import ( + "fmt" + + "github.com/vmware/harbor/job/config" + "github.com/vmware/harbor/utils/log" + "os" + "path/filepath" + "strconv" +) + +// NewLogger create a logger for a speicified job +func NewLogger(jobID int64) *log.Logger { + logFile := GetJobLogPath(jobID) + d := filepath.Dir(logFile) + if _, err := os.Stat(d); os.IsNotExist(err) { + err := os.MkdirAll(d, 0660) + if err != nil { + log.Errorf("Failed to create directory for log file %s, the error: %v", logFile, err) + } + } + f, err := os.OpenFile(logFile, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0660) + if err != nil { + log.Errorf("Failed to open log file %s, the log of job %d will be printed to standard output, the error: %v", logFile, jobID, err) + f = os.Stdout + } + return log.New(f, log.NewTextFormatter(), log.InfoLevel) +} + +// GetJobLogPath returns the absolute path in which the job log file is located. +func GetJobLogPath(jobID int64) string { + f := fmt.Sprintf("job_%d.log", jobID) + k := jobID / 1000 + p := "" + var d string + for k > 0 { + d = strconv.FormatInt(k%1000, 10) + k = k / 1000 + if k > 0 && len(d) == 1 { + d = "00" + d + } + if k > 0 && len(d) == 2 { + d = "0" + d + } + + p = filepath.Join(d, p) + } + p = filepath.Join(config.LogDir(), p, f) + return p +} diff --git a/job/workerpool.go b/job/workerpool.go new file mode 100644 index 000000000..c01273ba3 --- /dev/null +++ b/job/workerpool.go @@ -0,0 +1,138 @@ +/* + 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. +*/ + +package job + +import ( + "github.com/vmware/harbor/dao" + "github.com/vmware/harbor/job/config" + "github.com/vmware/harbor/models" + "github.com/vmware/harbor/utils/log" +) + +type workerPool struct { + workerChan chan *Worker + workerList []*Worker +} + +// WorkerPool is a set of workers each worker is associate to a statemachine for handling jobs. +// it consists of a channel for free workers and a list to all workers +var WorkerPool *workerPool + +// StopJobs accepts a list of jobs and will try to stop them if any of them is being executed by the worker. +func (wp *workerPool) StopJobs(jobs []int64) { + log.Debugf("Works working on jobs: %v will be stopped", jobs) + for _, id := range jobs { + for _, w := range wp.workerList { + if w.SM.JobID == id { + log.Debugf("found a worker whose job ID is %d, will try to stop it", id) + w.SM.Stop(id) + } + } + } +} + +// Worker consists of a channel for job from which worker gets the next job to handle, and a pointer to a statemachine, +// the actual work to handle the job is done via state machine. +type Worker struct { + ID int + RepJobs chan int64 + SM *SM + quit chan bool +} + +// Start is a loop worker gets id from its channel and handle it. +func (w *Worker) Start() { + go func() { + for { + WorkerPool.workerChan <- w + select { + case jobID := <-w.RepJobs: + log.Debugf("worker: %d, will handle job: %d", w.ID, jobID) + w.handleRepJob(jobID) + case q := <-w.quit: + if q { + log.Debugf("worker: %d, will stop.", w.ID) + return + } + } + } + }() +} + +// Stop ... +func (w *Worker) Stop() { + go func() { + w.quit <- true + }() +} + +func (w *Worker) handleRepJob(id int64) { + err := w.SM.Reset(id) + if err != nil { + log.Errorf("Worker %d, failed to re-initialize statemachine for job: %d, error: %v", w.ID, id, err) + err2 := dao.UpdateRepJobStatus(id, models.JobError) + if err2 != nil { + log.Errorf("Failed to update job status to ERROR, job: %d, error:%v", id, err2) + } + return + } + if w.SM.Parms.Enabled == 0 { + log.Debugf("The policy of job:%d is disabled, will cancel the job") + _ = dao.UpdateRepJobStatus(id, models.JobCanceled) + } else { + w.SM.Start(models.JobRunning) + } +} + +// NewWorker returns a pointer to new instance of worker +func NewWorker(id int) *Worker { + w := &Worker{ + ID: id, + RepJobs: make(chan int64), + quit: make(chan bool), + SM: &SM{}, + } + w.SM.Init() + return w +} + +// InitWorkerPool create workers according to configuration. +func InitWorkerPool() { + WorkerPool = &workerPool{ + workerChan: make(chan *Worker, config.MaxJobWorkers()), + workerList: make([]*Worker, 0, config.MaxJobWorkers()), + } + for i := 0; i < config.MaxJobWorkers(); i++ { + worker := NewWorker(i) + WorkerPool.workerList = append(WorkerPool.workerList, worker) + worker.Start() + log.Debugf("worker %d started", worker.ID) + } +} + +// Dispatch will listen to the jobQueue of job service and try to pick a free worker from the worker pool and assign the job to it. +func Dispatch() { + for { + select { + case job := <-jobQueue: + go func(jobID int64) { + log.Debugf("Trying to dispatch job: %d", jobID) + worker := <-WorkerPool.workerChan + worker.RepJobs <- jobID + }(job) + } + } +} diff --git a/jobservice/main.go b/jobservice/main.go new file mode 100644 index 000000000..6f54b09e4 --- /dev/null +++ b/jobservice/main.go @@ -0,0 +1,50 @@ +/* + 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. +*/ + +package main + +import ( + "github.com/astaxie/beego" + "github.com/vmware/harbor/dao" + "github.com/vmware/harbor/job" + "github.com/vmware/harbor/models" + "github.com/vmware/harbor/utils/log" +) + +func main() { + dao.InitDB() + initRouters() + job.InitWorkerPool() + go job.Dispatch() + resumeJobs() + beego.Run() +} + +func resumeJobs() { + log.Debugf("Trying to resume halted jobs...") + err := dao.ResetRunningJobs() + if err != nil { + log.Warningf("Failed to reset all running jobs to pending, error: %v", err) + } + jobs, err := dao.GetRepJobByStatus(models.JobPending, models.JobRetrying) + if err == nil { + for _, j := range jobs { + log.Debugf("Resuming job: %d", j.ID) + job.Schedule(j.ID) + } + } else { + log.Warningf("Failed to jobs to resume, error: %v", err) + } +} diff --git a/jobservice/router.go b/jobservice/router.go new file mode 100644 index 000000000..f1705d1af --- /dev/null +++ b/jobservice/router.go @@ -0,0 +1,28 @@ +/* + 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. +*/ + +package main + +import ( + api "github.com/vmware/harbor/api/jobs" + + "github.com/astaxie/beego" +) + +func initRouters() { + beego.Router("/api/jobs/replication", &api.ReplicationJob{}) + beego.Router("/api/jobs/replication/:id/log", &api.ReplicationJob{}, "get:GetLog") + beego.Router("/api/jobs/replication/actions", &api.ReplicationJob{}, "post:HandleAction") +} diff --git a/migration/README.md b/migration/README.md index ee1b12dcb..454642c05 100644 --- a/migration/README.md +++ b/migration/README.md @@ -6,11 +6,15 @@ This module is for those machine running Harbor's old version, such as 0.1.0. If **WARNING!!** You must backup your data before migrating ###Installation -- step 1: change `db_username`, `db_password`, `db_port`, `db_name` in migration.cfg -- step 2: build image from dockerfile +- step 1: + + ``` + cd migration + ``` +- step 2: change `db_username`, `db_password`, `db_port`, `db_name` in migration.cfg +- step 3: build image from dockerfile + ``` - cd harbor-migration - docker build -t migrate-tool . ``` diff --git a/migration/changelog.md b/migration/changelog.md index ab14c17d5..888aeb5a6 100644 --- a/migration/changelog.md +++ b/migration/changelog.md @@ -17,3 +17,11 @@ Changelog for harbor database schema - delete data `AMDRWS` from table `role` - delete data `A` from table `access` +## 0.2.0 + + - create table `replication_policy` + - create table `replication_target` + - create table `replication_job` + - add column `repo_tag` to table `access_log` + - alter column `repo_name` on table `access_log` + - alter column `email` on table `user` diff --git a/migration/db_meta.py b/migration/db_meta.py index dcbdd4311..af8f444b0 100644 --- a/migration/db_meta.py +++ b/migration/db_meta.py @@ -85,3 +85,43 @@ class Project(Base): deleted = sa.Column(sa.Integer, nullable=False, server_default=sa.text("'0'")) public = sa.Column(sa.Integer, nullable=False, server_default=sa.text("'0'")) owner = relationship(u'User') + +class ReplicationPolicy(Base): + __tablename__ = "replication_policy" + + id = sa.Column(sa.Integer, primary_key=True) + name = sa.Column(sa.String(256)) + project_id = sa.Column(sa.Integer, nullable=False) + target_id = sa.Column(sa.Integer, nullable=False) + enabled = sa.Column(mysql.TINYINT(1), nullable=False, server_default=sa.text("'1'")) + description = sa.Column(sa.Text) + cron_str = sa.Column(sa.String(256)) + start_time = sa.Column(mysql.TIMESTAMP) + creation_time = sa.Column(mysql.TIMESTAMP, server_default = sa.text("CURRENT_TIMESTAMP")) + update_time = sa.Column(mysql.TIMESTAMP, server_default = sa.text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")) + +class ReplicationTarget(Base): + __tablename__ = "replication_target" + + id = sa.Column(sa.Integer, primary_key=True) + name = sa.Column(sa.String(64)) + url = sa.Column(sa.String(64)) + username = sa.Column(sa.String(40)) + password = sa.Column(sa.String(40)) + target_type = sa.Column(mysql.TINYINT(1), nullable=False, server_default=sa.text("'0'")) + creation_time = sa.Column(mysql.TIMESTAMP, server_default = sa.text("CURRENT_TIMESTAMP")) + update_time = sa.Column(mysql.TIMESTAMP, server_default = sa.text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")) + +class ReplicationJob(Base): + __tablename__ = "replication_job" + + id = sa.Column(sa.Integer, primary_key=True) + status = sa.Column(sa.String(64), nullable=False) + policy_id = sa.Column(sa.Integer, nullable=False) + repository = sa.Column(sa.String(256), nullable=False) + operation = sa.Column(sa.String(64), nullable=False) + tags = sa.Column(sa.String(16384)) + creation_time = sa.Column(mysql.TIMESTAMP, server_default = sa.text("CURRENT_TIMESTAMP")) + update_time = sa.Column(mysql.TIMESTAMP, server_default = sa.text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")) + + __table_args__ = (sa.Index('policy', "policy_id"),) diff --git a/migration/migration_harbor/versions/0_3_0.py b/migration/migration_harbor/versions/0_3_0.py new file mode 100644 index 000000000..b0164e829 --- /dev/null +++ b/migration/migration_harbor/versions/0_3_0.py @@ -0,0 +1,57 @@ +# Copyright (c) 2008-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. + +"""0.1.1 to 0.3.0 + +Revision ID: 0.1.1 +Revises: + +""" + +# revision identifiers, used by Alembic. +revision = '0.3.0' +down_revision = '0.1.1' +branch_labels = None +depends_on = None + +from alembic import op +from db_meta import * + +from sqlalchemy.dialects import mysql + +def upgrade(): + """ + update schema&data + """ + bind = op.get_bind() + #alter column user.email, alter column access_log.repo_name, and add column access_log.repo_tag + op.alter_column('user', 'email', type_=sa.String(128), existing_type=sa.String(30)) + op.alter_column('access_log', 'repo_name', type_=sa.String(256), existing_type=sa.String(40)) + try: + op.add_column('access_log', sa.Column('repo_tag', sa.String(128))) + except Exception as e: + if str(e).find("Duplicate column") >=0: + print "ignore dup column error for repo_tag" + else: + raise e + #create tables: replication_policy, replication_target, replication_job + ReplicationPolicy.__table__.create(bind) + ReplicationTarget.__table__.create(bind) + ReplicationJob.__table__.create(bind) + +def downgrade(): + """ + Downgrade has been disabled. + """ + pass diff --git a/models/accesslog.go b/models/accesslog.go index ca5b52370..defc7e37d 100644 --- a/models/accesslog.go +++ b/models/accesslog.go @@ -21,17 +21,16 @@ import ( // AccessLog holds information about logs which are used to record the actions that user take to the resourses. type AccessLog struct { - LogID int `orm:"column(log_id)" json:"log_id"` - UserID int `orm:"column(user_id)" json:"user_id"` - ProjectID int64 `orm:"column(project_id)" json:"project_id"` - RepoName string `orm:"column(repo_name)" json:"repo_name"` - RepoTag string `orm:"column(repo_tag)" json:"repo_tag"` - GUID string `orm:"column(GUID)" json:"guid"` - Operation string `orm:"column(operation)" json:"operation"` - OpTime time.Time `orm:"column(op_time)" json:"op_time"` - Username string `json:"username"` - Keywords string `json:"keywords"` - + LogID int `orm:"pk;column(log_id)" json:"log_id"` + UserID int `orm:"column(user_id)" json:"user_id"` + ProjectID int64 `orm:"column(project_id)" json:"project_id"` + RepoName string `orm:"column(repo_name)" json:"repo_name"` + RepoTag string `orm:"column(repo_tag)" json:"repo_tag"` + GUID string `orm:"column(GUID)" json:"guid"` + Operation string `orm:"column(operation)" json:"operation"` + OpTime time.Time `orm:"column(op_time)" json:"op_time"` + Username string `json:"username"` + Keywords string `json:"keywords"` BeginTime time.Time BeginTimestamp int64 `json:"begin_timestamp"` EndTime time.Time diff --git a/static/resources/js/login.js b/models/base.go similarity index 67% rename from static/resources/js/login.js rename to models/base.go index 21c0b6cd2..a36300955 100644 --- a/static/resources/js/login.js +++ b/models/base.go @@ -12,20 +12,19 @@ See the License for the specific language governing permissions and limitations under the License. */ -jQuery(function(){ - - new AjaxUtil({ - url: "/api/users/current", - type: "get", - success: function(data, status, xhr){ - if(xhr && xhr.status == 200){ - document.location = "/registry/project"; - } - }, - error: function(jqXhr){ - if(jqXhr.status == 401) - return false; - } - }).exec(); - -}); \ No newline at end of file + +package models + +import ( + "github.com/astaxie/beego/orm" +) + +func init() { + orm.RegisterModel(new(RepTarget), + new(RepPolicy), + new(RepJob), + new(User), + new(Project), + new(Role), + new(AccessLog)) +} diff --git a/models/project.go b/models/project.go index 7f5d54c25..0105f6a79 100644 --- a/models/project.go +++ b/models/project.go @@ -21,7 +21,7 @@ import ( // Project holds the details of a project. type Project struct { - ProjectID int64 `orm:"column(project_id)" json:"project_id"` + ProjectID int64 `orm:"pk;column(project_id)" json:"project_id"` OwnerID int `orm:"column(owner_id)" json:"owner_id"` Name string `orm:"column(name)" json:"name"` CreationTime time.Time `orm:"column(creation_time)" json:"creation_time"` @@ -37,3 +37,23 @@ type Project struct { Role int `json:"current_user_role_id"` RepoCount int `json:"repo_count"` } + +// ProjectSorter holds an array of projects +type ProjectSorter struct { + Projects []Project +} + +// Len returns the length of array in ProjectSorter +func (ps *ProjectSorter) Len() int { + return len(ps.Projects) +} + +// Less defines the comparison rules of project +func (ps *ProjectSorter) Less(i, j int) bool { + return ps.Projects[i].Name < ps.Projects[j].Name +} + +// Swap swaps the position of i and j +func (ps *ProjectSorter) Swap(i, j int) { + ps.Projects[i], ps.Projects[j] = ps.Projects[j], ps.Projects[i] +} diff --git a/models/replication_job.go b/models/replication_job.go new file mode 100644 index 000000000..8d847f9b0 --- /dev/null +++ b/models/replication_job.go @@ -0,0 +1,162 @@ +/* + 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. +*/ + +package models + +import ( + "time" + + "github.com/astaxie/beego/validation" + "github.com/vmware/harbor/utils" +) + +const ( + //JobPending ... + JobPending string = "pending" + //JobRunning ... + JobRunning string = "running" + //JobError ... + JobError string = "error" + //JobStopped ... + JobStopped string = "stopped" + //JobFinished ... + JobFinished string = "finished" + //JobCanceled ... + JobCanceled string = "canceled" + //JobRetrying indicate the job needs to be retried, it will be scheduled to the end of job queue by statemachine after an interval. + JobRetrying string = "retrying" + //JobContinue is the status returned by statehandler to tell statemachine to move to next possible state based on trasition table. + JobContinue string = "_continue" + //RepOpTransfer represents the operation of a job to transfer repository to a remote registry/harbor instance. + RepOpTransfer string = "transfer" + //RepOpDelete represents the operation of a job to remove repository from a remote registry/harbor instance. + RepOpDelete string = "delete" + //UISecretCookie is the cookie name to contain the UI secret + UISecretCookie string = "uisecret" +) + +// RepPolicy is the model for a replication policy, which associate to a project and a target (destination) +type RepPolicy struct { + ID int64 `orm:"column(id)" json:"id"` + ProjectID int64 `orm:"column(project_id)" json:"project_id"` + ProjectName string `json:"project_name,omitempty"` + TargetID int64 `orm:"column(target_id)" json:"target_id"` + TargetName string `json:"target_name,omitempty"` + Name string `orm:"column(name)" json:"name"` + // Target RepTarget `orm:"-" json:"target"` + Enabled int `orm:"column(enabled)" json:"enabled"` + Description string `orm:"column(description)" json:"description"` + CronStr string `orm:"column(cron_str)" json:"cron_str"` + StartTime time.Time `orm:"column(start_time)" json:"start_time"` + CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time"` + UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"` + ErrorJobCount int `json:"error_job_count"` +} + +// Valid ... +func (r *RepPolicy) Valid(v *validation.Validation) { + if len(r.Name) == 0 { + v.SetError("name", "can not be empty") + } + + if len(r.Name) > 256 { + v.SetError("name", "max length is 256") + } + + if r.ProjectID <= 0 { + v.SetError("project_id", "invalid") + } + + if r.TargetID <= 0 { + v.SetError("target_id", "invalid") + } + + if r.Enabled != 0 && r.Enabled != 1 { + v.SetError("enabled", "must be 0 or 1") + } + + if len(r.CronStr) > 256 { + v.SetError("cron_str", "max length is 256") + } +} + +// RepJob is the model for a replication job, which is the execution unit on job service, currently it is used to transfer/remove +// a repository to/from a remote registry instance. +type RepJob struct { + ID int64 `orm:"column(id)" json:"id"` + Status string `orm:"column(status)" json:"status"` + Repository string `orm:"column(repository)" json:"repository"` + PolicyID int64 `orm:"column(policy_id)" json:"policy_id"` + Operation string `orm:"column(operation)" json:"operation"` + Tags string `orm:"column(tags)" json:"-"` + TagList []string `orm:"-" json:"tags"` + // Policy RepPolicy `orm:"-" json:"policy"` + CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time"` + UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"` +} + +// RepTarget is the model for a replication targe, i.e. destination, which wraps the endpoint URL and username/password of a remote registry. +type RepTarget struct { + ID int64 `orm:"column(id)" json:"id"` + URL string `orm:"column(url)" json:"endpoint"` + Name string `orm:"column(name)" json:"name"` + Username string `orm:"column(username)" json:"username"` + Password string `orm:"column(password)" json:"password"` + Type int `orm:"column(target_type)" json:"type"` + CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time"` + UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"` +} + +// Valid ... +func (r *RepTarget) Valid(v *validation.Validation) { + if len(r.Name) == 0 { + v.SetError("name", "can not be empty") + } + + if len(r.Name) > 64 { + v.SetError("name", "max length is 64") + } + + if len(r.URL) == 0 { + v.SetError("endpoint", "can not be empty") + } + + r.URL = utils.FormatEndpoint(r.URL) + + if len(r.URL) > 64 { + v.SetError("endpoint", "max length is 64") + } + + // password is encoded using base64, the length of this field + // in DB is 64, so the max length in request is 48 + if len(r.Password) > 48 { + v.SetError("password", "max length is 48") + } +} + +//TableName is required by by beego orm to map RepTarget to table replication_target +func (r *RepTarget) TableName() string { + return "replication_target" +} + +//TableName is required by by beego orm to map RepJob to table replication_job +func (r *RepJob) TableName() string { + return "replication_job" +} + +//TableName is required by by beego orm to map RepPolicy to table replication_policy +func (r *RepPolicy) TableName() string { + return "replication_policy" +} diff --git a/models/role.go b/models/role.go index c55b29441..dc120956b 100644 --- a/models/role.go +++ b/models/role.go @@ -26,7 +26,7 @@ const ( // Role holds the details of a role. type Role struct { - RoleID int `orm:"column(role_id)" json:"role_id"` + RoleID int `orm:"pk;column(role_id)" json:"role_id"` RoleCode string `orm:"column(role_code)" json:"role_code"` Name string `orm:"column(name)" json:"role_name"` diff --git a/models/toprepo.go b/models/toprepo.go new file mode 100644 index 000000000..6ccb46227 --- /dev/null +++ b/models/toprepo.go @@ -0,0 +1,23 @@ +/* + 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. +*/ + +package models + +// TopRepo holds information about repository that accessed most +type TopRepo struct { + RepoName string `json:"name"` + AccessCount int64 `json:"count"` + Creator string `json:"creator"` +} diff --git a/models/user.go b/models/user.go index b140aeee1..ad93d0e5e 100644 --- a/models/user.go +++ b/models/user.go @@ -21,7 +21,7 @@ import ( // User holds the details of a user. type User struct { - UserID int `orm:"column(user_id)" json:"user_id"` + UserID int `orm:"pk;column(user_id)" json:"user_id"` Username string `orm:"column(username)" json:"username"` Email string `orm:"column(email)" json:"email"` Password string `orm:"column(password)" json:"password"` @@ -29,12 +29,13 @@ type User struct { Comment string `orm:"column(comment)" json:"comment"` Deleted int `orm:"column(deleted)" json:"deleted"` Rolename string `json:"role_name"` - RoleID int `json:"role_id"` + //if this field is named as "RoleID", beego orm can not map role_id + //to it. + Role int `json:"role_id"` // RoleList []Role `json:"role_list"` - HasAdminRole int `orm:"column(sysadmin_flag)" json:"has_admin_role"` - ResetUUID string `orm:"column(reset_uuid)" json:"reset_uuid"` - Salt string `orm:"column(salt)"` - + HasAdminRole int `orm:"column(sysadmin_flag)" json:"has_admin_role"` + ResetUUID string `orm:"column(reset_uuid)" json:"reset_uuid"` + Salt string `orm:"column(salt)"` CreationTime time.Time `orm:"creation_time" json:"creation_time"` UpdateTime time.Time `orm:"update_time" json:"update_time"` } diff --git a/service/utils/cache.go b/service/cache/cache.go similarity index 64% rename from service/utils/cache.go rename to service/cache/cache.go index 27a8cc8b5..762ea9cda 100644 --- a/service/utils/cache.go +++ b/service/cache/cache.go @@ -13,7 +13,7 @@ limitations under the License. */ -package utils +package cache import ( "os" @@ -21,17 +21,16 @@ import ( "github.com/vmware/harbor/utils/log" "github.com/vmware/harbor/utils/registry" + "github.com/vmware/harbor/utils/registry/auth" "github.com/astaxie/beego/cache" ) var ( // Cache is the global cache in system. - Cache cache.Cache - endpoint string - username string - registryClient *registry.Registry - repositoryClients map[string]*registry.Repository + Cache cache.Cache + endpoint string + username string ) const catalogKey string = "catalog" @@ -45,23 +44,18 @@ func init() { endpoint = os.Getenv("REGISTRY_URL") username = "admin" - repositoryClients = make(map[string]*registry.Repository, 10) } // RefreshCatalogCache calls registry's API to get repository list and write it to cache. func RefreshCatalogCache() error { log.Debug("refreshing catalog cache...") - if registryClient == nil { - var err error - registryClient, err = registry.NewRegistryWithUsername(endpoint, username) - if err != nil { - log.Errorf("error occurred while initializing registry client used by cache: %v", err) - return err - } + registryClient, err := NewRegistryClient(endpoint, true, username, + "registry", "catalog", "*") + if err != nil { + return err } - var err error rs, err := registryClient.Catalog() if err != nil { return err @@ -113,3 +107,38 @@ func GetRepoFromCache() ([]string, error) { } return result.([]string), nil } + +// NewRegistryClient ... +func NewRegistryClient(endpoint string, insecure bool, username, scopeType, scopeName string, + scopeActions ...string) (*registry.Registry, error) { + authorizer := auth.NewUsernameTokenAuthorizer(username, scopeType, scopeName, scopeActions...) + + store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer) + if err != nil { + return nil, err + } + + client, err := registry.NewRegistryWithModifiers(endpoint, insecure, store) + if err != nil { + return nil, err + } + return client, nil +} + +// NewRepositoryClient ... +func NewRepositoryClient(endpoint string, insecure bool, username, repository, scopeType, scopeName string, + scopeActions ...string) (*registry.Repository, error) { + + authorizer := auth.NewUsernameTokenAuthorizer(username, scopeType, scopeName, scopeActions...) + + store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer) + if err != nil { + return nil, err + } + + client, err := registry.NewRepositoryWithModifiers(repository, endpoint, insecure, store) + if err != nil { + return nil, err + } + return client, nil +} diff --git a/service/notification.go b/service/notification.go index 913d45b04..77ade18d3 100644 --- a/service/notification.go +++ b/service/notification.go @@ -20,9 +20,10 @@ import ( "regexp" "strings" + "github.com/vmware/harbor/api" "github.com/vmware/harbor/dao" "github.com/vmware/harbor/models" - svc_utils "github.com/vmware/harbor/service/utils" + "github.com/vmware/harbor/service/cache" "github.com/vmware/harbor/utils/log" "github.com/astaxie/beego" @@ -38,47 +39,85 @@ const manifestPattern = `^application/vnd.docker.distribution.manifest.v\d\+json // Post handles POST request, and records audit log or refreshes cache based on event. func (n *NotificationHandler) Post() { var notification models.Notification - //log.Info("Notification Handler triggered!\n") - // log.Infof("request body in string: %s", string(n.Ctx.Input.CopyBody())) err := json.Unmarshal(n.Ctx.Input.CopyBody(1<<32), ¬ification) if err != nil { - log.Errorf("error while decoding json: %v", err) + log.Errorf("failed to decode notification: %v", err) return } - var username, action, repo, project, repoTag string - var matched bool - for _, e := range notification.Events { - matched, err = regexp.MatchString(manifestPattern, e.Target.MediaType) - if err != nil { - log.Errorf("Failed to match the media type against pattern, error: %v", err) - matched = false - } - if matched && strings.HasPrefix(e.Request.UserAgent, "docker") { - username = e.Actor.Name - action = e.Action - repo = e.Target.Repository - repoTag = e.Target.Tag - log.Debugf("repo tag is : %v ", repoTag) - if strings.Contains(repo, "/") { - project = repo[0:strings.LastIndex(repo, "/")] + events, err := filterEvents(¬ification) + if err != nil { + log.Errorf("failed to filter events: %v", err) + return + } + + for _, event := range events { + repository := event.Target.Repository + + project := "" + if strings.Contains(repository, "/") { + project = repository[0:strings.LastIndex(repository, "/")] + } + + tag := event.Target.Tag + action := event.Action + + user := event.Actor.Name + if len(user) == 0 { + user = "anonymous" + } + + go func() { + if err := dao.AccessLog(user, project, repository, tag, action); err != nil { + log.Errorf("failed to add access log: %v", err) } - if username == "" { - username = "anonymous" - } - go dao.AccessLog(username, project, repo, repoTag, action) + }() + if action == "push" { + go func() { + if err := cache.RefreshCatalogCache(); err != nil { + log.Errorf("failed to refresh cache: %v", err) + } + }() + + operation := "" if action == "push" { - go func() { - err2 := svc_utils.RefreshCatalogCache() - if err2 != nil { - log.Errorf("Error happens when refreshing cache: %v", err2) - } - }() + operation = models.RepOpTransfer } + + go api.TriggerReplicationByRepository(repository, []string{tag}, operation) + } + } +} + +func filterEvents(notification *models.Notification) ([]*models.Event, error) { + events := []*models.Event{} + + for _, event := range notification.Events { + isManifest, err := regexp.MatchString(manifestPattern, event.Target.MediaType) + if err != nil { + log.Errorf("failed to match the media type against pattern: %v", err) + continue + } + + if !isManifest { + continue + } + + //pull and push manifest by docker-client + if strings.HasPrefix(event.Request.UserAgent, "docker") && (event.Action == "pull" || event.Action == "push") { + events = append(events, &event) + continue + } + + //push manifest by docker-client or job-service + if strings.ToLower(strings.TrimSpace(event.Request.UserAgent)) == "harbor-registry-client" && event.Action == "push" { + events = append(events, &event) + continue } } + return events, nil } // Render returns nil as it won't render any template. diff --git a/service/token/token.go b/service/token/token.go index 2d4c77673..50cff39a6 100644 --- a/service/token/token.go +++ b/service/token/token.go @@ -21,7 +21,7 @@ import ( "github.com/vmware/harbor/auth" "github.com/vmware/harbor/models" - //svc_utils "github.com/vmware/harbor/service/utils" + svc_utils "github.com/vmware/harbor/service/utils" "github.com/vmware/harbor/utils/log" "github.com/astaxie/beego" @@ -38,20 +38,27 @@ type Handler struct { // checkes the permission agains local DB and generates jwt token. func (h *Handler) Get() { + var username, password string request := h.Ctx.Request - log.Infof("request url: %v", request.URL.String()) - username, password, _ := request.BasicAuth() - authenticated := authenticate(username, password) service := h.GetString("service") scopes := h.GetStrings("scope") - - if len(scopes) == 0 && !authenticated { - log.Info("login request with invalid credentials") - h.CustomAbort(http.StatusUnauthorized, "") - } access := GetResourceActions(scopes) - for _, a := range access { - FilterAccess(username, authenticated, a) + log.Infof("request url: %v", request.URL.String()) + + if svc_utils.VerifySecret(request) { + log.Debugf("Will grant all access as this request is from job service with legal secret.") + username = "job-service-user" + } else { + username, password, _ = request.BasicAuth() + authenticated := authenticate(username, password) + + if len(scopes) == 0 && !authenticated { + log.Info("login request with invalid credentials") + h.CustomAbort(http.StatusUnauthorized, "") + } + for _, a := range access { + FilterAccess(username, authenticated, a) + } } h.serveToken(username, service, access) } diff --git a/service/utils/utils.go b/service/utils/utils.go new file mode 100644 index 000000000..ae54bc3d3 --- /dev/null +++ b/service/utils/utils.go @@ -0,0 +1,33 @@ +/* + 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. +*/ + +// Package utils contains methods to support security, cache, and webhook functions. +package utils + +import ( + "github.com/vmware/harbor/utils/log" + "net/http" + "os" +) + +// VerifySecret verifies the UI_SECRET cookie in a http request. +func VerifySecret(r *http.Request) bool { + secret := os.Getenv("UI_SECRET") + c, err := r.Cookie("uisecret") + if err != nil { + log.Errorf("Failed to get secret cookie, error: %v", err) + } + return c != nil && c.Value == secret +} diff --git a/static/Gruntfile.js b/static/Gruntfile.js new file mode 100644 index 000000000..567dd3850 --- /dev/null +++ b/static/Gruntfile.js @@ -0,0 +1,59 @@ +/*global module:false*/ +module.exports = function(grunt) { + + 'use strict'; + // Project configuration. + grunt.initConfig({ + // Task configuration. + jshint: { + options: { + browser: true, + curly: true, + freeze: true, + bitwise: true, + eqeqeq: true, + strict: true, + immed: true, + latedef: false, + newcap: false, + smarttabs: true, + noarg: true, + devel: true, + sub: true, + undef: true, + unused: false, + boss: true, + eqnull: true, + globals: { + jQuery: true, + angular: true, + $: true, + } + }, + gruntfile: { + src: 'Gruntfile.js' + }, + scripts: { + src: ['resources/**/**/*.js'] + } + }, + watch: { + gruntfile: { + files: '<%= jshint.gruntfile.src %>', + tasks: ['jshint:gruntfile'] + }, + scripts: { + files: '<%= jshint.scripts.src %>', + tasks: ['jshint:scripts'] + } + } + }); + + // These plugins provide necessary tasks. + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-contrib-watch'); + + // Default task. + grunt.registerTask('default', ['jshint']); + +}; diff --git a/static/i18n/locale_de-DE.ini b/static/i18n/locale_de-DE.ini deleted file mode 100644 index c5303e2f9..000000000 --- a/static/i18n/locale_de-DE.ini +++ /dev/null @@ -1,88 +0,0 @@ -page_title_index = Harbor -page_title_sign_in = Anmelden - Harbor -page_title_project = Projekt - Harbor -page_title_item_details = Details - Harbor -page_title_registration = Registrieren - Harbor -page_title_add_user = Benutzer anlegen - Harbor -page_title_forgot_password = Passwort vergessen - Harbor -title_forgot_password = Passwort vergessen -page_title_reset_password = Passwort zurücksetzen - Harbor -title_reset_password = Passwort zurücksetzen -page_title_change_password = Passwort ändern - Harbor -title_change_password = Passwort ändern -page_title_search = Suche - Harbor -sign_in = Anmelden -sign_up = Registrieren -add_user = Benutzer anlegen -log_out = Abmelden -search_placeholder = Projekte oder Repositories -change_password = Passwort ändern -username_email = Benutzername/E-Mail -password = Passwort -forgot_password = Passwort vergessen -welcome = Willkommen -my_projects = Meine Projekte -public_projects = Öffentliche Projekte -admin_options = Admin Optionen -project_name = Projektname -creation_time = Erstellungsdatum -publicity = Öffentlich -add_project = Projekt hinzufügen -check_for_publicity = öffentliches Projekt -button_save = Speichern -button_cancel = Abbrechen -button_submit = Absenden -username = Benutzername -email = E-Mail -system_admin = System Admininistrator -dlg_button_ok = OK -dlg_button_cancel = Abbrechen -registration = Registrieren -username_description = Dies wird Ihr Benutzername sein. -email_description = Die E-Mail Adresse wird für das Zurücksetzen des Passworts genutzt. -full_name = Sollständiger Name -full_name_description = Vor- und Nachname. -password_description = Mindestens sieben Zeichen bestehend aus einem Kleinbuchstaben, einem Großbuchstaben und einer Zahl -confirm_password = Passwort bestätigen -note_to_the_admin = Kommentar -old_password = Altes Passwort -new_password = Neues Passwort -forgot_password_description = Bitte gebe die E-Mail Adresse ein, die du zur Registrierung verwendet hast. Ein Link zur Wiederherstellung wird dir per E-Mail an diese Adresse geschickt. - -projects = Projekte -repositories = Repositories -search = Suche -home = Home -project = Projekt -owner = Besitzer -repo = Repositories -user = Benutzer -logs = Logs -repo_name = Repository -add_members = Benutzer hinzufügen -operation = Aktion -advance = erweiterte Suche -all = Alle -others = Andere -start_date = Start Datum -end_date = End Datum -timestamp = Zeitstempel -role = Rolle -reset_email_hint = Bitte klicke auf diesen Link um dein Passwort zurückzusetzen -reset_email_subject = Passwort zurücksetzen -language = Deutsch -language_en-US = English -language_zh-CN = 中文 -language_de-DE = Deutsch -language_ru-RU = Русский -language_ja-JP = 日本語 -copyright = Copyright -all_rights_reserved = Alle Rechte vorbehalten. -index_desc = Project Harbor ist ein zuverlässiger Enterprise-Class Registry Server. Unternehmen können ihren eigenen Registry Server aufsetzen um die Produktivität und Sicherheit zu erhöhen. Project Harbor kann für Entwicklungs- wie auch Produktiv-Umgebungen genutzt werden. -index_desc_0 = Vorteile: -index_desc_1 = 1. Sicherheit: Halten Sie ihr geistiges Eigentum innerhalb der Organisation. -index_desc_2 = 2. Effizienz: Ein privater Registry Server innerhalb des Netzwerks ihrer Organisation kann den Traffic zu öffentlichen Services im Internet signifikant reduzieren. -index_desc_3 = 3. Zugriffskontrolle: RBAC (Role Based Access Control) wird zur Verfügung gestellt. Benutzerverwaltung kann mit bestehenden Identitätsservices wie AD/LDAP integriert werden. -index_desc_4 = 4. Audit: Jeglicher Zugriff auf die Registry wird protokolliert und kann für ein Audit verwendet werden. -index_desc_5 = 5. GUI: Benutzerfreundliche Verwaltung über eine einzige Management-Konsole -index_title = Ein Enterprise-Class Registry Server diff --git a/static/i18n/locale_en-US.ini b/static/i18n/locale_en-US.ini index 7444d4537..97da9ebc9 100644 --- a/static/i18n/locale_en-US.ini +++ b/static/i18n/locale_en-US.ini @@ -1,89 +1,15 @@ -page_title_index = Harbor -page_title_sign_in = Sign In - Harbor -page_title_project = Project - Harbor -page_title_item_details = Details - Harbor -page_title_registration = Sign Up - Harbor -page_title_add_user = Add User - Harbor -page_title_forgot_password = Forgot Password - Harbor -title_forgot_password = Forgot Password -page_title_reset_password = Reset Password - Harbor -title_reset_password = Reset Password -page_title_change_password = Change Password - Harbor -title_change_password = Change Password -page_title_search = Search - Harbor -sign_in = Sign In -sign_up = Sign Up -add_user = Add User -log_out = Log Out -search_placeholder = projects or repositories -change_password = Change Password -username_email = Username/Email -password = Password -forgot_password = Forgot Password -welcome = Welcome -my_projects = My Projects -public_projects = Public Projects -admin_options = Admin Options -project_name = Project Name -creation_time = Creation Time -publicity = Publicity -add_project = Add Project -check_for_publicity = Public project -button_save = Save -button_cancel = Cancel -button_submit = Submit -username = Username -email = Email -system_admin = System Admin -dlg_button_ok = OK -dlg_button_cancel = Cancel -registration = Sign Up -username_description = This will be your username. -email_description = The Email address will be used for resetting password. -full_name = Full Name -full_name_description = First name & Last name. -password_description = At least 7 characters with 1 lowercase letter, 1 capital letter and 1 numeric character. -confirm_password = Confirm Password -note_to_the_admin = Comments -old_password = Old Password -new_password = New Password -forgot_password_description = Please input the Email used when you signed up, a reset password Email will be sent to you. - -projects = Projects -repositories = Repositories -search = Search -home = Home -project = Project -owner = Owner -repo = Repositories -user = Users -logs = Logs -repo_name = Repository Name -repo_tag = Tag -add_members = Add Members -operation = Operation -advance = Advanced Search -all = All -others = Others -start_date = Start Date -end_date = End Date -timestamp = Timestamp -role = Role reset_email_hint = Please click this link to reset your password -reset_email_subject = Reset your password -language = English -language_en-US = English -language_zh-CN = 中文 -language_de-DE = Deutsch -language_ru-RU = Русский -language_ja-JP = 日本語 -copyright = Copyright -all_rights_reserved = All rights reserved. -index_desc = Project Harbor is to build an enterprise-class, reliable registry server. Enterprises can set up a private registry server in their own environment to improve productivity as well as security. Project Harbor can be used in both development and production environment. -index_desc_0 = Key benefits: -index_desc_1 = 1. Security: Keep their intellectual properties within their organizations. -index_desc_2 = 2. Efficiency: A private registry server is set up within the organization's network and can reduce significantly the internet traffic to the public service. -index_desc_3 = 3. Access Control: RBAC (Role Based Access Control) is provided. User management can be integrated with existing enterprise identity services like AD/LDAP. -index_desc_4 = 4. Audit: All access to the registry are logged and can be used for audit purpose. -index_desc_5 = 5. GUI: User friendly single-pane-of-glass management console. -index_title = An enterprise-class registry server +reset_email_subject = Reset your password of Harbor account + +page_title_index = Harbor +page_title_dashboard = Dashboard - Harbor +page_title_account_setting = Account Settings - Harbor +page_title_reset_password = Reset Password - Harbor +page_title_change_password = Change Password - Harbor +page_title_forgot_password = Forgot Password - Harbor +page_title_project = Project - Harbor +page_title_repository = Project Details - Harbor +page_title_search = Search - Harbor +page_title_sign_up = Sign Up - Harbor +page_title_add_new = Add New User - Harbor +page_title_admin_option = Admin Options - Harbor \ No newline at end of file diff --git a/static/i18n/locale_ja-JP.ini b/static/i18n/locale_ja-JP.ini deleted file mode 100644 index cf93bcbe1..000000000 --- a/static/i18n/locale_ja-JP.ini +++ /dev/null @@ -1,89 +0,0 @@ -page_title_index = Harbor -page_title_sign_in = ログイン - Harbor -page_title_project = プロジェクト - Harbor -page_title_item_details = 詳しい - Harbor -page_title_registration = 登録 - Harbor -page_title_add_user = ユーザを追加 - Harbor -page_title_forgot_password = パスワードを忘れました - Harbor -title_forgot_password = パスワードを忘れました -page_title_reset_password = パスワードをリセット - Harbor -title_reset_password = パスワードをリセット -page_title_change_password = パスワードを変更 - Harbor -title_change_password = パスワードを変更 -page_title_search = サーチ - Harbor -sign_in = ログイン -sign_up = 登録 -add_user = ユーザを追加 -log_out = ログアウト -search_placeholder = プロジェクト名またはイメージ名 -change_password = パスワードを変更 -username_email = ユーザ名/メールアドレス -password = パスワード -forgot_password = パスワードを忘れました -welcome = ようこそ -my_projects = マイプロジェクト -public_projects = パブリックプロジェクト -admin_options = 管理者 -project_name = プロジェクト名 -creation_time = 作成日時 -publicity = パブリック -add_project = プロジェクトを追加 -check_for_publicity = パブリックプロジェクト -button_save = 保存する -button_cancel = 取り消しする -button_submit = 送信する -username = ユーザ名 -email = メールアドレス -system_admin = システム管理者 -dlg_button_ok = OK -dlg_button_cancel = 取り消し -registration = 登録 -username_description = ログイン際に使うユーザ名を入力してください。 -email_description = メールアドレスはパスワードをリセットする際に使われます。 -full_name = フルネーム -full_name_description = フルネームを入力してください。 -password_description = パスワード7英数字以上で、少なくとも 1小文字、 1大文字と 1数字でなければなりません。 -confirm_password = パスワードを確認する -note_to_the_admin = メモ -old_password = 現在のパスワード -new_password = 新しいパスワード -forgot_password_description = ぱプロジェクトをリセットするメールはこのアドレスに送信します。 - -projects = プロジェクト -repositories = リポジトリ -search = サーチ -home = ホーム -project = プロジェクト -owner = オーナー -repo = リポジトリ -user = ユーザ -logs = ログ -repo_name = リポジトリ名 -repo_tag = リポジトリタグ -add_members = メンバーを追加 -operation = 操作 -advance = さらに絞りこみで検索 -all = 全部 -others = その他 -start_date = 開始日 -end_date = 終了日 -timestamp = タイムスタンプ -role = 役割 -reset_email_hint = このリンクをクリックしてパスワードリセットの処理を続けてください -reset_email_subject = パスワードをリセットします -language = 日本語 -language_en-US = English -language_zh-CN = 中文 -language_de-DE = Deutsch -language_ru-RU = Русский -language_ja-JP = 日本語 -copyright = コピーライト -all_rights_reserved = 無断複写・転載を禁じます -index_desc = Harborは、信頼性の高いエンタープライズクラスのRegistryサーバです。タープライズユーザはHarborを利用し、プライベートのRegistryサビースを構築し、生産性および安全性を向上させる事ができます。開発環境はもちろん、生産環境にも使用する事ができます。 -index_desc_0 = 主な利点: -index_desc_1 = 1. セキュリティ: 知的財産権を組織内で確保する。 -index_desc_2 = 2. 効率: プライベートなので、パブリックRegistryサビースにネットワーク通信が減らす。 -index_desc_3 = 3. アクセス制御: ロールベースアクセス制御機能を実装し、更に既存のユーザ管理システム(AD/LDAP)と統合することも可能。 -index_desc_4 = 4. 監査: すべてRegistryサビースへの操作が記録され、検査にに利用できる。 -index_desc_5 = 5. 管理UI: 使いやすい管理UIが搭載する。 -index_title = エンタープライズ Registry サビース diff --git a/static/i18n/locale_messages.js b/static/i18n/locale_messages.js deleted file mode 100644 index 819dd53d0..000000000 --- a/static/i18n/locale_messages.js +++ /dev/null @@ -1,457 +0,0 @@ -/* - 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. -*/ -var global_messages = { - "username_is_required" : { - "en-US": "Username is required.", - "zh-CN": "用户名为必填项。", - "ja-JP": "ユーザ名は必須項目です。", - "de-DE": "Benutzername erforderlich.", - "ru-RU": "Требуется ввести имя пользователя." - }, - "username_has_been_taken" : { - "en-US": "Username has been taken.", - "zh-CN": "用户名已被占用。", - "ja-JP": "ユーザ名はすでに登録されました。", - "de-DE": "Benutzername bereits vergeben.", - "ru-RU": "Имя пользователя уже используется." - }, - "username_is_too_long" : { - "en-US": "Username is too long. (maximum 20 characters)", - "zh-CN": "用户名长度超出限制。(最长为20个字符)", - "ja-JP": "ユーザ名が長すぎです。(20文字まで)", - "de-DE": "Benutzername ist zu lang. (maximal 20 Zeichen)", - "ru-RU": "Имя пользователя слишком длинное. (максимум 20 символов)" - }, - "username_contains_illegal_chars": { - "en-US": "Username contains illegal character(s).", - "zh-CN": "用户名包含不合法的字符。", - "ja-JP": "ユーザ名に使えない文字が入っています。", - "de-DE": "Benutzername enthält ungültige Zeichen.", - "ru-RU": "Имя пользователя содержит недопустимые символы." - }, - "email_is_required" : { - "en-US": "Email is required.", - "zh-CN": "邮箱为必填项。", - "ja-JP": "メールアドレスが必須です。", - "de-DE": "E-Mail Adresse erforderlich.", - "ru-RU": "Требуется ввести E-mail адрес." - }, - "email_contains_illegal_chars" : { - "en-US": "Email contains illegal character(s).", - "zh-CN": "邮箱包含不合法的字符。", - "ja-JP": "メールアドレスに使えない文字が入っています。", - "de-DE": "E-Mail Adresse enthält ungültige Zeichen.", - "ru-RU": "E-mail адрес содержит недопеустимые символы." - }, - "email_has_been_taken" : { - "en-US": "Email has been taken.", - "zh-CN": "邮箱已被占用。", - "ja-JP": "メールアドレスがすでに使われました。", - "de-DE": "E-Mail Adresse wird bereits verwendet.", - "ru-RU": "Такой E-mail адрес уже используется." - }, - "email_content_illegal" : { - "en-US": "Email format is illegal.", - "zh-CN": "邮箱格式不合法。", - "ja-JP": "メールアドレスフォーマットエラー。", - "de-DE": "Format der E-Mail Adresse ist ungültig.", - "ru-RU": "Недопустимый формат E-mail адреса." - }, - "email_does_not_exist" : { - "en-US": "Email does not exist.", - "zh-CN": "邮箱不存在。", - "ja-JP": "メールアドレスが存在しません。", - "de-DE": "E-Mail Adresse existiert nicht.", - "ru-RU": "E-mail адрес не существует." - }, - "realname_is_required" : { - "en-US": "Full name is required.", - "zh-CN": "全名为必填项。", - "ja-JP": "フルネームが必須です。", - "de-DE": "Vollständiger Name erforderlich.", - "ru-RU": "Требуется ввести полное имя." - }, - "realname_is_too_long" : { - "en-US": "Full name is too long. (maximum 20 characters)", - "zh-CN": "全名长度超出限制。(最长为20个字符)", - "ja-JP": "フルネームは長すぎです。(20文字まで)", - "de-DE": "Vollständiger Name zu lang. (maximal 20 Zeichen)", - "ru-RU": "Полное имя слишком длинное. (максимум 20 символов)" - }, - "realname_contains_illegal_chars" : { - "en-US": "Full name contains illegal character(s).", - "zh-CN": "全名包含不合法的字符。", - "ja-JP": "フルネームに使えない文字が入っています。", - "de-DE": "Vollständiger Name enthält ungültige Zeichen.", - "ru-RU": "Полное имя содержит недопустимые символы." - }, - "password_is_required" : { - "en-US": "Password is required.", - "zh-CN": "密码为必填项。", - "ja-JP": "パスワードは必須です。", - "de-DE": "Passwort erforderlich.", - "ru-RU": "Требуется ввести пароль." - }, - "password_is_invalid" : { - "en-US": "Password is invalid. At least 7 characters with 1 lowercase letter, 1 capital letter and 1 numeric character.", - "zh-CN": "密码无效。至少输入 7个字符且包含 1个小写字母,1个大写字母和 1个数字。", - "ja-JP": "無効なパスワードです。7英数字以上で、 少なくとも1小文字、1大文字と1数字となります。", - "de-DE": "Passwort ungültig. Mindestens sieben Zeichen bestehend aus einem Kleinbuchstaben, einem Großbuchstaben und einer Zahl", - "ru-RU": "Такой пароль недопустим. Парольл должен содержать Минимум 7 символов, в которых будет присутствовать по меньшей мере 1 буква нижнего регистра, 1 буква верхнего регистра и 1 цифра" - }, - "password_is_too_long" : { - "en-US": "Password is too long. (maximum 20 characters)", - "zh-CN": "密码长度超出限制。(最长为20个字符)", - "ja-JP": "パスワードは長すぎです。(20文字まで)", - "de-DE": "Passwort zu lang. (maximal 20 Zeichen)", - "ru-RU": "Пароль слишком длинный (максимум 20 символов)" - }, - "password_does_not_match" : { - "en-US": "Passwords do not match.", - "zh-CN": "两次密码输入不一致。", - "ja-JP": "確認のパスワードが正しくありません。", - "de-DE": "Passwörter stimmen nicht überein.", - "ru-RU": "Пароли не совпадают." - }, - "comment_is_too_long" : { - "en-US": "Comment is too long. (maximum 20 characters)", - "zh-CN": "备注长度超出限制。(最长为20个字符)", - "ja-JP": "コメントは長すぎです。(20文字まで)", - "de-DE": "Kommentar zu lang. (maximal 20 Zeichen)", - "ru-RU": "Комментарий слишком длинный. (максимум 20 символов)" - }, - "comment_contains_illegal_chars" : { - "en-US": "Comment contains illegal character(s).", - "zh-CN": "备注包含不合法的字符。", - "ja-JP": "コメントに使えない文字が入っています。", - "de-DE": "Kommentar enthält ungültige Zeichen.", - "ru-RU": "Комментарий содержит недопустимые символы." - }, - "project_name_is_required" : { - "en-US": "Project name is required.", - "zh-CN": "项目名称为必填项。", - "ja-JP": "プロジェクト名は必須です。", - "de-DE": "Projektname erforderlich.", - "ru-RU": "Необходимо ввести название Проекта." - }, - "project_name_is_too_short" : { - "en-US": "Project name is too short. (minimum 4 characters)", - "zh-CN": "项目名称至少要求 4个字符。", - "ja-JP": "プロジェクト名は4文字以上です。", - "de-DE": "Projektname zu kurz. (mindestens 4 Zeichen)", - "ru-RU": "Название проекта слишком короткое. (миниму 4 символа)" - }, - "project_name_is_too_long" : { - "en-US": "Project name is too long. (maximum 30 characters)", - "zh-CN": "项目名称长度超出限制。(最长为30个字符)", - "ja-JP": "プロジェクト名は長すぎです。(30文字まで)", - "de-DE": "Projektname zu lang. (maximal 30 Zeichen)", - "ru-RU": "Название проекта слишком длинное (максимум 30 символов)" - }, - "project_name_contains_illegal_chars" : { - "en-US": "Project name contains illegal character(s).", - "zh-CN": "项目名称包含不合法的字符。", - "ja-JP": "プロジェクト名に使えない文字が入っています。", - "de-DE": "Projektname enthält ungültige Zeichen.", - "ru-RU": "Название проекта содержит недопустимые символы." - }, - "project_exists" : { - "en-US": "Project exists.", - "zh-CN": "项目已存在。", - "ja-JP": "プロジェクトはすでに存在しました。", - "de-DE": "Projekt existiert bereits.", - "ru-RU": "Такой проект уже существует." - }, - "delete_user" : { - "en-US": "Delete User", - "zh-CN": "删除用户", - "ja-JP": "ユーザを削除", - "de-DE": "Benutzer löschen", - "ru-RU": "Удалить пользователя" - }, - "are_you_sure_to_delete_user" : { - "en-US": "Are you sure to delete ", - "zh-CN": "确认要删除用户 ", - "ja-JP": "ユーザを削除でよろしでしょうか ", - "de-DE": "Sind Sie sich sicher, dass Sie folgenden Benutzer löschen möchten: ", - "ru-RU": "Вы уверены что хотите удалить пользователя? " - }, - "input_your_username_and_password" : { - "en-US": "Please input your username and password.", - "zh-CN": "请输入用户名和密码。", - "ja-JP": "ユーザ名とパスワードを入力してください。", - "de-DE": "Bitte geben Sie ihr Benutzername und Passwort ein.", - "ru-RU": "Введите имя пользователя и пароль." - }, - "check_your_username_or_password" : { - "en-US": "Please check your username or password.", - "zh-CN": "请输入正确的用户名或密码。", - "ja-JP": "正しいユーザ名とパスワードを入力してください。", - "de-DE": "Bitte überprüfen Sie ihren Benutzernamen und Passwort.", - "ru-RU": "Проверьте свои имя пользователя и пароль." - }, - "title_login_failed" : { - "en-US": "Login Failed", - "zh-CN": "登录失败", - "ja-JP": "ログインに失敗しました。", - "de-DE": "Anmeldung fehlgeschlagen", - "ru-RU": "Ошибка входа" - }, - "title_change_password" : { - "en-US": "Change Password", - "zh-CN": "修改密码", - "ja-JP": "パスワードを変更します。", - "de-DE": "Passwort ändern", - "ru-RU": "Сменить пароль" - }, - "change_password_successfully" : { - "en-US": "Password changed successfully.", - "zh-CN": "密码已修改。", - "ja-JP": "パスワードを変更しました。", - "de-DE": "Passwort erfolgreich geändert.", - "ru-RU": "Пароль успешно изменен." - }, - "title_forgot_password" : { - "en-US": "Forgot Password", - "zh-CN": "忘记密码", - "ja-JP": "パスワードをリセットします。", - "de-DE": "Passwort vergessen", - "ru-RU": "Забыли пароль?" - }, - "email_has_been_sent" : { - "en-US": "Email for resetting password has been sent.", - "zh-CN": "重置密码邮件已发送。", - "ja-JP": "パスワードをリセットするメールを送信しました。", - "de-DE": "Eine E-Mail mit einem Wiederherstellungslink wurde an Sie gesendet.", - "ru-RU": "На ваш E-mail было выслано письмо с инструкциями по сбросу пароля." - }, - "send_email_failed" : { - "en-US": "Failed to send Email for resetting password.", - "zh-CN": "重置密码邮件发送失败。", - "ja-JP": "パスワードをリセットするメールを送信する際エラーが出ました", - "de-DE": "Fehler beim Senden der Wiederherstellungs-E-Mail.", - "ru-RU": "Ошибка отправки сообщения." - }, - "please_login_first" : { - "en-US": "Please login first.", - "zh-CN": "请先登录。", - "ja-JP": "この先にログインが必要です。", - "de-DE": "Bitte melden Sie sich zuerst an.", - "ru-RU": "Сначала выполните вход в систему." - }, - "old_password_is_not_correct" : { - "en-US": "Old password is not correct.", - "zh-CN": "原密码输入不正确。", - "ja-JP": "現在のパスワードが正しく入力されていません。", - "de-DE": "Altes Passwort ist nicht korrekt.", - "ru-RU": "Старый пароль введен неверно." - }, - "please_input_new_password" : { - "en-US": "Please input new password.", - "zh-CN": "请输入新密码。", - "ja-JP": "あたらしいパスワードを入力してください", - "de-DE": "Bitte geben Sie ihr neues Passwort ein.", - "ru-RU": "Пожалуйста, введите новый пароль." - }, - "invalid_reset_url": { - "en-US": "Invalid URL for resetting password.", - "zh-CN": "无效密码重置链接。", - "ja-JP": "無効なパスワードをリセットするリンク。", - "de-DE": "Ungültige URL zum Passwort wiederherstellen.", - "ru-RU": "Неверный URL для сброса пароля." - }, - "reset_password_successfully" : { - "en-US": "Reset password successfully.", - "zh-CN": "密码重置成功。", - "ja-JP": "パスワードをリセットしました。", - "de-DE": "Passwort erfolgreich wiederhergestellt.", - "ru-RU": "Пароль успешно сброшен." - }, - "internal_error": { - "en-US": "Internal error.", - "zh-CN": "内部错误,请联系系统管理员。", - "ja-JP": "エラーが出ました、管理者に連絡してください。", - "de-DE": "Interner Fehler.", - "ru-RU": "Внутренняя ошибка." - }, - "title_reset_password" : { - "en-US": "Reset Password", - "zh-CN": "重置密码", - "ja-JP": "パスワードをリセットする", - "de-DE": "Passwort zurücksetzen", - "ru-RU": "Сбросить пароль" - }, - "title_sign_up" : { - "en-US": "Sign Up", - "zh-CN": "注册", - "ja-JP": "登録", - "de-DE": "Registrieren", - "ru-RU": "Регистрация" - }, - "title_add_user": { - "en-US": "Add User", - "zh-CN": "新增用户", - "ja-JP": "ユーザを追加", - "de-DE": "Benutzer hinzufügen", - "ru-RU": "Добавить пользователя" - }, - "registered_successfully": { - "en-US": "Signed up successfully.", - "zh-CN": "注册成功。", - "ja-JP": "登録しました。", - "de-DE": "Erfolgreich registriert.", - "ru-RU": "Регистрация прошла успешно." - }, - "registered_failed" : { - "en-US": "Failed to sign up.", - "zh-CN": "注册失败。", - "ja-JP": "登録でませんでした。", - "de-DE": "Registrierung fehlgeschlagen.", - "ru-RU": "Ошибка регистрации." - }, - "added_user_successfully": { - "en-US": "Added user successfully.", - "zh-CN": "新增用户成功。", - "ja-JP": "ユーザを追加しました。", - "de-DE": "Benutzer erfolgreich erstellt.", - "ru-RU": "Пользователь успешно добавлен." - }, - "added_user_failed": { - "en-US": "Adding user failed.", - "zh-CN": "新增用户失败。", - "ja-JP": "ユーザを追加できませんでした。", - "de-DE": "Benutzer erstellen fehlgeschlagen.", - "ru-RU": "Ошибка добавления пользователя." - }, - "projects": { - "en-US": "Projects", - "zh-CN": "项目", - "ja-JP": "プロジェクト", - "de-DE": "Projekte", - "ru-RU": "Проекты" - }, - "repositories" : { - "en-US": "Repositories", - "zh-CN": "镜像仓库", - "ja-JP": "リポジトリ", - "de-DE": "Repositories", - "ru-RU": "Репозитории" - }, - "no_repo_exists" : { - "en-US": "No repositories found, please use 'docker push' to upload images.", - "zh-CN": "未发现镜像,请用‘docker push’命令上传镜像。", - "ja-JP": "イメージが見つかりませんでした。’docker push’を利用しイメージをアップロードしてください。", - "de-DE": "Keine Repositories gefunden, bitte benutzen Sie 'docker push' um ein Image hochzuladen.", - "ru-RU": "Репозитории не найдены, используйте команду 'docker push' для добавления образов." - }, - "tag" : { - "en-US": "Tag", - "zh-CN": "标签", - "ja-JP": "タグ", - "de-DE": "Tag", - "ru-RU": "Метка" - }, - "pull_command": { - "en-US": "Pull Command", - "zh-CN": "Pull 命令", - "ja-JP": "Pull コマンド", - "de-DE": "Pull Befehl", - "ru-RU": "Команда для скачивания образа" - }, - "image_details" : { - "en-US": "Image Details", - "zh-CN": "镜像详细信息", - "ja-JP": "イメージ詳細", - "de-DE": "Image Details", - "ru-RU": "Информация об образе" - }, - "add_members" : { - "en-US": "Add Member", - "zh-CN": "添加成员", - "ja-JP": "メンバーを追加する", - "de-DE": "Mitglied hinzufügen", - "ru-RU": "Добавить Участника" - }, - "edit_members" : { - "en-US": "Edit Members", - "zh-CN": "编辑成员", - "ja-JP": "メンバーを編集する", - "de-DE": "Mitglieder bearbeiten", - "ru-RU": "Редактировать Участников" - }, - "add_member_failed" : { - "en-US": "Adding Member Failed", - "zh-CN": "添加成员失败", - "ja-JP": "メンバーを追加できません出した", - "de-DE": "Mitglied hinzufügen fehlgeschlagen", - "ru-RU": "Ошибка при добавлении нового участника" - }, - "please_input_username" : { - "en-US": "Please input a username.", - "zh-CN": "请输入用户名。", - "ja-JP": "ユーザ名を入力してください。", - "de-DE": "Bitte geben Sie einen Benutzernamen ein.", - "ru-RU": "Пожалуйста, введите имя пользователя." - }, - "please_assign_a_role_to_user" : { - "en-US": "Please assign a role to the user.", - "zh-CN": "请为用户分配角色。", - "ja-JP": "ユーザーに役割を割り当てるしてください。", - "de-DE": "Bitte weisen Sie dem Benutzer eine Rolle zu.", - "ru-RU": "Пожалуйста, назначьте роль пользователю." - }, - "user_id_exists" : { - "en-US": "User is already a member.", - "zh-CN": "用户已经是成员。", - "ja-JP": "すでにメンバーに登録しました。", - "de-DE": "Benutzer ist bereits Mitglied.", - "ru-RU": "Пользователь уже является участником." - }, - "user_id_does_not_exist" : { - "en-US": "User does not exist.", - "zh-CN": "不存在此用户。", - "ja-JP": "ユーザが見つかりませんでした。", - "de-DE": "Benutzer existiert nicht.", - "ru-RU": "Пользователя с таким именем не существует." - }, - "insufficient_privileges" : { - "en-US": "Insufficient privileges.", - "zh-CN": "权限不足。", - "ja-JP": "権限エラー。", - "de-DE": "Unzureichende Berechtigungen.", - "ru-RU": "Недостаточно прав." - }, - "operation_failed" : { - "en-US": "Operation Failed", - "zh-CN": "操作失败", - "ja-JP": "操作に失敗しました。", - "de-DE": "Befehl fehlgeschlagen", - "ru-RU": "Ошибка при выполнении данной операции" - }, - "button_on" : { - "en-US": "On", - "zh-CN": "打开", - "ja-JP": "オン", - "de-DE": "An", - "ru-RU": "Вкл." - }, - "button_off" : { - "en-US": "Off", - "zh-CN": "关闭", - "ja-JP": "オフ", - "de-DE": "Aus", - "ru-RU": "Откл." - } -}; diff --git a/static/i18n/locale_ru-RU.ini b/static/i18n/locale_ru-RU.ini deleted file mode 100644 index eb5521e0b..000000000 --- a/static/i18n/locale_ru-RU.ini +++ /dev/null @@ -1,89 +0,0 @@ -page_title_index = Harbor -page_title_sign_in = Войти - Harbor -page_title_project = Проект - Harbor -page_title_item_details = Подробнее - Harbor -page_title_registration = Регистрация - Harbor -page_title_add_user = Добавить пользователя - Harbor -page_title_forgot_password = Забыли пароль - Harbor -title_forgot_password = Забыли пароль -page_title_reset_password = Сбросить пароль - Harbor -title_reset_password = Сбросить пароль -page_title_change_password = Поменять пароль - Harbor -title_change_password = Поменять пароль -page_title_search = Поиск - Harbor -sign_in = Войти -sign_up = Регистрация -add_user = Добавить пользователя -log_out = Выйти -search_placeholder = проекты или репозитории -change_password = Сменить Пароль -username_email = Логин/Email -password = Пароль -forgot_password = Забыли пароль -welcome = Добро пожаловать -my_projects = Мои Проекты -public_projects = Общедоступные Проекты -admin_options = Административные Настройки -project_name = Название Проекта -creation_time = Время Создания -publicity = Публичность -add_project = Добавить Проект -check_for_publicity = Публичный проекта -button_save = Сохранить -button_cancel = Отмена -button_submit = Применить -username = Имя пользователя -email = Email -system_admin = Системный администратор -dlg_button_ok = OK -dlg_button_cancel = Отмена -registration = Регистрация -username_description = Ваше имя пользователя. -email_description = Email адрес, который будет использоваться для сброса пароля. -full_name = Полное Имя -full_name_description = Имя и Фамилия. -password_description = Минимум 7 символов, в которых будет присутствовать по меньшей мере 1 буква нижнего регистра, 1 буква верхнего регистра и 1 цифра. -confirm_password = Подтвердить Пароль -note_to_the_admin = Комментарии -old_password = Старый Пароль -new_password = Новый Пароль -forgot_password_description = Введите Email, который вы использовали для регистрации, вам будет выслано письмо для сброса пароля. - -projects = Проекты -repositories = Репозитории -search = Поиск -home = Домой -project = Проект -owner = Владелец -repo = Репозитории -user = Пользователи -logs = Логи -repo_name = Имя Репозитория -repo_tag = Метка -add_members = Добавить Участников -operation = Операция -advance = Расширенный Поиск -all = Все -others = Другие -start_date = Дата Начала -end_date = Дата Окончания -timestamp = Временная метка -role = Роль -reset_email_hint = Нажмите на ссылку ниже для сброса вашего пароля -reset_email_subject = Сброс вашего пароля -language = Русский -language_en-US = English -language_zh-CN = 中文 -language_de-DE = Deutsch -language_ru-RU = Русский -language_ja-JP = 日本語 -copyright = Copyright -all_rights_reserved = Все права защищены. -index_desc = Проект Harbor представляет собой надежный сервер управления docker-образами корпоративного класса. Компании могут использовать данный сервер в своей инфарструктуе для повышения производительности и безопасности . Проект Harbor может использоваться как в среде разработки так и в продуктивной среде. -index_desc_0 = Основные преимущества данного решения: -index_desc_1 = 1. Безопасность: Хранение интеллектуальной собственности внутри организации. -index_desc_2 = 2. Эффективность: сервер хранения docker образов устанавливается в рамках внутренней сети организации, и может значительно сократить расход Интернет траффика -index_desc_3 = 3. Управление доступом: реализована модель RBAC (Ролевая модель управление доступом). Управление пользователями может быть интегрировано с существующими корпоративными сервисами идентификациями такими как AD/LDAP. -index_desc_4 = 4. Аудит: Любой доступ к хранилищу логируется и может быть использован для последующего анализа. -index_desc_5 = 5. GUI-интерфейс: удобная, единая консоль управления. -index_title = Сервер управления docker-образами корпоративного класса diff --git a/static/i18n/locale_zh-CN.ini b/static/i18n/locale_zh-CN.ini index f5940a9af..0353ea5ad 100644 --- a/static/i18n/locale_zh-CN.ini +++ b/static/i18n/locale_zh-CN.ini @@ -1,89 +1,15 @@ -page_title_index = Harbor -page_title_sign_in = 登录 - Harbor -page_title_project = 项目 - Harbor -page_title_item_details = 详细信息 - Harbor -page_title_registration = 注册 - Harbor -page_title_add_user = 新增用户 - Harbor -page_title_forgot_password = 忘记密码 - Harbor -title_forgot_password = 忘记密码 -page_title_reset_password = 重置密码 - Harbor -title_reset_password = 重置密码 -page_title_change_password = 修改密码 - Harbor -title_change_password = 修改密码 -page_title_search = 搜索 - Harbor -sign_in = 登录 -sign_up = 注册 -add_user = 新增用户 -log_out = 注销 -search_placeholder = 项目或镜像名称 -change_password = 修改密码 -username_email = 用户名/邮箱 -password = 密码 -forgot_password = 忘记密码 -welcome = 欢迎 -my_projects = 我的项目 -public_projects = 公开项目 -admin_options = 管理员选项 -project_name = 项目名称 -creation_time = 创建时间 -publicity = 公开 -add_project = 新建项目 -check_for_publicity = 公开项目 -button_save = 保存 -button_cancel = 取消 -button_submit = 提交 -username = 用户名 -email = 邮箱 -system_admin = 系统管理员 -dlg_button_ok = 确定 -dlg_button_cancel = 取消 -registration = 注册 -username_description = 在此填入登录时的用户名。 -email_description = 此邮箱将用于重置密码。 -full_name = 全名 -full_name_description = 请输入全名。 -password_description = 至少输入 7个字符且包含 1个小写字母, 1个大写字母和 1个数字。 -confirm_password = 确认密码 -note_to_the_admin = 备注 -old_password = 原密码 -new_password = 新密码 -forgot_password_description = 重置邮件将发送到此邮箱。 - -projects = 项目 -repositories = 镜像仓库 -search = 搜索 -home = 首页 -project = 项目 -owner = 所有者 -repo = 镜像仓库 -user = 用户 -logs = 日志 -repo_name = 镜像名称 -repo_tag = 镜像标签 -add_members = 添加成员 -operation = 操作 -advance = 高级检索 -all = 全部 -others = 其他 -start_date = 开始日期 -end_date = 结束日期 -timestamp = 时间戳 -role = 角色 reset_email_hint = 请点击下面的链接进行重置密码操作 -reset_email_subject = 重置您的密码 -language = 中文 -language_en-US = English -language_zh-CN = 中文 -language_de-DE = Deutsch -language_ru-RU = Русский -language_ja-JP = 日本語 -copyright = 版权所有 -all_rights_reserved = 保留所有权利。 -index_desc = Harbor是可靠的企业级Registry服务器。企业用户可使用Harbor搭建私有容器Registry服务,提高生产效率和安全度,既可应用于生产环境,也可以在开发环境中使用。 -index_desc_0 = 主要优点: -index_desc_1 = 1. 安全: 确保知识产权在自己组织内部的管控之下。 -index_desc_2 = 2. 效率: 搭建组织内部的私有容器Registry服务,可显著降低访问公共Registry服务的网络需求。 -index_desc_3 = 3. 访问控制: 提供基于角色的访问控制,可集成企业目前拥有的用户管理系统(如:AD/LDAP)。 -index_desc_4 = 4. 审计: 所有访问Registry服务的操作均被记录,便于日后审计。 -index_desc_5 = 5. 管理界面: 具有友好易用图形管理界面。 -index_title = 企业级 Registry 服务 +reset_email_subject = 重置您的 Harbor 密码 + +page_title_index = Harbor +page_title_dashboard = 控制面板 - Harbor +page_title_account_setting = 账户设置 - Harbor +page_title_reset_password = 重置密码 - Harbor +page_title_change_password = 修改密码 - Harbor +page_title_forgot_password = 忘记密码 - Harbor +page_title_project = 项目 - Harbor +page_title_repository = 项目明细 - Harbor +page_title_search = 搜索 - Harbor +page_title_sign_up = 注册 - Harbor +page_title_add_new = 增加用户 - Harbor +page_title_admin_option = 管理员选项 - Harbor \ No newline at end of file diff --git a/static/package.json b/static/package.json new file mode 100644 index 000000000..763d881ec --- /dev/null +++ b/static/package.json @@ -0,0 +1,10 @@ +{ + "engines": { + "node": ">= 0.10.0" + }, + "devDependencies": { + "grunt": "~0.4.5", + "grunt-contrib-jshint": "~0.10.0", + "grunt-contrib-watch": "~0.6.1" + } +} diff --git a/static/resources/css/account-settings.css b/static/resources/css/account-settings.css new file mode 100644 index 000000000..57914e569 --- /dev/null +++ b/static/resources/css/account-settings.css @@ -0,0 +1,14 @@ +/* + 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. +*/ diff --git a/static/resources/css/admin-options.css b/static/resources/css/admin-options.css new file mode 100644 index 000000000..2cd2966f7 --- /dev/null +++ b/static/resources/css/admin-options.css @@ -0,0 +1,31 @@ +/* + 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. +*/ +.switch-pane-admin-options { + display: inline; + width: 245px; + float: right; + list-style-type: none; +} + +.switch-pane-admin-options a, .switch-pane-admin-options span { + display: inline-block; + text-decoration: none; + float: left; +} + +.switch-pane-admin-options li .active { + border-bottom: 2px solid rgb(0, 84, 190); + font-weight: bold; +} \ No newline at end of file diff --git a/static/resources/css/base.css b/static/resources/css/dashboard.css similarity index 68% rename from static/resources/css/base.css rename to static/resources/css/dashboard.css index 685da65ea..45bd75b23 100644 --- a/static/resources/css/base.css +++ b/static/resources/css/dashboard.css @@ -12,27 +12,17 @@ See the License for the specific language governing permissions and limitations under the License. */ -.footer { - width: 100%; - /* Set the fixed height of the footer here */ - height: 60px; -} -.div-height { - overflow-y: auto; -} -.blue { - color: #428bca; -} -sub { - font-size: 6pt; - color: blue; +.up-section .up-table-pane { + overflow-y: auto; + height: 220px; + margin-top: -10px; } -.table-responsive { - height: 430px; - overflow-y: auto; +.up-section .dl-horizontal dt{ + line-height: 25px; +} + +.up-section .dl-horizontal dt { + text-align: left; } -table > tbody > tr > td { - vertical-align: bottom; -} \ No newline at end of file diff --git a/static/resources/css/destination.css b/static/resources/css/destination.css new file mode 100644 index 000000000..3cc3d97f6 --- /dev/null +++ b/static/resources/css/destination.css @@ -0,0 +1,17 @@ +/* + 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. +*/ +.create-destination { + height: 275px; +} \ No newline at end of file diff --git a/static/resources/css/footer.css b/static/resources/css/footer.css new file mode 100644 index 000000000..38e9d85c9 --- /dev/null +++ b/static/resources/css/footer.css @@ -0,0 +1,41 @@ +/* + 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. +*/ +.footer-absolute { + position: absolute; + bottom: 0; + left: 0; + padding: 0; + margin: 0; +} + +.footer-static { + position: static; + +} + +.footer{ + width: 100%; + clear: both; + background-color: #A8A8A8; + height: 44px; + z-index: 10; +} +.footer p { + padding-top: 8px; + color: #FFFFFF; + margin-left: auto; + margin-right: auto; + width: 385px; +} diff --git a/static/resources/css/header.css b/static/resources/css/header.css new file mode 100644 index 000000000..4085ac542 --- /dev/null +++ b/static/resources/css/header.css @@ -0,0 +1,145 @@ +/* + 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. +*/ +.navbar-custom { + background-image: none; + background-color: #057ac9; + height: 66px; + margin-bottom: 0; +} + +nav .container { + margin-top: 15px; +} + +nav .container-custom { + min-width: 1024px; +} + +.navbar-custom .navbar-nav > li > a, +.navbar-custom .navbar-nav > li > a:hover, +.navbar-custom .navbar-nav > li > a:focus, +.navbar-custom .navbar-nav > li > a:active { + color: white; /*Change active text color here*/ +} + +.navbar-brand > img { + margin-top: -30px; +} + +.navbar-form { + margin-top: 0; + padding-right: 0; +} + +.search-icon { + background: url("/static/resources/img/magnitude-glass.jpg") no-repeat 97% 6px; + background-size: 1.5em; + background-color: #FFFFFF; +} + +.nav-custom li { + float: left; + padding: 10px 0 0 0; + margin-right: 12px; + list-style: none; + display: inline-block; +} + +.nav-custom li a { + font-size: 14px; + color: #FFFFFF; + text-decoration: none; +} + +.nav-custom .active { + border-bottom: 3px solid #EFEFEF; + font-weight: bold; +} + +.dropdown { + float: left; +} + +.dropdown .btn-link:hover, +.dropdown .btn-link:visited, +.dropdown .btn-link:link { + display:inline-block; + text-decoration: none; + color: #FFFFFF; +} + +.dropdown-submenu { + position: relative; +} + +.dropdown-submenu>.dropdown-menu { + top: 0; + left: 100%; + margin-top: -6px; + margin-left: -1px; + -webkit-border-radius: 0 6px 6px 6px; + -moz-border-radius: 0 6px 6px; + border-radius: 0 6px 6px 6px; +} + +.dropdown-submenu:hover>.dropdown-menu { + display: block; +} + +.dropdown-submenu>a:after { + display: block; + content: " "; + float: right; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; + border-width: 5px 0 5px 5px; + border-left-color: #ccc; + margin-top: 5px; + margin-right: -10px; +} + +.dropdown-submenu:hover>a:after { + border-left-color: #fff; +} + +.dropdown-submenu.pull-left { + float: none; +} + +.dropdown-submenu.pull-left>.dropdown-menu { + left: -100%; + margin-left: 10px; + -webkit-border-radius: 6px 0 6px 6px; + -moz-border-radius: 6px 0 6px 6px; + border-radius: 6px 0 6px 6px; +} + +.loading-progress { + display: inline-block; + position: relative; + background-image: url('/static/resources/img/loading.gif'); + background-position: center; + background-size: 107px; + width: 1em; + height: 1.2em; + margin-bottom: 2px; + vertical-align: middle; +} + +a:hover, a:visited, a:link { + text-decoration: none; +} \ No newline at end of file diff --git a/static/resources/css/index.css b/static/resources/css/index.css new file mode 100644 index 000000000..93ae84f18 --- /dev/null +++ b/static/resources/css/index.css @@ -0,0 +1,142 @@ +/* + 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. +*/ +body { + margin: 0; + padding: 0; + width: 100%; + height: 100%; + position: fixed; +} + +.has-logged-in { + position: relative; + top: 50px; +} + +.has-logged-in h4 { + float: left; + width: 100%; + line-height: 2em; + text-align: center; +} + +.has-logged-in .last-logged-in-time { + text-align: center; +} + +.has-logged-in .control-button { + height: 2em; + width: 100%; + padding-right: 10%; + clear: both; +} + +.container-fluid-custom { + background-color: #EFEFEF; + width: 100%; + height: 100%; + min-height: 1px; + overflow-y: auto; + overflow-x: hidden; +} + +.up-section { + position: relative; + padding: 15px 15px 15px; + margin: 20px 0 0 -15px; + background-color: #FFFFFF; + height: 277px; +} + +.up-section h4 label { + margin-left: 5px; +} + +.right-part { + padding-right: 0; + margin-right: -15px; +} + +.thumbnail { + margin-top: 10px; + display: inline-block; + border: none; + padding: 2px; + box-shadow: none; + width: 30%; + vertical-align: top; +} + +.down-section { + position: relative; + padding: 15px 15px 15px; + margin: 20px -15px 0 -15px; + background-color: #FFFFFF; + height: 350px; +} + +.down-section-left { + margin-right: 0; +} + +.down-section ul { + padding: 0; + margin-left: 30px; + margin-right: 20px; +} + +.long-line { + overflow: hidden; + width: 100%; +} + +.long-line-margin-right { + margin-right: 30px; +} + +.down-table-pane { + overflow-y: auto; + height: 260px; + padding-left: 10px; + padding-right: 10px; +} + +.page-header { + padding-bottom: 10px; + margin: 10px; + border-bottom: none; +} + +.underlined { + border-bottom: 2px solid #EFEFEF; +} + +.step-content { + text-align: center; +} + +.display-inline-block { + display: inline-block; +} + +.title-color { + color: #057ac9; +} + +.page-content { + margin: 0 20px 0 20px; + text-align: left; + overflow-y: hidden; +} diff --git a/static/resources/css/project.css b/static/resources/css/project.css new file mode 100644 index 000000000..667283bba --- /dev/null +++ b/static/resources/css/project.css @@ -0,0 +1,97 @@ +/* + 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. +*/ +.container-custom { + position: relative; + min-height: 1px; +} + +.extend-height { + height: 100%; + padding-left: 0; + padding-right: 0; +} + +.section { + padding: 15px; + margin-top: 20px; + background-color: #FFFFFF; + height: 100%; + min-height: 672px; + width: 100%; +} + +.search-pane { + margin: 0 10px 0 10px; + height: 3em; +} + + +.table>tbody>tr>td, .table>tbody>tr>th { + vertical-align: middle; +} + +.table-header { + margin-bottom: 0; +} + +.table-head-container { + +} + +.table-body-container { + overflow-y: auto; +} + +.gutter { + margin: 0 1em 0 1em; +} + +.project-pane { + margin: 0 10px 0 10px; +} + +.pane { + height: auto; + min-height: 1px; +} + +.tab-pane { + min-height: 1px; + max-height: 1px; +} + +.sub-pane { + margin: 15px; + min-height: 380px; + overflow-y: auto; +} + +.well-custom { + + width: 100%; + background-color: #f5f5f5; + background-image: none; + + z-index: 10; +} + +.form-custom { + margin-top: 1em; +} + +.empty-hint { + text-align: center; + vertical-align: middle; +} \ No newline at end of file diff --git a/static/resources/css/replication.css b/static/resources/css/replication.css new file mode 100644 index 000000000..126725ea2 --- /dev/null +++ b/static/resources/css/replication.css @@ -0,0 +1,98 @@ +/* + 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. +*/ +.create-policy { + height: 565px; + overflow-y: auto; +} + +.form-group-custom { + margin-top: 10px; +} + +.h4-custom { + margin-top: 20px; +} + +.h4-custom-down { + display: inline-block; + margin-top: 20px; +} + +.hr-line { + margin-top: 0; + margin-bottom: 10px; +} + +.form-control-custom { + width: 100% !important; +} + +.pane-split { + overflow-x: hidden; + overflow-y: auto; + width: 100%; +} + +#upon-pane { + margin-top: 10px; + height: 320px; +} + +#upon-pane table>tbody>tr { + cursor: all-scroll; +} +#down-pane { + height: 450px; +} + +.sub-pane-split { + margin: 15px; + height: auto; + min-height: 50px; +} + +.well-split { + margin: 0; + position: relative; +} + +.split-handle { + margin-left: auto; + margin-right: auto; + width: 1em; + height: 1em; + cursor: ns-resize; + color: #C0C0C0; +} + +.color-success { + color: #5cb85c; +} + +.color-danger { + color: #d9534f +} + +.color-warning { + color: #f0ad4e; +} + +.label-custom { + margin: 0 5px 0 10px; +} + +.dialog-message { + padding: 15px; +} \ No newline at end of file diff --git a/static/resources/css/repository.css b/static/resources/css/repository.css new file mode 100644 index 000000000..ad55044fa --- /dev/null +++ b/static/resources/css/repository.css @@ -0,0 +1,159 @@ +/* + 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. +*/ + +.panel { + margin-bottom: 0; +} + +.pane-container { + overflow-y: auto; +} + +.pane-container .list-group-item { + border: none; +} + +.item-line { + margin: 0 0 10px 0; +} + +.switch-pane { + padding: 10px 15px 0 15px; + margin: 0 10px 10px 10px; + height: 3em; + background-color: rgb(231,244,254); +} + +.switch-pane-projects { + float: left; +} + +.switch-pane-tabs { + width: 265px; + list-style-type: none; +} + +.switch-pane-tabs a,.switch-pane-tabs span { + display: inline-block; + text-decoration: none; + float: left; +} + +.switch-pane-tabs li .active { + border-bottom: 2px solid rgb(0, 84, 190); + font-weight: bold; +} + +.switch-pane-drop-down { + display: block; + position: absolute; + margin: -10px 0 0 10px; + z-index: 1000; +} + +.search-projects { + padding: 15px 10px 10px; +} + +.project-list { + height: 440px; +} + +.project-selected { + margin-left: -1.2em; +} + +.list-group { + box-shadow: none; +} + +.list-group-item { + text-align: left; + margin-left: 20%; +} + +.panel-group { + margin-top: 10px; +} + +.repository-table { + width: 100%; +} + +.repository-table th, .repository-table td { + padding: 10px; + text-align: left; +} + +.each-tab-pane { + padding: 0 10px; +} + +.inline-block { + display: inline-block; +} + +.datetime-picker-title { + float: left; + line-height: 2.5em; + margin: 0 0.5em 0 0; +} + +.input-group .form-control { + z-index: 1; +} + +.well { + padding: 12px; +} + +.popover{ + max-width: 500px; +} + +.popover-header { + padding:8px 14px; + background-color:#f7f7f7; + border-bottom:1px solid #ebebeb; + -webkit-border-radius:5px 5px 0 0; + -moz-border-radius:5px 5px 0 0; + border-radius:5px 5px 0 0; +} + +.popover-title { + height: 2.5em; + padding: 8px 14px; + margin: 0; + font-size: 14px; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-radius: 5px 5px 0 0; +} + +.alert-custom { + position: relative; + bottom: 42px; + z-index: 99; + width: 100%; + margin-left: auto; + margin-right: auto; + padding: 10px; + background-color: #f2dede; + background-image: none; +} + +.alert-custom .close { + right: 0; +} \ No newline at end of file diff --git a/static/resources/css/sign-in.css b/static/resources/css/search.css similarity index 82% rename from static/resources/css/sign-in.css rename to static/resources/css/search.css index 2bea229cf..bf532006a 100644 --- a/static/resources/css/sign-in.css +++ b/static/resources/css/search.css @@ -12,16 +12,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -body { - background-color: #fff; +.search-result { + min-height: 200px; + max-height: 200px; + overflow-y: auto; } -.form-signin { - padding-top: 40px; - padding-bottom: 40px; - max-width: 380px; - margin: 10% auto; -} - - - +.search-result li { + margin-bottom: 15px; +} \ No newline at end of file diff --git a/static/resources/css/sign-up.css b/static/resources/css/sign-up.css new file mode 100644 index 000000000..02885b62d --- /dev/null +++ b/static/resources/css/sign-up.css @@ -0,0 +1,62 @@ +/* + 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. +*/ +.main-title { + margin-top: 20px; + margin-left: 180px; +} + +.main-content { + width: 60%; + margin-top: 40px; +} + +.form-horizontal .control-label { + text-align: right; +} + +.row { + margin-left: 0; + margin-right: 0; +} + +.form-horizontal .form-group { + margin-left: -15px; + margin-right: -15px; +} + +.small-size-fonts { + font-size: 10pt; +} + +.asterisk { + display: inline-block; + margin-left: -25px; + padding-top: 8px; + color: red; + font-size: 14pt; +} + + + +.css-form input.ng-invalid.ng-touched { + border-color: red; +} + +.error-message { + color: red; + width: 100%; + margin-right: auto; + margin-left: auto; +} \ No newline at end of file diff --git a/static/resources/image/Harbor_Logo_rec.png b/static/resources/image/Harbor_Logo_rec.png deleted file mode 100755 index 5cfafb87c..000000000 Binary files a/static/resources/image/Harbor_Logo_rec.png and /dev/null differ diff --git a/static/resources/img/Harbor_Logo_rec.png b/static/resources/img/Harbor_Logo_rec.png new file mode 100644 index 000000000..3a603109e Binary files /dev/null and b/static/resources/img/Harbor_Logo_rec.png differ diff --git a/static/resources/img/Step1.png b/static/resources/img/Step1.png new file mode 100644 index 000000000..4ddbefa08 Binary files /dev/null and b/static/resources/img/Step1.png differ diff --git a/static/resources/img/Step2.png b/static/resources/img/Step2.png new file mode 100644 index 000000000..64ab8a04b Binary files /dev/null and b/static/resources/img/Step2.png differ diff --git a/static/resources/img/Step3.png b/static/resources/img/Step3.png new file mode 100644 index 000000000..1cc5fc62b Binary files /dev/null and b/static/resources/img/Step3.png differ diff --git a/static/resources/img/loading.gif b/static/resources/img/loading.gif new file mode 100644 index 000000000..678bcd6a8 Binary files /dev/null and b/static/resources/img/loading.gif differ diff --git a/static/resources/img/magnitude-glass.jpg b/static/resources/img/magnitude-glass.jpg new file mode 100644 index 000000000..0a3c176cc Binary files /dev/null and b/static/resources/img/magnitude-glass.jpg differ diff --git a/static/resources/js/change-password.js b/static/resources/js/change-password.js deleted file mode 100644 index 0a536b7b4..000000000 --- a/static/resources/js/change-password.js +++ /dev/null @@ -1,98 +0,0 @@ -/* - 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. -*/ -jQuery(function(){ - - new AjaxUtil({ - url: "/api/users/current", - type: "get", - success: function(data, status, xhr){}, - errors: { - 403: "" - }, - error: function(jqXhr){ - if(jqXhr && jqXhr.status == 401){ - document.location = "/signIn"; - } - } - }).exec(); - - $("#divErrMsg").css({"display": "none"}); - - $("#OldPassword,#Password,#ConfirmedPassword").on("blur", validateCallback); - validateOptions.Items = ["#OldPassword", "#Password", "#ConfirmedPassword"]; - - function bindEnterKey(){ - $(document).on("keydown", function(e){ - if(e.keyCode == 13){ - e.preventDefault(); - if($("#txtCommonSearch").is(":focus")){ - document.location = "/search?q=" + $("#txtCommonSearch").val(); - }else{ - $("#btnSubmit").trigger("click"); - } - } - }); - } - function unbindEnterKey(){ - $(document).off("keydown"); - } - bindEnterKey(); - - var spinner = new Spinner({scale:1}).spin(); - - $("#btnSubmit").on("click", function(){ - validateOptions.Validate(function(){ - var oldPassword = $("#OldPassword").val(); - var password = $("#Password").val(); - new AjaxUtil({ - url: "/api/users/current/password", - type: "put", - data: {"old_password": oldPassword, "new_password" : password}, - beforeSend: function(e){ - unbindEnterKey(); - $("h1").append(spinner.el); - $("#btnSubmit").prop("disabled", true); - }, - complete: function(xhr, status){ - spinner.stop(); - $("#btnSubmit").prop("disabled", false); - if(xhr && xhr.status == 200){ - $("#dlgModal") - .dialogModal({ - "title": i18n.getMessage("title_change_password"), - "content": i18n.getMessage("change_password_successfully"), - "callback": function(){ - window.close(); - } - }); - } - }, - error: function(jqXhr, status, error){ - if(jqXhr && jqXhr.responseText.length){ - $("#dlgModal") - .dialogModal({ - "title": i18n.getMessage("title_change_password"), - "content": i18n.getMessage(jqXhr.responseText), - "callback": function(){ - bindEnterKey(); - return; - } - }); - } - } - }).exec(); - }); - }); -}); \ No newline at end of file diff --git a/static/resources/js/common.js b/static/resources/js/common.js deleted file mode 100644 index f434d0960..000000000 --- a/static/resources/js/common.js +++ /dev/null @@ -1,169 +0,0 @@ -/* - 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. -*/ - -var AjaxUtil = function(params){ - - this.url = params.url; - this.data = params.data; - this.dataRaw = params.dataRaw; - this.type = params.type; - this.errors = params.errors || {}; - - this.success = params.success; - this.complete = params.complete; - this.error = params.error; - -}; - -AjaxUtil.prototype.exec = function(){ - - var self = this; - - return $.ajax({ - url: self.url, - contentType: (self.dataRaw ? "application/x-www-form-urlencoded; charset=UTF-8" : "application/json; charset=utf-8"), - data: JSON.stringify(self.data) || self.dataRaw, - type: self.type, - dataType: "json", - success: function(data, status, xhr){ - if(self.success != null){ - self.success(data, status, xhr); - } - }, - complete: function(jqXhr, status) { - if(self.complete != null){ - self.complete(jqXhr, status); - } - }, - error: function(jqXhr){ - if(self.error != null){ - self.error(jqXhr); - }else{ - var errorMessage = self.errors[jqXhr.status] || jqXhr.responseText; - if(jqXhr.status == 401){ - var lastUri = location.pathname + location.search; - if(lastUri != ""){ - document.location = "/signIn?uri=" + encodeURIComponent(lastUri); - }else{ - document.location = "/signIn"; - } - }else if($.trim(errorMessage).length > 0){ - $("#dlgModal").dialogModal({"title": i18n.getMessage("operation_failed"), "content": errorMessage}); - } - } - } - }); -}; - -var SUPPORT_LANGUAGES = { - "en-US": "English", - "zh-CN": "Chinese", - "de-DE": "German", - "ru-RU": "Russian", - "ja-JP": "Japanese" -}; - -var DEFAULT_LANGUAGE = "en-US"; - -var I18n = function(messages) { - this.messages = messages; -}; - -I18n.prototype.isSupportLanguage = function(lang){ - return (lang in SUPPORT_LANGUAGES); -} - -I18n.prototype.getLocale = function(){ - var lang = $("#currentLanguage").val(); - if(this.isSupportLanguage(lang)){ - return lang; - }else{ - return DEFAULT_LANGUAGE; - } -}; - -I18n.prototype.getMessage = function(key){ - return this.messages[key][this.getLocale()]; -}; - -var i18n = new I18n(global_messages); - -moment().locale(i18n.getLocale()); - -jQuery(function(){ - - $("#aLogout").on("click", function(){ - new AjaxUtil({ - url:'/logout', - dataRaw:{"timestamp" : new Date().getTime()}, - type: "get", - complete: function(jqXhr){ - if(jqXhr && jqXhr.status == 200){ - document.location = "/"; - } - } - }).exec(); - }); - - $.fn.dialogModal = function(options){ - var settings = $.extend({ - title: '', - content: '', - text: false, - callback: null, - enableCancel: false, - }, options || {}); - - if(settings.enableCancel){ - $("#dlgCancel").show(); - $("#dlgCancel").on("click", function(){ - $(self).modal('close'); - }); - } - - var self = this; - $("#dlgLabel", self).text(settings.title); - - if(options.text){ - $("#dlgBody", self).html(settings.content); - }else if(typeof settings.content == "object"){ - $(".modal-dialog", self).addClass("modal-lg"); - var lines = ['
']; - for(var item in settings.content){ - lines.push('
'+ - '' + - '

' + settings.content[item] + '

' + - '
'); - } - lines.push('
'); - $("#dlgBody", self).html(lines.join("")); - }else{ - $(".modal-dialog", self).removeClass("modal-lg"); - $("#dlgBody", self).text(settings.content); - } - - if(settings.callback != null){ - var hasEntered = false; - $("#dlgConfirm").on("click", function(e){ - if(!hasEntered) { - hasEntered = true; - settings.callback(); - - } - }); - } - $(self).modal('show'); - } -}); diff --git a/static/resources/js/components/details/retrieve-projects.directive.html b/static/resources/js/components/details/retrieve-projects.directive.html new file mode 100644 index 000000000..965d3d12b --- /dev/null +++ b/static/resources/js/components/details/retrieve-projects.directive.html @@ -0,0 +1,35 @@ + +
+
+
+
+
+
+ +
+
+ +
+ +
+
+
+
+
\ No newline at end of file diff --git a/static/resources/js/components/details/retrieve-projects.directive.js b/static/resources/js/components/details/retrieve-projects.directive.js new file mode 100644 index 000000000..ab4850718 --- /dev/null +++ b/static/resources/js/components/details/retrieve-projects.directive.js @@ -0,0 +1,173 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.details') + .directive('retrieveProjects', retrieveProjects); + + RetrieveProjectsController.$inject = ['$scope', 'nameFilter', '$filter', 'trFilter', 'ListProjectService', '$location', 'getParameterByName', 'CurrentProjectMemberService', '$window']; + + function RetrieveProjectsController($scope, nameFilter, $filter, trFilter, ListProjectService, $location, getParameterByName, CurrentProjectMemberService, $window) { + var vm = this; + + vm.projectName = ''; + vm.isOpen = false; + + if(getParameterByName('is_public', $location.absUrl())) { + vm.isPublic = getParameterByName('is_public', $location.absUrl()) === 'true' ? 1 : 0; + vm.publicity = (vm.isPublic === 1) ? true : false; + } + + vm.retrieve = retrieve; + vm.filterInput = ''; + vm.selectItem = selectItem; + vm.checkProjectMember = checkProjectMember; + + $scope.$watch('vm.selectedProject', function(current, origin) { + if(current) { + vm.selectedId = current.project_id; + } + }); + + $scope.$watch('vm.publicity', function(current, origin) { + vm.publicity = current ? true : false; + vm.isPublic = vm.publicity ? 1 : 0; + vm.projectType = (vm.isPublic === 1) ? 'public_projects' : 'my_projects'; + vm.retrieve(); + }); + + function retrieve() { + ListProjectService(vm.projectName, vm.isPublic) + .success(getProjectSuccess) + .error(getProjectFailed); + } + + function getProjectSuccess(data, status) { + vm.projects = data; + + if(vm.projects == null) { + vm.isPublic = 1; + vm.publicity = true; + vm.projectType = 'public_projects'; + console.log('vm.projects is null, load public projects.'); + return; + } + + if(angular.isArray(vm.projects) && vm.projects.length > 0) { + vm.selectedProject = vm.projects[0]; + }else{ + $window.location.href = '/project'; + } + + if(getParameterByName('project_id', $location.absUrl())){ + angular.forEach(vm.projects, function(value, index) { + if(value['project_id'] === Number(getParameterByName('project_id', $location.absUrl()))) { + vm.selectedProject = value; + } + }); + } + + $location.search('project_id', vm.selectedProject.project_id); + + vm.checkProjectMember(vm.selectedProject.project_id); + vm.resultCount = vm.projects.length; + + $scope.$watch('vm.filterInput', function(current, origin) { + vm.resultCount = $filter('name')(vm.projects, vm.filterInput, 'name').length; + }); + } + + function getProjectFailed(data) { + $scope.$emit('modalTitle', $filter('tr')('error')); + $scope.$emit('modalMessage', $filter('tr')('failed_to_get_project')); + $scope.$emit('raiseError', true); + console.log('Failed to list projects.'); + } + + function selectItem(item) { + vm.selectedProject = item; + $location.search('project_id', vm.selectedProject.project_id); + } + + $scope.$on('$locationChangeSuccess', function(e) { + var projectId = getParameterByName('project_id', $location.absUrl()); + vm.isOpen = false; + }); + + function checkProjectMember(projectId) { + CurrentProjectMemberService(projectId) + .success(getCurrentProjectMemberSuccess) + .error(getCurrentProjectMemberFailed); + } + + function getCurrentProjectMemberSuccess(data, status) { + console.log('Successful get current project member:' + status); + vm.isProjectMember = true; + } + + function getCurrentProjectMemberFailed(data, status) { + vm.isProjectMember = false; + console.log('Current user has no member for the project:' + status + ', location.url:' + $location.url()); + } + + } + + function retrieveProjects() { + var directive = { + restrict: 'E', + templateUrl: '/static/resources/js/components/details/retrieve-projects.directive.html', + scope: { + 'isOpen': '=', + 'selectedProject': '=', + 'publicity': '=', + 'isProjectMember': '=' + }, + link: link, + controller: RetrieveProjectsController, + bindToController: true, + controllerAs: 'vm' + }; + + return directive; + + function link(scope, element, attrs, ctrl) { + $(document).on('click', clickHandler); + + function clickHandler(e) { + $('[data-toggle="popover"]').each(function () { + if (!$(this).is(e.target) && + $(this).has(e.target).length === 0 && + $('.popover').has(e.target).length === 0) { + $(this).parent().popover('hide'); + } + }); + var targetId = $(e.target).attr('id'); + if(targetId === 'switchPane' || + targetId === 'retrievePane' || + targetId === 'retrieveFilter') { + return; + }else{ + ctrl.isOpen = false; + scope.$apply(); + } + } + } + + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/details/switch-pane-projects.directive.html b/static/resources/js/components/details/switch-pane-projects.directive.html new file mode 100644 index 000000000..d83d29be4 --- /dev/null +++ b/static/resources/js/components/details/switch-pane-projects.directive.html @@ -0,0 +1,19 @@ + +
+ //vm.projectName// + + +
\ No newline at end of file diff --git a/static/resources/js/components/details/switch-pane-projects.directive.js b/static/resources/js/components/details/switch-pane-projects.directive.js new file mode 100644 index 000000000..d0a8c994e --- /dev/null +++ b/static/resources/js/components/details/switch-pane-projects.directive.js @@ -0,0 +1,64 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.details') + .directive('switchPaneProjects', switchPaneProjects); + + SwitchPaneProjectsController.$inject = ['$scope']; + + function SwitchPaneProjectsController($scope) { + var vm = this; + + $scope.$watch('vm.selectedProject', function(current, origin) { + if(current){ + vm.projectName = current.name; + vm.selectedProject = current; + } + }); + + vm.switchPane = switchPane; + + function switchPane() { + if(vm.isOpen) { + vm.isOpen = false; + }else{ + vm.isOpen = true; + } + } + + } + + function switchPaneProjects() { + var directive = { + restrict: 'E', + templateUrl: '/static/resources/js/components/details/switch-pane-projects.directive.html', + scope: { + 'isOpen': '=', + 'selectedProject': '=' + }, + controller: SwitchPaneProjectsController, + controllerAs: 'vm', + bindToController: true + }; + + return directive; + + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/dismissable-alerts/dismissable-alerts.directive.html b/static/resources/js/components/dismissable-alerts/dismissable-alerts.directive.html new file mode 100644 index 000000000..9c8c5dbbd --- /dev/null +++ b/static/resources/js/components/dismissable-alerts/dismissable-alerts.directive.html @@ -0,0 +1,18 @@ + + \ No newline at end of file diff --git a/static/resources/js/components/dismissable-alerts/dismissable-alerts.directive.js b/static/resources/js/components/dismissable-alerts/dismissable-alerts.directive.js new file mode 100644 index 000000000..9b94a1641 --- /dev/null +++ b/static/resources/js/components/dismissable-alerts/dismissable-alerts.directive.js @@ -0,0 +1,48 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.dismissable.alerts') + .directive('dismissableAlerts', dismissableAlerts); + + function dismissableAlerts() { + var directive = { + 'restrict': 'E', + 'templateUrl': '/static/resources/js/components/dismissable-alerts/dismissable-alerts.directive.html', + 'link': link + }; + return directive; + function link(scope, element, attrs, ctrl) { + + scope.close = function() { + scope.toggleAlert = false; + } + scope.$on('raiseAlert', function(e, val) { + console.log('received raiseAlert:' + angular.toJson(val)); + if(val.show) { + scope.message = val.message; + scope.toggleAlert = true; + }else{ + scope.message = '' + scope.toggleAlert = false; + } + }); + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/dismissable-alerts/dismissable-alerts.module.js b/static/resources/js/components/dismissable-alerts/dismissable-alerts.module.js new file mode 100644 index 000000000..cc1f2e2a7 --- /dev/null +++ b/static/resources/js/components/dismissable-alerts/dismissable-alerts.module.js @@ -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. +*/ +(function() { + + 'use strict'; + + angular.module('harbor.dismissable.alerts', []); + +})(); \ No newline at end of file diff --git a/static/resources/js/components/element-height/element-height.inspector.js b/static/resources/js/components/element-height/element-height.inspector.js new file mode 100644 index 000000000..769f2f068 --- /dev/null +++ b/static/resources/js/components/element-height/element-height.inspector.js @@ -0,0 +1,62 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.element.height') + .directive('elementHeight', elementHeight); + + function elementHeight($window) { + var directive = { + 'restrict': 'A', + 'link': link + }; + + return directive; + + function link(scope, element, attrs) { + + var w = angular.element($window); + + scope.getDimension = function() { + return {'h' : w.height()}; + }; + + if(!angular.isDefined(scope.subsHeight)) scope.subsHeight = 110; + if(!angular.isDefined(scope.subsSection)) scope.subsSection = 32; + if(!angular.isDefined(scope.subsSubPane)) scope.subsSubPane = 226; + if(!angular.isDefined(scope.subsTblBody)) scope.subsTblBody = 40; + + scope.$watch(scope.getDimension, function(current) { + if(current) { + var h = current.h; + element.css({'height': (h - scope.subsHeight) + 'px'}); + element.find('.section').css({'height': (h - scope.subsHeight - scope.subsSection) + 'px'}); + element.find('.sub-pane').css({'height': (h - scope.subsHeight - scope.subsSubPane) + 'px'}); + element.find('.tab-pane').css({'height': (h - scope.subsHeight - scope.subsSubPane - scope.subsSection -100) + 'px'}); +// var subPaneHeight = element.find('.sub-pane').height(); +// element.find('.table-body-container').css({'height': (subPaneHeight - scope.subsTblBody) + 'px'}); + } + }, true); + + w.on('pageshow, resize', function() { + scope.$apply(); + }); + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/element-height/element-height.module.js b/static/resources/js/components/element-height/element-height.module.js new file mode 100644 index 000000000..14be71f1e --- /dev/null +++ b/static/resources/js/components/element-height/element-height.module.js @@ -0,0 +1,22 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.element.height', []); + +})(); \ No newline at end of file diff --git a/views/segment/base-layout.tpl b/static/resources/js/components/inline-help/inline-help.directive.html similarity index 76% rename from views/segment/base-layout.tpl rename to static/resources/js/components/inline-help/inline-help.directive.html index e332907ba..e7b935299 100644 --- a/views/segment/base-layout.tpl +++ b/static/resources/js/components/inline-help/inline-help.directive.html @@ -12,17 +12,6 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - - {{.HeaderInc}} - {{.PageTitle}} - - - {{.HeaderContent}} - {{.BodyContent}} - {{.FooterInc}} - {{.ModalDialog}} - {{.FootContent}} - - \ No newline at end of file + + + \ No newline at end of file diff --git a/static/resources/js/components/inline-help/inline-help.directive.js b/static/resources/js/components/inline-help/inline-help.directive.js new file mode 100644 index 000000000..cf2f30fe7 --- /dev/null +++ b/static/resources/js/components/inline-help/inline-help.directive.js @@ -0,0 +1,47 @@ +/* + 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. +*/ +(function() { + 'use strict'; + + angular + .module('harbor.inline.help') + .directive('inlineHelp', inlineHelp); + function InlineHelpController() { + var vm = this; + } + function inlineHelp() { + var directive = { + 'restrict': 'E', + 'templateUrl': '/static/resources/js/components/inline-help/inline-help.directive.html', + 'scope': { + 'helpTitle': '@', + 'content': '@' + }, + 'link': link, + 'controller': InlineHelpController, + 'controllerAs': 'vm', + 'bindToController': true + }; + return directive; + function link(scope, element, attr, ctrl) { + element.popover({ + 'title': ctrl.helpTitle, + 'content': ctrl.content, + 'html': true + }); + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/inline-help/inline-help.module.js b/static/resources/js/components/inline-help/inline-help.module.js new file mode 100644 index 000000000..26311190e --- /dev/null +++ b/static/resources/js/components/inline-help/inline-help.module.js @@ -0,0 +1,22 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.inline.help', []); + +})(); \ No newline at end of file diff --git a/static/resources/js/components/loading-progress/loading-progress.directive.js b/static/resources/js/components/loading-progress/loading-progress.directive.js new file mode 100644 index 000000000..030f19edf --- /dev/null +++ b/static/resources/js/components/loading-progress/loading-progress.directive.js @@ -0,0 +1,70 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.loading.progress') + .directive('loadingProgress', loadingProgress); + + function loadingProgress() { + var directive = { + 'restrict': 'EA', + 'scope': { + 'toggleInProgress': '=', + 'hideTarget': '@' + }, + 'link': link + }; + + return directive; + + function link(scope, element, attrs) { + var spinner = $(''); + + function convertToBoolean(val) { + return val === 'true' ? true : false; + } + + var hideTarget = convertToBoolean(scope.hideTarget); + + console.log('loading-progress, toggleInProgress:' + scope.toggleInProgress + ', hideTarget:' + hideTarget); + + var pristine = element.html(); + + scope.$watch('toggleInProgress', function(current) { + if(scope.toggleInProgress) { + element.attr('disabled', 'disabled'); + if(hideTarget) { + element.html(spinner); + }else{ + spinner = spinner.css({'margin-left': '5px'}); + element.append(spinner); + } + }else{ + if(hideTarget) { + element.html(pristine); + }else{ + element.find('.loading-progress').remove(); + } + element.removeAttr('disabled'); + } + }); + + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/loading-progress/loading-progress.module.js b/static/resources/js/components/loading-progress/loading-progress.module.js new file mode 100644 index 000000000..076da55c5 --- /dev/null +++ b/static/resources/js/components/loading-progress/loading-progress.module.js @@ -0,0 +1,22 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.loading.progress', []); + +})(); \ No newline at end of file diff --git a/static/resources/js/components/log/advanced-search.directive.html b/static/resources/js/components/log/advanced-search.directive.html new file mode 100644 index 000000000..022dc9614 --- /dev/null +++ b/static/resources/js/components/log/advanced-search.directive.html @@ -0,0 +1,68 @@ + +
+
+
+
+
+
+ +
+
+  // 'all' | tr //   +  Create   +  Pull   +  Push   +  Delete   +  // 'others' | tr //   + +
+ +
+ +
+ +
+ // 'from' | tr //: + +
+ + + + +
+
+ +
+ // 'to' | tr //: +
+ + + + +
+
+ +
+
+
+
+
+ +
+
+
+
+
\ No newline at end of file diff --git a/static/resources/js/components/log/advanced-search.directive.js b/static/resources/js/components/log/advanced-search.directive.js new file mode 100644 index 000000000..31a858deb --- /dev/null +++ b/static/resources/js/components/log/advanced-search.directive.js @@ -0,0 +1,165 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.log') + .directive('advancedSearch', advancedSearch); + + AdvancedSearchController.$inject = ['$scope', 'ListLogService']; + + function AdvancedSearchController($scope, ListLogService) { + var vm = this; + + vm.checkOperation = checkOperation; + vm.close = close; + + vm.opAll = true; + + vm.doSearch = doSearch; + + $scope.$watch('vm.op', function(current) { + if(current && vm.op[0] === 'all') { + vm.opCreate = true; + vm.opPull = true; + vm.opPush = true; + vm.opDelete = true; + vm.opOthers = true; + } + }, true); + + $scope.$watch('vm.fromDate', function(current) { + if(current) { + vm.fromDate = current; + } + }); + + $scope.$watch('vm.toDate', function(current) { + if(current) { + vm.toDate = current; + } + }); + + + vm.opCreate = true; + vm.opPull = true; + vm.opPush = true; + vm.opDelete = true; + vm.opOthers = true; + vm.others = ''; + + vm.op = []; + vm.op.push('all'); + function checkOperation(e) { + if(e.checked === 'all') { + vm.opCreate = vm.opAll; + vm.opPull = vm.opAll; + vm.opPush = vm.opAll; + vm.opDelete = vm.opAll; + vm.opOthers = vm.opAll; + }else { + vm.opAll = false; + } + + vm.op = []; + + if(vm.opCreate) { + vm.op.push('create'); + } + if(vm.opPull) { + vm.op.push('pull'); + } + if(vm.opPush) { + vm.op.push('push'); + } + if(vm.opDelete) { + vm.op.push('delete'); + } + if(vm.opOthers && $.trim(vm.others) !== '') { + vm.op.push($.trim(vm.others)); + } + } + + vm.pickUp = pickUp; + + function pickUp(e) { + switch(e.key){ + case 'fromDate': + vm.fromDate = e.value; + break; + case 'toDate': + vm.toDate = e.value; + break; + } + $scope.$apply(); + } + + function close() { + vm.op = []; + vm.op.push('all'); + vm.fromDate = ''; + vm.toDate = ''; + vm.others = ''; + vm.isOpen = false; + } + + function doSearch (e){ + if(vm.opOthers && $.trim(vm.others) !== '') { + e.op.push(vm.others); + } + vm.search(e); + } + } + + function advancedSearch(I18nService) { + var directive = { + 'restrict': 'E', + 'templateUrl': '/static/resources/js/components/log/advanced-search.directive.html', + 'scope': { + 'isOpen': '=', + 'op': '=', + 'opOthers': '=', + 'others': '=', + 'fromDate': '=', + 'toDate': '=', + 'search': '&' + }, + 'link': link, + 'controller': AdvancedSearchController, + 'controllerAs': 'vm', + 'bindToController': true + }; + return directive; + + function link(scope, element, attrs, ctrl) { + element.find('.datetimepicker').datetimepicker({ + locale: I18nService().getCurrentLanguage(), + ignoreReadonly: true, + format: 'L', + showClear: true + }); + element.find('#fromDatePicker').on('blur', function(){ + ctrl.pickUp({'key': 'fromDate', 'value': $(this).val()}); + }); + element.find('#toDatePicker').on('blur', function(){ + ctrl.pickUp({'key': 'toDate', 'value': $(this).val()}); + }); + + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/log/list-log.directive.html b/static/resources/js/components/log/list-log.directive.html new file mode 100644 index 000000000..912498dd4 --- /dev/null +++ b/static/resources/js/components/log/list-log.directive.html @@ -0,0 +1,53 @@ + +
+
+
+
+ + + + + + + +
+
+ +
+
+
+ + + + + + + +
// 'username' | tr //// 'repository_name' | tr //// 'operation' | tr //// 'timestamp' | tr //
+
+
+ + + + + + +
//log.username////log.repo_name////log.operation////log.op_time | dateL : 'YYYY-MM-DD HH:mm:ss'//
+
+
+
+
+
diff --git a/static/resources/js/components/log/list-log.directive.js b/static/resources/js/components/log/list-log.directive.js new file mode 100644 index 000000000..5c4868125 --- /dev/null +++ b/static/resources/js/components/log/list-log.directive.js @@ -0,0 +1,163 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.log') + .directive('listLog', listLog); + + ListLogController.$inject = ['$scope','ListLogService', 'getParameterByName', '$location', '$filter', 'trFilter']; + + function ListLogController($scope, ListLogService, getParameterByName, $location, $filter, trFilter) { + + $scope.subsTabPane = 30; + + var vm = this; + + vm.sectionHeight = {'min-height': '579px'}; + + vm.isOpen = false; + + vm.beginTimestamp = 0; + vm.endTimestamp = 0; + vm.keywords = ''; + vm.username = ''; + + vm.op = []; + vm.opOthers = true; + + vm.search = search; + vm.showAdvancedSearch = showAdvancedSearch; + + vm.projectId = getParameterByName('project_id', $location.absUrl()); + vm.queryParams = { + 'beginTimestamp' : vm.beginTimestamp, + 'endTimestamp' : vm.endTimestamp, + 'keywords' : vm.keywords, + 'projectId': vm.projectId, + 'username' : vm.username + }; + + retrieve(vm.queryParams); + + $scope.$on('$locationChangeSuccess', function() { + + if(vm.publicity) { + vm.target = 'repositories'; + } + + vm.projectId = getParameterByName('project_id', $location.absUrl()); + vm.queryParams = { + 'beginTimestamp' : vm.beginTimestamp, + 'endTimestamp' : vm.endTimestamp, + 'keywords' : vm.keywords, + 'projectId': vm.projectId, + 'username' : vm.username + }; + vm.username = ''; + retrieve(vm.queryParams); + }); + + function search(e) { + + if(e.op[0] === 'all') { + e.op = ['create', 'pull', 'push', 'delete']; + } + if(vm.opOthers && $.trim(vm.others) !== '') { + e.op.push(vm.others); + } + + vm.queryParams.keywords = e.op.join('/'); + vm.queryParams.username = e.username; + + vm.queryParams.beginTimestamp = toUTCSeconds(vm.fromDate, 0, 0, 0); + vm.queryParams.endTimestamp = toUTCSeconds(vm.toDate, 23, 59, 59); + + retrieve(vm.queryParams); + } + + function showAdvancedSearch() { + if(vm.isOpen){ + vm.isOpen = false; + }else{ + vm.isOpen = true; + } + } + + function retrieve(queryParams) { + ListLogService(queryParams) + .then(listLogComplete) + .catch(listLogFailed); + } + + function listLogComplete(response) { + vm.logs = response.data; + + vm.queryParams = { + 'beginTimestamp' : 0, + 'endTimestamp' : 0, + 'keywords' : '', + 'projectId': vm.projectId, + 'username' : '' + }; + vm.op = ['all']; + vm.fromDate = ''; + vm.toDate = ''; + vm.others = ''; + vm.opOthers = true; + vm.isOpen = false; + } + function listLogFailed(response){ + $scope.$emit('modalTitle', $filter('tr')('error')); + $scope.$emit('modalMessage', $filter('tr')('failed_to_get_log') + response); + $scope.$emit('raiseError', true); + console.log('Failed to get log:' + response); + } + + function toUTCSeconds(date, hour, min, sec) { + if(!angular.isDefined(date) || date === '') { + return 0; + } + + var t = new Date(date); + t.setHours(hour); + t.setMinutes(min); + t.setSeconds(sec); + + return t.getTime() / 1000; + } + + } + + function listLog() { + var directive = { + restrict: 'E', + templateUrl: '/static/resources/js/components/log/list-log.directive.html', + scope: { + 'sectionHeight': '=', + 'target': '=', + 'publicity': '=' + }, + controller: ListLogController, + controllerAs: 'vm', + bindToController: true + }; + + return directive; + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/log/log.config.js b/static/resources/js/components/log/log.config.js new file mode 100644 index 000000000..1f5e1c73d --- /dev/null +++ b/static/resources/js/components/log/log.config.js @@ -0,0 +1,23 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.log'); + + +})(); \ No newline at end of file diff --git a/static/resources/js/components/log/log.module.js b/static/resources/js/components/log/log.module.js new file mode 100644 index 000000000..94bf397ae --- /dev/null +++ b/static/resources/js/components/log/log.module.js @@ -0,0 +1,24 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.log', [ + 'harbor.services.log' + ]); + +})(); \ No newline at end of file diff --git a/static/resources/js/components/modal-dialog/modal-dialog.directive.html b/static/resources/js/components/modal-dialog/modal-dialog.directive.html new file mode 100644 index 000000000..257d6b09e --- /dev/null +++ b/static/resources/js/components/modal-dialog/modal-dialog.directive.html @@ -0,0 +1,30 @@ + + \ No newline at end of file diff --git a/static/resources/js/components/modal-dialog/modal-dialog.directive.js b/static/resources/js/components/modal-dialog/modal-dialog.directive.js new file mode 100644 index 000000000..dc1722049 --- /dev/null +++ b/static/resources/js/components/modal-dialog/modal-dialog.directive.js @@ -0,0 +1,90 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.modal.dialog') + .directive('modalDialog', modalDialog); + + ModalDialogController.$inject = ['$scope']; + + function ModalDialogController($scope) { + var vm = this; + + } + + function modalDialog() { + var directive = { + 'restrict': 'E', + 'templateUrl': '/static/resources/js/components/modal-dialog/modal-dialog.directive.html', + 'link': link, + 'scope': { + 'contentType': '@', + 'modalTitle': '@', + 'modalMessage': '@', + 'action': '&', + 'confirmOnly': '=' + }, + 'controller': ModalDialogController, + 'controllerAs': 'vm', + 'bindToController': true + }; + return directive; + + function link(scope, element, attrs, ctrl) { + + scope.$watch('contentType', function(current) { + if(current) { + ctrl.contentType = current; + } + }) + scope.$watch('confirmOnly', function(current) { + if(current) { + ctrl.confirmOnly = current; + } + }) + + scope.$watch('vm.modalMessage', function(current) { + if(current) { + switch(ctrl.contentType) { + case 'text/html': + element.find('.modal-body').html(current); break; + case 'text/plain': + element.find('.modal-body').text(current); break; + default: + element.find('.modal-body').text(current); break; + } + } + }); + + scope.$on('showDialog', function(e, val) { + if(val) { + element.find('#myModal').modal('show'); + }else{ + element.find('#myModal').modal('hide'); + } + }); + + element.find('#btnOk').on('click', clickHandler); + + function clickHandler(e) { + ctrl.action(); + } + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/modal-dialog/modal-dialog.module.js b/static/resources/js/components/modal-dialog/modal-dialog.module.js new file mode 100644 index 000000000..2ac2ba083 --- /dev/null +++ b/static/resources/js/components/modal-dialog/modal-dialog.module.js @@ -0,0 +1,22 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.modal.dialog', []); + +})(); \ No newline at end of file diff --git a/static/resources/js/components/optional-menu/optional-menu.directive.js b/static/resources/js/components/optional-menu/optional-menu.directive.js new file mode 100644 index 000000000..4852b7eeb --- /dev/null +++ b/static/resources/js/components/optional-menu/optional-menu.directive.js @@ -0,0 +1,71 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.optional.menu') + .directive('optionalMenu', optionalMenu); + + OptionalMenuController.$inject = ['$window', 'I18nService', 'LogOutService', 'currentUser', '$timeout']; + + function OptionalMenuController($window, I18nService, LogOutService, currentUser, $timeout) { + var vm = this; + + vm.currentLanguage = I18nService().getCurrentLanguage(); + vm.languageName = I18nService().getLanguageName(vm.currentLanguage); + + I18nService().setCurrentLanguage(vm.currentLanguage); + + console.log('current language:' + vm.languageName); + + vm.supportLanguages = I18nService().getSupportLanguages(); + vm.user = currentUser.get(); + vm.setLanguage = setLanguage; + vm.logOut = logOut; + + function setLanguage(language) { + I18nService().setCurrentLanguage(language); + var hash = $window.location.hash; + $window.location.href = '/language?lang=' + language + '&hash=' + encodeURIComponent(hash); + } + function logOut() { + LogOutService() + .success(logOutSuccess) + .error(logOutFailed); + } + function logOutSuccess(data, status) { + currentUser.unset(); + $window.location.href= '/'; + } + function logOutFailed(data, status) { + console.log('Failed to log out:' + data); + } + } + + function optionalMenu() { + var directive = { + 'restrict': 'E', + 'templateUrl': '/optional_menu?timestamp=' + new Date().getTime(), + 'scope': true, + 'controller': OptionalMenuController, + 'controllerAs': 'vm', + 'bindToController': true + }; + return directive; + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/optional-menu/optional-menu.module.js b/static/resources/js/components/optional-menu/optional-menu.module.js new file mode 100644 index 000000000..71cde6b35 --- /dev/null +++ b/static/resources/js/components/optional-menu/optional-menu.module.js @@ -0,0 +1,25 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.optional.menu', [ + 'harbor.services.user', + 'harbor.services.i18n' + ]); + +})(); \ No newline at end of file diff --git a/static/resources/js/components/project-member/add-project-member.directive.html b/static/resources/js/components/project-member/add-project-member.directive.html new file mode 100644 index 000000000..2519fac55 --- /dev/null +++ b/static/resources/js/components/project-member/add-project-member.directive.html @@ -0,0 +1,44 @@ + +
+
+
+
+
+ +
+
+ // 'username_is_required' | tr // +
+ // vm.errorMessage | tr // +
+
+
+ +    + +  //role.name | tr//   + +
+
+
+
+ + +
+
+
+ +
\ No newline at end of file diff --git a/static/resources/js/components/project-member/add-project-member.directive.js b/static/resources/js/components/project-member/add-project-member.directive.js new file mode 100644 index 000000000..1f6251492 --- /dev/null +++ b/static/resources/js/components/project-member/add-project-member.directive.js @@ -0,0 +1,115 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.project.member') + .directive('addProjectMember', addProjectMember); + + AddProjectMemberController.$inject = ['$scope', 'roles', 'AddProjectMemberService']; + + function AddProjectMemberController($scope, roles, AddProjectMemberService) { + var vm = this; + + $scope.pm = {}; + + var pm = $scope.pm; + + vm.roles = roles(); + vm.optRole = 1; + + vm.save = save; + vm.cancel = cancel; + vm.reset = reset; + + vm.hasError = false; + vm.errorMessage = ''; + + function save(pm) { + if(pm && angular.isDefined(pm.username)) { + AddProjectMemberService(vm.projectId, vm.optRole, pm.username) + .success(addProjectMemberComplete) + .error(addProjectMemberFailed); + } + } + + function cancel(form) { + + form.$setPristine(); + form.$setUntouched(); + + vm.isOpen = false; + pm.username = ''; + vm.optRole = 1; + + vm.hasError = false; + vm.errorMessage = ''; + } + + function addProjectMemberComplete(data, status, header) { + console.log('addProjectMemberComplete: status:' + status + ', data:' + data); + vm.reload(); + vm.isOpen = false; + } + + function addProjectMemberFailed(data, status, headers) { + if(status === 403) { + vm.hasError = true; + vm.errorMessage = 'failed_to_add_member'; + } + if(status === 409 && pm.username != '') { + vm.hasError = true; + vm.errorMessage = 'username_already_exist'; + } + if(status === 404) { + vm.hasError = true; + vm.errorMessage = 'username_does_not_exist'; + } + console.log('addProjectMemberFailed: status:' + status + ', data:' + data); + } + + function reset() { + vm.hasError = false; + vm.errorMessage = ''; + } + + } + + function addProjectMember() { + var directive = { + 'restrict': 'E', + 'templateUrl': '/static/resources/js/components/project-member/add-project-member.directive.html', + 'scope': { + 'projectId': '@', + 'isOpen': '=', + 'reload': '&' + }, + 'link': link, + 'controller': AddProjectMemberController, + 'controllerAs': 'vm', + 'bindToController': true + }; + + return directive; + + function link(scope, element, attrs, ctrl) { + scope.form.$setPristine(); + scope.form.$setUntouched(); + } + } + +})(); diff --git a/static/resources/js/components/project-member/edit-project-member.directive.html b/static/resources/js/components/project-member/edit-project-member.directive.html new file mode 100644 index 000000000..4f83d1a13 --- /dev/null +++ b/static/resources/js/components/project-member/edit-project-member.directive.html @@ -0,0 +1,25 @@ + +//vm.username// + + + + + + + + + + \ No newline at end of file diff --git a/static/resources/js/components/project-member/edit-project-member.directive.js b/static/resources/js/components/project-member/edit-project-member.directive.js new file mode 100644 index 000000000..f1e73b48e --- /dev/null +++ b/static/resources/js/components/project-member/edit-project-member.directive.js @@ -0,0 +1,99 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.project.member') + .directive('editProjectMember', editProjectMember); + + EditProjectMemberController.$inject = ['$scope', 'roles', 'getRole','EditProjectMemberService', '$filter', 'trFilter']; + + function EditProjectMemberController($scope, roles, getRole, EditProjectMemberService, $filter, trFilter) { + var vm = this; + + vm.roles = roles(); + vm.editMode = false; + vm.lastRoleName = vm.roleName; + + $scope.$watch('vm.roleName', function(current, origin) { + if(current) { + vm.currentRole = getRole({'key': 'roleName', 'value': current}); + vm.roleId = vm.currentRole.id; + } + }); + + vm.updateProjectMember = updateProjectMember; + vm.deleteProjectMember = deleteProjectMember; + vm.cancelUpdate = cancelUpdate; + + function updateProjectMember(e) { + if(vm.editMode) { + console.log('update project member, roleId:' + e.roleId); + EditProjectMemberService(e.projectId, e.userId, e.roleId) + .success(editProjectMemberComplete) + .error(editProjectMemberFailed); + }else { + vm.editMode = true; + } + } + + function deleteProjectMember() { + vm.delete(); + } + + function editProjectMemberComplete(data, status, headers) { + console.log('edit project member complete: ' + status); + vm.lastRoleName = vm.roleName; + vm.editMode = false; + vm.reload(); + } + + function editProjectMemberFailed(e) { + $scope.$emit('modalTitle', $filter('tr')('error')); + $scope.$emit('modalMessage', $filter('tr')('failed_to_change_member')); + $scope.$emit('raiseError', true); + console.log('Failed to edit project member:' + e); + } + + function cancelUpdate() { + vm.editMode = false; + vm.roleName = vm.lastRoleName; + } + + } + + function editProjectMember() { + var directive = { + 'restrict': 'A', + 'templateUrl': '/static/resources/js/components/project-member/edit-project-member.directive.html', + 'scope': { + 'username': '=', + 'userId': '=', + 'currentUserId': '=', + 'roleName': '=', + 'projectId': '=', + 'delete': '&', + 'reload': '&' + }, + 'controller': EditProjectMemberController, + 'controllerAs': 'vm', + 'bindToController': true + }; + return directive; + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/project-member/list-project-member.directive.html b/static/resources/js/components/project-member/list-project-member.directive.html new file mode 100644 index 000000000..7e2654168 --- /dev/null +++ b/static/resources/js/components/project-member/list-project-member.directive.html @@ -0,0 +1,47 @@ + +
+
+
+
+ + + + +
+ + +
+
+ +
+
+ + + + +
// 'username' | tr //// 'role' | tr //// 'operation' | tr //
+
+
+ + + + +
+
+
+
+
+
diff --git a/static/resources/js/components/project-member/list-project-member.directive.js b/static/resources/js/components/project-member/list-project-member.directive.js new file mode 100644 index 000000000..cdcc28607 --- /dev/null +++ b/static/resources/js/components/project-member/list-project-member.directive.js @@ -0,0 +1,117 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.project.member') + .directive('listProjectMember', listProjectMember); + + ListProjectMemberController.$inject = ['$scope', 'ListProjectMemberService', 'DeleteProjectMemberService', 'getParameterByName', '$location', 'currentUser', '$filter', 'trFilter', '$window']; + + function ListProjectMemberController($scope, ListProjectMemberService, DeleteProjectMemberService, getParameterByName, $location, currentUser, $filter, trFilter, $window) { + + $scope.subsTabPane = 30; + + var vm = this; + + vm.sectionHeight = {'min-height': '579px'}; + + vm.isOpen = false; + vm.search = search; + vm.addProjectMember = addProjectMember; + vm.deleteProjectMember = deleteProjectMember; + vm.retrieve = retrieve; + vm.username = ''; + + vm.projectId = getParameterByName('project_id', $location.absUrl()); + vm.retrieve(); + + $scope.$on('$locationChangeSuccess', function() { + vm.projectId = getParameterByName('project_id', $location.absUrl()); + vm.username = ''; + vm.retrieve(); + }); + + function search(e) { + vm.projectId = e.projectId; + vm.username = e.username; + retrieve(); + } + + function addProjectMember() { + if(vm.isOpen) { + vm.isOpen = false; + }else{ + vm.isOpen = true; + } + } + + function deleteProjectMember(e) { + DeleteProjectMemberService(e.projectId, e.userId) + .success(deleteProjectMemberSuccess) + .error(deleteProjectMemberFailed); + } + + function deleteProjectMemberSuccess(data, status) { + console.log('Successful delete project member.'); + vm.retrieve(); + } + + function deleteProjectMemberFailed(e) { + $scope.$emit('modalTitle', $filter('tr')('error')); + $scope.$emit('modalMessage', $filter('tr')('failed_to_delete_member')); + $scope.$emit('raiseError', true); + console.log('Failed to edit project member:' + e); + } + + function retrieve() { + ListProjectMemberService(vm.projectId, {'username': vm.username}) + .then(getProjectMemberComplete) + .catch(getProjectMemberFailed); + } + + function getProjectMemberComplete(response) { + vm.user = currentUser.get(); + vm.projectMembers = response.data || []; + } + + function getProjectMemberFailed(response) { + console.log('Failed to get project members:' + response); + vm.projectMembers = []; + vm.target = 'repositories'; + $location.url('repositories').search('project_id', vm.projectId); + } + + } + + function listProjectMember() { + var directive = { + 'restrict': 'E', + 'templateUrl': '/static/resources/js/components/project-member/list-project-member.directive.html', + 'scope': { + 'sectionHeight': '=', + 'target': '=' + }, + 'controller': ListProjectMemberController, + 'controllerAs': 'vm', + 'bindToController': true + }; + + return directive; + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/project-member/project-member.config.js b/static/resources/js/components/project-member/project-member.config.js new file mode 100644 index 000000000..a63acc2ad --- /dev/null +++ b/static/resources/js/components/project-member/project-member.config.js @@ -0,0 +1,48 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.project.member') + .constant('roles', roles) + .factory('getRole', getRole); + + function roles() { + return [ + {'id': '1', 'name': 'project_admin', 'roleName': 'projectAdmin'}, + {'id': '2', 'name': 'developer', 'roleName': 'developer'}, + {'id': '3', 'name': 'guest', 'roleName': 'guest'} + ]; + } + + getRole.$inject = ['roles', '$filter', 'trFilter']; + + function getRole(roles, $filter, trFilter) { + var r = roles(); + return get; + function get(query) { + + for(var i = 0; i < r.length; i++) { + var role = r[i]; + if(query.key === 'roleName' && role.roleName === query.value + || query.key === 'roleId' && role.id === String(query.value)) { + return role; + } + } + } + } +})(); \ No newline at end of file diff --git a/static/resources/js/components/project-member/project-member.module.js b/static/resources/js/components/project-member/project-member.module.js new file mode 100644 index 000000000..45c26aab3 --- /dev/null +++ b/static/resources/js/components/project-member/project-member.module.js @@ -0,0 +1,25 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.project.member', [ + 'harbor.services.project.member', + 'harbor.services.user' + ]); + +})(); \ No newline at end of file diff --git a/static/resources/js/components/project-member/switch-role.directive.html b/static/resources/js/components/project-member/switch-role.directive.html new file mode 100644 index 000000000..dc3c8cef5 --- /dev/null +++ b/static/resources/js/components/project-member/switch-role.directive.html @@ -0,0 +1,19 @@ + + + //vm.currentRole.name | tr// + + \ No newline at end of file diff --git a/static/resources/js/components/project-member/switch-role.directive.js b/static/resources/js/components/project-member/switch-role.directive.js new file mode 100644 index 000000000..6f9d0a352 --- /dev/null +++ b/static/resources/js/components/project-member/switch-role.directive.js @@ -0,0 +1,60 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.project.member') + .directive('switchRole', switchRole); + + SwitchRoleController.$inject = ['getRole', '$scope']; + + function SwitchRoleController(getRole, $scope) { + var vm = this; + + $scope.$watch('vm.roleName', function(current,origin) { + if(current) { + vm.currentRole = getRole({'key': 'roleName', 'value': current}); + } + }); + + vm.selectRole = selectRole; + + function selectRole(role) { + vm.currentRole = getRole({'key': 'roleName', 'value': role.roleName}); + vm.roleName = role.roleName; + } + + } + + function switchRole() { + var directive = { + 'restrict': 'E', + 'templateUrl': '/static/resources/js/components/project-member/switch-role.directive.html', + 'scope': { + 'roles': '=', + 'editMode': '=', + 'userId': '=', + 'roleName': '=' + }, + 'controller' : SwitchRoleController, + 'controllerAs': 'vm', + 'bindToController': true + }; + return directive; + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/project/add-project.directive.html b/static/resources/js/components/project/add-project.directive.html new file mode 100644 index 000000000..588da0a1c --- /dev/null +++ b/static/resources/js/components/project/add-project.directive.html @@ -0,0 +1,42 @@ + +
+
+
+
+
+ +
+
+ // 'project_name_is_required' | tr // + // 'project_name_is_invalid' | tr // +
+ // vm.errorMessage | tr // +
+
+
+  // 'public' | tr // + +
+
+
+
+ + +
+
+
+
+
\ No newline at end of file diff --git a/static/resources/js/components/project/add-project.directive.js b/static/resources/js/components/project/add-project.directive.js new file mode 100644 index 000000000..226eff3ad --- /dev/null +++ b/static/resources/js/components/project/add-project.directive.js @@ -0,0 +1,109 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.project') + .directive('addProject', addProject); + + AddProjectController.$inject = ['AddProjectService', '$scope']; + + function AddProjectController(AddProjectService, $scope) { + var vm = this; + + $scope.p = {}; + var vm0 = $scope.p; + vm0.projectName = ''; + vm.isPublic = false; + + vm.addProject = addProject; + vm.cancel = cancel; + + vm.reset = reset; + + vm.hasError = false; + vm.errorMessage = ''; + + function addProject(p) { + if(p && angular.isDefined(p.projectName)) { + AddProjectService(p.projectName, vm.isPublic) + .success(addProjectSuccess) + .error(addProjectFailed); + } + } + + function addProjectSuccess(data, status) { + $scope.$emit('addedSuccess', true); + vm.hasError = false; + vm.errorMessage = ''; + vm.isOpen = false; + } + + function addProjectFailed(data, status) { + vm.hasError = true; + if(status === 400 && vm0.projectName!= '' && vm0.projectName.length < 4) { + vm.errorMessage = 'project_name_is_too_short'; + } + if(status === 400 && vm0.projectName.length > 30) { + vm.errorMessage = 'project_name_is_too_long'; + } + if(status === 409 && vm0.projectName != '') { + vm.errorMessage = 'project_already_exist'; + } + console.log('Failed to add project:' + status); + } + + function cancel(form){ + if(form) { + form.$setPristine(); + form.$setUntouched(); + } + vm.isOpen = false; + vm0.projectName = ''; + vm.isPublic = false; + + vm.hasError = false; vm.close = close; + vm.errorMessage = ''; + } + + function reset() { + vm.hasError = false; + vm.errorMessage = ''; + } + } + + function addProject() { + var directive = { + 'restrict': 'E', + 'templateUrl': '/static/resources/js/components/project/add-project.directive.html', + 'controller': AddProjectController, + 'scope' : { + 'isOpen': '=' + }, + 'link': link, + 'controllerAs': 'vm', + 'bindToController': true + }; + return directive; + + function link(scope, element, attrs, ctrl) { + scope.form.$setPristine(); + scope.form.$setUntouched(); + } + } + +})(); diff --git a/static/resources/js/components/project/project.module.js b/static/resources/js/components/project/project.module.js new file mode 100644 index 000000000..dd87d77b4 --- /dev/null +++ b/static/resources/js/components/project/project.module.js @@ -0,0 +1,23 @@ +/* + 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. +*/ +(function() { + 'use strict'; + + angular + .module('harbor.project', [ + 'harbor.services.project', + 'harbor.services.user' + ]); +})(); \ No newline at end of file diff --git a/views/segment/foot-content.tpl b/static/resources/js/components/project/publicity-button.directive.html similarity index 70% rename from views/segment/foot-content.tpl rename to static/resources/js/components/project/publicity-button.directive.html index 0bfa230a3..62460d3db 100644 --- a/views/segment/foot-content.tpl +++ b/static/resources/js/components/project/publicity-button.directive.html @@ -12,12 +12,5 @@ See the License for the specific language governing permissions and limitations under the License. --> - \ No newline at end of file + + \ No newline at end of file diff --git a/static/resources/js/components/project/publicity-button.directive.js b/static/resources/js/components/project/publicity-button.directive.js new file mode 100644 index 000000000..2bb5d3b5d --- /dev/null +++ b/static/resources/js/components/project/publicity-button.directive.js @@ -0,0 +1,91 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.project') + .directive('publicityButton', publicityButton); + + PublicityButtonController.$inject = ['$scope', 'ToggleProjectPublicityService', '$filter', 'trFilter']; + + function PublicityButtonController($scope, ToggleProjectPublicityService, $filter, trFilter) { + var vm = this; + vm.toggle = toggle; + + function toggle() { + if(vm.isPublic) { + vm.isPublic = false; + }else{ + vm.isPublic = true; + } + ToggleProjectPublicityService(vm.projectId, vm.isPublic) + .success(toggleProjectPublicitySuccess) + .error(toggleProjectPublicityFailed); + } + + function toggleProjectPublicitySuccess(data, status) { + + console.log('Successful toggle project publicity.'); + } + + function toggleProjectPublicityFailed(e, status) { + $scope.$emit('modalTitle', $filter('tr')('error')); + var message; + if(status === 403) { + message = $filter('tr')('failed_to_toggle_publicity_insuffient_permissions'); + }else{ + message = $filter('tr')('failed_to_toggle_publicity'); + } + $scope.$emit('modalMessage', message); + $scope.$emit('raiseError', true); + + if(vm.isPublic) { + vm.isPublic = false; + }else{ + vm.isPublic = true; + } + + console.log('Failed to toggle project publicity:' + e); + } + } + + function publicityButton() { + var directive = { + 'restrict': 'E', + 'templateUrl': '/static/resources/js/components/project/publicity-button.directive.html', + 'scope': { + 'isPublic': '=', + 'owned': '=', + 'projectId': '=' + }, + 'link': link, + 'controller': PublicityButtonController, + 'controllerAs': 'vm', + 'bindToController': true + }; + return directive; + + function link(scope, element, attr, ctrl) { + scope.$watch('vm.isPublic', function(current, origin) { + if(current) { + ctrl.isPublic = current; + } + }); + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/replication/create-policy.directive.html b/static/resources/js/components/replication/create-policy.directive.html new file mode 100644 index 000000000..73ba59753 --- /dev/null +++ b/static/resources/js/components/replication/create-policy.directive.html @@ -0,0 +1,120 @@ + + diff --git a/static/resources/js/components/replication/create-policy.directive.js b/static/resources/js/components/replication/create-policy.directive.js new file mode 100644 index 000000000..2a62c86ae --- /dev/null +++ b/static/resources/js/components/replication/create-policy.directive.js @@ -0,0 +1,432 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.replication') + .directive('createPolicy', createPolicy); + + CreatePolicyController.$inject = ['$scope', 'ListReplicationPolicyService', 'ListDestinationService', 'CreateDestinationService', 'UpdateDestinationService', 'PingDestinationService', 'CreateReplicationPolicyService', 'UpdateReplicationPolicyService', 'ListDestinationPolicyService','$location', 'getParameterByName', '$filter', 'trFilter', '$q', '$timeout']; + + function CreatePolicyController($scope, ListReplicationPolicyService, ListDestinationService, CreateDestinationService, UpdateDestinationService, PingDestinationService, CreateReplicationPolicyService, UpdateReplicationPolicyService, ListDestinationPolicyService, $location, getParameterByName, $filter, trFilter, $q, $timeout) { + var vm = this; + + //Since can not set value for textarea by using vm + //use $scope for instead. + $scope.replication = {}; + $scope.replication.policy = {}; + $scope.replication.destination = {}; + + var vm0 = $scope.replication.policy; + var vm1 = $scope.replication.destination; + + vm.selectDestination = selectDestination; + vm.projectId = getParameterByName('project_id', $location.absUrl()); + + $scope.$on('$locationChangeSuccess', function() { + vm.projectId = getParameterByName('project_id', $location.absUrl()); + }); + + vm.addNew = addNew; + vm.edit = edit; + vm.prepareDestination = prepareDestination; + vm.create = create; + vm.update = update; + vm.pingDestination = pingDestination; + vm.checkDestinationPolicyStatus = checkDestinationPolicyStatus; + + vm.targetEditable = true; + vm.checkedAddTarget = false; + + vm.notAvailable = false; + vm.pingAvailable = true; + vm.pingMessage = ''; + + vm.pingTIP = false; + vm.saveTIP = false; + + vm.closeError = closeError; + vm.toggleErrorMessage = false; + vm.errorMessages = []; + + $scope.$watch('vm.destinations', function(current) { + if(current) { + if(!angular.isArray(current) || current.length === 0) { + vm.notAvailable = true; + return; + } + if(!angular.isDefined(vm1.selection)) { + vm1.selection = current[0]; + vm1.endpoint = current[0].endpoint; + vm1.username = current[0].username; + vm1.password = current[0].password; + } + } + }); + + $scope.$watch('vm.checkedAddTarget', function(current) { + if(current) { + vm.targetEditable = true; + vm1.name = ''; + vm1.endpoint = ''; + vm1.username = ''; + vm1.password = ''; + vm.pingMessage = ''; + } + }); + + $scope.$watch('vm.targetId', function(current) { + if(current) { + vm1.selection.id = current; + } + }); + + $scope.$watch('replication.destination.endpoint', function(current) { + if(current) { + vm.notAvailable = false; + }else{ + vm.notAvailable = true; + } + }); + + function selectDestination(item) { + vm1.selection = item; + if(angular.isDefined(item)) { + vm.targetId = item.id; + vm1.endpoint = item.endpoint; + vm1.username = item.username; + vm1.password = item.password; + } + } + + function prepareDestination() { + ListDestinationService('') + .success(listDestinationSuccess) + .error(listDestinationFailed); + } + + function addNew() { + vm.modalTitle = $filter('tr')('add_new_policy', []); + + vm0.name = ''; + vm0.description = ''; + vm0.enabled = true; + } + + function edit(policyId) { + console.log('Edit policy ID:' + policyId); + vm.policyId = policyId; + + vm.modalTitle = $filter('tr')('edit_policy', []); + + ListReplicationPolicyService(policyId) + .success(listReplicationPolicySuccess) + .error(listReplicationPolicyFailed); + } + + function create(policy) { + vm.policy = policy; + saveDestination(); + } + + function saveDestination() { + + var target = { + 'name' : vm1.name, + 'endpoint': vm1.endpoint, + 'username': vm1.username, + 'password': vm1.password + }; + + if(vm.checkedAddTarget){ + CreateDestinationService(target.name, target.endpoint, target.username, target.password) + .success(createDestinationSuccess) + .error(createDestinationFailed); + }else{ + vm.policy.targetId = vm1.selection.id || vm.destinations[0].id; + saveOrUpdatePolicy(); + } + } + + function saveOrUpdatePolicy() { + vm.saveTIP = true; + + switch(vm.action) { + case 'ADD_NEW': + CreateReplicationPolicyService(vm.policy) + .success(createReplicationPolicySuccess) + .error(createReplicationPolicyFailed); + break; + case 'EDIT': + UpdateReplicationPolicyService(vm.policyId, vm.policy) + .success(updateReplicationPolicySuccess) + .error(updateReplicationPolicyFailed); + break; + default: + vm.saveTIP = false; + } + } + + function update(policy) { + vm.policy = policy; + if(vm.targetEditable) { + vm.policy.targetId = vm1.selection.id; + saveDestination(); + } + } + + function pingDestination() { + + var target = { + 'endpoint': vm1.endpoint, + 'username': vm1.username, + 'password': vm1.password + }; + + if(vm.checkedAddTarget) { + target.name = vm1.name; + } + + vm.pingAvailable = false; + vm.pingMessage = $filter('tr')('pinging_target'); + vm.pingTIP = true; + + PingDestinationService(target) + .success(pingDestinationSuccess) + .error(pingDestinationFailed); + } + + function checkDestinationPolicyStatus() { + console.log('Checking destination policy status, target_ID:' + vm.targetId); + ListDestinationPolicyService(vm.targetId) + .success(listDestinationPolicySuccess) + .error(listDestinationPolicyFailed); + } + + function closeError() { + vm.errorMessages = []; + vm.toggleErrorMessage = false; + } + + function listDestinationSuccess(data, status) { + vm.destinations = data || []; + } + function listDestinationFailed(data, status) { + vm.errorMessages.push($filter('tr')('failed_to_get_destination')); + console.log('Failed to get destination:' + data); + } + + function listDestinationPolicySuccess(data, status) { + if(vm.action === 'EDIT') { + console.log('Current target editable:' + vm.targetEditable + ', policy ID:' + vm.policyId); + vm.targetEditable = true; + for(var i in data) { + if(data[i].enabled === 1) { + vm.targetEditable = false; + break; + } + } + } + } + + function listDestinationPolicyFailed(data, status) { + vm.errorMessages.push($filter('tr')('failed_to_get_destination_policies')); + console.log('Failed to list destination policy:' + data); + } + + function listReplicationPolicySuccess(data, status) { + + var replicationPolicy = data; + + vm.targetId = replicationPolicy.target_id; + + vm0.name = replicationPolicy.name; + vm0.description = replicationPolicy.description; + vm0.enabled = (replicationPolicy.enabled == 1); + + angular.forEach(vm.destinations, function(item) { + if(item.id === vm.targetId) { + vm1.endpoint = item.endpoint; + vm1.username = item.username; + vm1.password = item.password; + } + }); + + vm.checkDestinationPolicyStatus(); + } + function listReplicationPolicyFailed(data, status) { + vm.errorMessages.push($filter('tr')('failed_to_get_replication_policy') + data); + console.log('Failed to list replication policy:' + data); + } + function createReplicationPolicySuccess(data, status) { + vm.saveTIP = false; + console.log('Successful create replication policy.'); + vm.reload(); + } + function createReplicationPolicyFailed(data, status) { + vm.saveTIP = false; + if(status === 409) { + vm.errorMessages.push($filter('tr')('policy_already_exists')); + }else{ + vm.errorMessages.push($filter('tr')('failed_to_create_replication_policy') + data); + } + console.log('Failed to create replication policy.'); + } + function updateReplicationPolicySuccess(data, status) { + console.log('Successful update replication policy.'); + vm.reload(); + vm.saveTIP = false; + } + function updateReplicationPolicyFailed(data, status) { + vm.saveTIP = false; + vm.errorMessages.push($filter('tr')('failed_to_update_replication_policy') + data); + console.log('Failed to update replication policy.'); + } + function createDestinationSuccess(data, status, headers) { + var content = headers('Location'); + vm.policy.targetId = Number(content.substr(content.lastIndexOf('/') + 1)); + console.log('Successful create destination, targetId:' + vm.policy.targetId); + saveOrUpdatePolicy(); + } + function createDestinationFailed(data, status) { + vm.errorMessages.push($filter('tr')('failed_to_create_destination') + data); + console.log('Failed to create destination.'); + } + function updateDestinationSuccess(data, status) { + console.log('Successful update destination.'); + vm.policy.targetId = vm1.selection.id; + saveOrUpdatePolicy(); + } + function updateDestinationFailed(data, status) { + vm.errorMessages.push($filter('tr')('failed_to_update_destination') + data); + $scope.$broadcast('showDialog', true); + console.log('Failed to update destination.'); + } + function pingDestinationSuccess(data, status) { + vm.pingAvailable = true; + vm.pingMessage = $filter('tr')('successful_ping_target', []); + vm.pingTIP = false; + } + function pingDestinationFailed(data, status) { + vm.pingAvailable = true; + vm.pingMessage = $filter('tr')('failed_to_ping_target', []) + (data && data.length > 0 ? ':' + data : ''); + vm.pingTIP = false; + } + } + + function createPolicy($timeout) { + var directive = { + 'restrict': 'E', + 'templateUrl': '/static/resources/js/components/replication/create-policy.directive.html', + 'scope': { + 'policyId': '@', + 'modalTitle': '@', + 'reload': '&', + 'action': '=' + }, + 'link': link, + 'controller': CreatePolicyController, + 'controllerAs': 'vm', + 'bindToController': true + }; + return directive; + + function link(scope, element, attr, ctrl) { + + element.find('#createPolicyModal').on('show.bs.modal', function() { + scope.$apply(function() { + scope.form.$setPristine(); + scope.form.$setUntouched(); + + scope.$watch('vm.checkedAddTarget', function(current, origin) { + if(origin) { + var d = scope.replication.destination; + if(angular.isDefined(d) && angular.isDefined(d.selection)) { + ctrl.targetId = d.selection.id; + d.endpoint = d.selection.endpoint; + d.username = d.selection.username; + d.password = d.selection.password; + ctrl.checkDestinationPolicyStatus(); + } + } + }); + + scope.$watch('vm.errorMessages', function(current) { + if(current && current.length > 0) { + ctrl.toggleErrorMessage = true; + } + }, true); + + ctrl.checkedAddTarget = false; + ctrl.targetEditable = true; + + ctrl.notAvailable = false; + + ctrl.pingMessage = ''; + ctrl.pingAvailable = true; + + ctrl.saveTIP = false; + ctrl.pingTIP = false; + ctrl.toggleErrorMessage = false; + ctrl.errorMessages = []; + + ctrl.prepareDestination(); + + switch(ctrl.action) { + case 'ADD_NEW': + ctrl.addNew(); + break; + case 'EDIT': + ctrl.edit(ctrl.policyId); + break; + } + }); + }); + + ctrl.save = save; + + function save(form) { + + ctrl.toggleErrorMessage = false; + ctrl.errorMessages = []; + + var postPayload = { + 'projectId': Number(ctrl.projectId), + 'name': form.policy.name, + 'enabled': form.policy.enabled ? 1 : 0, + 'description': form.policy.description, + 'cron_str': '', + 'start_time': '' + }; + switch(ctrl.action) { + case 'ADD_NEW': + ctrl.create(postPayload); + break; + case 'EDIT': + ctrl.update(postPayload); + break; + } + $timeout(function() { + if(!ctrl.toggleErrorMessage) { + element.find('#createPolicyModal').modal('hide'); + } + }, 150); + } + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/replication/list-replication.directive.html b/static/resources/js/components/replication/list-replication.directive.html new file mode 100644 index 000000000..2d8eb16f6 --- /dev/null +++ b/static/resources/js/components/replication/list-replication.directive.html @@ -0,0 +1,152 @@ + +
+
+
+
+ + + + +
+ + +
+
+
+
+ + + + + + + + + + +
// 'name' | tr //// 'description' | tr //// 'destination' | tr //// 'last_start_time' | tr //// 'activation' | tr// // 'actions' | tr //
+
+
+ + + + + + + + + + + + + + + +

// 'no_replication_policies_add_new' | tr //

//r.name////r.description////r.target_name////r.start_time | dateL : 'YYYY-MM-DD HH:mm:ss'// + // 'enabled' | tr // + // 'disabled' | tr // + +
+ + +
+   + +   + +
+ +
+
+
+
+
//vm.replicationPolicies ? vm.replicationPolicies.length : 0// // 'items' | tr //
+ +

// 'replication_jobs' | tr //

+
+
+
+ + + + +
+
+ +
+ +
+
+
+ +
+ + + + +
+
+
+ +
+ + + + +
+
+
+ +
+
+
+
+
+ + + + + + + + + +
// 'name' | tr //// 'operation' | tr //// 'creation_time' | tr //// 'end_time' | tr //// 'status' | tr //// 'logs' | tr //
+
+
+ + + + + + + + + + + + + + +

// 'no_replication_jobs' | tr //

//r.repository////r.operation////r.creation_time | dateL : 'YYYY-MM-DD HH:mm:ss'////r.update_time | dateL : 'YYYY-MM-DD HH:mm:ss'////r.status// + +
+
+
+
+
+
+ diff --git a/static/resources/js/components/replication/list-replication.directive.js b/static/resources/js/components/replication/list-replication.directive.js new file mode 100644 index 000000000..04411646c --- /dev/null +++ b/static/resources/js/components/replication/list-replication.directive.js @@ -0,0 +1,309 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.replication') + .directive('listReplication', listReplication) + .factory('jobStatus', jobStatus); + + jobStatus.inject = ['$filter', 'trFilter']; + function jobStatus($filter, trFilter) { + return function() { + return [ + {'key': 'all' , 'value': $filter('tr')('all')}, + {'key': 'pending', 'value': $filter('tr')('pending')}, + {'key': 'running', 'value': $filter('tr')('running')}, + {'key': 'error' , 'value': $filter('tr')('error')}, + {'key': 'retrying', 'value': $filter('tr')('retrying')}, + {'key': 'stopped', 'value': $filter('tr')('stopped')}, + {'key': 'finished', 'value':$filter('tr')('finished')}, + {'key': 'canceled', 'value': $filter('tr')('canceled')} + ]; + } + } + + ListReplicationController.$inject = ['$scope', 'getParameterByName', '$location', 'ListReplicationPolicyService', 'ToggleReplicationPolicyService', 'ListReplicationJobService', '$window', '$filter', 'trFilter', 'jobStatus']; + + function ListReplicationController($scope, getParameterByName, $location, ListReplicationPolicyService, ToggleReplicationPolicyService, ListReplicationJobService, $window, $filter, trFilter, jobStatus) { + var vm = this; + + vm.sectionHeight = {'min-height': '1200px'}; + + $scope.$on('$locationChangeSuccess', function() { + vm.projectId = getParameterByName('project_id', $location.absUrl()); + vm.retrievePolicy(); + }); + + vm.addReplication = addReplication; + vm.editReplication = editReplication; + + vm.searchReplicationPolicy = searchReplicationPolicy; + vm.searchReplicationJob = searchReplicationJob; + vm.refreshReplicationJob = refreshReplicationJob; + + vm.retrievePolicy = retrievePolicy; + vm.retrieveJob = retrieveJob; + + vm.confirmToTogglePolicy = confirmToTogglePolicy; + vm.togglePolicy = togglePolicy; + + vm.downloadLog = downloadLog; + + vm.last = false; + + vm.projectId = getParameterByName('project_id', $location.absUrl()); + vm.retrievePolicy(); + + vm.jobStatus = jobStatus; + vm.currentStatus = vm.jobStatus()[0]; + + vm.pickUp = pickUp; + + vm.searchJobTIP = false; + vm.refreshJobTIP = false; + + function searchReplicationPolicy() { + vm.retrievePolicy(); + } + + function searchReplicationJob() { + if(vm.lastPolicyId !== -1) { + vm.searchJobTIP = true; + vm.retrieveJob(vm.lastPolicyId); + } + } + + function refreshReplicationJob() { + if(vm.lastPolicyId !== -1) { + vm.refreshJobTIP = true; + vm.retrieveJob(vm.lastPolicyId); + } + } + + function retrievePolicy() { + ListReplicationPolicyService('', vm.projectId, vm.replicationPolicyName) + .success(listReplicationPolicySuccess) + .error(listReplicationPolicyFailed); + } + + function retrieveJob(policyId) { + var status = (vm.currentStatus.key === 'all' ? '' : vm.currentStatus.key); + ListReplicationJobService(policyId, vm.replicationJobName, status, toUTCSeconds(vm.fromDate, 0, 0, 0), toUTCSeconds(vm.toDate, 23, 59, 59)) + .success(listReplicationJobSuccess) + .error(listReplicationJobFailed); + } + + function listReplicationPolicySuccess(data, status) { + vm.replicationJobs = []; + vm.replicationPolicies = data || []; + } + + function listReplicationPolicyFailed(data, status) { + console.log('Failed to list replication policy:' + data); + } + + function listReplicationJobSuccess(data, status) { + vm.replicationJobs = data || []; + var alertInfo = { + 'show': false, + 'message': '' + }; + angular.forEach(vm.replicationJobs, function(item) { + for(var key in item) { + var value = item[key]; + if(key === 'status' && (value === 'error' || value === 'retrying')) { + alertInfo.show = true; + alertInfo.message = $filter('tr')('alert_job_contains_error'); + } + switch(key) { + case 'operation': + case 'status': + item[key] = $filter('tr')(value); + default: + break; + } + } + }); + + $scope.$emit('raiseAlert', alertInfo); + vm.searchJobTIP = false; + vm.refreshJobTIP = false; + } + + function listReplicationJobFailed(data, status) { + console.log('Failed to list replication job:' + data); + vm.searchJobTIP = false; + vm.refreshJobTIP = false; + } + + function addReplication() { + vm.modalTitle = $filter('tr')('add_new_policy', []); + vm.action = 'ADD_NEW'; + } + + function editReplication(policyId) { + vm.policyId = policyId; + vm.modalTitle = $filter('tr')('edit_policy', []); + vm.action = 'EDIT'; + + console.log('Selected policy ID:' + vm.policyId); + } + + function confirmToTogglePolicy(policyId, enabled, name) { + vm.policyId = policyId; + vm.enabled = enabled; + + var status = $filter('tr')(vm.enabled === 1 ? 'enable':'disable'); + + var title; + var message; + if(enabled === 1){ + title = $filter('tr')('confirm_to_toggle_enabled_policy_title'); + message = $filter('tr')('confirm_to_toggle_enabled_policy'); + }else{ + title = $filter('tr')('confirm_to_toggle_disabled_policy_title'); + message = $filter('tr')('confirm_to_toggle_disabled_policy'); + } + $scope.$emit('modalTitle', title); + $scope.$emit('modalMessage', message); + + var emitInfo = { + 'contentType': 'text/html', + 'confirmOnly': false, + 'action': vm.togglePolicy + } + + $scope.$emit('raiseInfo', emitInfo); + } + + function togglePolicy() { + ToggleReplicationPolicyService(vm.policyId, vm.enabled) + .success(toggleReplicationPolicySuccess) + .error(toggleReplicationPolicyFailed); + } + + function toggleReplicationPolicySuccess(data, status) { + console.log('Successful toggle replication policy.'); + vm.retrievePolicy(); + } + + function toggleReplicationPolicyFailed(data, status) { + console.log('Failed to toggle replication policy.'); + } + + function downloadLog(policyId) { + $window.open('/api/jobs/replication/' + policyId + '/log', '_blank'); + } + + function pickUp(e) { + switch(e.key){ + case 'fromDate': + vm.fromDate = e.value; + break; + case 'toDate': + vm.toDate = e.value; + break; + } + $scope.$apply(); + } + + function toUTCSeconds(date, hour, min, sec) { + if(!angular.isDefined(date) || date === '') { + return ''; + } + var t = new Date(date); + t.setHours(hour); + t.setMinutes(min); + t.setSeconds(sec); + return t.getTime() / 1000; + } + + } + + function listReplication($timeout, I18nService) { + var directive = { + 'restrict': 'E', + 'templateUrl': '/static/resources/js/components/replication/list-replication.directive.html', + 'scope': { + 'sectionHeight': '=' + }, + 'link': link, + 'controller': ListReplicationController, + 'controllerAs': 'vm', + 'bindToController': true + }; + return directive; + + function link(scope, element, attrs, ctrl) { + + ctrl.lastPolicyId = -1; + + scope.$watch('vm.replicationPolicies', function(current) { + $timeout(function(){ + if(current) { + if(current.length > 0) { + element.find('#upon-pane table>tbody>tr').on('click', trClickHandler); + if(ctrl.lastPolicyId === -1) { + element.find('#upon-pane table>tbody>tr:eq(0)').trigger('click'); + }else{ + element.find('#upon-pane table>tbody>tr').filter('[policy_id="' + ctrl.lastPolicyId + '"]').trigger('click'); + } + }else{ + element + .find('#upon-pane table>tbody>tr') + .css({'background-color': '#FFFFFF'}) + .css({'color': '#000'}); + } + } + }); + }); + + function trClickHandler(e) { + element + .find('#upon-pane table>tbody>tr') + .css({'background-color': '#FFFFFF'}) + .css({'color': '#000'}) + .css({'cursor': 'default'}); + element + .find('#upon-pane table>tbody>tr a') + .css({'color': '#337ab7'}); + $(this) + .css({'background-color': '#057ac9'}) + .css({'color': '#fff'}); + $('a', this) + .css({'color': '#fff'}); + ctrl.retrieveJob($(this).attr('policy_id')); + ctrl.lastPolicyId = $(this).attr('policy_id'); + } + + element.find('.datetimepicker').datetimepicker({ + locale: I18nService().getCurrentLanguage(), + ignoreReadonly: true, + format: 'L', + showClear: true + }); + element.find('#fromDatePicker').on('blur', function(){ + ctrl.pickUp({'key': 'fromDate', 'value': $(this).val()}); + }); + element.find('#toDatePicker').on('blur', function(){ + ctrl.pickUp({'key': 'toDate', 'value': $(this).val()}); + }); + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/replication/replication.module.js b/static/resources/js/components/replication/replication.module.js new file mode 100644 index 000000000..bb276f733 --- /dev/null +++ b/static/resources/js/components/replication/replication.module.js @@ -0,0 +1,25 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.replication', [ + 'harbor.services.replication.policy', + 'harbor.services.replication.job' + ]); + +})(); \ No newline at end of file diff --git a/static/resources/js/components/repository/list-repository.directive.html b/static/resources/js/components/repository/list-repository.directive.html new file mode 100644 index 000000000..518d96611 --- /dev/null +++ b/static/resources/js/components/repository/list-repository.directive.html @@ -0,0 +1,35 @@ +
+
+
+
+ + + + +
+
+
+
+
+

// 'no_repositories' | tr //

+
+ +
+
+
+
\ No newline at end of file diff --git a/static/resources/js/components/repository/list-repository.directive.js b/static/resources/js/components/repository/list-repository.directive.js new file mode 100644 index 000000000..0c509777d --- /dev/null +++ b/static/resources/js/components/repository/list-repository.directive.js @@ -0,0 +1,183 @@ +/* + 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. +*/ +(function() { + 'use strict'; + + angular + .module('harbor.repository') + .directive('listRepository', listRepository); + + ListRepositoryController.$inject = ['$scope', 'ListRepositoryService', 'DeleteRepositoryService', '$filter', 'trFilter', '$location', 'getParameterByName']; + + function ListRepositoryController($scope, ListRepositoryService, DeleteRepositoryService, $filter, trFilter, $location, getParameterByName) { + + $scope.subsTabPane = 30; + + var vm = this; + + vm.sectionHeight = {'min-height': '579px'}; + + vm.filterInput = ''; + vm.toggleInProgress = []; + + var hashValue = $location.hash(); + if(hashValue) { + var slashIndex = hashValue.indexOf('/'); + if(slashIndex >=0) { + vm.filterInput = hashValue.substring(slashIndex + 1); + }else{ + vm.filterInput = hashValue; + } + } + + vm.retrieve = retrieve; + vm.tagCount = {}; + + vm.projectId = getParameterByName('project_id', $location.absUrl()); + vm.retrieve(); + + $scope.$on('$locationChangeSuccess', function() { + vm.projectId = getParameterByName('project_id', $location.absUrl()); + vm.filterInput = ''; + vm.retrieve(); + }); + + + $scope.$watch('vm.repositories', function(current) { + if(current) { + vm.repositories = current || []; + } + }); + + $scope.$on('repoName', function(e, val) { + vm.repoName = val; + }); + + $scope.$on('tag', function(e, val){ + vm.tag = val; + }); + + $scope.$on('tagCount', function(e, val) { + vm.tagCount = val; + }); + + $scope.$on('tags', function(e, val) { + vm.tags = val; + }); + + vm.deleteByRepo = deleteByRepo; + vm.deleteByTag = deleteByTag; + vm.deleteImage = deleteImage; + + function retrieve(){ + ListRepositoryService(vm.projectId, vm.filterInput) + .success(getRepositoryComplete) + .error(getRepositoryFailed); + } + + function getRepositoryComplete(data, status) { + vm.repositories = data || []; + $scope.$broadcast('refreshTags', true); + } + + function getRepositoryFailed(response) { + console.log('Failed to list repositories:' + response); + } + + function deleteByRepo(repoName) { + vm.repoName = repoName; + vm.tag = ''; + + $scope.$emit('modalTitle', $filter('tr')('alert_delete_repo_title', [repoName])); + $scope.$emit('modalMessage', $filter('tr')('alert_delete_repo', [repoName])); + + var emitInfo = { + 'confirmOnly': false, + 'contentType': 'text/html', + 'action' : vm.deleteImage + }; + + $scope.$emit('raiseInfo', emitInfo); + } + + function deleteByTag() { + $scope.$emit('modalTitle', $filter('tr')('alert_delete_tag_title', [vm.tag])); + var message; + console.log('vm.tagCount:' + angular.toJson(vm.tagCount[vm.repoName])); + $scope.$emit('modalMessage', $filter('tr')('alert_delete_tag', [vm.tag])); + + var emitInfo = { + 'confirmOnly': false, + 'contentType': 'text/html', + 'action' : vm.deleteImage + }; + + $scope.$emit('raiseInfo', emitInfo); + } + + function deleteImage() { + + console.log('Delete image, repoName:' + vm.repoName + ', tag:' + vm.tag); + vm.toggleInProgress[vm.repoName + '|' + vm.tag] = true; + DeleteRepositoryService(vm.repoName, vm.tag) + .success(deleteRepositorySuccess) + .error(deleteRepositoryFailed); + } + + function deleteRepositorySuccess(data, status) { + vm.toggleInProgress[vm.repoName + '|' + vm.tag] = false; + vm.retrieve(); + } + + function deleteRepositoryFailed(data, status) { + vm.toggleInProgress[vm.repoName + '|' + vm.tag] = false; + + $scope.$emit('modalTitle', $filter('tr')('error')); + var message; + if(status === 401) { + message = $filter('tr')('failed_to_delete_repo_insuffient_permissions'); + }else{ + message = $filter('tr')('failed_to_delete_repo'); + } + $scope.$emit('modalMessage', message); + $scope.$emit('raiseError', true); + + console.log('Failed to delete repository:' + angular.toJson(data)); + } + + } + + function listRepository() { + var directive = { + 'restrict': 'E', + 'templateUrl': '/static/resources/js/components/repository/list-repository.directive.html', + 'scope': { + 'sectionHeight': '=' + }, + 'link': link, + 'controller': ListRepositoryController, + 'controllerAs': 'vm', + 'bindToController': true + }; + + return directive; + + function link(scope, element, attr, ctrl) { + + } + + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/repository/list-tag.directive.html b/static/resources/js/components/repository/list-tag.directive.html new file mode 100644 index 000000000..94fc5dab0 --- /dev/null +++ b/static/resources/js/components/repository/list-tag.directive.html @@ -0,0 +1,22 @@ +
+
+ + + + + + + + + + + + + + + +
// 'tag' | tr //// 'image_details' | tr //// 'pull_command' | tr //// 'operation' | tr //
//tag// + +
+
+
\ No newline at end of file diff --git a/static/resources/js/components/repository/list-tag.directive.js b/static/resources/js/components/repository/list-tag.directive.js new file mode 100644 index 000000000..c856512f5 --- /dev/null +++ b/static/resources/js/components/repository/list-tag.directive.js @@ -0,0 +1,100 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.repository') + .directive('listTag', listTag); + + ListTagController.$inject = ['$scope', 'ListTagService', '$filter', 'trFilter']; + + function ListTagController($scope, ListTagService, $filter, trFilter) { + var vm = this; + + vm.tags = []; + vm.retrieve = retrieve; + + $scope.$watch('vm.repoName', function(current, origin) { + if(current) { + console.log('vm.repoName in tags:' + current); + vm.retrieve(); + } + }); + + $scope.$on('refreshTags', function(e, val) { + if(val) { + vm.retrieve(); + } + }); + + vm.deleteTag = deleteTag; + + function retrieve() { + ListTagService(vm.repoName) + .success(getTagSuccess) + .error(getTagFailed); + } + + function getTagSuccess(data) { + + vm.tags = data || []; + vm.tagCount[vm.repoName] = vm.tags.length; + + $scope.$emit('tags', vm.tags); + $scope.$emit('tagCount', vm.tagCount); + + angular.forEach(vm.tags, function(item) { + vm.toggleInProgress[vm.repoName + '|' + item] = false; + }); + } + + function getTagFailed(data) { + $scope.$emit('modalTitle', $filter('tr')('error')); + $scope.$emit('modalMessage', $filter('tr')('failed_to_get_tag') + response); + $scope.$emit('raiseError', true); + console.log('Failed to get tag:' + data); + } + + function deleteTag(e) { + $scope.$emit('repoName', e.repoName); + $scope.$emit('tag', e.tag); + vm.deleteByTag(); + } + + } + + function listTag() { + var directive = { + 'restrict': 'E', + 'templateUrl': '/static/resources/js/components/repository/list-tag.directive.html', + 'scope': { + 'tagCount': '=', + 'associateId': '=', + 'repoName': '=', + 'toggleInProgress': '=', + 'deleteByTag': '&' + }, + 'replace': true, + 'controller': ListTagController, + 'controllerAs': 'vm', + 'bindToController': true + }; + return directive; + + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/repository/popup-details.directive.html b/static/resources/js/components/repository/popup-details.directive.html new file mode 100644 index 000000000..83d77b26f --- /dev/null +++ b/static/resources/js/components/repository/popup-details.directive.html @@ -0,0 +1,4 @@ + + + + diff --git a/static/resources/js/components/repository/popup-details.directive.js b/static/resources/js/components/repository/popup-details.directive.js new file mode 100644 index 000000000..59311917a --- /dev/null +++ b/static/resources/js/components/repository/popup-details.directive.js @@ -0,0 +1,113 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.repository') + .directive('popupDetails', popupDetails); + + PopupDetailsController.$inject = ['ListManifestService', '$filter', 'dateLFilter']; + + function PopupDetailsController(ListManifestService, $filter, dateLFilter) { + var vm = this; + + vm.retrieve = retrieve; + + function retrieve() { + ListManifestService(vm.repoName, vm.tag) + .success(getManifestSuccess) + .error(getManifestFailed); + } + + function getManifestSuccess(data, status) { + console.log('Successful get manifest:' + data); + vm.manifest = data; + vm.manifest['Created'] = $filter('dateL')(vm.manifest['Created'], 'YYYY-MM-DD HH:mm:ss'); + } + + function getManifestFailed(data, status) { + console.log('Failed to get manifest:' + data); + } + } + + function popupDetails() { + var directive = { + 'restrict': 'E', + 'templateUrl': '/static/resources/js/components/repository/popup-details.directive.html', + 'scope': { + 'repoName': '@', + 'tag': '@', + 'index': '@' + }, + 'replace': true, + 'link': link, + 'controller': PopupDetailsController, + 'controllerAs': 'vm', + 'bindToController': true + }; + return directive; + + function link(scope, element, attrs, ctrl) { + ctrl.retrieve(); + scope.$watch('vm.manifest', function(current) { + if(current) { + + element + .popover({ + 'template': '', + 'title': '
', + 'content': generateContent, + 'html': true + }) + .on('shown.bs.popover', function(e){ + var self = jQuery(this); + $('[type="text"]:input', self.parent()) + .on('click', function() { + $(this).select(); + }); + self.parent().find('.glyphicon.glyphicon-remove-circle').on('click', function() { + element.trigger('click'); + }); + }); + } + }); + function generateContent() { + var content = '
' + + '
' + + '' + + '

' + + '
' + + '

' + + '
' + + '

' + ctrl.manifest['Created'] + '

' + + '
' + + '

' + (ctrl.manifest['Duration Days'] === '' ? 'N/A' : ctrl.manifest['Duration Days']) + ' days

' + + '
' + + '

' + (ctrl.manifest['Author'] === '' ? 'N/A' : ctrl.manifest['Author']) + '

' + + '
' + + '

' + (ctrl.manifest['Architecture'] === '' ? 'N/A' : ctrl.manifest['Architecture']) + '

' + + '
' + + '

' + (ctrl.manifest['Docker Version'] === '' ? 'N/A' : ctrl.manifest['Docker Version']) + '

' + + '
' + + '

' + (ctrl.manifest['OS'] === '' ? 'N/A' : ctrl.manifest['OS']) + '

' + + '
'; + return content; + } + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/repository/pull-command.directive.html b/static/resources/js/components/repository/pull-command.directive.html new file mode 100644 index 000000000..0a2b07f3a --- /dev/null +++ b/static/resources/js/components/repository/pull-command.directive.html @@ -0,0 +1,10 @@ +
+
+
+ +
+ +
+
+
+
\ No newline at end of file diff --git a/static/resources/js/components/repository/pull-command.directive.js b/static/resources/js/components/repository/pull-command.directive.js new file mode 100644 index 000000000..a002f011c --- /dev/null +++ b/static/resources/js/components/repository/pull-command.directive.js @@ -0,0 +1,55 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.repository') + .directive('pullCommand', pullCommand); + + function PullCommandController() { + + } + + function pullCommand() { + var directive = { + 'restrict': 'E', + 'templateUrl': '/static/resources/js/components/repository/pull-command.directive.html', + 'scope': { + 'repoName': '@', + 'tag': '@' + }, + 'link': link, + 'controller': PullCommandController, + 'controllerAs': 'vm', + 'bindToController': true + }; + return directive; + + function link(scope, element, attrs, ctrl) { + + ctrl.harborRegUrl = $('#HarborRegUrl').val() + '/'; + + element.find('a').on('click', clickHandler); + function clickHandler(e) { + element.find('input[type="text"]').select(); + } + + } + + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/repository/repository.module.js b/static/resources/js/components/repository/repository.module.js new file mode 100644 index 000000000..10b3ee18a --- /dev/null +++ b/static/resources/js/components/repository/repository.module.js @@ -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. +*/ +(function() { + 'use strict'; + + angular + .module('harbor.repository', [ + 'harbor.services.repository']); +})(); \ No newline at end of file diff --git a/static/resources/js/components/search/search-input.directive.html b/static/resources/js/components/search/search-input.directive.html new file mode 100644 index 000000000..dadde61ef --- /dev/null +++ b/static/resources/js/components/search/search-input.directive.html @@ -0,0 +1,19 @@ + + \ No newline at end of file diff --git a/static/resources/js/components/search/search-input.directive.js b/static/resources/js/components/search/search-input.directive.js new file mode 100644 index 000000000..5fd9173d5 --- /dev/null +++ b/static/resources/js/components/search/search-input.directive.js @@ -0,0 +1,68 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.search') + .directive('searchInput', searchInput); + + SearchInputController.$inject = ['$scope', '$location', '$window']; + + function SearchInputController($scope, $location, $window) { + var vm = this; + + vm.searchFor = searchFor; + + function searchFor(searchContent) { + $location + .path('/search') + .search({'q': searchContent}); + $window.location.href = $location.url(); + } + + } + + function searchInput() { + + var directive = { + 'restrict': 'E', + 'templateUrl': '/static/resources/js/components/search/search-input.directive.html', + 'scope': { + 'searchInput': '=', + }, + 'link': link, + 'controller': SearchInputController, + 'controllerAs': 'vm', + 'bindToController': true + }; + return directive; + + function link(scope, element, attrs, ctrl) { + element + .find('input[type="text"]') + .on('keydown', keydownHandler); + + function keydownHandler(e) { + if(e.keyCode === 13) { + ctrl.searchFor($(this).val()); + } + } + + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/search/search.directive.html b/static/resources/js/components/search/search.directive.html new file mode 100644 index 000000000..9086c0bba --- /dev/null +++ b/static/resources/js/components/search/search.directive.html @@ -0,0 +1,26 @@ + +
+ + + + + + + + + +
// 'project_repo_name' | tr //// 'creation_time' | tr //// 'author' | tr //
//s.repository_name//N/AN/A
+
\ No newline at end of file diff --git a/static/resources/js/components/search/search.directive.js b/static/resources/js/components/search/search.directive.js new file mode 100644 index 000000000..66182aa43 --- /dev/null +++ b/static/resources/js/components/search/search.directive.js @@ -0,0 +1,66 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.search') + .directive('search', search); + + SearchController.$inject = ['SearchService', '$scope']; + + function SearchController(SearchService, $scope) { + var vm = this; + vm.keywords = ""; + vm.search = searchByFilter; + vm.filterBy = 'repository'; + + searchByFilter(); + + + function searchByFilter() { + SearchService(vm.keywords) + .success(searchSuccess) + .error(searchFailed); + } + + function searchSuccess(data, status) { + console.log('filterBy:' + vm.filterBy + ", data:" + data); + vm.searchResult = data[vm.filterBy]; + } + + function searchFailed(data, status) { + console.log('Failed to search:' + data); + } + + } + + function search() { + var directive = { + 'restrict': 'E', + 'templateUrl': '/static/resources/js/components/search/search.directive.html', + 'scope': { + 'filterBy': '=' + }, + 'controller': SearchController, + 'controllerAs': 'vm', + 'bindToController': true + }; + + return directive; + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/search/search.module.js b/static/resources/js/components/search/search.module.js new file mode 100644 index 000000000..5be6cf0a3 --- /dev/null +++ b/static/resources/js/components/search/search.module.js @@ -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. +*/ +(function() { + 'use strict'; + + angular + .module('harbor.search', [ + 'harbor.services.search']); +})(); \ No newline at end of file diff --git a/static/resources/js/components/sign-in/sign-in.directive.js b/static/resources/js/components/sign-in/sign-in.directive.js new file mode 100644 index 000000000..885239cfd --- /dev/null +++ b/static/resources/js/components/sign-in/sign-in.directive.js @@ -0,0 +1,115 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.sign.in') + .directive('signIn', signIn); + + SignInController.$inject = ['SignInService', 'LogOutService', 'currentUser', 'I18nService', '$window', '$scope', 'getParameterByName', '$location']; + function SignInController(SignInService, LogOutService, currentUser, I18nService, $window, $scope, getParameterByName, $location) { + var vm = this; + + vm.hasError = false; + vm.errorMessage = ''; + + vm.reset = reset; + vm.doSignIn = doSignIn; + vm.doSignUp = doSignUp; + vm.doForgotPassword = doForgotPassword; + + vm.doContinue = doContinue; + vm.doLogOut = doLogOut; + + vm.signInTIP = false; + + function reset() { + vm.hasError = false; + vm.errorMessage = ''; + } + + function doSignIn(user) { + if(user && angular.isDefined(user.principal) && angular.isDefined(user.password)) { + vm.lastUrl = getParameterByName('last_url', $location.absUrl()); + vm.signInTIP = true; + SignInService(user.principal, user.password) + .success(signedInSuccess) + .error(signedInFailed); + } + } + + function signedInSuccess(data, status) { + if(vm.lastUrl) { + $window.location.href = vm.lastUrl; + return; + } + $window.location.href = "/dashboard"; + } + + function signedInFailed(data, status) { + vm.signInTIP = false; + if(status === 401) { + vm.hasError = true; + vm.errorMessage = 'username_or_password_is_incorrect'; + } + console.log('Failed to sign in:' + data + ', status:' + status); + } + + function doSignUp() { + $window.location.href = '/sign_up'; + } + + function doForgotPassword() { + $window.location.href = '/forgot_password'; + } + + function doContinue() { + $window.location.href = '/dashboard'; + } + + function doLogOut() { + LogOutService() + .success(logOutSuccess) + .error(logOutFailed); + } + + function logOutSuccess(data, status) { + currentUser.unset(); + I18nService().unset(); + $window.location.href= '/'; + } + + function logOutFailed(data, status) { + console.log('Failed to to log out:' + data); + } + } + + function signIn() { + var directive = { + 'restrict': 'E', + 'templateUrl': '/sign_in', + 'scope': true, + 'controller': SignInController, + 'controllerAs': 'vm', + 'bindToController': true + }; + + return directive; + + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/sign-in/sign-in.module.js b/static/resources/js/components/sign-in/sign-in.module.js new file mode 100644 index 000000000..5c257f635 --- /dev/null +++ b/static/resources/js/components/sign-in/sign-in.module.js @@ -0,0 +1,24 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.sign.in', [ + 'harbor.services.user' + ]); + +})(); \ No newline at end of file diff --git a/static/resources/js/components/summary/summary.directive.html b/static/resources/js/components/summary/summary.directive.html new file mode 100644 index 000000000..a24ff5221 --- /dev/null +++ b/static/resources/js/components/summary/summary.directive.html @@ -0,0 +1,19 @@ + + +
+
// key | tr //:
//value//
+
// key | tr //:
//value//
+
diff --git a/static/resources/js/components/summary/summary.directive.js b/static/resources/js/components/summary/summary.directive.js new file mode 100644 index 000000000..2273650c7 --- /dev/null +++ b/static/resources/js/components/summary/summary.directive.js @@ -0,0 +1,56 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.summary') + .directive('projectSummary', projectSummary); + + ProjectSummaryController.$inject = ['$scope', 'StatProjectService', '$filter', 'trFilter']; + + function ProjectSummaryController($scope, StatProjectService, $filter, trFilter) { + var vm = this; + + StatProjectService() + .success(statProjectSuccess) + .error(statProjectFailed); + + function statProjectSuccess(data) { + vm.statProjects = data; + } + + function statProjectFailed(data) { + $scope.$emit('modalTitle', $filter('tr')('error')); + $scope.$emit('modalMessage', $filter('tr')('failed_to_get_stat') + data); + $scope.$emit('raiseError', true); + console.log('Failed to get stat:' + data); + } + } + + function projectSummary() { + var directive = { + 'restrict': 'E', + 'templateUrl': '/static/resources/js/components/summary/summary.directive.html', + 'controller': ProjectSummaryController, + 'scope' : true, + 'controllerAs': 'vm', + 'bindToController': true + }; + return directive; + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/summary/summary.module.js b/static/resources/js/components/summary/summary.module.js new file mode 100644 index 000000000..3828e956d --- /dev/null +++ b/static/resources/js/components/summary/summary.module.js @@ -0,0 +1,24 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.summary', [ + 'harbor.services.project' + ]); + +})(); \ No newline at end of file diff --git a/static/resources/js/components/system-management/configuration.directive.html b/static/resources/js/components/system-management/configuration.directive.html new file mode 100644 index 000000000..74d2a5b9b --- /dev/null +++ b/static/resources/js/components/system-management/configuration.directive.html @@ -0,0 +1,76 @@ + +
+
+
System Settings
+
+
+
+
+ +
+ +
+ Host name is required. +
+
+
+
+ +
+ +
+ Url protocol is required. +
+
+
+
+ +
+ +
+ Email server is required. +
+
+
+
+ +
+ +
+ LDAP URL is required. +
+
+
+
+
+
Registration
+
+
+
+
+ +
+ +
+
+
+
+ + +
+
+
+
\ No newline at end of file diff --git a/static/resources/js/components/system-management/configuration.directive.js b/static/resources/js/components/system-management/configuration.directive.js new file mode 100644 index 000000000..bc21345ad --- /dev/null +++ b/static/resources/js/components/system-management/configuration.directive.js @@ -0,0 +1,68 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.system.management') + .directive('configuration', configuration); + + ConfigurationController.$inject = []; + + function ConfigurationController() { + var vm = this; + + vm.registrationOptions = [ + { + 'name': 'on', + 'value': true + }, + { + 'name': 'off', + 'value': false + } + ]; + vm.currentRegistration = { + 'name': 'on', + 'value': true + }; + + vm.changeSettings = changeSettings; + + vm.selectRegistration = selectRegistration; + + function selectRegistration() { + + } + + function changeSettings(system) { + console.log(system); + } + } + + function configuration() { + var directive = { + 'restrict': 'E', + 'templateUrl': '/static/resources/js/components/system-management/configuration.directive.html', + 'scope': true, + 'controller': ConfigurationController, + 'controllerAs': 'vm', + 'bindToController': true + }; + return directive; + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/system-management/create-destination.directive.html b/static/resources/js/components/system-management/create-destination.directive.html new file mode 100644 index 000000000..1f0d8bd5a --- /dev/null +++ b/static/resources/js/components/system-management/create-destination.directive.html @@ -0,0 +1,84 @@ + + diff --git a/static/resources/js/components/system-management/create-destination.directive.js b/static/resources/js/components/system-management/create-destination.directive.js new file mode 100644 index 000000000..2d857bc27 --- /dev/null +++ b/static/resources/js/components/system-management/create-destination.directive.js @@ -0,0 +1,249 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.system.management') + .directive('createDestination', createDestination); + + CreateDestinationController.$inject = ['$scope', 'ListDestinationService', 'CreateDestinationService', 'UpdateDestinationService', 'PingDestinationService', 'ListDestinationPolicyService', '$filter', 'trFilter', '$timeout']; + + function CreateDestinationController($scope, ListDestinationService, CreateDestinationService, UpdateDestinationService, PingDestinationService, ListDestinationPolicyService, $filter, trFilter, $timeout) { + var vm = this; + + $scope.destination = {}; + + var vm0 = $scope.destination; + vm.addNew = addNew; + vm.edit = edit; + vm.create = create; + vm.update = update; + vm.pingDestination = pingDestination; + + vm.editable = true; + vm.notAvailable = false; + vm.pingAvailable = true; + vm.pingMessage = ''; + + vm.closeError = closeError; + vm.toggleErrorMessage = false; + vm.errorMessages = []; + + vm.pingTIP = false; + + $scope.$watch('destination.endpoint', function(current) { + if(current) { + vm.notAvailable = false; + }else{ + vm.notAvailable = true; + } + }); + + function addNew() { + vm.modalTitle = $filter('tr')('add_new_destination', []); + vm0.name = ''; + vm0.endpoint = ''; + vm0.username = ''; + vm0.password = ''; + } + + function edit(targetId) { + vm.editable = true; + vm.modalTitle = $filter('tr')('edit_destination', []); + ListDestinationService(targetId) + .success(getDestinationSuccess) + .error(getDestinationFailed); + } + + function create(destination) { + CreateDestinationService(destination.name, destination.endpoint, + destination.username, destination.password) + .success(createDestinationSuccess) + .error(createDestinationFailed); + } + + function createDestinationSuccess(data, status) { + console.log('Successful created destination.'); + vm.reload(); + } + + function createDestinationFailed(data, status) { + if(status === 409) { + vm.errorMessages.push($filter('tr')('destination_already_exists')); + }else{ + vm.errorMessages.push($filter('tr')('failed_to_create_destination') + data); + } + console.log('Failed to create destination:' + data); + } + + function update(destination) { + UpdateDestinationService(vm.targetId, destination) + .success(updateDestinationSuccess) + .error(updateDestinationFailed); + } + + function updateDestinationSuccess(data, status) { + console.log('Successful update destination.'); + vm.reload(); + } + + function updateDestinationFailed(data, status) { + vm.errorMessages.push($filter('tr')('failed_to_update_destination') + data); + console.log('Failed to update destination.'); + } + + + function getDestinationSuccess(data, status) { + var destination = data; + vm0.name = destination.name; + vm0.endpoint = destination.endpoint; + vm0.username = destination.username; + vm0.password = destination.password; + + ListDestinationPolicyService(destination.id) + .success(listDestinationPolicySuccess) + .error(listDestinationPolicyFailed); + } + + function getDestinationFailed(data, status) { + vm.errorMessages.push($filter('tr')('failed_get_destination')); + console.log('Failed to get destination.'); + } + + function listDestinationPolicySuccess(data, status) { + for(var i in data) { + if(data[i].enabled === 1) { + vm.editable = false; + break; + } + } + } + + function listDestinationPolicyFailed(data, status) { + vm.errorMessages.push($filter('tr')('failed_get_destination_policies')); + console.log('Failed to list destination policy:' + data); + } + + function pingDestination() { + + vm.pingTIP = true; + vm.pingAvailable = false; + + var target = { + 'name': vm0.name, + 'endpoint': vm0.endpoint, + 'username': vm0.username, + 'password': vm0.password + }; + PingDestinationService(target) + .success(pingDestinationSuccess) + .error(pingDestinationFailed); + } + + function closeError() { + vm.errorMessages = []; + vm.toggleErrorMessage = false; + } + + function pingDestinationSuccess(data, status) { + vm.pingAvailable = true; + vm.pingTIP = false; + vm.pingMessage = $filter('tr')('successful_ping_target', []); + } + function pingDestinationFailed(data, status) { + + vm.pingTIP = false; + vm.pingMessage = $filter('tr')('failed_to_ping_target', []) + (data && data.length > 0 ? ':' + data : ''); + } + } + + function createDestination($timeout) { + var directive = { + 'restrict': 'E', + 'templateUrl': '/static/resources/js/components/system-management/create-destination.directive.html', + 'scope': { + 'action': '@', + 'targetId': '@', + 'reload': '&' + }, + 'link': link, + 'controller': CreateDestinationController, + 'controllerAs': 'vm', + 'bindToController': true + }; + return directive; + + function link(scope, element, attrs, ctrl) { + + element.find('#createDestinationModal').on('show.bs.modal', function() { + scope.$apply(function(){ + scope.form.$setPristine(); + scope.form.$setUntouched(); + + ctrl.notAvailble = false; + ctrl.pingAvailable = true; + ctrl.pingMessage = ''; + + ctrl.pingTIP = false; + ctrl.toggleErrorMessage = false; + ctrl.errorMessages = []; + + switch(ctrl.action) { + case 'ADD_NEW': + ctrl.addNew(); + break; + case 'EDIT': + ctrl.edit(ctrl.targetId); + break; + } + + scope.$watch('vm.errorMessages', function(current) { + if(current && current.length > 0) { + ctrl.toggleErrorMessage = true; + } + }, true); + + }); + }); + + ctrl.save = save; + + function save(destination) { + if(destination) { + ctrl.toggleErrorMessage = false; + ctrl.errorMessages = []; + + switch(ctrl.action) { + case 'ADD_NEW': + ctrl.create(destination); + break; + case 'EDIT': + ctrl.update(destination); + break; + } + + $timeout(function() { + if(!ctrl.toggleErrorMessage) { + element.find('#createDestinationModal').modal('hide'); + } + }, 50); + } + } + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/system-management/destination.directive.html b/static/resources/js/components/system-management/destination.directive.html new file mode 100644 index 000000000..37774c994 --- /dev/null +++ b/static/resources/js/components/system-management/destination.directive.html @@ -0,0 +1,63 @@ + +
+
+
+
+ + + + +
+ + +
+
+
+
+ + + + + + + +
// 'name' | tr //// 'endpoint' | tr //// 'creation_time' | tr //// 'actions' | tr //
+
+
+ + + + + + + + + + + + +

// 'no_destinations' | tr //

//r.name////r.endpoint////r.creation_time | dateL : 'YYYY-MM-DD HH:mm:ss'// + +   + +
+
+
+
+
//vm.destinations ? vm.destinations.length : 0// // 'items' | tr //
+
+
+
diff --git a/static/resources/js/components/system-management/destination.directive.js b/static/resources/js/components/system-management/destination.directive.js new file mode 100644 index 000000000..071fee268 --- /dev/null +++ b/static/resources/js/components/system-management/destination.directive.js @@ -0,0 +1,118 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.system.management') + .directive('destination', destination); + + DestinationController.$inject = ['$scope', 'ListDestinationService', 'DeleteDestinationService', '$filter', 'trFilter']; + + function DestinationController($scope, ListDestinationService, DeleteDestinationService, $filter, trFilter) { + + $scope.subsSubPane = 276; + $scope.subsTblBody = 40; + var vm = this; + + vm.retrieve = retrieve; + vm.search = search; + vm.addDestination = addDestination; + vm.editDestination = editDestination; + vm.confirmToDelete = confirmToDelete; + vm.deleteDestination = deleteDestination; + + vm.retrieve(); + + function retrieve() { + ListDestinationService('', vm.destinationName) + .success(listDestinationSuccess) + .error(listDestinationFailed); + } + + function search() { + vm.retrieve(); + } + + function addDestination() { + vm.action = 'ADD_NEW'; + console.log('Action for destination:' + vm.action); + } + + function editDestination(targetId) { + vm.action = 'EDIT'; + vm.targetId = targetId; + console.log('Action for destination:' + vm.action + ', target ID:' + vm.targetId); + } + + function confirmToDelete(targetId, name) { + vm.selectedTargetId = targetId; + + $scope.$emit('modalTitle', $filter('tr')('confirm_delete_destination_title')); + $scope.$emit('modalMessage', $filter('tr')('confirm_delete_destination', [name])); + + var emitInfo = { + 'confirmOnly': false, + 'contentType': 'text/plain', + 'action': vm.deleteDestination + }; + + $scope.$emit('raiseInfo', emitInfo); + } + + function deleteDestination() { + DeleteDestinationService(vm.selectedTargetId) + .success(deleteDestinationSuccess) + .error(deleteDestinationFailed); + } + + function listDestinationSuccess(data, status) { + vm.destinations = data || []; + } + + function listDestinationFailed(data, status) { + $scope.$emit('modalTitle', $filter('tr')('error')); + $scope.$emit('modalMessage', $filter('tr')('failed_to_list_destination')); + $scope.$emit('raiseError', true); + console.log('Failed to list destination:' + data); + } + + function deleteDestinationSuccess(data, status) { + console.log('Successful delete destination.'); + vm.retrieve(); + } + + function deleteDestinationFailed(data, status) { + $scope.$emit('modalTitle', $filter('tr')('error')); + $scope.$emit('modalMessage', $filter('tr')('failed_to_delete_destination') + data); + $scope.$emit('raiseError', true); + console.log('Failed to delete destination.'); + } + } + + function destination() { + var directive = { + 'restrict': 'E', + 'templateUrl': '/static/resources/js/components/system-management/destination.directive.html', + 'scope': true, + 'controller': DestinationController, + 'controllerAs': 'vm', + 'bindToController': true + }; + return directive; + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/system-management/replication.directive.html b/static/resources/js/components/system-management/replication.directive.html new file mode 100644 index 000000000..f4f420cb8 --- /dev/null +++ b/static/resources/js/components/system-management/replication.directive.html @@ -0,0 +1,77 @@ + +
+
+
+
+ + + + +
+ +
+
+
+
+ + + + + + + + + + +
// 'name' | tr //// 'description' | tr //// 'projects' | tr //// 'destination' | tr //// 'start_time' | tr //// 'activation' | tr //// 'actions' | tr //
+
+
+ + + + + + + + + + + + + + + +

// 'no_replication_policies' | tr //

//r.name////r.description////r.project_name////r.target_name////r.start_time | dateL : 'YYYY-MM-DD HH:mm:ss'// + // 'enabled' | tr // + // 'disabled' | tr // + +
+ + +
+   + +   + +
+
+
+
+
+
//vm.replications ? vm.replications.length : 0// // 'items' | tr //
+
+
+
diff --git a/static/resources/js/components/system-management/replication.directive.js b/static/resources/js/components/system-management/replication.directive.js new file mode 100644 index 000000000..265e010ca --- /dev/null +++ b/static/resources/js/components/system-management/replication.directive.js @@ -0,0 +1,93 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.system.management') + .directive('replication', replication); + + ReplicationController.$inject = ['$scope', 'ListReplicationPolicyService', 'ToggleReplicationPolicyService', '$filter', 'trFilter']; + + function ReplicationController($scope, ListReplicationPolicyService, ToggleReplicationPolicyService, $filter, trFilter) { + + $scope.subsSubPane = 276; + + var vm = this; + vm.retrieve = retrieve; + vm.search = search; + vm.togglePolicy = togglePolicy; + vm.editReplication = editReplication; + vm.retrieve(); + + function search() { + vm.retrieve(); + } + + function retrieve() { + ListReplicationPolicyService('', '', vm.replicationName) + .success(listReplicationPolicySuccess) + .error(listReplicationPolicyFailed); + } + + function listReplicationPolicySuccess(data, status) { + vm.replications = data || []; + } + + function listReplicationPolicyFailed(data, status) { + $scope.$emit('modalTitle', $filter('tr')('error')); + $scope.$emit('modalMessage', $filter('tr')('failed_to_list_replication')); + $scope.$emit('raiseError', true); + console.log('Failed to list replication policy.'); + } + + function togglePolicy(policyId, enabled) { + ToggleReplicationPolicyService(policyId, enabled) + .success(toggleReplicationPolicySuccess) + .error(toggleReplicationPolicyFailed); + } + + function toggleReplicationPolicySuccess(data, status) { + console.log('Successful toggle replication policy.'); + vm.retrieve(); + } + + function toggleReplicationPolicyFailed(data, status) { + $scope.$emit('modalTitle', $filter('tr')('error')); + $scope.$emit('modalMessage', $filter('tr')('failed_to_toggle_policy')); + $scope.$emit('raiseError', true); + console.log('Failed to toggle replication policy.'); + } + + function editReplication(policyId) { + vm.action = 'EDIT'; + vm.policyId = policyId; + } + } + + function replication() { + var directive = { + 'restrict': 'E', + 'templateUrl': '/static/resources/js/components/system-management/replication.directive.html', + 'scope': true, + 'controller': ReplicationController, + 'controllerAs': 'vm', + 'bindToController': true + }; + return directive; + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/system-management/system-management.directive.html b/static/resources/js/components/system-management/system-management.directive.html new file mode 100644 index 000000000..2005152c2 --- /dev/null +++ b/static/resources/js/components/system-management/system-management.directive.html @@ -0,0 +1,24 @@ + +
+ + + +
+
+ + + +
diff --git a/static/resources/js/components/system-management/system-management.directive.js b/static/resources/js/components/system-management/system-management.directive.js new file mode 100644 index 000000000..68d17938d --- /dev/null +++ b/static/resources/js/components/system-management/system-management.directive.js @@ -0,0 +1,54 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.system.management') + .directive('systemManagement', systemManagement); + + SystemManagementController.$inject = ['$scope', '$location']; + + function SystemManagementController($scope, $location) { + var vm = this; + var currentTarget = $location.path().substring(1); + + switch(currentTarget) { + case 'destinations': + case 'replication': + $location.path('/' + currentTarget); + vm.target = currentTarget; + break; + default: + $location.path('/destinations'); + vm.target = 'destinations'; + } + + } + + function systemManagement() { + var directive = { + 'restrict': 'E', + 'templateUrl': '/static/resources/js/components/system-management/system-management.directive.html', + 'scope': true, + 'controller': SystemManagementController, + 'controllerAs': 'vm', + 'bindToController': true + }; + return directive; + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/system-management/system-management.module.js b/static/resources/js/components/system-management/system-management.module.js new file mode 100644 index 000000000..f52f7aa1a --- /dev/null +++ b/static/resources/js/components/system-management/system-management.module.js @@ -0,0 +1,22 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.system.management', []); + +})(); \ No newline at end of file diff --git a/static/resources/js/components/top-repository/top-repository.directive.html b/static/resources/js/components/top-repository/top-repository.directive.html new file mode 100644 index 000000000..c45a7adfa --- /dev/null +++ b/static/resources/js/components/top-repository/top-repository.directive.html @@ -0,0 +1,38 @@ + + +
+
+ + + + + + +
// 'repository_name' | tr //// 'count' | tr //// 'creator' | tr //
+
+
+ + + + + + + + + +

// 'no_top_repositories' | tr //

//t.name////t.count////t.creator === '' ? '-' : t.creator //
+
+
diff --git a/static/resources/js/components/top-repository/top-repository.directive.js b/static/resources/js/components/top-repository/top-repository.directive.js new file mode 100644 index 000000000..800f7e767 --- /dev/null +++ b/static/resources/js/components/top-repository/top-repository.directive.js @@ -0,0 +1,58 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.top.repository') + .directive('topRepository', topRepository); + + TopRepositoryController.$inject = ['$scope', 'ListTopRepositoryService', '$filter', 'trFilter']; + + function TopRepositoryController($scope, ListTopRepositoryService, $filter, trFilter) { + var vm = this; + + ListTopRepositoryService(5) + .success(listTopRepositorySuccess) + .error(listTopRepositoryFailed); + + function listTopRepositorySuccess(data) { + vm.top10Repositories = data || []; + } + + function listTopRepositoryFailed(data, status) { + $scope.$emit('modalTitle', $filter('tr')('error')); + $scope.$emit('modalMessage', $filter('tr')('failed_to_get_top_repo')); + $scope.$emit('raiseError', true); + console.log('Failed to get top repo:' + data); + } + } + + function topRepository() { + var directive = { + 'restrict': 'E', + 'templateUrl': '/static/resources/js/components/top-repository/top-repository.directive.html', + 'controller': TopRepositoryController, + 'scope' : { + 'customBodyHeight': '=' + }, + 'controllerAs': 'vm', + 'bindToController': true + }; + return directive; + } + +})(); diff --git a/static/resources/js/components/top-repository/top-repository.module.js b/static/resources/js/components/top-repository/top-repository.module.js new file mode 100644 index 000000000..988f4e2f6 --- /dev/null +++ b/static/resources/js/components/top-repository/top-repository.module.js @@ -0,0 +1,24 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.top.repository', [ + 'harbor.services.repository' + ]); + +})(); \ No newline at end of file diff --git a/static/resources/js/components/user-log/user-log.directive.html b/static/resources/js/components/user-log/user-log.directive.html new file mode 100644 index 000000000..5500b0ffd --- /dev/null +++ b/static/resources/js/components/user-log/user-log.directive.html @@ -0,0 +1,38 @@ + +
+
+ + + + + + + +
// 'operation' | tr //// 'details' | tr //// 'user' | tr //// 'creation_time' | tr //
+
+
+ + + + + + + + + +

// 'no_user_logs' | tr //

//t.operation////t.repo_name////t.username////t.op_time | dateL : 'YYYY-MM-DD HH:mm:ss'//
+
+
diff --git a/static/resources/js/components/user-log/user-log.directive.js b/static/resources/js/components/user-log/user-log.directive.js new file mode 100644 index 000000000..b735b00ef --- /dev/null +++ b/static/resources/js/components/user-log/user-log.directive.js @@ -0,0 +1,57 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.user.log') + .directive('userLog', userLog); + + UserLogController.$inject = ['$scope', 'ListIntegratedLogService', '$filter', 'trFilter']; + + function UserLogController($scope, ListIntegratedLogService, $filter, trFilter) { + var vm = this; + + ListIntegratedLogService() + .success(listIntegratedLogSuccess) + .error(listIntegratedLogFailed); + + function listIntegratedLogSuccess(data) { + vm.integratedLogs = data || [] + } + + function listIntegratedLogFailed(data, status) { + $scope.$emit('modalTitle', $filter('tr')('error')); + $scope.$emit('modalMessage', $filter('tr')('failed_to_get_user_log') + data); + $scope.$emit('raiseError', true); + console.log('Failed to get user logs:' + data); + } + } + + function userLog() { + var directive = { + 'restrict': 'E', + 'templateUrl': '/static/resources/js/components/user-log/user-log.directive.html', + 'controller': UserLogController, + 'scope' : true, + 'controllerAs': 'vm', + 'bindToController': true + }; + + return directive; + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/user-log/user-log.module.js b/static/resources/js/components/user-log/user-log.module.js new file mode 100644 index 000000000..6610262b1 --- /dev/null +++ b/static/resources/js/components/user-log/user-log.module.js @@ -0,0 +1,24 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.user.log', [ + 'harbor.services.log' + ]); + +})(); \ No newline at end of file diff --git a/static/resources/js/components/user/list-user.directive.html b/static/resources/js/components/user/list-user.directive.html new file mode 100644 index 000000000..f84eedc46 --- /dev/null +++ b/static/resources/js/components/user/list-user.directive.html @@ -0,0 +1,64 @@ + +
+
+
+
+ + + + +
+
+
+
+
+ + + + + + + + +
// 'username' | tr //// 'email' | tr //// 'registration_time' | tr //// 'administrator' | tr //// 'operation' | tr //
+
+ +
+ + + + + + + + + + +
//u.username////u.email////u.creation_time | dateL : 'YYYY-MM-DD HH:mm:ss'// + + +    +
+
+
+
+
//vm.users ? vm.users.length : 0// items
+
+
+
+
+ + diff --git a/static/resources/js/components/user/list-user.directive.js b/static/resources/js/components/user/list-user.directive.js new file mode 100644 index 000000000..bbd7c929c --- /dev/null +++ b/static/resources/js/components/user/list-user.directive.js @@ -0,0 +1,110 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.user') + .directive('listUser', listUser); + + ListUserController.$inject = ['$scope', 'ListUserService', 'DeleteUserService', '$filter', 'trFilter']; + + function ListUserController($scope, ListUserService, DeleteUserService, $filter, $trFilter) { + + $scope.subsSubPane = 226; + + var vm = this; + + vm.username = ''; + vm.searchUser = searchUser; + vm.deleteUser = deleteUser; + vm.confirmToDelete = confirmToDelete; + vm.retrieve = retrieve; + + vm.retrieve(); + + function searchUser() { + vm.retrieve(); + } + + function deleteUser() { + DeleteUserService(vm.selectedUserId) + .success(deleteUserSuccess) + .error(deleteUserFailed); + } + + function confirmToDelete(userId, username) { + vm.selectedUserId = userId; + + $scope.$emit('modalTitle', $filter('tr')('confirm_delete_user_title')); + $scope.$emit('modalMessage', $filter('tr')('confirm_delete_user', [username])); + + var emitInfo = { + 'confirmOnly': false, + 'contentType': 'text/plain', + 'action': vm.deleteUser + }; + + $scope.$emit('raiseInfo', emitInfo); + } + + function retrieve() { + ListUserService(vm.username) + .success(listUserSuccess) + .error(listUserFailed); + } + + function deleteUserSuccess(data, status) { + console.log('Successful delete user.'); + vm.retrieve(); + } + + function deleteUserFailed(data, status) { + $scope.$emit('modalTitle', $filter('tr')('error')); + $scope.$emit('modalMessage', $filter('tr')('failed_to_delete_user')); + $scope.$emit('raiseError', true); + console.log('Failed to delete user.'); + } + + function listUserSuccess(data, status) { + vm.users = data; + } + + function listUserFailed(data, status) { + $scope.$emit('modalTitle', $filter('tr')('error')); + $scope.$emit('modalMessage', $filter('tr')('failed_to_list_user')); + $scope.$emit('raiseError', true); + console.log('Failed to list user:' + data); + } + } + + function listUser() { + var directive = { + 'restrict': 'E', + 'templateUrl': '/static/resources/js/components/user/list-user.directive.html', + 'link': link, + 'controller': ListUserController, + 'controllerAs': 'vm', + 'bindToController': true + }; + return directive; + + function link(scope, element, attrs) { + + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/user/toggle-admin.directive.html b/static/resources/js/components/user/toggle-admin.directive.html new file mode 100644 index 000000000..5c43a9d19 --- /dev/null +++ b/static/resources/js/components/user/toggle-admin.directive.html @@ -0,0 +1,16 @@ + + + \ No newline at end of file diff --git a/static/resources/js/components/user/toggle-admin.directive.js b/static/resources/js/components/user/toggle-admin.directive.js new file mode 100644 index 000000000..09424b499 --- /dev/null +++ b/static/resources/js/components/user/toggle-admin.directive.js @@ -0,0 +1,80 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.user') + .directive('toggleAdmin', toggleAdmin); + + ToggleAdminController.$inject = ['$scope', 'ToggleAdminService', '$filter', 'trFilter']; + + function ToggleAdminController($scope, ToggleAdminService, $filter, trFilter) { + var vm = this; + + vm.isAdmin = (vm.hasAdminRole == 1) ? true : false; + vm.enabled = vm.isAdmin ? 0 : 1; + vm.toggle = toggle; + + function toggle() { + ToggleAdminService(vm.userId, vm.enabled) + .success(toggleAdminSuccess) + .error(toggleAdminFailed); + } + + function toggleAdminSuccess(data, status) { + if(vm.isAdmin) { + vm.isAdmin = false; + }else{ + vm.isAdmin = true; + } + console.log('Toggled userId:' + vm.userId + ' to admin:' + vm.isAdmin); + } + + function toggleAdminFailed(data, status) { + $scope.$emit('modalTitle', $filter('tr')('error')); + $scope.$emit('modalMessage', $filter('tr')('failed_to_toggle_admin')); + $scope.$emit('raiseError', true); + if(vm.isAdmin) { + vm.isAdmin = false; + }else{ + vm.isAdmin = true; + } + console.log('Failed to toggle admin:' + data); + } + } + + function toggleAdmin() { + var directive = { + 'restrict': 'E', + 'templateUrl': '/static/resources/js/components/user/toggle-admin.directive.html', + 'scope': { + 'hasAdminRole': '=', + 'userId': '@' + }, + 'link': link, + 'controller': ToggleAdminController, + 'controllerAs': 'vm', + 'bindToController': true + }; + return directive; + + function link(scope, element, attrs, ctrl) { + + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/user/user.module.js b/static/resources/js/components/user/user.module.js new file mode 100644 index 000000000..a81aa1614 --- /dev/null +++ b/static/resources/js/components/user/user.module.js @@ -0,0 +1,22 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.user', [ + 'harbor.services.user']); +})(); \ No newline at end of file diff --git a/static/resources/js/components/validator/confirm-password.validator.js b/static/resources/js/components/validator/confirm-password.validator.js new file mode 100644 index 000000000..f52050a73 --- /dev/null +++ b/static/resources/js/components/validator/confirm-password.validator.js @@ -0,0 +1,47 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.validator') + .directive('compareTo', compareTo); + + function compareTo() { + var directive = { + 'require' : 'ngModel', + 'scope':{ + 'otherModelValue': '=compareTo' + }, + 'link': link + }; + return directive; + + function link (scope, element, attrs, ctrl) { + + ctrl.$validators.compareTo = validator; + + function validator(modelValue) { + return modelValue === scope.otherModelValue; + } + + scope.$watch("otherModelValue", function(current, origin) { + ctrl.$validate(); + }); + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/validator/invalid-chars.validator.js b/static/resources/js/components/validator/invalid-chars.validator.js new file mode 100644 index 000000000..9e36f3096 --- /dev/null +++ b/static/resources/js/components/validator/invalid-chars.validator.js @@ -0,0 +1,55 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.validator') + .directive('invalidChars', invalidChars); + + invalidChars.$inject = ['INVALID_CHARS']; + + function invalidChars(INVALID_CHARS) { + var directive = { + 'require': 'ngModel', + 'link': link + }; + + return directive; + + function link(scope, element, attrs, ctrl) { + + ctrl.$validators.invalidChars = validator; + + function validator(modelValue, viewValue) { + if(ctrl.$isEmpty(modelValue)) { + return true; + } + + for(var i = 0; i < INVALID_CHARS.length; i++) { + if(modelValue.indexOf(INVALID_CHARS[i]) >= 0) { + return false; + } + } + + return true; + } + + } + } + + +})(); \ No newline at end of file diff --git a/static/resources/js/components/validator/password.validator.js b/static/resources/js/components/validator/password.validator.js new file mode 100644 index 000000000..3f67d7257 --- /dev/null +++ b/static/resources/js/components/validator/password.validator.js @@ -0,0 +1,44 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.validator') + .directive('password', password); + + password.$inject = ['PASSWORD_REGEXP']; + + function password(PASSWORD_REGEXP) { + var directive = { + 'require' : 'ngModel', + 'link': link + }; + return directive; + + function link (scope, element, attrs, ctrl) { + + ctrl.$validators.password = validator; + + function validator(modelValue, viewValue) { + + return PASSWORD_REGEXP.test(modelValue); + + } + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/validator/project-name.validator.js b/static/resources/js/components/validator/project-name.validator.js new file mode 100644 index 000000000..3c36c5827 --- /dev/null +++ b/static/resources/js/components/validator/project-name.validator.js @@ -0,0 +1,41 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.validator') + .directive('projectName', projectName); + + projectName.$inject = ['PROJECT_REGEXP'] + + function projectName(PROJECT_REGEXP) { + var directive = { + 'require': 'ngModel', + 'link': link + }; + return directive; + + function link(scope, element, attrs, ctrl) { + ctrl.$validators.projectName = validator; + + function validator(modelValue, viewValue) { + return PROJECT_REGEXP.test(modelValue); + } + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/components/validator/user-exist.validator.js b/static/resources/js/components/validator/user-exist.validator.js new file mode 100644 index 000000000..7fa7990ec --- /dev/null +++ b/static/resources/js/components/validator/user-exist.validator.js @@ -0,0 +1,70 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.validator') + .directive('userExists', userExists); + + userExists.$inject = ['UserExistService']; + + function userExists(UserExistService) { + var directive = { + 'require': 'ngModel', + 'scope': { + 'target': '@' + }, + 'link': link + }; + return directive; + + function link(scope, element, attrs, ctrl) { + + var valid = true; + + ctrl.$validators.userExists = validator; + + function validator(modelValue, viewValue) { + + console.log('modelValue:' + modelValue + ', viewValue:' + viewValue); + + if(ctrl.$isEmpty(modelValue)) { + console.log('Model value is empty.'); + return true; + } + + UserExistService(attrs.target, modelValue) + .success(userExistSuccess) + .error(userExistFailed); + + function userExistSuccess(data, status) { + valid = !data; + if(!valid) { + console.log('Model value already exists'); + } + } + + function userExistFailed(data, status) { + console.log('Failed to in retrieval:' + data); + } + + return valid; + } + } + + } +})(); \ No newline at end of file diff --git a/static/resources/js/components/validator/validator.config.js b/static/resources/js/components/validator/validator.config.js new file mode 100644 index 000000000..e9fe1cfde --- /dev/null +++ b/static/resources/js/components/validator/validator.config.js @@ -0,0 +1,24 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.validator') + .constant('INVALID_CHARS', [",","~","#", "$", "%"]) + .constant('PASSWORD_REGEXP', /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?!.*\s).{7,20}$/) + .constant('PROJECT_REGEXP', /^[a-z0-9](?:-*[a-z0-9])*(?:[._][a-z0-9](?:-*[a-z0-9])*)*$/); +})(); \ No newline at end of file diff --git a/static/resources/js/components/validator/validator.module.js b/static/resources/js/components/validator/validator.module.js new file mode 100644 index 000000000..06988ab57 --- /dev/null +++ b/static/resources/js/components/validator/validator.module.js @@ -0,0 +1,24 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.validator', [ + 'harbor.services.user' + ]); + +})(); \ No newline at end of file diff --git a/static/resources/js/forgot-password.js b/static/resources/js/forgot-password.js deleted file mode 100644 index d6fdea319..000000000 --- a/static/resources/js/forgot-password.js +++ /dev/null @@ -1,84 +0,0 @@ -/* - 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. -*/ -jQuery(function(){ - - $("#divErrMsg").css({"display": "none"}); - - validateOptions.Items = ["#EmailF"]; - function bindEnterKey(){ - $(document).on("keydown", function(e){ - if(e.keyCode == 13){ - e.preventDefault(); - if($("#txtCommonSearch").is(":focus")){ - document.location = "/search?q=" + $("#txtCommonSearch").val(); - }else{ - $("#btnSubmit").trigger("click"); - } - } - }); - } - function unbindEnterKey(){ - $(document).off("keydown"); - } - bindEnterKey(); - var spinner = new Spinner({scale:1}).spin(); - - $("#btnSubmit").on("click", function(){ - validateOptions.Validate(function(){ - var username = $("#UsernameF").val(); - var email = $("#EmailF").val(); - $.ajax({ - "url":"/sendEmail", - "type": "get", - "data": {"username": username, "email": email}, - "beforeSend": function(e){ - unbindEnterKey(); - $("h1").append(spinner.el); - $("#btnSubmit").prop("disabled", true); - }, - "success": function(data, status, xhr){ - if(xhr && xhr.status == 200){ - $("#dlgModal") - .dialogModal({ - "title": i18n.getMessage("title_forgot_password"), - "content": i18n.getMessage("email_has_been_sent"), - "callback": function(){ - document.location="/"; - } - }); - } - - }, - "complete": function(){ - spinner.stop(); - $("#btnSubmit").prop("disabled", false); - }, - "error": function(jqXhr, status, error){ - if(jqXhr){ - $("#dlgModal") - .dialogModal({ - "title": i18n.getMessage("title_forgot_password"), - "content": i18n.getMessage(jqXhr.responseText), - "callback": function(){ - bindEnterKey(); - return; - } - }); - } - } - }); - }); - }); -}); \ No newline at end of file diff --git a/static/resources/js/harbor.config.js b/static/resources/js/harbor.config.js new file mode 100644 index 000000000..b67001fac --- /dev/null +++ b/static/resources/js/harbor.config.js @@ -0,0 +1,120 @@ +/* + 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. +*/ +(function() { + 'use strict'; + angular + .module('harbor.app') + .config(function($interpolateProvider){ + $interpolateProvider.startSymbol('//'); + $interpolateProvider.endSymbol('//'); + }) + .config(function($httpProvider) { + $httpProvider.defaults.headers.common = {'Accept': 'application/json, text/javascript, */*; q=0.01'}; + $httpProvider.interceptors.push('redirectInterceptor'); + }) + .service('redirectInterceptor', RedirectInterceptorService) + .factory('getParameterByName', getParameterByName) + .filter('dateL', localizeDate) + .filter('tr', tr); + + RedirectInterceptorService.$inject = ['$q', '$window', '$location']; + + function RedirectInterceptorService($q, $window, $location) { + return { + 'responseError': function(rejection) { + var url = rejection.config.url; + console.log('url:' + url); + var exclusion = [ + '/', + '/search', + '/reset_password', + '/sign_up', + '/forgot_password', + '/api/targets/ping', + '/api/users/current', + '/api/repositories', + /^\/api\/projects\/[0-9]+\/members\/current$/ + ]; + var isExcluded = false; + for(var i in exclusion) { + switch(typeof(exclusion[i])) { + case 'string': + isExcluded = (exclusion[i] === url); + break; + case 'object': + isExcluded = exclusion[i].test(url); + break; + } + if(isExcluded) { + break; + } + } + if(!isExcluded && rejection.status === 401) { + $window.location.href = '/?last_url=' + encodeURIComponent(location.pathname + '#' + $location.url()); + return; + } + return $q.reject(rejection); + } + }; + } + + function getParameterByName() { + return get; + function get(name, url) { + name = name.replace(/[\[\]]/g, "\\$&"); + var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), + results = regex.exec(url); + if (!results) { + return null; + } + + if (!results[2]) { + return ''; + } + + return decodeURIComponent(results[2].replace(/\+/g, " ")); + } + } + + function localizeDate() { + return filter; + + function filter(input, pattern) { + var d = new Date(input || ''); + if(d.getTime() <= 0) return '-'; + return moment(d).format(pattern); + } + } + + tr.$inject = ['I18nService']; + + function tr(I18nService) { + return tr; + function tr(label, params) { + var currentLanguage = I18nService().getCurrentLanguage(); + var result = ''; + if(label && label.length > 0){ + result = I18nService().getValue(label, currentLanguage); + } + if(angular.isArray(params)) { + angular.forEach(params, function(value, index) { + result = result.replace('$' + index, params[index]); + }); + } + return result; + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/harbor.constants.js b/static/resources/js/harbor.constants.js new file mode 100644 index 000000000..2e5449842 --- /dev/null +++ b/static/resources/js/harbor.constants.js @@ -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. +*/ +(function() { + 'use strict'; + + angular + .module('harbor.app'); + +})(); \ No newline at end of file diff --git a/static/resources/js/harbor.data.js b/static/resources/js/harbor.data.js new file mode 100644 index 000000000..8dc1744ae --- /dev/null +++ b/static/resources/js/harbor.data.js @@ -0,0 +1,56 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.app') + .factory('currentUser', currentUser) + .factory('currentProjectMember', currentProjectMember); + + currentUser.$inject = ['$cookies', '$timeout']; + + function currentUser($cookies, $timeout) { + return { + set: function(user) { + $cookies.putObject('user', user, {'path': '/'}); + }, + get: function() { + return $cookies.getObject('user'); + }, + unset: function() { + $cookies.remove('user', {'path': '/'}); + } + }; + } + + currentProjectMember.$inject = ['$cookies']; + + function currentProjectMember($cookies) { + return { + set: function(member) { + $cookies.putObject('member', member, {'path': '/'}); + }, + get: function() { + return $cookies.getObject('member'); + }, + unset: function() { + $cookies.remove('member', {'path': '/'}); + } + }; + } + +})(); \ No newline at end of file diff --git a/static/resources/js/harbor.initialize.js b/static/resources/js/harbor.initialize.js new file mode 100644 index 000000000..c267bd383 --- /dev/null +++ b/static/resources/js/harbor.initialize.js @@ -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. +*/ +(function() { + 'use strict'; + + angular + .module('harbor.app'); + +})(); \ No newline at end of file diff --git a/static/resources/js/harbor.module.js b/static/resources/js/harbor.module.js new file mode 100644 index 000000000..500ae0f58 --- /dev/null +++ b/static/resources/js/harbor.module.js @@ -0,0 +1,65 @@ +/* + 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. +*/ +(function() { + 'use strict'; + angular + .module('harbor.app', [ + 'ngMessages', + 'ngCookies', + 'harbor.session', + 'harbor.layout.element.height', + 'harbor.layout.header', + 'harbor.layout.footer', + 'harbor.layout.navigation', + 'harbor.layout.sign.up', + 'harbor.layout.add.new', + 'harbor.layout.account.setting', + 'harbor.layout.change.password', + 'harbor.layout.forgot.password', + 'harbor.layout.reset.password', + 'harbor.layout.index', + 'harbor.layout.dashboard', + 'harbor.layout.project', + 'harbor.layout.admin.option', + 'harbor.layout.search', + 'harbor.services.i18n', + 'harbor.services.project', + 'harbor.services.user', + 'harbor.services.repository', + 'harbor.services.project.member', + 'harbor.services.replication.policy', + 'harbor.services.replication.job', + 'harbor.services.destination', + 'harbor.summary', + 'harbor.user.log', + 'harbor.top.repository', + 'harbor.optional.menu', + 'harbor.modal.dialog', + 'harbor.sign.in', + 'harbor.search', + 'harbor.project', + 'harbor.details', + 'harbor.repository', + 'harbor.project.member', + 'harbor.user', + 'harbor.log', + 'harbor.validator', + 'harbor.replication', + 'harbor.system.management', + 'harbor.loading.progress', + 'harbor.inline.help', + 'harbor.dismissable.alerts' + ]); +})(); diff --git a/static/resources/js/item-detail.js b/static/resources/js/item-detail.js deleted file mode 100644 index bfab81536..000000000 --- a/static/resources/js/item-detail.js +++ /dev/null @@ -1,473 +0,0 @@ -/* - 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. -*/ -jQuery(function(){ - - $.when( - new AjaxUtil({ - url: "/api/users/current", - type: "get", - error: function(jqXhr){ - if(jqXhr){ - if(jqXhr.status == 403){ - return false; - } - } - } - }).exec() - ).then(function(){ - noNeedToLoginCallback(); - needToLoginCallback(); - }).fail(function(){ - noNeedToLoginCallback(); - }); - - function noNeedToLoginCallback(){ - - $("#tabItemDetail a:first").tab("show"); - $("#btnFilterOption button:first").addClass("active"); - $("#divErrMsg").hide(); - - if($("#public").val() == 1){ - $("#tabItemDetail li:eq(1)").hide(); - $("#tabItemDetail li:eq(2)").hide(); - } - - listRepo($("#repoName").val()); - - function listRepo(repoName){ - - $("#divErrMsg").hide(); - - new AjaxUtil({ - url: "/api/repositories?project_id=" + $("#projectId").val() + "&q=" + repoName, - type: "get", - success: function(data, status, xhr){ - if(xhr && xhr.status == 200){ - $("#accordionRepo").children().remove(); - if(data == null){ - $("#divErrMsg").show(); - $("#divErrMsg center").html(i18n.getMessage("no_repo_exists")); - return; - } - $.each(data, function(i, e){ - var targetId = e.replace(/\//g, "------").replace(/\./g, "---"); - var row = '
' + - '' + - '
' + - '
' + - '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
' + i18n.getMessage("tag")+ ' ' + i18n.getMessage("pull_command") + '
' - '
' + - '
' + - '
' + - '
'; - $("#accordionRepo").append(row); - }); - if(repoName != ""){ - $("#txtRepoName").val(repoName); - $("#accordionRepo #heading0 a").trigger("click"); - } - } - } - }).exec(); - } - $("#btnSearchRepo").on("click", function(){ - listRepo($.trim($("#txtRepoName").val())); - }); - - $('#accordionRepo').on('show.bs.collapse', function (e) { - $('#accordionRepo .in').collapse('hide'); - var targetId = $(e.target).attr("targetId"); - var repoName = targetId.replace(/[-]{6}/g, "/").replace(/[-]{3}/g, "."); - new AjaxUtil({ - url: "/api/repositories/tags?repo_name=" + repoName, - type: "get", - success: function(data, status, xhr){ - $('#' + targetId +' table tbody tr').remove(); - var row = []; - for(var i in data){ - var tagName = data[i] - row.push('' + tagName + ''); - } - $('#' + targetId +' table tbody').append(row.join("")); - $('#' + targetId +' table tbody tr a').on("click", function(e){ - var imageId = $(this).attr("imageId"); - var repoName = $(this).attr("repoName"); - new AjaxUtil({ - url: "/api/repositories/manifests?repo_name=" + repoName + "&tag=" + imageId, - type: "get", - success: function(data, status, xhr){ - if(data){ - for(var i in data){ - if(data[i] == ""){ - data[i] = "N/A"; - } - } - data.Created = moment(new Date(data.Created)).format("YYYY-MM-DD HH:mm:ss"); - $("#dlgModal").dialogModal({"title": i18n.getMessage("image_details"), "content": data}); - } - } - }).exec(); - }); - } - }).exec(); - }); - } - - function needToLoginCallback(){ - - var hasAuthorization = false; - - $.when( - new AjaxUtil({ - url: "/api/projects/" + $("#projectId").val() + "/members/current", - type: "get", - success: function(data, status, xhr){ - if(xhr && xhr.status == 200 && data.roles != null && data.roles.length > 0){ - hasAuthorization = true; - } - } - }).exec()) - .done(function(){ - - if(!hasAuthorization) return false; - - $("#tabItemDetail a:eq(1)").css({"visibility": "visible"}); - $("#tabItemDetail a:eq(2)").css({"visibility": "visible"}); - - $(".glyphicon .glyphicon-pencil", "#tblUser").on("click", function(e){ - $("#txtUserName").hide(); - $("#lblUserName").show(); - $("#dlgUserTitle").text(i18n.getMessage("edit_members")); - }); - - $("#btnAddUser").on("click", function(){ - $("#operationType").val("add"); - $("#spnSearch").show(); - $("#txtUserName").prop("disabled", false) - $("#txtUserName").val(""); - $("#lstRole input[name=chooseRole]:radio").prop("checked", false); - $("#dlgUserTitle").text(i18n.getMessage("add_members")); - }); - - $("#btnSave").on("click", function(){ - - var username = $("#txtUserName").val(); - if($.trim(username).length == 0){ - $("#dlgModal").dialogModal({"title": i18n.getMessage("add_member_failed"), "content": i18n.getMessage("please_input_username")}); - return; - } - var projectId = $("#projectId").val(); - var operationType = $("#operationType").val(); - var userId = $("#editUserId").val(); - - var checkedRole = $("#lstRole input[name='chooseRole']:checked") - if(checkedRole.length == 0){ - $("#dlgModal").dialogModal({"title": i18n.getMessage("add_member_failed"), "content": i18n.getMessage("please_assign_a_role_to_user")}); - return; - } - - var checkedRoleItemList = []; - $.each(checkedRole, function(i, e){ - checkedRoleItemList.push(new Number($(this).val())); - }); - - var ajaxOpts = {}; - if(operationType == "add"){ - ajaxOpts.url = "/api/projects/" + projectId + "/members/"; - ajaxOpts.type = "post"; - ajaxOpts.data = {"roles" : checkedRoleItemList, "username": username}; - }else if(operationType == "edit"){ - ajaxOpts.url = "/api/projects/" + projectId + "/members/" + userId; - ajaxOpts.type = "put"; - ajaxOpts.data = {"roles" : checkedRoleItemList}; - } - - new AjaxUtil({ - url: ajaxOpts.url, - data: ajaxOpts.data, - type: ajaxOpts.type, - complete: function(jqXhr, status){ - if(jqXhr && jqXhr.status == 200){ - $("#btnCancel").trigger("click"); - listUser(null); - } - }, - errors: { - 404: i18n.getMessage("user_id_does_not_exist"), - 409: i18n.getMessage("user_id_exists"), - 403: i18n.getMessage("insufficient_privileges") - } - }).exec(); - }); - - var name_mapping = { - "projectAdmin": "Project Admin", - "developer": "Developer", - "guest": "Guest" - } - - function listUserByProjectCallback(userList){ - var loginedUserId = $("#userId").val(); - var loginedUserRoleId = $("#roleId").val(); - var ownerId = $("#ownerId").val(); - - $("#tblUser tbody tr").remove(); - for(var i = 0; i < userList.length; i++){ - - var userId = userList[i].user_id; - var roleId = userList[i].role_id; - var username = userList[i].username; - - var row = '' + - '' + username + '' + - '' + name_mapping[userList[i].role_name] + '' + - ''; - var isShowOperations = true; - if(loginedUserRoleId >= 3 /*role: developer guest*/){ - isShowOperations = false; - }else if(ownerId == userId){ - isShowOperations = false; - }else if (loginedUserId == userId){ - isShowOperations = false; - } - if(isShowOperations){ - row += ' ' + - ''; - } - - row += ''; - $("#tblUser tbody").append(row); - - } - } - - function searchAccessLogCallback(LogList){ - $("#tabOperationLog tbody tr").remove(); - $.each(LogList || [], function(i, e){ - $("#tabOperationLog tbody").append( - '' + - '' + e.username + '' + - '' + e.repo_name + '' + - '' + e.repo_tag + '' + - '' + e.operation + '' + - '' + moment(new Date(e.op_time)).format("YYYY-MM-DD HH:mm:ss") + '' + - ''); - }); - } - - function getUserRoleCallback(userId){ - new AjaxUtil({ - url: "/api/projects/" + $("#projectId").val() + "/members/" + userId, - type: "get", - success: function(data, status, xhr){ - var user = data; - $("#operationType").val("edit"); - $("#editUserId").val(user.user_id); - $("#spnSearch").hide(); - $("#txtUserName").val(user.username); - $("#txtUserName").prop("disabled", true); - $("#btnSave").removeClass("disabled"); - $("#dlgUserTitle").text(i18n.getMessage("edit_members")); - $("#lstRole input[name=chooseRole]:radio").not('[value=' + user.role_id + ']').prop("checked", false) - $.each(user.roles, function(i, e){ - $("#lstRole input[name=chooseRole]:radio").filter('[value=' + e.role_id + ']').prop("checked", "checked"); - }); - } - }).exec(); - } - function listUser(username){ - $.when( - new AjaxUtil({ - url: "/api/projects/" + $("#projectId").val() + "/members?username=" + (username == null ? "" : username), - type: "get", - errors: { - 403: "" - }, - success: function(data, status, xhr){ - return data || []; - } - }).exec() - ).done(function(userList){ - listUserByProjectCallback(userList || []); - $("#tblUser .glyphicon-pencil").on("click", function(e){ - var userId = $(this).attr("userid") - getUserRoleCallback(userId); - }); - $("#tblUser .glyphicon-trash").on("click", function(){ - var userId = $(this).attr("userid"); - new AjaxUtil({ - url: "/api/projects/" + $("#projectId").val() + "/members/" + userId, - type: "delete", - complete: function(jqXhr, status){ - if(jqXhr && jqXhr.status == 200){ - listUser(null); - } - } - }).exec(); - }); - }); - } - listUser(null); - listOperationLogs(); - - function listOperationLogs(){ - var projectId = $("#projectId").val(); - - $.when( - new AjaxUtil({ - url : "/api/projects/" + projectId + "/logs/filter", - data: {}, - type: "post", - success: function(data){ - return data || []; - } - }).exec() - ).done(function(operationLogs){ - searchAccessLogCallback(operationLogs); - }); - } - - $("#btnSearchUser").on("click", function(){ - var username = $("#txtSearchUser").val(); - if($.trim(username).length == 0){ - username = null; - } - listUser(username); - }); - - function toUTCSeconds(date, hour, min, sec) { - var t = new Date(date); - t.setHours(hour); - t.setMinutes(min); - t.setSeconds(sec); - var utcTime = new Date(t.getUTCFullYear(), - t.getUTCMonth(), - t.getUTCDate(), - t.getUTCHours(), - t.getUTCMinutes(), - t.getUTCSeconds()); - return utcTime.getTime() / 1000; - } - - $("#btnFilterLog").on("click", function(){ - - var projectId = $("#projectId").val(); - var username = $("#txtSearchUserName").val(); - - var beginTimestamp = 0; - var endTimestamp = 0; - - if($("#begindatepicker").val() != ""){ - beginTimestamp = toUTCSeconds($("#begindatepicker").val(), 0, 0, 0); - } - if($("#enddatepicker").val() != ""){ - endTimestamp = toUTCSeconds($("#enddatepicker").val(), 23, 59, 59); - } - - new AjaxUtil({ - url: "/api/projects/" + projectId + "/logs/filter", - data:{"username":username, "project_id" : Number(projectId), "keywords" : getKeyWords() , "begin_timestamp" : beginTimestamp, "end_timestamp" : endTimestamp}, - type: "post", - success: function(data, status, xhr){ - if(xhr && xhr.status == 200){ - searchAccessLogCallback(data); - } - } - }).exec(); - }); - - $("#spnFilterOption input[name=chkAll]").on("click", function(){ - $("#spnFilterOption input[name=chkOperation]").prop("checked", $(this).prop("checked")); - }); - - $("#spnFilterOption input[name=chkOperation]").on("click", function(){ - if(!$(this).prop("checked")){ - $("#spnFilterOption input[name=chkAll]").prop("checked", false); - } - - var selectedAll = true; - - $("#spnFilterOption input[name=chkOperation]").each(function(i, e){ - if(!$(e).prop("checked")){ - selectedAll = false; - } - }); - - if(selectedAll){ - $("#spnFilterOption input[name=chkAll]").prop("checked", true); - } - }); - - function getKeyWords(){ - var keywords = ""; - var checkedItemList=$("#spnFilterOption input[name=chkOperation]:checked"); - var keywords = []; - $.each(checkedItemList, function(i, e){ - var itemValue = $(e).val(); - if(itemValue == "others" && $.trim($("#txtOthers").val()).length > 0){ - keywords.push($("#txtOthers").val()); - }else{ - keywords.push($(e).val()); - } - }); - return keywords.join("/"); - } - - $('#datetimepicker1').datetimepicker({ - locale: i18n.getLocale(), - ignoreReadonly: true, - format: 'L', - showClear: true - }); - $('#datetimepicker2').datetimepicker({ - locale: i18n.getLocale(), - ignoreReadonly: true, - format: 'L', - showClear: true - }); - }); -} - -$(document).on("keydown", function(e){ - if(e.keyCode == 13){ - e.preventDefault(); - if($("#tabItemDetail li:eq(0)").is(":focus") || $("#txtRepoName").is(":focus")){ - $("#btnSearchRepo").trigger("click"); - }else if($("#tabItemDetail li:eq(1)").is(":focus") || $("#txtSearchUser").is(":focus")){ - $("#btnSearchUser").trigger("click"); - }else if($("#tabItemDetail li:eq(2)").is(":focus") || $("#txtSearchUserName").is(":focus")){ - $("#btnFilterLog").trigger("click"); - }else if($("#txtUserName").is(":focus") || $("#lstRole :radio").is(":focus")){ - $("#btnSave").trigger("click"); - } - } -}); -}) \ No newline at end of file diff --git a/static/resources/js/layout/account-setting/account-setting.controller.js b/static/resources/js/layout/account-setting/account-setting.controller.js new file mode 100644 index 000000000..764117348 --- /dev/null +++ b/static/resources/js/layout/account-setting/account-setting.controller.js @@ -0,0 +1,112 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.account.setting') + .controller('AccountSettingController', AccountSettingController); + + AccountSettingController.$inject = ['ChangePasswordService', 'UpdateUserService', '$filter', 'trFilter', '$scope', '$window', 'currentUser']; + + function AccountSettingController(ChangePasswordService, UpdateUserService, $filter, trFilter, $scope, $window, currentUser) { + + var vm = this; + vm.isOpen = false; + + vm.hasError = false; + vm.errorMessage = ''; + + vm.reset = reset; + vm.confirm = confirm; + vm.updateUser = updateUser; + vm.cancel = cancel; + + $scope.user = currentUser.get(); + if(!$scope.user) { + $window.location.href = '/'; + return; + } + var userId = $scope.user.user_id; + + //Error message dialog handler for account setting. + $scope.$on('modalTitle', function(e, val) { + vm.modalTitle = val; + }); + + $scope.$on('modalMessage', function(e, val) { + vm.modalMessage = val; + }); + + $scope.$on('raiseError', function(e, val) { + if(val) { + vm.action = function() { + $scope.$broadcast('showDialog', false); + }; + vm.contentType = 'text/plain'; + vm.confirmOnly = true; + $scope.$broadcast('showDialog', true); + } + }); + + function reset() { + $scope.form.$setUntouched(); + $scope.form.$setPristine(); + vm.hasError = false; + vm.errorMessage = ''; + } + + function confirm() { + $window.location.href = '/dashboard'; + } + + function updateUser(user) { + vm.confirmOnly = true; + vm.action = vm.confirm; + if(user && angular.isDefined(user.username) && angular.isDefined(user.realname)) { + UpdateUserService(userId, user) + .success(updateUserSuccess) + .error(updateUserFailed); + currentUser.set(user); + } + } + + function updateUserSuccess(data, status) { + vm.modalTitle = $filter('tr')('change_profile', []); + vm.modalMessage = $filter('tr')('successful_changed_profile', []); + $scope.$broadcast('showDialog', true); + } + + function updateUserFailed(data, status) { + $scope.$emit('modalTitle', $filter('tr')('error')); + var message; + if(status === 409) { + message = $filter('tr')('email_has_been_taken'); + }else{ + message = $filter('tr')('failed_to_update_user') + data; + } + $scope.$emit('modalMessage', message); + $scope.$emit('raiseError', true); + console.log('Failed to update user.'); + } + + function cancel(form) { + $window.location.href = '/dashboard'; + } + + } + +})(); diff --git a/static/resources/js/layout/account-setting/account-setting.module.js b/static/resources/js/layout/account-setting/account-setting.module.js new file mode 100644 index 000000000..3b0d9df8a --- /dev/null +++ b/static/resources/js/layout/account-setting/account-setting.module.js @@ -0,0 +1,23 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.account.setting', [ + 'harbor.services.user']); + +})(); \ No newline at end of file diff --git a/static/resources/js/layout/add-new/add-new.controller.js b/static/resources/js/layout/add-new/add-new.controller.js new file mode 100644 index 000000000..ce9c8ccd4 --- /dev/null +++ b/static/resources/js/layout/add-new/add-new.controller.js @@ -0,0 +1,29 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.add.new') + .controller('AddNewController', AddNewController); + + AddNewController.$inject = []; + + function AddNewController() { + var vm = this; + } + +})(); \ No newline at end of file diff --git a/static/resources/js/layout/add-new/add-new.module.js b/static/resources/js/layout/add-new/add-new.module.js new file mode 100644 index 000000000..05e34afac --- /dev/null +++ b/static/resources/js/layout/add-new/add-new.module.js @@ -0,0 +1,22 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.add.new', []); + +})(); \ No newline at end of file diff --git a/static/resources/js/layout/admin-option/admin-option.config.js b/static/resources/js/layout/admin-option/admin-option.config.js new file mode 100644 index 000000000..78e21a6b0 --- /dev/null +++ b/static/resources/js/layout/admin-option/admin-option.config.js @@ -0,0 +1,22 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.admin.option'); + +})(); \ No newline at end of file diff --git a/static/resources/js/layout/admin-option/admin-option.controller.js b/static/resources/js/layout/admin-option/admin-option.controller.js new file mode 100644 index 000000000..f383407c3 --- /dev/null +++ b/static/resources/js/layout/admin-option/admin-option.controller.js @@ -0,0 +1,94 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.admin.option') + .controller('AdminOptionController', AdminOptionController); + + AdminOptionController.$inject = ['$scope', '$timeout', '$location']; + + function AdminOptionController($scope, $timeout, $location) { + + $scope.subsSubPane = 296; + + var vm = this; + vm.toggle = false; + vm.target = 'users'; + vm.toggleAdminOption = toggleAdminOption; + + $scope.$on('$locationChangeSuccess', function(e) { + if($location.path() === '') { + vm.target = 'users'; + vm.toggle = false; + }else{ + vm.target = 'system_management'; + vm.toggle = true; + } + }); + + //Message dialog handler for admin-options. + $scope.$on('modalTitle', function(e, val) { + vm.modalTitle = val; + }); + + $scope.$on('modalMessage', function(e, val) { + vm.modalMessage = val; + }); + + $scope.$on('raiseError', function(e, val) { + if(val) { + vm.action = function() { + $scope.$broadcast('showDialog', false); + }; + vm.contentType = 'text/plain'; + vm.confirmOnly = true; + + $timeout(function() { + $scope.$broadcast('showDialog', true); + }, 350); + } + }); + + $scope.$on('raiseInfo', function(e, val) { + if(val) { + vm.action = function() { + val.action(); + $scope.$broadcast('showDialog', false); + } + vm.contentType = val.contentType; + vm.confirmOnly = val.confirmOnly; + + $scope.$broadcast('showDialog', true); + } + }); + + + function toggleAdminOption(e) { + switch(e.target) { + case 'users': + vm.toggle = false; + break; + case 'system_management': + vm.toggle = true; + break; + } + vm.target = e.target; + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/layout/admin-option/admin-option.module.js b/static/resources/js/layout/admin-option/admin-option.module.js new file mode 100644 index 000000000..7f486a965 --- /dev/null +++ b/static/resources/js/layout/admin-option/admin-option.module.js @@ -0,0 +1,22 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.admin.option', []); + +})(); \ No newline at end of file diff --git a/static/resources/js/layout/change-password/change-password.controller.js b/static/resources/js/layout/change-password/change-password.controller.js new file mode 100644 index 000000000..f3c4244af --- /dev/null +++ b/static/resources/js/layout/change-password/change-password.controller.js @@ -0,0 +1,114 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.change.password') + .controller('ChangePasswordController', ChangePasswordController); + + ChangePasswordController.$inject = ['ChangePasswordService', 'UpdateUserService', '$filter', 'trFilter', '$scope', '$window', 'currentUser']; + + function ChangePasswordController(ChangePasswordService, UpdateUserService, $filter, trFilter, $scope, $window, currentUser) { + + var vm = this; + vm.isOpen = false; + + vm.hasError = false; + vm.errorMessage = ''; + + vm.reset = reset; + + vm.confirm = confirm; + vm.updatePassword = updatePassword; + vm.cancel = cancel; + + $scope.user = currentUser.get(); + if(!$scope.user) { + $window.location.href = '/'; + return; + } + var userId = $scope.user.user_id; + + //Error message dialog handler for account setting. + $scope.$on('modalTitle', function(e, val) { + vm.modalTitle = val; + }); + + $scope.$on('modalMessage', function(e, val) { + vm.modalMessage = val; + }); + + $scope.$on('raiseError', function(e, val) { + if(val) { + vm.action = function() { + $scope.$broadcast('showDialog', false); + }; + vm.contentType = 'text/plain'; + vm.confirmOnly = true; + $scope.$broadcast('showDialog', true); + } + }); + + function reset() { + $scope.form.$setUntouched(); + $scope.form.$setPristine(); + vm.hasError = false; + vm.errorMessage = ''; + } + + function confirm() { + $window.location.href = '/dashboard'; + } + + function updatePassword(user) { + if(user && angular.isDefined(user.oldPassword) && angular.isDefined(user.password)) { + vm.action = vm.confirm; + ChangePasswordService(userId, user.oldPassword, user.password) + .success(changePasswordSuccess) + .error(changePasswordFailed); + } + + } + + function changePasswordSuccess(data, status) { + vm.modalTitle = $filter('tr')('change_password', []); + vm.modalMessage = $filter('tr')('successful_changed_password', []); + $scope.$broadcast('showDialog', true); + } + + function changePasswordFailed(data, status) { + + var message; + $scope.$emit('modalTitle', $filter('tr')('error')); + console.log('Failed to change password:' + data); + if(data == 'old_password_is_not_correct') { + message = $filter('tr')('old_password_is_incorrect'); + }else{ + message = $filter('tr')('failed_to_change_password'); + } + + $scope.$emit('modalMessage', message); + $scope.$emit('raiseError', true); + } + + function cancel(form) { + $window.location.href = '/dashboard'; + } + + } + +})(); diff --git a/static/resources/js/layout/change-password/change-password.module.js b/static/resources/js/layout/change-password/change-password.module.js new file mode 100644 index 000000000..ed7e81c6b --- /dev/null +++ b/static/resources/js/layout/change-password/change-password.module.js @@ -0,0 +1,23 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.change.password', [ + 'harbor.services.user']); + +})(); \ No newline at end of file diff --git a/static/resources/js/layout/dashboard/dashboard.controller.js b/static/resources/js/layout/dashboard/dashboard.controller.js new file mode 100644 index 000000000..bc5de1578 --- /dev/null +++ b/static/resources/js/layout/dashboard/dashboard.controller.js @@ -0,0 +1,50 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.dashboard') + .controller('DashboardController', DashboardController); + + DashboardController.$inject = ['$scope']; + + function DashboardController($scope) { + var vm = this; + vm.customBodyHeight = {'height': '165px'}; + + //Error message dialog handler for dashboard. + $scope.$on('modalTitle', function(e, val) { + vm.modalTitle = val; + }); + + $scope.$on('modalMessage', function(e, val) { + vm.modalMessage = val; + }); + + $scope.$on('raiseError', function(e, val) { + if(val) { + vm.action = function() { + $scope.$broadcast('showDialog', false); + }; + vm.contentType = 'text/plain'; + vm.confirmOnly = true; + $scope.$broadcast('showDialog', true); + } + }); + } + +})(); \ No newline at end of file diff --git a/static/resources/js/layout/dashboard/dashboard.module.js b/static/resources/js/layout/dashboard/dashboard.module.js new file mode 100644 index 000000000..f9f24f9bd --- /dev/null +++ b/static/resources/js/layout/dashboard/dashboard.module.js @@ -0,0 +1,24 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.dashboard', [ + 'harbor.services.repository' + ]); + +})(); \ No newline at end of file diff --git a/static/resources/js/layout/details/details.config.js b/static/resources/js/layout/details/details.config.js new file mode 100644 index 000000000..cfcacca29 --- /dev/null +++ b/static/resources/js/layout/details/details.config.js @@ -0,0 +1,46 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.details') + .filter('name', nameFilter); + + function nameFilter() { + + return filter; + + function filter(input, filterInput, key) { + input = input || []; + var filteredResults = []; + + if (filterInput !== '') { + for(var i = 0; i < input.length; i++) { + var item = input[i]; + if((key === "" && item.indexOf(filterInput) >= 0) || (key !== "" && item[key].indexOf(filterInput) >= 0)) { + filteredResults.push(item); + continue; + } + } + input = filteredResults; + } + return input; + } + } + + +})(); \ No newline at end of file diff --git a/static/resources/js/layout/details/details.controller.js b/static/resources/js/layout/details/details.controller.js new file mode 100644 index 000000000..c891c8e87 --- /dev/null +++ b/static/resources/js/layout/details/details.controller.js @@ -0,0 +1,76 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.details') + .controller('DetailsController', DetailsController); + + DetailsController.$inject = ['$scope', '$timeout']; + + function DetailsController($scope, $timeout) { + var vm = this; + + vm.publicity = false; + vm.isProjectMember = false; + + vm.togglePublicity = togglePublicity; + + vm.sectionDefaultHeight = {'min-height': '579px'}; + + //Message dialog handler for details. + $scope.$on('modalTitle', function(e, val) { + vm.modalTitle = val; + }); + + $scope.$on('modalMessage', function(e, val) { + vm.modalMessage = val; + }); + + $scope.$on('raiseError', function(e, val) { + if(val) { + vm.action = function() { + $scope.$broadcast('showDialog', false); + }; + vm.contentType = 'text/plain'; + vm.confirmOnly = true; + + $timeout(function() { + $scope.$broadcast('showDialog', true); + }, 350); + } + }); + + $scope.$on('raiseInfo', function(e, val) { + if(val) { + vm.action = function() { + val.action(); + $scope.$broadcast('showDialog', false); + } + vm.contentType = val.contentType; + vm.confirmOnly = val.confirmOnly; + + $scope.$broadcast('showDialog', true); + } + }); + + function togglePublicity(e) { + vm.publicity = e.publicity; + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/layout/details/details.module.js b/static/resources/js/layout/details/details.module.js new file mode 100644 index 000000000..e60cd842e --- /dev/null +++ b/static/resources/js/layout/details/details.module.js @@ -0,0 +1,25 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.details', [ + 'harbor.services.project', + 'harbor.services.project.member' + ]); + +})(); \ No newline at end of file diff --git a/static/resources/js/layout/footer/footer.controller.js b/static/resources/js/layout/footer/footer.controller.js new file mode 100644 index 000000000..5053c544f --- /dev/null +++ b/static/resources/js/layout/footer/footer.controller.js @@ -0,0 +1,27 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.footer') + .controller('FooterController', FooterController); + + function FooterController() { + var vm = this; + } + +})(); \ No newline at end of file diff --git a/static/resources/js/layout/footer/footer.module.js b/static/resources/js/layout/footer/footer.module.js new file mode 100644 index 000000000..ccdc389d4 --- /dev/null +++ b/static/resources/js/layout/footer/footer.module.js @@ -0,0 +1,22 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.footer', []); + +})(); \ No newline at end of file diff --git a/static/resources/js/layout/forgot-password/forgot-password.controller.js b/static/resources/js/layout/forgot-password/forgot-password.controller.js new file mode 100644 index 000000000..798fe7e02 --- /dev/null +++ b/static/resources/js/layout/forgot-password/forgot-password.controller.js @@ -0,0 +1,103 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.forgot.password') + .controller('ForgotPasswordController', ForgotPasswordController); + + ForgotPasswordController.$inject = ['SendMailService', '$window', '$scope', '$filter', 'trFilter']; + + function ForgotPasswordController(SendMailService, $window, $scope, $filter, trFilter) { + var vm = this; + + vm.hasError = false; + vm.show = false; + vm.errorMessage = ''; + + vm.reset = reset; + vm.sendMail = sendMail; + + vm.confirm = confirm; + vm.toggleInProgress = false; + + //Error message dialog handler for forgotting password. + $scope.$on('modalTitle', function(e, val) { + vm.modalTitle = val; + }); + + $scope.$on('modalMessage', function(e, val) { + vm.modalMessage = val; + }); + + $scope.$on('raiseError', function(e, val) { + if(val) { + vm.action = function() { + $scope.$broadcast('showDialog', false); + }; + vm.contentType = 'text/plain'; + vm.confirmOnly = true; + $scope.$broadcast('showDialog', true); + } + }); + + function reset(){ + vm.hasError = false; + vm.errorMessage = ''; + } + + function sendMail(user) { + if(user && angular.isDefined(user.email)) { + + vm.action = vm.confirm; + + vm.toggleInProgress = true; + SendMailService(user.email) + .success(sendMailSuccess) + .error(sendMailFailed); + } + } + + function sendMailSuccess(data, status) { + vm.toggleInProgress = false; + vm.modalTitle = $filter('tr')('forgot_password'); + vm.modalMessage = $filter('tr')('mail_has_been_sent'); + vm.confirmOnly = true; + $scope.$broadcast('showDialog', true); + } + + function sendMailFailed(data, status) { + vm.toggleInProgress = false; + vm.hasError = true; + vm.errorMessage = data; + + if(status === 500) { + $scope.$emit('modalTitle', $filter('tr')('error')); + $scope.$emit('modalMessage', $filter('tr')('failed_to_send_email')); + $scope.$emit('raiseError', true); + } + console.log('Failed to send mail:' + data); + } + + function confirm() { + $window.location.href = '/'; + } + + + } + +})(); \ No newline at end of file diff --git a/static/resources/js/layout/forgot-password/forgot-password.module.js b/static/resources/js/layout/forgot-password/forgot-password.module.js new file mode 100644 index 000000000..7c5ae0dc0 --- /dev/null +++ b/static/resources/js/layout/forgot-password/forgot-password.module.js @@ -0,0 +1,24 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.forgot.password', [ + 'harbor.services.user' + ]); + +})(); \ No newline at end of file diff --git a/static/resources/js/layout/header/header.controller.js b/static/resources/js/layout/header/header.controller.js new file mode 100644 index 000000000..869ab9d09 --- /dev/null +++ b/static/resources/js/layout/header/header.controller.js @@ -0,0 +1,47 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.header') + .controller('HeaderController', HeaderController); + + HeaderController.$inject = ['$scope', '$window', 'getParameterByName', '$location', 'currentUser']; + + function HeaderController($scope, $window, getParameterByName, $location, currentUser) { + var vm = this; + vm.user = currentUser.get(); + + if(location.pathname === '/dashboard') { + vm.defaultUrl = '/dashboard'; + }else{ + vm.defaultUrl = '/'; + } + + $scope.$watch('vm.user', function(current) { + if(current) { + vm.defaultUrl = '/dashboard'; + } + }); + + if($window.location.search) { + vm.searchInput = getParameterByName('q', $window.location.search); + console.log('vm.searchInput at header:' + vm.searchInput); + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/layout/header/header.module.js b/static/resources/js/layout/header/header.module.js new file mode 100644 index 000000000..b9c84401d --- /dev/null +++ b/static/resources/js/layout/header/header.module.js @@ -0,0 +1,24 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.header', [ + 'harbor.services.user', + 'harbor.services.i18n' + ]); +})(); \ No newline at end of file diff --git a/static/resources/js/layout/index/index.controller.js b/static/resources/js/layout/index/index.controller.js new file mode 100644 index 000000000..a0dc20eb5 --- /dev/null +++ b/static/resources/js/layout/index/index.controller.js @@ -0,0 +1,105 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.index') + .controller('IndexController', IndexController); + + IndexController.$inject = ['$scope', '$filter', 'trFilter', '$timeout']; + + function IndexController($scope, $filter, trFilter, $timeout) { + + $scope.subsHeight = 110; + $scope.subsSection = 32; + $scope.subsSubPane = 226; + + var vm = this; + + vm.customBodyHeight = {'height': '180px'}; + vm.viewAll = viewAll; + + function viewAll() { + var indexDesc = $filter('tr')('index_desc', []); + var indexDesc1 = $filter('tr')('index_desc_1', []); + var indexDesc2 = $filter('tr')('index_desc_2', []); + var indexDesc3 = $filter('tr')('index_desc_3', []); + var indexDesc4 = $filter('tr')('index_desc_4', []); + var indexDesc5 = $filter('tr')('index_desc_5', []); + var indexDesc6 = $filter('tr')('index_desc_6', []); + + $scope.$emit('modalTitle', $filter('tr')('harbor_intro_title')); + $scope.$emit('modalMessage', '

'+ + indexDesc + + '

' + + ''); + var emitInfo = { + 'contentType': 'text/html', + 'confirmOnly': true, + 'action': function() { + $scope.$broadcast('showDialog', false); + } + }; + $scope.$emit('raiseInfo', emitInfo); + } + + //Message dialog handler for index. + $scope.$on('modalTitle', function(e, val) { + vm.modalTitle = val; + }); + + $scope.$on('modalMessage', function(e, val) { + vm.modalMessage = val; + }); + + $scope.$on('raiseError', function(e, val) { + if(val) { + vm.action = function() { + $scope.$broadcast('showDialog', false); + }; + vm.contentType = 'text/plain'; + vm.confirmOnly = true; + + $timeout(function() { + $scope.$broadcast('showDialog', true); + }, 350); + } + }); + + $scope.$on('raiseInfo', function(e, val) { + if(val) { + vm.action = function() { + val.action(); + $scope.$broadcast('showDialog', false); + } + vm.contentType = val.contentType; + vm.confirmOnly = val.confirmOnly; + + $scope.$broadcast('showDialog', true); + } + }); + + } + +})(); \ No newline at end of file diff --git a/static/resources/js/layout/index/index.module.js b/static/resources/js/layout/index/index.module.js new file mode 100644 index 000000000..167a920fb --- /dev/null +++ b/static/resources/js/layout/index/index.module.js @@ -0,0 +1,22 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.index', []); + +})(); \ No newline at end of file diff --git a/static/resources/js/layout/navigation/navigation-admin-options.directive.html b/static/resources/js/layout/navigation/navigation-admin-options.directive.html new file mode 100644 index 000000000..ecbd0fafe --- /dev/null +++ b/static/resources/js/layout/navigation/navigation-admin-options.directive.html @@ -0,0 +1,21 @@ + + \ No newline at end of file diff --git a/static/resources/js/layout/navigation/navigation-admin-options.directive.js b/static/resources/js/layout/navigation/navigation-admin-options.directive.js new file mode 100644 index 000000000..89de7e4a4 --- /dev/null +++ b/static/resources/js/layout/navigation/navigation-admin-options.directive.js @@ -0,0 +1,65 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.navigation') + .directive('navigationAdminOptions', navigationAdminOptions); + + NavigationAdminOptions.$inject = ['$location']; + + function NavigationAdminOptions($location) { + var vm = this; + vm.path = $location.path(); + } + + function navigationAdminOptions() { + var directive = { + 'restrict': 'E', + 'templateUrl': '/static/resources/js/layout/navigation/navigation-admin-options.directive.html', + 'scope': { + 'target': '=' + }, + 'link': link, + 'controller': NavigationAdminOptions, + 'controllerAs': 'vm', + 'bindToController': true + }; + return directive; + + function link(scope, element, attrs, ctrl) { + var visited = ctrl.path.substring(1); + console.log('visited:' + visited); + + if(visited) { + element.find('a[tag="' + visited + '"]').addClass('active'); + }else{ + element.find('a:first').addClass('active'); + } + + element.find('a').on('click', click); + + function click(event) { + element.find('a').removeClass('active'); + $(event.target).addClass('active'); + ctrl.target = $(this).attr('tag'); + scope.$apply(); + } + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/layout/navigation/navigation-details.directive.js b/static/resources/js/layout/navigation/navigation-details.directive.js new file mode 100644 index 000000000..bf9b027c7 --- /dev/null +++ b/static/resources/js/layout/navigation/navigation-details.directive.js @@ -0,0 +1,80 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.navigation') + .directive('navigationDetails', navigationDetails); + + NavigationDetailsController.$inject = ['$window', '$location', '$scope', 'getParameterByName']; + + function NavigationDetailsController($window, $location, $scope, getParameterByName) { + var vm = this; + + vm.projectId = getParameterByName('project_id', $location.absUrl()); + + $scope.$on('$locationChangeSuccess', function() { + vm.projectId = getParameterByName('project_id', $location.absUrl()); + }); + + vm.path = $location.path(); + } + + function navigationDetails() { + var directive = { + restrict: 'E', + templateUrl: '/navigation_detail?timestamp=' + new Date().getTime(), + link: link, + scope: { + 'target': '=' + }, + replace: true, + controller: NavigationDetailsController, + controllerAs: 'vm', + bindToController: true + }; + + return directive; + + function link(scope, element, attrs, ctrl) { + + var visited = ctrl.path.substring(1); + if(visited.indexOf('?') >= 0) { + visited = ctrl.url.substring(1, ctrl.url.indexOf('?')); + } + + if(visited) { + element.find('a[tag="' + visited + '"]').addClass('active'); + }else{ + element.find('a:first').addClass('active'); + } + + ctrl.target = visited; + element.find('a').on('click', click); + + function click(event) { + element.find('a').removeClass('active'); + $(event.target).addClass('active'); + ctrl.target = $(this).attr('tag'); + scope.$apply(); + } + + } + + } + +})(); \ No newline at end of file diff --git a/static/resources/js/layout/navigation/navigation-header.directive.js b/static/resources/js/layout/navigation/navigation-header.directive.js new file mode 100644 index 000000000..d1655f909 --- /dev/null +++ b/static/resources/js/layout/navigation/navigation-header.directive.js @@ -0,0 +1,58 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.navigation') + .directive('navigationHeader', navigationHeader); + + NavigationHeaderController.$inject = ['$window', '$scope', 'currentUser', '$timeout']; + + function NavigationHeaderController($window, $scope, currentUser, $timeout) { + var vm = this; + vm.url = $window.location.pathname; + } + + function navigationHeader() { + var directive = { + restrict: 'E', + templateUrl: '/navigation_header?timestamp=' + new Date().getTime(), + link: link, + scope: true, + controller: NavigationHeaderController, + controllerAs: 'vm', + bindToController: true + }; + + return directive; + + function link(scope, element, attrs, ctrl) { + var visited = ctrl.url; + console.log('visited:' + visited); + if (visited !== '' && visited !== '/') { + element.find('a[href*="' + visited + '"]').addClass('active'); + } + element.find('a').on('click', click); + function click(event) { + element.find('a').removeClass('active'); + $(event.target).not('span').addClass('active'); + } + } + + } + +})(); \ No newline at end of file diff --git a/static/resources/js/layout/navigation/navigation.module.js b/static/resources/js/layout/navigation/navigation.module.js new file mode 100644 index 000000000..c1154f746 --- /dev/null +++ b/static/resources/js/layout/navigation/navigation.module.js @@ -0,0 +1,22 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.navigation', []); + +})(); \ No newline at end of file diff --git a/static/resources/js/layout/project/project.controller.js b/static/resources/js/layout/project/project.controller.js new file mode 100644 index 000000000..cb497daf5 --- /dev/null +++ b/static/resources/js/layout/project/project.controller.js @@ -0,0 +1,121 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.project') + .controller('ProjectController', ProjectController); + + ProjectController.$inject = ['$scope', 'ListProjectService', '$timeout', 'currentUser', 'getRole', '$filter', 'trFilter']; + + function ProjectController($scope, ListProjectService, $timeout, currentUser, getRole, $filter, trFilter) { + var vm = this; + + vm.isOpen = false; + vm.projectName = ''; + vm.publicity = 0; + + vm.retrieve = retrieve; + vm.showAddProject = showAddProject; + vm.searchProject = searchProject; + vm.showAddButton = showAddButton; + vm.togglePublicity = togglePublicity; + vm.user = currentUser.get(); + vm.retrieve(); + vm.getProjectRole = getProjectRole; + + + //Error message dialog handler for project. + $scope.$on('modalTitle', function(e, val) { + vm.modalTitle = val; + }); + + $scope.$on('modalMessage', function(e, val) { + vm.modalMessage = val; + }); + + $scope.$on('raiseError', function(e, val) { + if(val) { + vm.action = function() { + $scope.$broadcast('showDialog', false); + }; + vm.contentType = 'text/plain'; + vm.confirmOnly = true; + $scope.$broadcast('showDialog', true); + } + }); + + + function retrieve() { + ListProjectService(vm.projectName, vm.publicity) + .success(listProjectSuccess) + .error(listProjectFailed); + } + + function listProjectSuccess(data, status) { + vm.projects = data || []; + } + + function getProjectRole(roleId) { + if(roleId !== 0) { + var role = getRole({'key': 'roleId', 'value': roleId}); + return role.name; + } + return ''; + } + + function listProjectFailed(data, status) { + $scope.$emit('modalTitle', $filter('tr')('error')); + $scope.$emit('modalMessage', $filter('tr')('failed_to_get_project')); + $scope.$emit('raiseError', true); + console.log('Failed to get Project.'); + } + + $scope.$on('addedSuccess', function(e, val) { + vm.retrieve(); + }); + + function showAddProject() { + if(vm.isOpen){ + vm.isOpen = false; + }else{ + vm.isOpen = true; + } + } + + function searchProject() { + vm.retrieve(); + } + + function showAddButton() { + if(vm.publicity === 0) { + return true; + }else{ + return false; + } + } + + function togglePublicity(e) { + vm.publicity = e.publicity; + vm.isOpen = false; + vm.retrieve(); + console.log('vm.publicity:' + vm.publicity); + } + + } + +})(); \ No newline at end of file diff --git a/static/resources/js/layout/project/project.module.js b/static/resources/js/layout/project/project.module.js new file mode 100644 index 000000000..82db3c9bb --- /dev/null +++ b/static/resources/js/layout/project/project.module.js @@ -0,0 +1,26 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.project', [ + 'harbor.project.member', + 'harbor.services.project', + 'harbor.services.user' + ]); + +})(); \ No newline at end of file diff --git a/static/resources/js/layout/reset-password/reset-password.controller.js b/static/resources/js/layout/reset-password/reset-password.controller.js new file mode 100644 index 000000000..3ee85fb3c --- /dev/null +++ b/static/resources/js/layout/reset-password/reset-password.controller.js @@ -0,0 +1,100 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.reset.password') + .controller('ResetPasswordController', ResetPasswordController); + + ResetPasswordController.$inject = ['$scope', '$location', 'ResetPasswordService', '$window', 'getParameterByName', '$filter', 'trFilter']; + + function ResetPasswordController($scope, $location, ResetPasswordService, $window, getParameterByName, $filter, trFilter) { + var vm = this; + vm.resetUuid = getParameterByName('reset_uuid', $location.absUrl()); + + vm.reset = reset; + vm.resetPassword = resetPassword; + vm.confirm = confirm; + vm.cancel = cancel; + + vm.hasError = false; + vm.errorMessage = ''; + + //Error message dialog handler for resetting password. + $scope.$on('modalTitle', function(e, val) { + vm.modalTitle = val; + }); + + $scope.$on('modalMessage', function(e, val) { + vm.modalMessage = val; + }); + + $scope.$on('raiseError', function(e, val) { + if(val) { + vm.action = function() { + $scope.$broadcast('showDialog', false); + }; + vm.contentType = 'text/plain'; + vm.confirmOnly = true; + $scope.$broadcast('showDialog', true); + } + }); + + function reset() { + vm.hasError = false; + vm.errorMessage = ''; + } + + function resetPassword(user) { + if(user && angular.isDefined(user.password)) { + + vm.action = vm.confirm; + + console.log('rececived password:' + user.password + ', reset_uuid:' + vm.resetUuid); + ResetPasswordService(vm.resetUuid, user.password) + .success(resetPasswordSuccess) + .error(resetPasswordFailed); + } + } + + function confirm() { + $window.location.href = '/'; + } + + function resetPasswordSuccess(data, status) { + vm.modalTitle = $filter('tr')('reset_password'); + vm.modalMessage = $filter('tr')('successful_reset_password'); + vm.confirmOnly = true; + $scope.$broadcast('showDialog', true); + } + + function resetPasswordFailed(data) { + vm.hasError = true; + + $scope.$emit('modalTitle', $filter('tr')('error')); + $scope.$emit('modalMessage', $filter('tr')('failed_to_reset_pasword') + data); + $scope.$emit('raiseError', true); + + console.log('Failed to reset password:' + data); + } + + function cancel() { + $window.location.href = '/'; + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/layout/reset-password/reset-password.module.js b/static/resources/js/layout/reset-password/reset-password.module.js new file mode 100644 index 000000000..ce10e0bb6 --- /dev/null +++ b/static/resources/js/layout/reset-password/reset-password.module.js @@ -0,0 +1,24 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.reset.password', [ + 'harbor.services.user' + ]); + +})(); \ No newline at end of file diff --git a/static/resources/js/layout/search/search.controller.js b/static/resources/js/layout/search/search.controller.js new file mode 100644 index 000000000..37ad1d211 --- /dev/null +++ b/static/resources/js/layout/search/search.controller.js @@ -0,0 +1,69 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.search') + .controller('SearchController', SearchController); + + SearchController.$inject = ['$location', 'SearchService', '$scope', '$filter', 'trFilter', 'getParameterByName']; + + function SearchController($location, SearchService, $scope, $filter, trFilter, getParameterByName) { + var vm = this; + + vm.q = getParameterByName('q', $location.absUrl()); + console.log('vm.q:' + vm.q); + SearchService(vm.q) + .success(searchSuccess) + .error(searchFailed); + + //Error message dialog handler for search. + $scope.$on('modalTitle', function(e, val) { + vm.modalTitle = val; + }); + + $scope.$on('modalMessage', function(e, val) { + vm.modalMessage = val; + }); + + $scope.$on('raiseError', function(e, val) { + if(val) { + vm.action = function() { + $scope.$broadcast('showDialog', false); + }; + vm.contentType = 'text/plain'; + vm.confirmOnly = true; + $scope.$broadcast('showDialog', true); + } + }); + + function searchSuccess(data, status) { + vm.repository = data['repository']; + vm.project = data['project']; + } + + function searchFailed(data, status) { + + $scope.$emit('modalTitle', $filter('tr')('error')); + $scope.$emit('modalMessage', $filter('tr')('failed_in_search')); + $scope.$emit('raiseError', true); + + console.log('Failed to search:' + data); + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/layout/search/search.module.js b/static/resources/js/layout/search/search.module.js new file mode 100644 index 000000000..0e8f0fb7b --- /dev/null +++ b/static/resources/js/layout/search/search.module.js @@ -0,0 +1,23 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.search', []); + + +})(); \ No newline at end of file diff --git a/static/resources/js/layout/sign-up/sign-up.controller.js b/static/resources/js/layout/sign-up/sign-up.controller.js new file mode 100644 index 000000000..f182ee106 --- /dev/null +++ b/static/resources/js/layout/sign-up/sign-up.controller.js @@ -0,0 +1,107 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.sign.up') + .controller('SignUpController', SignUpController); + + SignUpController.$inject = ['$scope', 'SignUpService', '$window', '$filter', 'trFilter']; + + function SignUpController($scope, SignUpService, $window, $filter, trFilter) { + var vm = this; + + vm.user = {}; + vm.signUp = signUp; + vm.confirm = confirm; + + //Error message dialog handler for signing up. + $scope.$on('modalTitle', function(e, val) { + vm.modalTitle = val; + }); + + $scope.$on('modalMessage', function(e, val) { + vm.modalMessage = val; + }); + + $scope.$on('raiseError', function(e, val) { + if(val) { + vm.action = function() { + $scope.$broadcast('showDialog', false); + }; + vm.contentType = 'text/plain'; + vm.confirmOnly = true; + $scope.$broadcast('showDialog', true); + } + }); + + function signUp(user) { + var userObject = { + 'username': user.username, + 'email': user.email, + 'password': user.password, + 'realname': user.fullName, + 'comment': user.comment + }; + + vm.action = vm.confirm; + + SignUpService(userObject) + .success(signUpSuccess) + .error(signUpFailed); + } + + function signUpSuccess(data, status) { + var title; + var message; + if(vm.targetType) { + title = $filter('tr')('add_new_title'); + message = $filter('tr')('successful_added'); + }else{ + title = $filter('tr')('sign_up'); + message = $filter('tr')('successful_signed_up'); + } + vm.modalTitle = title; + vm.modalMessage = message; + $scope.$broadcast('showDialog', true); + } + + function signUpFailed(data, status) { + $scope.$emit('modalTitle', $filter('tr')('error')); + var message; + if(vm.targetType) { + message = $filter('tr')('failed_to_add_user'); + }else{ + message = $filter('tr')('failed_to_sign_up'); + } + $scope.$emit('modalMessage', message); + $scope.$emit('raiseError', true); + + console.log('Signed up failed.'); + } + + function confirm() { + if(location.pathname === '/add_new') { + $window.location.href = '/dashboard'; + }else{ + $window.location.href = '/'; + } + } + + } + +})(); \ No newline at end of file diff --git a/static/resources/js/layout/sign-up/sign-up.module.js b/static/resources/js/layout/sign-up/sign-up.module.js new file mode 100644 index 000000000..c038e7789 --- /dev/null +++ b/static/resources/js/layout/sign-up/sign-up.module.js @@ -0,0 +1,23 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.layout.sign.up', [ + 'harbor.services.user']); + +})(); \ No newline at end of file diff --git a/static/resources/js/project.js b/static/resources/js/project.js deleted file mode 100644 index 427aa39dd..000000000 --- a/static/resources/js/project.js +++ /dev/null @@ -1,235 +0,0 @@ -/* - 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. -*/ -jQuery(function(){ - - new AjaxUtil({ - url: "/api/users/current", - type: "get", - success: function(data, status, xhr){ - if(xhr && xhr.status == 200){ - if(data.has_admin_role == 1) { - renderForAdminRole(); - } - renderForAnyRole(); - } - } - }).exec(); - - function renderForAnyRole(){ - $("#tabProject a:first").tab("show"); - - $(document).on("keydown", function(e){ - if(e.keyCode == 13){ - e.preventDefault(); - if($("#tabProject li:eq(0)").is(":focus") || $("#txtSearchProject").is(":focus")){ - $("#btnSearch").trigger("click"); - }else if($("#tabProject li:eq(1)").is(":focus") || $("#txtSearchPublicProjects").is(":focus")){ - $("#btnSearchPublicProjects").trigger("click"); - }else if($("#tabProject li:eq(1)").is(":focus") || $("#txtSearchUsername").is(":focus")){ - $("#btnSearchUsername").trigger("click"); - }else if($("#dlgAddProject").is(":focus") || $("#projectName").is(":focus")){ - $("#btnSave").trigger("click"); - } - } - }); - - function listProject(projectName, isPublic){ - currentPublic = isPublic; - $.when( - new AjaxUtil({ - url: "/api/projects?is_public=" + isPublic + "&project_name=" + (projectName == null ? "" : projectName) + "×tamp=" + new Date().getTime(), - type: "get", - success: function(data, status, xhr){ - $("#tblProject tbody tr").remove(); - $.each(data || [], function(i, e){ - var row = '' + - '' + e.name + '' + - '' + moment(new Date(e.creation_time)).format("YYYY-MM-DD HH:mm:ss") + ''; - if(e.public == 1 && e.Togglable){ - row += '' - } else if (e.public == 1) { - row += ''; - } else if (e.public == 0 && e.Togglable) { - row += ''; - } else if (e.public == 0) { - row += ''; - row += ''; - } - $("#tblProject tbody").append(row); - }); - } - }).exec()) - .done(function() { - $("#tblProject tbody tr :button").on("click", function(){ - var projectId = $(this).attr("projectid"); - var self = this; - new AjaxUtil({ - url: "/api/projects/" + projectId, - data: {"public": ($(self).hasClass("btn-success") ? false : true)}, - type: "put", - complete: function(jqXhr, status) { - if($(self).hasClass("btn-success")){ - $(self).removeClass("btn-success").addClass("btn-danger"); - $(self).html(i18n.getMessage("button_off")); - }else{ - $(self).removeClass("btn-danger").addClass("btn-success"); - $(self).html(i18n.getMessage("button_on")); - } - } - }).exec(); - }); - }); - } - listProject(null, 0); - var currentPublic = 0; - - $("#tabProject a:eq(0)").on("click", function(){ - $("#btnAddProject").css({"visibility": "visible"}); - listProject(null, 0); - }); - - $("#tabProject a:eq(1)").on("click", function(){ - $("#btnAddProject").css({"visibility": "hidden"}); - listProject(null, 1); - }); - - $("#divErrMsg").css({"display": "none"}); - validateOptions.Items.push("#projectName"); - - $('#dlgAddProject').on('hide.bs.modal', function () { - $("#divErrMsg").css({"display": "none"}); - $("#projectName").val(""); - $("#projectName").parent().removeClass("has-feedback").removeClass("has-error").removeClass("has-success"); - $("#projectName").siblings("span").removeClass("glyphicon-warning-sign").removeClass("glyphicon-ok"); - }); - $('#dlgAddProject').on('show.bs.modal', function () { - $("#divErrMsg").css({"display": "none"}); - $("#projectName").val(""); - $("#projectName").parent().addClass("has-feedback"); - $("#projectName").siblings("span").removeClass("glyphicon-warning-sign").removeClass("glyphicon-ok"); - $("#isPublic").prop('checked', false); - }); - - $("#btnSave").on("click", function(){ - validateOptions.Validate(function() { - new AjaxUtil({ - url: "/api/projects", - data: {"project_name" : $.trim($("#projectName").val()), "public":$("#isPublic").prop('checked'), "timestamp" : new Date().getTime()}, - type: "post", - errors: { - 409: i18n.getMessage("project_exists") - }, - complete: function(jqXhr, status){ - $("#btnCancel").trigger("click"); - listProject(null, currentPublic); - } - }).exec(); - }); - }); - - $("#btnSearch").on("click", function(){ - var projectName = $("#txtSearchProject").val(); - if($.trim(projectName).length == 0){ - projectName = null; - } - listProject(projectName, currentPublic); - }); - } - - - function renderForAdminRole(){ - $("#liAdminOption").css({"visibility": "visible"}); - $("#tabAdminOption").css({"visibility": "visible"}); - function listUserAdminRole(searchUsername){ - $.when( - new AjaxUtil({ - url: "/api/users?username=" + (searchUsername == null ? "" : searchUsername), - type: "get", - success: function(data){ - $("#tblUser tbody tr").remove(); - $.each(data || [], function(i, e){ - var row = '' + - '' + e.username + '' + - '' + e.email + ''; - if(e.has_admin_role == 1){ - row += ''; - } else { - row += ''; - } - row += ''; - row += ''; - $("#tblUser tbody").append(row); - }); - } - }).exec() - ).done(function(){ - $("#tblUser tbody tr :button").on("click",function(){ - var userId = $(this).attr("userid"); - var self = this; - new AjaxUtil({ - url: "/api/users/" + userId, - type: "put", - complete: function(jqXhr, status){ - if(jqXhr && jqXhr.status == 200){ - if($(self).hasClass("btn-success")){ - $(self).removeClass("btn-success").addClass("btn-danger"); - $(self).html(i18n.getMessage("button_off")); - }else{ - $(self).removeClass("btn-danger").addClass("btn-success"); - $(self).html(i18n.getMessage("button_on")); - } - } - } - }).exec(); - }); - $("#tblUser tbody tr").on("mouseover", function(e){ - $(".tdDeleteUser", this).css({"visibility":"visible"}); - }).on("mouseout", function(e){ - $(".tdDeleteUser", this).css({"visibility":"hidden"}); - }); - $("#tblUser tbody tr .tdDeleteUser").on("click", function(e){ - var userId = $(this).attr("userid"); - $("#dlgModal") - .dialogModal({ - "title": i18n.getMessage("delete_user"), - "content": i18n.getMessage("are_you_sure_to_delete_user") + $(this).attr("username") + " ?", - "enableCancel": true, - "callback": function(){ - new AjaxUtil({ - url: "/api/users/" + userId, - type: "delete", - complete: function(jqXhr, status){ - if(jqXhr && jqXhr.status == 200){ - $("#btnSearchUsername").trigger("click"); - } - }, - error: function(jqXhr){} - }).exec(); - } - }); - - }); - }); - } - listUserAdminRole(null); - $("#btnSearchUsername").on("click", function(){ - var username = $("#txtSearchUsername").val(); - if($.trim(username).length == 0){ - username = null; - } - listUserAdminRole(username); - }); - } - }) diff --git a/static/resources/js/register.js b/static/resources/js/register.js deleted file mode 100644 index 397087c01..000000000 --- a/static/resources/js/register.js +++ /dev/null @@ -1,78 +0,0 @@ -/* - 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. -*/ -jQuery(function(){ - $("#divErrMsg").css({"display": "none"}); - - $(document).on("keydown", function(e){ - - if(e.keyCode == 13){ - e.preventDefault(); - if(!$("#txtCommonSearch").is(":focus")){ - $("#btnPageSignUp").trigger("click"); - } - } - }); - - $("#Username,#Email,#Realname,#Password,#ConfirmedPassword").on("blur", validateCallback); - validateOptions.Items = ["#Username","#Email","#Realname","#Password","#ConfirmedPassword"]; - - $("#btnPageSignUp").on("click", function(){ - validateOptions.Validate(function() { - var username = $.trim($("#Username").val()); - var email = $.trim($("#Email").val()); - var password = $.trim($("#Password").val()); - var confirmedPassword = $.trim($("#ConfirmedPassword").val()); - var realname = $.trim($("#Realname").val()); - var comment = $.trim($("#Comment").val()); - var isAdmin = $("#isAdmin").val(); - - new AjaxUtil({ - url : "/api/users", - data: {"username": username, "password": password, "realname": realname, "comment": comment, "email": email}, - type: "POST", - beforeSend: function(e){ - $("#btnPageSignUp").prop("disabled", true); - }, - error:function(jqxhr, status, error){ - $("#dlgModal") - .dialogModal({ - "title": i18n.getMessage("title_sign_up"), - "content": i18n.getMessage("internal_error"), - "callback": function(){ - return; - } - }); - }, - complete: function(xhr, status){ - $("#btnPageSignUp").prop("disabled", false); - if(xhr && xhr.status == 201){ - $("#dlgModal") - .dialogModal({ - "title": isAdmin == "true" ? i18n.getMessage("title_add_user") : i18n.getMessage("title_sign_up"), - "content": isAdmin == "true" ? i18n.getMessage("added_user_successfully") : i18n.getMessage("registered_successfully"), - "callback": function(){ - if(isAdmin == "true") { - document.location = "/registry/project"; - }else{ - document.location = "/signIn"; - } - } - }); - } - } - }).exec(); - }); - }); -}); \ No newline at end of file diff --git a/static/resources/js/reset-password.js b/static/resources/js/reset-password.js deleted file mode 100644 index a6c504f48..000000000 --- a/static/resources/js/reset-password.js +++ /dev/null @@ -1,81 +0,0 @@ -/* - 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. -*/ -jQuery(function(){ - $("#divErrMsg").css({"display": "none"}); - - $("#Password,#ConfirmedPassword").on("blur", validateCallback); - validateOptions.Items = ["#Password", "#ConfirmedPassword"]; - function bindEnterKey(){ - $(document).on("keydown", function(e){ - if(e.keyCode == 13){ - e.preventDefault(); - $("#btnSubmit").trigger("click"); - } - }); - } - function unbindEnterKey(){ - $(document).off("keydown"); - } - bindEnterKey(); - - var spinner = new Spinner({scale:1}).spin(); - - $("#btnSubmit").on("click", function(){ - validateOptions.Validate(function(){ - var resetUuid = $("#resetUuid").val(); - var password = $("#Password").val(); - $.ajax({ - "url": "/reset", - "type": "post", - "data": {"reset_uuid": resetUuid, "password": password}, - "beforeSend": function(e){ - unbindEnterKey(); - $("h1").append(spinner.el); - $("#btnSubmit").prop("disabled", true); - }, - "success": function(data, status, xhr){ - if(xhr && xhr.status == 200){ - $("#dlgModal") - .dialogModal({ - "title": i18n.getMessage("title_reset_password"), - "content": i18n.getMessage("reset_password_successfully"), - "callback": function(){ - document.location="/signIn"; - } - }); - } - - }, - "complete": function(){ - spinner.stop(); - $("#btnSubmit").prop("disabled", false); - }, - "error": function(jqXhr, status, error){ - if(jqXhr){ - $("#dlgModal") - .dialogModal({ - "title": i18n.getMessage("title_reset_password"), - "content": i18n.getMessage(jqXhr.responseText), - "callback": function(){ - bindEnterKey(); - return; - } - }); - } - } - }); - }); - }); -}); \ No newline at end of file diff --git a/static/resources/js/search.js b/static/resources/js/search.js deleted file mode 100644 index e0158baa8..000000000 --- a/static/resources/js/search.js +++ /dev/null @@ -1,79 +0,0 @@ -/* - 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. -*/ -jQuery(function(){ - - $(document).on("keydown", function(e){ - if(e.keyCode == 13){ - e.preventDefault(); - if($("#txtCommonSearch").is(":focus")){ - search($("#txtCommonSearch").val()); - } - } - }); - - var queryParam = $("#queryParam").val(); - $("#txtCommonSearch").val(queryParam); - - search(queryParam); - - function search(keyword){ - keyword = $.trim(keyword) - if(keyword.length > 0){ - $.ajax({ - url: "/api/search", - data: {"q": keyword}, - type: "get", - dataType: "json", - success: function(data, status, xhr){ - if(xhr && xhr.status == 200){ - $("#panelCommonSearchProjectsHeader").text(i18n.getMessage("projects") + " (" + data.project.length + ")"); - $("#panelCommonSearchRepositoriesHeader").text(i18n.getMessage("repositories") +" (" + data.repository.length + ")"); - - render($("#panelCommonSearchProjectsBody"), data.project, "project"); - render($("#panelCommonSearchRepositoriesBody"), data.repository, "repository"); - } - } - }); - } - } - - var Project = function(id, name, public){ - this.id = id; - this.name = name; - this.public = public; - } - - function render(element, data, discriminator){ - $(element).children().remove(); - $.each(data, function(i, e){ - var project, description, repoName; - switch(discriminator){ - case "project": - project = new Project(e.id, e.name, e.public); - description = project.name; - repoName = ""; - break; - case "repository": - project = new Project(e.project_id, e.project_name, e.project_public); - description = e.repository_name; - repoName = e.repository_name.substring(e.repository_name.lastIndexOf("/") + 1); - break; - } - if(project){ - $(element).append('
' + description + '
'); - } - }); - } -}); \ No newline at end of file diff --git a/static/resources/js/services/destination/services.create-destination.js b/static/resources/js/services/destination/services.create-destination.js new file mode 100644 index 000000000..8a2c1f3ad --- /dev/null +++ b/static/resources/js/services/destination/services.create-destination.js @@ -0,0 +1,38 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.destination') + .factory('CreateDestinationService', CreateDestinationService); + + CreateDestinationService.$inject = ['$http']; + + function CreateDestinationService($http) { + return createDestination; + function createDestination(name, endpoint, username, password) { + return $http + .post('/api/targets', { + 'name': name, + 'endpoint': endpoint, + 'username': username, + 'password': password + }); + } + } + +})() \ No newline at end of file diff --git a/static/resources/js/services/destination/services.delete-destination.js b/static/resources/js/services/destination/services.delete-destination.js new file mode 100644 index 000000000..6a34585da --- /dev/null +++ b/static/resources/js/services/destination/services.delete-destination.js @@ -0,0 +1,33 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.destination') + .factory('DeleteDestinationService', DeleteDestinationService); + + DeleteDestinationService.$inject = ['$http']; + + function DeleteDestinationService($http) { + return deleteDestination; + function deleteDestination(targetId) { + return $http + .delete('/api/targets/' + targetId); + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/services/destination/services.destination.module.js b/static/resources/js/services/destination/services.destination.module.js new file mode 100644 index 000000000..919d47734 --- /dev/null +++ b/static/resources/js/services/destination/services.destination.module.js @@ -0,0 +1,22 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.destination', []); + +})(); diff --git a/static/resources/js/services/destination/services.list-destination-policy.js b/static/resources/js/services/destination/services.list-destination-policy.js new file mode 100644 index 000000000..9aae0445c --- /dev/null +++ b/static/resources/js/services/destination/services.list-destination-policy.js @@ -0,0 +1,33 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.destination') + .factory('ListDestinationPolicyService', ListDestinationPolicyService); + + ListDestinationPolicyService.$inject = ['$http']; + + function ListDestinationPolicyService($http) { + return listDestinationPolicy; + function listDestinationPolicy(targetId) { + return $http + .get('/api/targets/' + targetId + '/policies/'); + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/services/destination/services.list-destination.js b/static/resources/js/services/destination/services.list-destination.js new file mode 100644 index 000000000..eb8a3f7c0 --- /dev/null +++ b/static/resources/js/services/destination/services.list-destination.js @@ -0,0 +1,37 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.destination') + .factory('ListDestinationService', ListDestinationService); + + ListDestinationService.$inject = ['$http']; + + function ListDestinationService($http) { + return listDestination; + function listDestination(targetId, name) { + return $http + .get('/api/targets/' + targetId, { + 'params': { + 'name': name + } + }); + } + } + +})() \ No newline at end of file diff --git a/static/resources/js/services/destination/services.ping-destination.js b/static/resources/js/services/destination/services.ping-destination.js new file mode 100644 index 000000000..58a103f66 --- /dev/null +++ b/static/resources/js/services/destination/services.ping-destination.js @@ -0,0 +1,57 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.destination') + .factory('PingDestinationService', PingDestinationService); + + PingDestinationService.$inject = ['$http']; + + function PingDestinationService($http) { + return pingDestination; + function pingDestination(target) { + var payload = {}; + if(target['id']) { + payload = {'id': target['id']}; + }else { + payload = { + 'name': target['name'], + 'endpoint': target['endpoint'], + 'username': target['username'], + 'password': target['password'] + }; + } + + return $http({ + 'method': 'POST', + 'url': '/api/targets/ping', + 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}, + 'transformRequest': function(obj) { + var str = []; + for(var p in obj) { + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); + } + return str.join("&"); + }, + 'timeout': 30000, + 'data': payload + }); + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/services/destination/services.update-destination.js b/static/resources/js/services/destination/services.update-destination.js new file mode 100644 index 000000000..1791f4349 --- /dev/null +++ b/static/resources/js/services/destination/services.update-destination.js @@ -0,0 +1,38 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.destination') + .factory('UpdateDestinationService', UpdateDestinationService); + + UpdateDestinationService.$inject = ['$http']; + + function UpdateDestinationService($http) { + return updateDestination; + function updateDestination(targetId, target) { + return $http + .put('/api/targets/' + targetId, { + 'name': target.name, + 'endpoint': target.endpoint, + 'username': target.username, + 'password': target.password + }); + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/services/i18n/locale_messages_en-US.js b/static/resources/js/services/i18n/locale_messages_en-US.js new file mode 100644 index 000000000..68793ec7d --- /dev/null +++ b/static/resources/js/services/i18n/locale_messages_en-US.js @@ -0,0 +1,270 @@ +/* + 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. +*/ +var locale_messages = { + 'sign_in': 'Sign In', + 'sign_up': 'Sign Up', + 'forgot_password': 'Forgot Password', + 'login_now': 'Login Now', + 'its_easy_to_get_started': 'It\'s easy to get started ...', + 'icon_label_1': 'Anonymous repository access', + 'icon_label_2': 'Repositories managed by project', + 'icon_label_3': 'Role based access control', + 'why_use_harbor': 'Why use Harbor?', + 'index_desc': 'Project Harbor is an enterprise-class registry server, which extends the open source Docker Registry server by adding the functionality usually required by an enterprise, such as security, control, and management. Harbor is primarily designed to be a private registry - providing the needed security and control that enterprises require. It also helps minimize bandwidth usage, which is helpful to both improve productivity as well as performance.', + 'index_desc_1': 'Security: Keep their intellectual properties within their organizations.', + 'index_desc_2': 'Efficiency: A private registry server is set up within the organization\'s network and can reduce significantly the internet traffic to the public service. ', + 'index_desc_3': 'Access Control: RBAC (Role Based Access Control) is provided. User management can be integrated with existing enterprise identity services like AD/LDAP. ', + 'index_desc_4': 'Audit: All access to the registry are logged and can be used for audit purpose.', + 'index_desc_5': 'GUI: User friendly single-pane-of-glass management console.', + 'index_desc_6': 'Image Replication: Replicate images between instances.', + 'view_all': 'View all...', + 'repositories': 'Repositories', + 'project_repo_name': 'Project/Repository Name', + 'creation_time': 'Creation Time', + 'author': 'Author', + 'username': 'Username', + 'username_is_required': 'Username is required.', + 'username_has_been_taken': 'Username has been taken.', + 'username_is_too_long': 'Username is too long. (maximum 20 characters)', + 'username_contains_illegal_chars': 'Username contains illegal character(s).', + 'email': 'Email', + 'email_desc': 'The Email address will be used for resetting password.', + 'email_is_required': 'Email is required.', + 'email_has_been_taken': 'Email has been taken.', + 'email_content_illegal': 'Email format is illegal.', + 'email_does_not_exist': 'Email does not exist.', + 'email_is_too_long': 'Email is to long. (maximum 50 characters)', + 'full_name': 'Full Name', + 'full_name_desc': 'First name & Last name', + 'full_name_is_required': 'Full name is required.', + 'full_name_is_too_long': 'Full name is too long. (maximum 20 characters)', + 'full_name_contains_illegal_chars': 'Full name contains illegal character(s).', + 'password': 'Password', + 'password_desc': 'At least 7 characters with 1 lowercase letter, 1 capital letter and 1 numeric character.', + 'password_is_required': 'Password is required.', + 'password_is_invalid': 'Password is invalid. At least 7 characters with 1 lowercase letter, 1 capital letter and 1 numeric character.', + 'confirm_password': 'Confirm Password', + 'password_does_not_match': 'Passwords do not match.', + 'comments': 'Comments', + 'comment_is_too_long': 'Comment is too long. (maximum 20 characters)', + 'forgot_password_description': 'Please input the Email used when you signed up, a reset password Email will be sent to you.', + 'reset_password': 'Reset Password', + 'successful_reset_password': 'Password has been reset successfully.', + 'failed_to_change_password': 'Failed to change password.', + 'summary': 'Summary', + 'projects': 'Projects', + 'public_projects': 'Public Projects', + 'public': 'Public', + 'public_repositories': 'Public Repositories', + 'my_project_count': 'My Projects', + 'my_repo_count': 'My Repositories', + 'public_project_count': 'Public Projects', + 'public_repo_count': 'Public Repositories', + 'total_project_count': 'Total Projects', + 'total_repo_count': 'Total Repositories', + 'top_10_repositories': 'Top 10 Repositories', + 'repository_name': 'Repository Name', + 'size': 'Size', + 'count': 'Downloads', + 'creator': 'Creator', + 'no_top_repositories': 'No data, start with Harbor now!', + 'logs': 'Logs', + 'task_name': 'Task Name', + 'details': 'Details', + 'user': 'User', + 'no_user_logs': 'No data, start with Harbor now!', + 'users': 'Users', + 'my_projects': 'My Projects', + 'project_name': 'Project Name', + 'role': 'Role', + 'publicity': 'Publicity', + 'button_on': 'On', + 'button_off': 'Off', + 'new_project': 'New Project', + 'save': 'Save', + 'cancel': 'Cancel', + 'confirm': 'Confirm', + 'items': 'item(s)', + 'add_member': 'Add Member', + 'operation': 'Operation', + 'advanced_search': 'Advanced Search', + 'all': 'All', + 'others': 'Others', + 'search': 'Search', + 'duration': 'Duration', + 'from': 'From', + 'to': 'To', + 'timestamp': 'Timestamp', + 'dashboard': 'Dashboard', + 'admin_options': 'Admin Options', + 'account_setting': 'Account Settings', + 'log_out': 'Log Out', + 'registration_time': 'Registration Time', + 'system_management': 'System Management', + 'change_password': 'Change Password', + 'search_result': 'Search Result', + 'old_password': 'Old Password', + 'old_password_is_required': 'Old password is required.', + 'old_password_is_incorrect': 'Old password is incorrect.', + 'new_password_is_required': 'New password is required.', + 'new_password_is_invalid': 'New password is invalid. At least 7 characters with 1 lowercase letter, 1 capital letter and 1 numeric character.', + 'new_password': 'New Password', + 'username_already_exist': 'Username already exist.', + 'username_does_not_exist': 'Username does not exist.', + 'username_or_password_is_incorrect': 'Username or password is incorrect', + 'username_email': 'Username/Email', + 'project_name_is_required': 'Project name is required', + 'project_already_exist': 'Project already exist', + 'project_name_is_invalid': 'Project name is invalid, it should be all lowercase and with no space.', + 'project_name_is_too_short': 'Project name is too short, it should be greater than 4 characters.', + 'project_name_is_too_long': 'Project name is too long, it should be less than 30 characters.', + 'search_projects_or_repositories': 'Search projects or repositories', + 'tag': 'Tag', + 'image_details': 'Image Details', + 'pull_command': 'Pull Command', + 'alert_delete_repo_title': 'Confirm Deletion', + 'alert_delete_repo': 'All tags under this repository will be deleted. ' + + 'The space of this repository will be recycled during garbage collection.
' + + '
Delete repository "$0" now?', + 'alert_delete_tag_title': 'Confirm Deletion', + 'alert_delete_tag': 'Delete tag "$0" now?', + 'close': 'Close', + 'ok': 'OK', + 'welcome': 'Welcome to Harbor!', + 'continue' : 'Continue', + 'no_projects_add_new_project': 'No projects available now.', + 'no_repositories': 'No repositories found, please use "docker push" to upload images.', + 'failed_to_add_member': 'Project member can not be added, insuffient permissions.', + 'failed_to_change_member': 'Project member can not be changed, insuffient permissions.', + 'failed_to_delete_member': 'Project member can not be deleted, insuffient permissions.', + 'confirm_delete_user_title': 'User Deletion', + 'confirm_delete_user': 'Are you sure to delete the user "$0" ?', + 'confirm_delete_destination_title': 'Destination Deletion', + 'confirm_delete_destination': 'Are you sure to delete the destination "$0" ?', + 'replication': 'Replication', + 'name': 'Name', + 'description': 'Description', + 'destination': 'Destination', + 'start_time': 'Start Time', + 'last_start_time': 'Last Start Time', + 'end_time': 'End Time', + 'activation': 'Activation', + 'replication_jobs': 'Replication Jobs', + 'actions': 'Actions', + 'status': 'Status', + 'logs' : 'Logs', + 'enabled': 'Enabled', + 'enable': 'Enable', + 'disabled': 'Disabled', + 'disable': 'Disable', + 'no_replication_policies_add_new': 'No replication policies, please add new policy.', + 'no_replication_policies': 'No replication policies.', + 'no_replication_jobs': 'No replication jobs.', + 'no_destinations': 'No destinations, please add new destination.', + 'name_is_required': 'Name is required.', + 'name_is_too_long': 'Name is too long. (maximum 20 characters)', + 'description_is_too_long': 'Description is too long. ', + 'general_setting': 'General', + 'destination_setting': 'Destination Settings', + 'endpoint': 'Endpoint', + 'endpoint_is_required': 'Endpoint is required.', + 'test_connection': 'Test connection', + 'add_new_destination': 'New Destination', + 'edit_destination': 'Edit Destination', + 'successful_changed_password': 'Password has been changed successfully.', + 'change_profile': 'Change Profile', + 'successful_changed_profile': 'User profile has been changed successfully.', + 'administrator': 'Administrator', + 'popular_repositories': 'Popular Repositories', + 'harbor_intro_title': 'About Harbor', + 'mail_has_been_sent': 'Password resetting Email has been sent.', + 'send': 'Send', + 'successful_signed_up': 'Signed up successfully.', + 'add_new_policy': 'Add New Policy', + 'edit_policy': 'Edit Policy', + 'add_new_title': 'Add User', + 'add_new': 'Add', + 'successful_added': 'New user added successfully.', + 'copyright': 'Copyright', + 'all_rights_reserved': 'All Rights Reserved.', + 'pinging_target': 'Testing connection ...', + 'successful_ping_target': 'Connection tested successfully.', + 'failed_to_ping_target': 'Connetion test failed, please check your settings.', + 'policy_already_exists': 'Policy already exists.', + 'destination_already_exists': 'Destination already exists.', + 'refresh': 'Refresh', + 'select_all': 'Select All', + 'delete_tag': 'Delete Tag', + 'delete_repo': 'Delete Repo', + 'download_log': 'View Logs', + 'edit': 'Edit', + 'delete': 'Delete', + 'transfer': 'Transfer', + 'all': 'All', + 'pending': 'Pending', + 'running': 'Running', + 'finished': 'Finished', + 'canceled': 'Canceled', + 'stopped': 'Stopped', + 'retrying': 'Retrying', + 'error': 'Error', + 'failed_to_get_project_member': 'Failed to get current project member.', + 'failed_to_delete_repo': 'Failed to delete repository. ', + 'failed_to_delete_repo_insuffient_permissions': 'Failed to delete repository, insuffient permissions.', + 'failed_to_get_tag': 'Failed to get tag.', + 'failed_to_get_log': 'Failed to get logs.', + 'failed_to_get_project': 'Failed to get projects.', + 'failed_to_update_user': 'Failed to update user.', + 'failed_to_get_stat': 'Failed to get stat data.', + 'failed_to_get_top_repo': 'Failed to get top repositories.', + 'failed_to_get_user_log': 'Failed to get user logs.', + 'failed_to_send_email': 'Failed to send email.', + 'failed_to_reset_pasword': 'Failed to reset password.', + 'failed_in_search': 'Failed in search.', + 'failed_to_sign_up': 'Failed to sign up.', + 'failed_to_add_user': 'Failed to add user.', + 'failed_to_delete_user': 'Failed to delete user.', + 'failed_to_list_user': 'Failed to list user data.', + 'failed_to_toggle_admin': 'Failed to toggle admin user.', + 'failed_to_list_destination': 'Failed to list destinations.', + 'failed_to_list_replication': 'Failed to list replication policies.', + 'failed_to_toggle_policy': 'Failed to toggle replication policy.', + 'failed_to_create_replication_policy': 'Failed to create replication policy.', + 'failed_to_get_destination': 'Failed to get destination.', + 'failed_to_get_destination_policies': 'Failed to get destination policies.', + 'failed_to_get_replication_policy': 'Failed to get replication policy.', + 'failed_to_update_replication_policy': 'Failed to update replication policy.', + 'failed_to_delete_destination': 'Failed to delete destination.', + 'failed_to_create_destination': 'Failed to create destination.', + 'failed_to_update_destination': 'Failed to update destination.', + 'failed_to_toggle_publicity_insuffient_permissions': 'Failed to toggle project publicity, insuffient permissions.', + 'failed_to_toggle_publicity': 'Failed to toggle project publicity.', + 'project_admin': 'Project Admin', + 'developer': 'Developer', + 'guest': 'Guest', + 'inline_help_role_title': 'The Definitions of Roles', + 'inline_help_role': 'Project Admin: Project Admin has read/write and member management privileges to the project.
' + + 'Developer: Developer has read and write privileges to the project.
' + + 'Guest: Guest has read-only privilege for a specified project.', + 'inline_help_publicity_title': 'Publicity of Project', + 'inline_help_publicity': 'When a project is set to public, anyone will have read permission to the repositories under this project, and user will not need to run "docker login" before pulling images under this project.', + 'alert_job_contains_error': 'Found errors in the current replication jobs, please check.', + 'found_error_in_replication_job': 'Found $0 error(s).', + 'caution': 'Caution', + 'confirm_to_toggle_enabled_policy_title': 'Enable Policy', + 'confirm_to_toggle_enabled_policy': 'After enabling the policy, replication jobs will be triggered to replicate all repositories under the project to the destination of the policy. Please confirm to continue.', + 'confirm_to_toggle_disabled_policy_title': 'Disable Policy', + 'confirm_to_toggle_disabled_policy': 'After disabling the policy, running replication jobs of this policy will be canceled. Please confirm to continue.' +}; diff --git a/static/resources/js/services/i18n/locale_messages_zh-CN.js b/static/resources/js/services/i18n/locale_messages_zh-CN.js new file mode 100644 index 000000000..98bc435bb --- /dev/null +++ b/static/resources/js/services/i18n/locale_messages_zh-CN.js @@ -0,0 +1,272 @@ +/* + 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. +*/ +var locale_messages = { + 'sign_in': '登录', + 'sign_up': '注册', + 'forgot_password': '忘记密码', + 'login_now': '登录', + 'its_easy_to_get_started': '这很容易上手...', + 'icon_label_1': '匿名访问公开镜像仓库', + 'icon_label_2': '基于项目的镜像管理', + 'icon_label_3': '用户角色访问控制', + 'why_use_harbor': '为什么要使用Harbor?', + 'index_desc': 'Harbor是可靠的企业级Registry服务器。企业用户可使用Harbor搭建私有容器Registry服务,提高生产效率和安全度,既可应用于生产环境,也可以在开发环境中使用。', + 'index_desc_1': '安全: 确保知识产权在自己组织内部的管控之下。', + 'index_desc_2': '效率: 搭建组织内部的私有容器Registry服务,可显著降低访问公共Registry服务的网络需求。', + 'index_desc_3': '访问控制: 提供基于角色的访问控制,可集成企业目前拥有的用户管理系统(如:AD/LDAP)。', + 'index_desc_4': '审计: 所有访问Registry服务的操作均被记录,便于日后审计。', + 'index_desc_5': '管理界面: 具有友好易用图形管理界面。', + 'index_desc_6': '镜像复制: 在实例之间复制镜像。', + 'view_all': '显示全部...', + 'repositories': '镜像仓库', + 'project_repo_name': '项目/镜像仓库名称', + 'creation_time': '创建时间', + 'author': '作者', + 'username': '用户名', + 'username_is_required' : '用户名为必填项。', + 'username_has_been_taken' : '用户名已被占用。', + 'username_is_too_long' : '用户名长度超出限制。(最长为20个字符)', + 'username_contains_illegal_chars': '用户名包含不合法的字符。', + 'email': '邮箱', + 'email_desc': '此邮箱将用于重置密码。', + 'email_is_required' : '邮箱为必填项。', + 'email_has_been_taken' : '邮箱已被占用。', + 'email_content_illegal' : '邮箱格式不合法。', + 'email_does_not_exist' : '邮箱不存在。', + 'email_is_too_long': '邮箱名称长度超出限制。(最长为50个字符)', + 'full_name': '全名', + 'full_name_desc': '请输入全名。', + 'full_name_is_required' : '全名为必填项。', + 'full_name_is_too_long' : '全名长度超出限制。(最长为20个字符)', + 'full_name_contains_illegal_chars' : '全名包含不合法的字符。', + 'password': '密码', + 'password_desc': '至少输入 7个字符且包含 1个小写字母, 1个大写字母和 1个数字。', + 'password_is_required' : '密码为必填项。', + 'password_is_invalid' : '密码无效。至少输入 7个字符且包含 1个小写字母,1个大写字母和 1个数字。', + 'confirm_password': '确认密码', + 'password_does_not_match' : '两次密码输入不一致。', + 'comments': '备注', + 'comment_is_too_long' : '备注长度超出限制。(最长为20个字符)', + 'forgot_password_description': '重置邮件将发送到此邮箱。', + 'reset_password': '重置密码', + 'successful_reset_password': '重置密码成功。', + 'failed_to_change_password': '修改密码失败。', + 'summary': '摘要', + 'projects': '项目', + 'public_projects': '公开项目', + 'public': '公开', + 'public_repositories': '公开镜像仓库', + 'my_project_count': '我的项目', + 'my_repo_count': '我的镜像仓库', + 'public_project_count': '公开项目', + 'public_repo_count': '公开镜像仓库', + 'total_project_count': '全部项目', + 'total_repo_count': '全部镜像仓库', + 'top_10_repositories': 'Top 10 镜像仓库', + 'repository_name': '镜像仓库名', + 'size': '大小', + 'count': '下载次数', + 'creator': '创建者', + 'no_top_repositories': '暂时没有数据。', + 'logs': '日志', + 'task_name': '任务名称', + 'details': '详细信息', + 'user': '用户', + 'no_user_logs': '暂时没有数据。', + 'users': '用户', + 'my_projects': '我的项目', + 'project_name': '项目名称', + 'role': '角色', + 'publicity': '公开', + 'button_on': '是', + 'button_off': '否', + 'new_project': '新增项目', + 'save': '保存', + 'cancel': '取消', + 'confirm': '确认', + 'items': '条记录', + 'add_member': '新增成员', + 'operation': '操作', + 'advanced_search': '高级搜索', + 'all': '全部', + 'others': '其他', + 'search':'搜索', + 'duration': '持续时间', + 'from': '起始', + 'to': '结束', + 'timestamp': '时间戳', + 'dashboard': '控制面板', + 'admin_options': '管理员选项', + 'account_setting': '账户设置', + 'log_out': '注销', + 'registration_time': '注册时间', + 'system_management': '系统管理', + 'change_password': '修改密码', + 'search_result': '搜索结果', + 'old_password': '原密码', + 'old_password_is_required': '原密码为必填项。', + 'old_password_is_incorrect': '原密码不正确。', + 'new_password_is_required': '新密码为必填项。', + 'new_password_is_invalid': '新密码无效。至少输入 7个字符且包含 1个小写字母,1个大写字母和 1个数字。', + 'new_password': '新密码', + 'username_already_exist': '用户名已存在。', + 'username_does_not_exist': '用户名不存在。', + 'username_or_password_is_incorrect': '用户名或密码不正确。', + 'username_email': '用户名/邮箱', + 'project_name_is_required': '项目名称为必填项。', + 'project_already_exist': '项目已存在。', + 'project_name_is_invalid': '项目名称无效。全部为小写字母,且不能包含空格。', + 'project_name_is_too_short': '项目名称长度过短,至少多于4个字符。', + 'project_name_is_too_long': '项目名称长度超出限制,最长30个字符。', + 'search_projects_or_repositories': '搜索项目和镜像资源', + 'tag': '标签', + 'image_details': '镜像明细', + 'pull_command': 'Pull 命令', + 'alert_delete_repo_title': '确认删除', + 'alert_delete_repo': '即将删除镜像仓库下的所有标签,镜像空间将在垃圾回收过程中释放。
' + + '
是否删除镜像仓库 "$0" ?', + 'alert_delete_tag_title': '确认删除', + 'alert_delete_tag': '删除镜像标签 "$0" ?', + 'close': '关闭', + 'ok': '确认', + 'welcome': '欢迎使用Harbor!', + 'continue' : '继续', + 'no_projects_add_new_project': '当前没有项目。', + 'no_repositories': '未发现镜像,请用"docker push"命令上传镜像。', + 'failed_to_add_member': '无法添加项目成员,权限不足。', + 'failed_to_change_member': '无法修改项目成员,权限不足。', + 'failed_to_delete_member': '无法删除项目成员,权限不足。', + 'confirm_delete_user_title': '删除用户', + 'confirm_delete_user': '确认删除用户 "$0" ?', + 'confirm_delete_destination_title': '删除目标', + 'confirm_delete_destination': '确认删除目标 "$0"?', + 'replication': '复制', + 'name': '名称', + 'description': '描述', + 'destination': '目标', + 'start_time': '起始时间', + 'last_start_time': '上次起始时间', + 'end_time': '结束时间', + 'activation': '活动状态', + 'replication_jobs': '复制任务', + 'actions': '操作', + 'status': '状态', + 'logs': '日志', + 'enabled': '已启用', + 'enable': '启用', + 'disabled': '已停用', + 'disable': '停用', + 'no_replication_policies_add_new': '没有复制策略,请新增复制策略。', + 'no_replication_policies': '没有复制策略。', + 'no_replications': '没有复制策略。', + 'no_replication_jobs': '没有复制任务。', + 'no_destinations': '没有目标设置,请新增目标。', + 'name_is_required': '名称为必填项', + 'name_is_too_long': '名称长度超出限制。(最长为20个字符)', + 'description_is_too_long': '描述内容长度超出限制。', + 'general_setting': '一般设置', + 'destination_setting': '目标设置', + 'endpoint': '终端URL', + 'endpoint_is_required': '终端URL为必填项。', + 'test_connection': '测试连接', + 'add_new_destination': '新建目标', + 'edit_destination': '编辑目标', + 'successful_changed_password': '修改密码操作成功。', + 'change_profile': '修改账户信息', + 'successful_changed_profile': '修改账户信息操作成功。', + 'administrator': '管理员', + 'popular_repositories': '热门镜像仓库', + 'harbor_intro_title': '关于 Harbor', + 'mail_has_been_sent': '重置密码邮件已发送。', + 'send': '发送', + 'successful_signed_up': '注册成功。', + 'add_new_policy': '新增策略', + 'edit_policy': '修改策略', + 'add_new_title': '新增用户', + 'add_new': '新增', + 'successful_added': '新增用户成功。', + 'copyright': '版权所有', + 'all_rights_reserved': '保留所有权利。', + 'pinging_target': '正在测试连接……', + 'successful_ping_target': '测试连接目标成功。', + 'failed_to_ping_target': '测试连接目标失败,请检查设置。', + 'policy_already_exists': '策略已存在。', + 'destination_already_exists': '目标已存在。', + 'refresh': '刷新', + 'select_all': '全选', + 'delete_tag': '删除镜像标签', + 'delete_repo': '删除镜像仓库', + 'download_log': '查看日志', + 'edit': '修改', + 'delete': '删除', + 'all': '全部', + 'transfer': '复制', + 'pending': '等待中', + 'running': '进行中', + 'finished': '已完成', + 'canceled': '已取消', + 'stopped': '已终止', + 'retrying': '重试中', + 'error': '错误', + 'failed_to_get_project_member': '无法获取当前项目成员。', + 'failed_to_delete_repo': '无法删除镜像仓库。', + 'failed_to_delete_repo_insuffient_permissions': '无法删除镜像仓库,权限不足。', + 'failed_to_get_tag': '获取标签数据失败。', + 'failed_to_get_log': '获取日志数据失败。', + 'failed_to_get_project': '获取项目数据失败。', + 'failed_to_update_user': '更新用户信息失败。', + 'failed_to_get_stat': '获取统计数据失败。', + 'failed_to_get_top_repo': '获取热门镜像仓库数据失败。', + 'failed_to_get_user_log': '获取用户日志数据失败。', + 'failed_to_send_email': '发送邮件失败。', + 'failed_to_reset_pasword': '重置邮件失败。', + 'failed_in_search': '搜索操作失败。', + 'failed_to_sign_up': '注册用户失败。', + 'failed_to_add_user': '新增用户失败。', + 'failed_to_delete_user': '删除用户失败。', + 'failed_to_list_user': '获取用户数据失败。', + 'failed_to_toggle_admin': '切换管理员用户失败。', + 'failed_to_list_destination': '获取目标数据失败。', + 'failed_to_list_replication': '获取复制策略数据失败。', + 'failed_to_toggle_policy': '切换复制策略状态失败。', + 'failed_to_create_replication_policy': '创建复制策略失败。', + 'failed_to_get_destination': '获取目标失败。', + 'failed_to_get_destination_policies': '获取目标关联策略数据失败。', + 'failed_to_get_replication_policy': '获取复制策略失败。', + 'failed_to_update_replication_policy': '修改复制策略失败。', + 'failed_to_delete_destination': '删除目标失败。', + 'failed_to_create_destination': '创建目标失败。', + 'failed_to_update_destination': '修改目标失败。', + 'failed_to_toggle_publicity_insuffient_permissions': '切换项目公开性失败,权限不足。', + 'failed_to_toggle_publicity': '切换项目公开性失败。', + 'project_admin': '项目管理员', + 'developer': '开发人员', + 'guest': '访客', + 'inline_help_role_title': '角色定义', + 'inline_help_role': '项目管理员: “项目管理员”拥有一个项目的读/写和成员管理的权限。
'+ + '开发人员: “开发人员” 拥有一个项目的读/写权限。
' + + '访客: “访客”拥有特定项目的只读权限。', + 'inline_help_publicity_title': '公开项目', + 'inline_help_publicity': '当项目设为公开后,任何人都有此项目下镜像的读权限。命令行用户不需要“docker login”就可以拉取此项目下的镜像。', + 'alert_job_contains_error': '当前复制任务中包含错误,请检查。', + 'found_error_in_replication_job': '发现 $0 个错误。', + 'caution': '注意', + 'confirm_to_toggle_policy_title': '切换复制策略状态', + 'confirm_to_toggle_policy': '确认将复制策略 $0 切换为 $1 状态吗?', + 'confirm_to_toggle_enabled_policy_title': '启用策略', + 'confirm_to_toggle_enabled_policy': '启用策略后,系统将触发复制任务来同步项目下的所有镜像仓库到策略的目标实例。请确认继续。', + 'confirm_to_toggle_disabled_policy_title': '停用策略', + 'confirm_to_toggle_disabled_policy': '停用策略后,属于此策略的所有复制任务将终止。请确认继续。' +}; diff --git a/static/resources/js/services/i18n/services.i18n.js b/static/resources/js/services/i18n/services.i18n.js new file mode 100644 index 000000000..1f77a7c4f --- /dev/null +++ b/static/resources/js/services/i18n/services.i18n.js @@ -0,0 +1,82 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.i18n') + .factory('I18nService', I18nService); + + I18nService.$inject = ['$cookies', '$window']; + + function I18nService($cookies, $window) { + + var cookieOptions = {'path': '/'}; + + var messages = $.extend(true, {}, eval('locale_messages')); + var defaultLanguage = 'en-US'; + var supportLanguages = { + 'en-US': 'English', + 'zh-CN': '中文' + }; + var isSupportLanguage = function(language) { + for (var i in supportLanguages) { + if(language === String(i)) { + return true; + } + } + return false; + }; + + + return tr; + function tr() { + + return { + 'setCurrentLanguage': function(language) { + if(!angular.isDefined(language) || !isSupportLanguage(language)) { + language = defaultLanguage; + } + $cookies.put('language', language, cookieOptions); + }, + 'setDefaultLanguage': function() { + $cookies.put('language', defaultLanguage, cookieOptions); + }, + 'getCurrentLanguage': function() { + return $cookies.get('language') || defaultLanguage; + }, + 'getLanguageName': function(language) { + if(!angular.isDefined(language) || !isSupportLanguage(language)) { + language = defaultLanguage; + } + $cookies.put('language', language, cookieOptions); + return supportLanguages[language]; + }, + 'getSupportLanguages': function() { + return supportLanguages; + }, + 'unset': function(){ + $cookies.put('language', defaultLanguage, cookieOptions); + }, + 'getValue': function(key) { + return messages[key]; + } + }; + + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/services/i18n/services.i18n.module.js b/static/resources/js/services/i18n/services.i18n.module.js new file mode 100644 index 000000000..ddaa9f266 --- /dev/null +++ b/static/resources/js/services/i18n/services.i18n.module.js @@ -0,0 +1,22 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.i18n', []); + +})(); \ No newline at end of file diff --git a/static/resources/js/services/log/services.list-integrated-log.js b/static/resources/js/services/log/services.list-integrated-log.js new file mode 100644 index 000000000..206bb63b7 --- /dev/null +++ b/static/resources/js/services/log/services.list-integrated-log.js @@ -0,0 +1,40 @@ +/* + 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. +*/ +(function() { + 'use strict'; + + angular + .module('harbor.services.log') + .factory('ListIntegratedLogService', ListIntegratedLogService); + + ListIntegratedLogService.$inject = ['$http', '$log']; + + function ListIntegratedLogService($http, $log) { + + return listIntegratedLog; + + function listIntegratedLog(lines) { + $log.info('Get recent logs of the projects which the user is a member of:'); + return $http + .get('/api/logs', { + 'params' : { + 'lines': lines, + } + }); + + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/services/log/services.list-log.js b/static/resources/js/services/log/services.list-log.js new file mode 100644 index 000000000..732c36c50 --- /dev/null +++ b/static/resources/js/services/log/services.list-log.js @@ -0,0 +1,46 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.log') + .factory('ListLogService', ListLogService); + + ListLogService.$inject = ['$http', '$log']; + + function ListLogService($http, $log) { + + return LogResult; + + function LogResult(queryParams) { + var projectId = queryParams.projectId; + var username = queryParams.username; + var beginTimestamp = queryParams.beginTimestamp; + var endTimestamp = queryParams.endTimestamp; + var keywords = queryParams.keywords; + + return $http + .post('/api/projects/' + projectId + '/logs/filter', { + 'begin_timestamp' : beginTimestamp, + 'end_timestamp' : endTimestamp, + 'keywords' : keywords, + 'project_id': Number(projectId), + 'username' : username + }); + } + } +})(); \ No newline at end of file diff --git a/static/resources/js/services/log/services.log.module.js b/static/resources/js/services/log/services.log.module.js new file mode 100644 index 000000000..d07727291 --- /dev/null +++ b/static/resources/js/services/log/services.log.module.js @@ -0,0 +1,22 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.log', []); + +})(); \ No newline at end of file diff --git a/static/resources/js/services/project-member/services.add-project-member.js b/static/resources/js/services/project-member/services.add-project-member.js new file mode 100644 index 000000000..a25b9ebfd --- /dev/null +++ b/static/resources/js/services/project-member/services.add-project-member.js @@ -0,0 +1,39 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.project.member') + .factory('AddProjectMemberService', AddProjectMemberService); + + AddProjectMemberService.$inject = ['$http', '$log']; + + function AddProjectMemberService($http, $log) { + + return AddProjectMember; + + function AddProjectMember(projectId, roles, username) { + return $http + .post('/api/projects/' + projectId + '/members/', { + 'roles': [ Number(roles) ], + 'username': username + }); + } + + } + +})(); \ No newline at end of file diff --git a/static/resources/js/services/project-member/services.current-project-member.js b/static/resources/js/services/project-member/services.current-project-member.js new file mode 100644 index 000000000..1be51ed92 --- /dev/null +++ b/static/resources/js/services/project-member/services.current-project-member.js @@ -0,0 +1,34 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.project.member') + .factory('CurrentProjectMemberService', CurrentProjectMemberService); + + CurrentProjectMemberService.$inject = ['$http', '$log']; + + function CurrentProjectMemberService($http, $log) { + return currentProjectMember; + + function currentProjectMember(projectId) { + return $http + .get('/api/projects/' + projectId + '/members/current'); + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/services/project-member/services.delete-project-member.js b/static/resources/js/services/project-member/services.delete-project-member.js new file mode 100644 index 000000000..a1df3da11 --- /dev/null +++ b/static/resources/js/services/project-member/services.delete-project-member.js @@ -0,0 +1,36 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.project.member') + .factory('DeleteProjectMemberService', DeleteProjectMemberService); + + DeleteProjectMemberService.$inject = ['$http', '$log']; + + function DeleteProjectMemberService($http, $log) { + + return DeleteProjectMember; + + function DeleteProjectMember(projectId, userId) { + return $http + .delete('/api/projects/' + projectId + '/members/' + userId); + } + + } + +})(); \ No newline at end of file diff --git a/static/resources/js/services/project-member/services.edit-project-member.js b/static/resources/js/services/project-member/services.edit-project-member.js new file mode 100644 index 000000000..c84a239c1 --- /dev/null +++ b/static/resources/js/services/project-member/services.edit-project-member.js @@ -0,0 +1,38 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.project.member') + .factory('EditProjectMemberService', EditProjectMemberService); + + EditProjectMemberService.$inject = ['$http', '$log']; + + function EditProjectMemberService($http, $log) { + + return EditProjectMember; + + function EditProjectMember(projectId, userId, roleId) { + return $http + .put('/api/projects/' + projectId + '/members/' + userId, { + 'roles' : [ Number(roleId) ] + }); + } + + } + +})(); \ No newline at end of file diff --git a/static/resources/js/services/project-member/services.list-project-member.js b/static/resources/js/services/project-member/services.list-project-member.js new file mode 100644 index 000000000..5e15d8753 --- /dev/null +++ b/static/resources/js/services/project-member/services.list-project-member.js @@ -0,0 +1,41 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.project.member') + .service('ListProjectMemberService', ListProjectMemberService); + + ListProjectMemberService.$inject = ['$http', '$log']; + + function ListProjectMemberService($http, $log) { + + return ListProjectMember; + + function ListProjectMember(projectId, queryParams) { + console.log('project_member project_id:' + projectId); + var username = queryParams.username; + return $http + .get('/api/projects/' + projectId + '/members', { + params: { + 'username': username + } + }); + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/services/project-member/services.project-member.module.js b/static/resources/js/services/project-member/services.project-member.module.js new file mode 100644 index 000000000..e2e4bb413 --- /dev/null +++ b/static/resources/js/services/project-member/services.project-member.module.js @@ -0,0 +1,22 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.project.member', []); + +})(); \ No newline at end of file diff --git a/static/resources/js/services/project/services.add-project.js b/static/resources/js/services/project/services.add-project.js new file mode 100644 index 000000000..cb1986daa --- /dev/null +++ b/static/resources/js/services/project/services.add-project.js @@ -0,0 +1,37 @@ +/* + 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. +*/ +(function() { + 'use strict'; + + angular + .module('harbor.services.project') + .factory('AddProjectService', AddProjectService); + + AddProjectService.$inject = ['$http', '$log']; + + function AddProjectService($http, $log) { + + return AddProject; + + function AddProject(projectName, isPublic) { + return $http + .post('/api/projects', { + 'project_name': projectName, + 'public': isPublic + }); + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/services/project/services.edit-project.js b/static/resources/js/services/project/services.edit-project.js new file mode 100644 index 000000000..cd5e3584e --- /dev/null +++ b/static/resources/js/services/project/services.edit-project.js @@ -0,0 +1,36 @@ +/* + 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. +*/ +(function() { + 'use strict'; + + angular + .module('harbor.services.project') + .factory('EditProjectService', EditProjectService); + + EditProjectService.$inject = ['$http', '$log']; + + function EditProjectService($http, $log) { + + return EditProject; + + function EditProject(projectId, isPublic) { + return $http + .put('/api/projects/' + projectId, { + 'public': isPublic + }); + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/services/project/services.get-project-by-id.js b/static/resources/js/services/project/services.get-project-by-id.js new file mode 100644 index 000000000..feb0ef215 --- /dev/null +++ b/static/resources/js/services/project/services.get-project-by-id.js @@ -0,0 +1,36 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.project') + .factory('GetProjectById', GetProjectById); + + GetProjectById.$inject = ['$http']; + + function GetProjectById($http) { + + return getProject; + + function getProject(id) { + return $http + .get('/api/projects/' + id); + } + + } + +})(); \ No newline at end of file diff --git a/static/resources/js/services/project/services.list-project.js b/static/resources/js/services/project/services.list-project.js new file mode 100644 index 000000000..91d5d5dbe --- /dev/null +++ b/static/resources/js/services/project/services.list-project.js @@ -0,0 +1,40 @@ +/* + 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. +*/ +(function() { + 'use strict'; + + angular + .module('harbor.services.project') + .factory('ListProjectService', ListProjectService); + + ListProjectService.$inject = ['$http', '$log']; + + function ListProjectService($http, $log) { + + return ListProject; + + function ListProject(projectName, isPublic) { + $log.info('list project projectName:' + projectName, ', isPublic:' + isPublic); + return $http + .get('/api/projects', { + 'params' : { + 'is_public': isPublic, + 'project_name': projectName + } + }); + + } + } +})(); \ No newline at end of file diff --git a/static/resources/js/services/project/services.project.module.js b/static/resources/js/services/project/services.project.module.js new file mode 100644 index 000000000..f9a3c54a3 --- /dev/null +++ b/static/resources/js/services/project/services.project.module.js @@ -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. +*/ +(function() { + 'use strict'; + + angular + .module('harbor.services.project', []); + +})(); \ No newline at end of file diff --git a/static/resources/js/services/project/services.stat-project.js b/static/resources/js/services/project/services.stat-project.js new file mode 100644 index 000000000..e471a162f --- /dev/null +++ b/static/resources/js/services/project/services.stat-project.js @@ -0,0 +1,37 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.project') + .factory('StatProjectService', StatProjectService); + + StatProjectService.$inject = ['$http', '$log']; + + function StatProjectService($http, $log) { + + return StatProject; + + function StatProject() { + $log.info('statistics projects and repositories'); + return $http + .get('/api/statistics'); + } + + } + +})(); \ No newline at end of file diff --git a/static/resources/js/services/project/services.toggle-project-publicity.js b/static/resources/js/services/project/services.toggle-project-publicity.js new file mode 100644 index 000000000..dfb9f5ccb --- /dev/null +++ b/static/resources/js/services/project/services.toggle-project-publicity.js @@ -0,0 +1,36 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.project') + .factory('ToggleProjectPublicityService', ToggleProjectPublicityService); + + ToggleProjectPublicityService.$inject = ['$http']; + + function ToggleProjectPublicityService($http) { + return toggleProjectPublicity; + function toggleProjectPublicity(projectId, isPublic) { + return $http + .put('/api/projects/' + projectId + '/publicity', { + 'public': isPublic + }); + } + + } + +})(); \ No newline at end of file diff --git a/static/resources/js/services/replication-job/services.list-replication-job.js b/static/resources/js/services/replication-job/services.list-replication-job.js new file mode 100644 index 000000000..231da6608 --- /dev/null +++ b/static/resources/js/services/replication-job/services.list-replication-job.js @@ -0,0 +1,42 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.replication.job') + .factory('ListReplicationJobService', ListReplicationJobService); + + ListReplicationJobService.$inject = ['$http']; + + function ListReplicationJobService($http) { + + return listReplicationJob; + + function listReplicationJob(policyId, repository, status, startTime, endTime) { + return $http + .get('/api/jobs/replication/', { + 'params': { + 'policy_id': policyId, + 'repository': repository, + 'status': status, + 'start_time': startTime, + 'end_time': endTime + } + }); + } + } +})(); \ No newline at end of file diff --git a/static/resources/js/services/replication-job/services.replication-job.module.js b/static/resources/js/services/replication-job/services.replication-job.module.js new file mode 100644 index 000000000..9b78fcd45 --- /dev/null +++ b/static/resources/js/services/replication-job/services.replication-job.module.js @@ -0,0 +1,22 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.replication.job', []); + +})(); \ No newline at end of file diff --git a/static/resources/js/services/replication-policy/services.create-replication-policy.js b/static/resources/js/services/replication-policy/services.create-replication-policy.js new file mode 100644 index 000000000..96e7c1c77 --- /dev/null +++ b/static/resources/js/services/replication-policy/services.create-replication-policy.js @@ -0,0 +1,42 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.replication.policy') + .factory('CreateReplicationPolicyService', CreateReplicationPolicyService); + + CreateReplicationPolicyService.$inject = ['$http']; + + function CreateReplicationPolicyService($http) { + return createReplicationPolicy; + + function createReplicationPolicy(policy) { + return $http + .post('/api/policies/replication', { + 'project_id': policy.projectId, + 'target_id': policy.targetId, + 'name': policy.name, + 'enabled': policy.enabled, + 'description': policy.description, + 'cron_str': policy.cronStr, + 'start_time': policy.startTime + }) + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/services/replication-policy/services.list-replication-policy.js b/static/resources/js/services/replication-policy/services.list-replication-policy.js new file mode 100644 index 000000000..dc93aa18b --- /dev/null +++ b/static/resources/js/services/replication-policy/services.list-replication-policy.js @@ -0,0 +1,41 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.replication.policy') + .factory('ListReplicationPolicyService', ListReplicationPolicyService); + + ListReplicationPolicyService.$inject = ['$http']; + + function ListReplicationPolicyService($http) { + + return listReplicationPolicy; + + function listReplicationPolicy(policyId, projectId, name) { + return $http + .get('/api/policies/replication/' + policyId, { + 'params': { + 'project_id': projectId, + 'name': name + } + }); + } + + } + +})(); \ No newline at end of file diff --git a/static/resources/js/services/replication-policy/services.replication-policy.module.js b/static/resources/js/services/replication-policy/services.replication-policy.module.js new file mode 100644 index 000000000..f01f674b2 --- /dev/null +++ b/static/resources/js/services/replication-policy/services.replication-policy.module.js @@ -0,0 +1,22 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.replication.policy', []); + +})(); \ No newline at end of file diff --git a/static/resources/js/services/replication-policy/services.toggle-replication-policy.js b/static/resources/js/services/replication-policy/services.toggle-replication-policy.js new file mode 100644 index 000000000..635aa4f6f --- /dev/null +++ b/static/resources/js/services/replication-policy/services.toggle-replication-policy.js @@ -0,0 +1,35 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.replication.policy') + .factory('ToggleReplicationPolicyService', ToggleReplicationPolicyService); + + ToggleReplicationPolicyService.$inject = ['$http']; + + function ToggleReplicationPolicyService($http) { + return toggleReplicationPolicy; + function toggleReplicationPolicy(policyId, enabled) { + return $http + .put('/api/policies/replication/' + policyId + '/enablement', { + 'enabled': enabled + }); + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/services/replication-policy/services.update-replication-policy.js b/static/resources/js/services/replication-policy/services.update-replication-policy.js new file mode 100644 index 000000000..fb49aeeac --- /dev/null +++ b/static/resources/js/services/replication-policy/services.update-replication-policy.js @@ -0,0 +1,38 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.replication.policy') + .factory('UpdateReplicationPolicyService', UpdateReplicationPolicyService); + + UpdateReplicationPolicyService.$inject = ['$http']; + + function UpdateReplicationPolicyService($http) { + return updateReplicationPolicy; + function updateReplicationPolicy(policyId, policy) { + return $http + .put('/api/policies/replication/' + policyId, { + 'name': policy.name, + 'description': policy.description, + 'enabled': policy.enabled, + 'target_id': policy.targetId + }); + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/services/repository/services.delete-repository.js b/static/resources/js/services/repository/services.delete-repository.js new file mode 100644 index 000000000..7d2e39ef0 --- /dev/null +++ b/static/resources/js/services/repository/services.delete-repository.js @@ -0,0 +1,36 @@ +/* + 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. +*/ +(function() { + 'use strict'; + + angular + .module('harbor.services.repository') + .factory('DeleteRepositoryService', DeleteRepositoryService); + + DeleteRepositoryService.$inject = ['$http', '$log']; + + function DeleteRepositoryService($http, $log) { + + return DeleteRepository; + + function DeleteRepository(repoName, tag) { + var params = (tag === '') ? {'repo_name' : repoName} : {'repo_name': repoName, 'tag': tag}; + return $http + .delete('/api/repositories', { + 'params': params + }); + } + } +})(); \ No newline at end of file diff --git a/static/resources/js/services/repository/services.list-manifest.js b/static/resources/js/services/repository/services.list-manifest.js new file mode 100644 index 000000000..b94d70c0c --- /dev/null +++ b/static/resources/js/services/repository/services.list-manifest.js @@ -0,0 +1,38 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.repository') + .factory('ListManifestService', ListManifestService); + + ListManifestService.$inject = ['$http', '$log']; + + function ListManifestService($http, $log) { + return ListManifest; + function ListManifest(repoName, tag) { + return $http + .get('/api/repositories/manifests', { + 'params': { + 'repo_name': repoName, + 'tag': tag + } + }); + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/services/repository/services.list-repository.js b/static/resources/js/services/repository/services.list-repository.js new file mode 100644 index 000000000..5cdb5cc6b --- /dev/null +++ b/static/resources/js/services/repository/services.list-repository.js @@ -0,0 +1,40 @@ +/* + 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. +*/ +(function() { + 'use strict'; + + angular + .module('harbor.services.repository') + .factory('ListRepositoryService', ListRepositoryService); + + ListRepositoryService.$inject = ['$http', '$log']; + + function ListRepositoryService($http, $log) { + + return ListRepository; + + function ListRepository(projectId, q) { + $log.info('list repositories:' + projectId + ', q:' + q); + + return $http + .get('/api/repositories', { + 'params':{ + 'project_id': projectId, + 'q': q + } + }); + } + } +})(); \ No newline at end of file diff --git a/static/resources/js/services/repository/services.list-tag.js b/static/resources/js/services/repository/services.list-tag.js new file mode 100644 index 000000000..74abd5026 --- /dev/null +++ b/static/resources/js/services/repository/services.list-tag.js @@ -0,0 +1,39 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.repository') + .factory('ListTagService', ListTagService); + + ListTagService.$inject = ['$http', '$log']; + + function ListTagService($http, $log) { + return ListTag; + + function ListTag(repoName) { + return $http + .get('/api/repositories/tags', { + 'params': { + 'repo_name': repoName + } + }); + } + } + + +})(); \ No newline at end of file diff --git a/static/resources/js/services/repository/services.list-top-repository.js b/static/resources/js/services/repository/services.list-top-repository.js new file mode 100644 index 000000000..6cd7ba97d --- /dev/null +++ b/static/resources/js/services/repository/services.list-top-repository.js @@ -0,0 +1,40 @@ +/* + 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. +*/ +(function() { + 'use strict'; + + angular + .module('harbor.services.repository') + .factory('ListTopRepositoryService', ListTopRepositoryService); + + ListTopRepositoryService.$inject = ['$http', '$log']; + + function ListTopRepositoryService($http, $log) { + + return listTopRepository; + + function listTopRepository(count) { + $log.info('Get public repositories which are accessed most:'); + return $http + .get('/api/repositories/top', { + 'params' : { + 'count': count, + } + }); + + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/services/repository/services.repository.module.js b/static/resources/js/services/repository/services.repository.module.js new file mode 100644 index 000000000..4a638d38c --- /dev/null +++ b/static/resources/js/services/repository/services.repository.module.js @@ -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. +*/ +(function() { + 'use strict'; + + angular + .module('harbor.services.repository', []); + +})(); \ No newline at end of file diff --git a/static/resources/js/services/search/services.search.js b/static/resources/js/services/search/services.search.js new file mode 100644 index 000000000..432310eea --- /dev/null +++ b/static/resources/js/services/search/services.search.js @@ -0,0 +1,40 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.search') + .factory('SearchService', SearchService); + + SearchService.$inject = ['$http', '$log']; + + function SearchService($http, $log) { + + return search; + + function search(keywords) { + return $http + .get('/api/search',{ + params: { + 'q': keywords + } + }); + } + + } + +})(); \ No newline at end of file diff --git a/static/resources/js/services/search/services.search.module.js b/static/resources/js/services/search/services.search.module.js new file mode 100644 index 000000000..d2dbb4680 --- /dev/null +++ b/static/resources/js/services/search/services.search.module.js @@ -0,0 +1,22 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.search', []); + +})(); \ No newline at end of file diff --git a/static/resources/js/services/user/services.change-password.js b/static/resources/js/services/user/services.change-password.js new file mode 100644 index 000000000..21fc8868c --- /dev/null +++ b/static/resources/js/services/user/services.change-password.js @@ -0,0 +1,39 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.user') + .factory('ChangePasswordService', ChangePasswordService); + + ChangePasswordService.$inject = ['$http', '$log']; + + function ChangePasswordService($http, $log) { + + return ChangePassword; + + function ChangePassword(userId, oldPassword, newPassword) { + return $http + .put('/api/users/' + userId + '/password', { + 'old_password': oldPassword, + 'new_password': newPassword + }); + } + + } + +})(); \ No newline at end of file diff --git a/static/resources/js/services/user/services.current-user.js b/static/resources/js/services/user/services.current-user.js new file mode 100644 index 000000000..577f53dca --- /dev/null +++ b/static/resources/js/services/user/services.current-user.js @@ -0,0 +1,34 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.user') + .factory('CurrentUserService', CurrentUserService); + + CurrentUserService.$inject = ['$http']; + + function CurrentUserService($http, $log) { + + return CurrentUser; + + function CurrentUser() { + return $http + .get('/api/users/current'); + } + } +})(); \ No newline at end of file diff --git a/static/resources/js/services/user/services.delete-user.js b/static/resources/js/services/user/services.delete-user.js new file mode 100644 index 000000000..85d5dd43a --- /dev/null +++ b/static/resources/js/services/user/services.delete-user.js @@ -0,0 +1,36 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.user') + .factory('DeleteUserService', DeleteUserService); + + DeleteUserService.$inject = ['$http', '$log']; + + function DeleteUserService($http, $log) { + + return DeleteUser; + + function DeleteUser(userId) { + return $http + .delete('/api/users/' + userId); + } + + } + +})(); \ No newline at end of file diff --git a/static/resources/js/services/user/services.is-admin.js b/static/resources/js/services/user/services.is-admin.js new file mode 100644 index 000000000..8375f2f14 --- /dev/null +++ b/static/resources/js/services/user/services.is-admin.js @@ -0,0 +1,35 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.user') + .factory('IsAdminService', IsAdminService); + + IsAdminService.$inject = ['$http', '$log']; + + function IsAdminService($http, $log) { + + return IsAdmin; + + function IsAdmin() { + + } + + } + +}) \ No newline at end of file diff --git a/static/resources/js/header-login.js b/static/resources/js/services/user/services.list-user.js similarity index 58% rename from static/resources/js/header-login.js rename to static/resources/js/services/user/services.list-user.js index 1bd5cda0d..fda39c897 100644 --- a/static/resources/js/header-login.js +++ b/static/resources/js/services/user/services.list-user.js @@ -12,21 +12,27 @@ See the License for the specific language governing permissions and limitations under the License. */ -jQuery(function(){ - $("#btnSignUp").css({"visibility": "visible"}); - - $(document).on("keydown", function(e){ - if(e.keyCode == 13){ - e.preventDefault(); - if($("#txtCommonSearch").is(":focus")){ - document.location = "/search?q=" + $("#txtCommonSearch").val(); - } - } - }); - $("#btnSignIn").on("click", function(){ - document.location = "/signIn"; - }); - $("#btnSignUp").on("click", function(){ - document.location = "/register"; - }); -}); \ No newline at end of file +(function() { + + 'use strict'; + + angular + .module('harbor.services.user') + .factory('ListUserService', ListUserService); + + ListUserService.$inject = ['$http', '$log']; + + function ListUserService($http, $log) { + + return listUser; + + function listUser(username) { + return $http + .get('/api/users', { + 'params' : { + 'username': username + } + }); + } + } +})(); \ No newline at end of file diff --git a/static/resources/js/services/user/services.log-out.js b/static/resources/js/services/user/services.log-out.js new file mode 100644 index 000000000..69bfb4dd3 --- /dev/null +++ b/static/resources/js/services/user/services.log-out.js @@ -0,0 +1,33 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.user') + .factory('LogOutService', LogOutService); + + LogOutService.$inject = ['$http']; + + function LogOutService($http) { + return logOut; + function logOut() { + return $http + .get('/log_out'); + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/services/user/services.reset-password.js b/static/resources/js/services/user/services.reset-password.js new file mode 100644 index 000000000..638f8eaa3 --- /dev/null +++ b/static/resources/js/services/user/services.reset-password.js @@ -0,0 +1,44 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.user') + .factory('ResetPasswordService', ResetPasswordService); + + ResetPasswordService.$inject = ['$http', '$log']; + + function ResetPasswordService($http, $log) { + return resetPassword; + function resetPassword(uuid, password) { + return $http({ + method: 'POST', + url: '/reset', + headers: {'Content-Type': 'application/x-www-form-urlencoded'}, + transformRequest: function(obj) { + var str = []; + for(var p in obj) { + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); + } + return str.join("&"); + }, + data: {'reset_uuid': uuid, 'password': password} + }); + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/services/user/services.send-mail.js b/static/resources/js/services/user/services.send-mail.js new file mode 100644 index 000000000..dee323131 --- /dev/null +++ b/static/resources/js/services/user/services.send-mail.js @@ -0,0 +1,40 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.user') + .factory('SendMailService', SendMailService); + + SendMailService.$inject = ['$http', '$log']; + + function SendMailService($http, $log) { + + return SendMail; + + function SendMail(email) { + return $http + .get('/sendEmail', { + 'params': { + 'email': email + } + }); + } + + } + +})(); \ No newline at end of file diff --git a/static/resources/js/services/user/services.sign-in.js b/static/resources/js/services/user/services.sign-in.js new file mode 100644 index 000000000..05010cc4f --- /dev/null +++ b/static/resources/js/services/user/services.sign-in.js @@ -0,0 +1,45 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.user') + .factory('SignInService', SignInService); + + SignInService.$inject = ['$http', '$log']; + + function SignInService($http, $log) { + + return SignIn; + + function SignIn(principal, password) { + return $http({ + method: 'POST', + url: '/login', + headers: {'Content-Type': 'application/x-www-form-urlencoded'}, + transformRequest: function(obj) { + var str = []; + for(var p in obj) { + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); + } + return str.join("&"); + }, + data: {'principal': principal, 'password': password} + }); + } + } +})(); \ No newline at end of file diff --git a/static/resources/js/services/user/services.sign-up.js b/static/resources/js/services/user/services.sign-up.js new file mode 100644 index 000000000..099e18f45 --- /dev/null +++ b/static/resources/js/services/user/services.sign-up.js @@ -0,0 +1,40 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.user') + .factory('SignUpService', SignUpService); + + SignUpService.$inject = ['$http', '$log']; + + function SignUpService($http, $log) { + + return SignUp; + + function SignUp(user) { + return $http + .post('/api/users', { + 'username': user.username, + 'email': user.email, + 'password': user.password, + 'realname': user.realname, + 'comment': user.comment + }); + } + } +})(); \ No newline at end of file diff --git a/static/resources/js/services/user/services.toggle-admin.js b/static/resources/js/services/user/services.toggle-admin.js new file mode 100644 index 000000000..287d7e9ec --- /dev/null +++ b/static/resources/js/services/user/services.toggle-admin.js @@ -0,0 +1,38 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.user') + .factory('ToggleAdminService', ToggleAdminService); + + ToggleAdminService.$inject = ['$http']; + + function ToggleAdminService($http) { + + return toggleAdmin; + + function toggleAdmin(userId, enabled) { + return $http + .put('/api/users/' + userId + '/sysadmin', { + 'has_admin_role' : enabled + }); + } + + } + +})(); \ No newline at end of file diff --git a/static/resources/js/services/user/services.update-user.js b/static/resources/js/services/user/services.update-user.js new file mode 100644 index 000000000..97892f0b1 --- /dev/null +++ b/static/resources/js/services/user/services.update-user.js @@ -0,0 +1,38 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.user') + .factory('UpdateUserService', UpdateUserService); + + UpdateUserService.$inject = ['$http']; + + function UpdateUserService($http) { + return updateUser; + function updateUser(userId, user) { + return $http + .put('/api/users/' + userId, { + 'username': user.username, + 'email': user.email, + 'realname': user.realname, + 'comment': user.comment + }); + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/services/user/services.user-exist.js b/static/resources/js/services/user/services.user-exist.js new file mode 100644 index 000000000..ba8a503e3 --- /dev/null +++ b/static/resources/js/services/user/services.user-exist.js @@ -0,0 +1,37 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.user') + .factory('UserExistService', UserExistService); + + UserExistService.$inject = ['$http', '$log']; + + function UserExistService($http, $log) { + return userExist; + function userExist(target, value) { + return $.ajax({ + type: 'POST', + url: '/userExists', + async: false, + data: {'target': target, 'value': value} + }); + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/services/user/services.user.module.js b/static/resources/js/services/user/services.user.module.js new file mode 100644 index 000000000..36a9b9278 --- /dev/null +++ b/static/resources/js/services/user/services.user.module.js @@ -0,0 +1,22 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.services.user', []); + +})(); \ No newline at end of file diff --git a/static/resources/js/session/session.current-user.js b/static/resources/js/session/session.current-user.js new file mode 100644 index 000000000..9e51af4cd --- /dev/null +++ b/static/resources/js/session/session.current-user.js @@ -0,0 +1,47 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.session') + .controller('CurrentUserController', CurrentUserController); + + CurrentUserController.$inject = ['$scope', 'CurrentUserService', 'currentUser', '$window', '$document']; + + function CurrentUserController($scope, CurrentUserService, currentUser, $window, $document) { + + var vm = this; + + CurrentUserService() + .then(getCurrentUserComplete) + .catch(getCurrentUserFailed); + + function getCurrentUserComplete(response) { + if(angular.isDefined(response)) { + currentUser.set(response.data); + if(location.pathname === '/') { + $window.location.href = '/dashboard'; + } + } + } + + function getCurrentUserFailed(e){ + console.log('No session of current user.'); + } + } + +})(); \ No newline at end of file diff --git a/static/resources/js/session/session.module.js b/static/resources/js/session/session.module.js new file mode 100644 index 000000000..8158793bd --- /dev/null +++ b/static/resources/js/session/session.module.js @@ -0,0 +1,24 @@ +/* + 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. +*/ +(function() { + + 'use strict'; + + angular + .module('harbor.session', [ + 'harbor.services.user' + ]); + +})(); \ No newline at end of file diff --git a/static/resources/js/sign-in.js b/static/resources/js/sign-in.js deleted file mode 100644 index 2aaa24c5f..000000000 --- a/static/resources/js/sign-in.js +++ /dev/null @@ -1,79 +0,0 @@ -/* - 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. -*/ -jQuery(function(){ - - new AjaxUtil({ - url: "/api/users/current", - type: "get", - success: function(data, status, xhr){ - if(xhr && xhr.status == 200){ - document.location = "/registry/project"; - } - }, - error: function(jqxhr){ - return false; - } - }).exec(); - - $(document).on("keydown", function(e){ - if(e.keyCode == 13){ - e.preventDefault(); - if($("#Principal").is(":focus") || $("#Password").is(":focus") || $("#btnPageSignIn").is(":focus")){ - $("#btnPageSignIn").trigger("click"); - } - } - }); - $("#btnForgot").on("click", function(){ - document.location = "/forgotPassword"; - }); - - $("#btnPageSignIn").on("click", function(){ - - var principal = $.trim($("#Principal").val()); - var password = $.trim($("#Password").val()); - - if($.trim(principal).length <= 0 || $.trim(password).length <= 0) { - $("#dlgModal").dialogModal({"title": i18n.getMessage("title_login_failed"), "content": i18n.getMessage("input_your_username_and_password")}); - return; - } - - $.ajax({ - url:'/login', - data: {principal: principal, password: password}, - type: "post", - success: function(jqXhr, status){ - var lastUri = location.search; - if(lastUri != "" && lastUri.indexOf("=") > 0){ - document.location = decodeURIComponent(lastUri.split("=")[1]); - }else{ - document.location = "/registry/project"; - } - }, - error: function(jqXhr){ - var i18nKey = ""; - if(jqXhr.status == 500){ - i18nKey = "internal_error"; - }else{ - i18nKey = "check_your_username_or_password" - } - $("#dlgModal") - .dialogModal({ - "title": i18n.getMessage("title_login_failed"), - "content": i18n.getMessage(i18nKey) - }); - } - }); - }); -}); \ No newline at end of file diff --git a/static/resources/js/validate-options.js b/static/resources/js/validate-options.js deleted file mode 100644 index 492366ca7..000000000 --- a/static/resources/js/validate-options.js +++ /dev/null @@ -1,183 +0,0 @@ -/* - 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. -*/ -var validateOptions = { - "Result" : [], - "Items" : [], - "Validate": function(callback){ - for(var i = 0; i < this.Items.length; i++){ - if(validateCallback(this.Items[i]) == false){ - return false; - } - } - callback(); - }, - "Username" :{ - "Required": { "value" : true, "errMsg" : i18n.getMessage("username_is_required")}, - "CheckExist": { "value" : function(value){ - var result = true; - $.ajax({ - url: "/userExists", - data: {"target": "username", "value" : value}, - dataType: "json", - type: "post", - async: false, - success: function(data){ - result = data; - } - }); - return result; - }, "errMsg" : i18n.getMessage("username_has_been_taken")}, - "MaxLength": {"value" : 20, "errMsg" : i18n.getMessage("username_is_too_long")}, - "IllegalChar": {"value": [",","~","#", "$", "%"] , "errMsg": i18n.getMessage("username_contains_illegal_chars")} - }, - "Email" :{ - "Required": { "value" : true, "errMsg" : i18n.getMessage("email_is_required")}, - "RegExp": {"value": /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, - "errMsg": i18n.getMessage("email_contains_illegal_chars")}, - "CheckExist": { "value" : function(value){ - var result = true; - $.ajax({ - url: "/userExists", - data: {"target": "email", "value": value}, - dataType: "json", - type: "post", - async: false, - success: function(data){ - result = data; - } - }); - return result; - }, "errMsg" : i18n.getMessage("email_has_been_taken")} - }, - "EmailF" :{ - "Required": { "value" : true, "errMsg" : i18n.getMessage("email_is_required")}, - "RegExp": {"value": /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, - "errMsg": i18n.getMessage("email_content_illegal")}, - "CheckIfNotExist": { "value" : function(value){ - var result = true; - $.ajax({ - url: "/userExists", - data: {"target": "email", "value": value}, - dataType: "json", - type: "post", - async: false, - success: function(data){ - result = data; - } - }); - return result; - }, "errMsg" : i18n.getMessage("email_does_not_exist")} - }, - "Realname" :{ - "Required": { "value" : true, "errMsg" : i18n.getMessage("realname_is_required")}, - "MaxLength": {"value" : 20, "errMsg" : i18n.getMessage("realname_is_too_long")}, - "IllegalChar": {"value": [",","~","#", "$", "%"] , "errMsg": i18n.getMessage("realname_contains_illegal_chars")} - }, - "OldPassword" :{ - "Required": { "value" : true, "errMsg" : i18n.getMessage("password_is_required")} - }, - "Password" :{ - "Required": { "value" : true, "errMsg" : i18n.getMessage("password_is_required")}, - "RegExp": {"value" : /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?!.*\s).{7,20}$/, "errMsg" : i18n.getMessage("password_is_invalid")}, - "MaxLength": {"value" : 20, "errMsg" : i18n.getMessage("password_is_too_long")} - }, - "ConfirmedPassword" :{ - "CompareWith": {"value" : "#Password", "errMsg" : i18n.getMessage("password_does_not_match")}, - "RegExp": {"value" : /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?!.*\s).{7,20}$/, "errMsg" : i18n.getMessage("password_is_invalid")} - }, - "Comment" :{ - "MaxLength": {"value" : 20, "errMsg" : i18n.getMessage("comment_is_too_long")}, - "IllegalChar": {"value": [",","~","#", "$", "%"] , "errMsg": i18n.getMessage("comment_contains_illegal_chars")} - }, - "projectName" :{ - "Required": { "value" : true, "errMsg" : i18n.getMessage("project_name_is_required")}, - "MinLength": {"value" : 4, "errMsg" : i18n.getMessage("project_name_is_too_short")}, - "MaxLength": {"value" : 30, "errMsg" : i18n.getMessage("project_name_is_too_long")}, - "IllegalChar": {"value": ["~","$","-", "\\", "[", "]", "{", "}", "(", ")", "&", "^", "%", "*", "<", ">", "\"", "'","/","?","@"] , "errMsg": i18n.getMessage("project_name_contains_illegal_chars")} - } -}; -function validateCallback(target){ - - if (typeof target != "string"){ - target = this; - } - - var isValid = true; - var inputValue = $.trim($(target).val()); - var currentId = $(target).attr("id"); - var validateItem = validateOptions[currentId]; - - var errMsg = ""; - - for(var checkTitle in validateItem){ - - var checkValue = validateItem[checkTitle].value; - - if(checkTitle == "Required" && checkValue && inputValue.length == 0){ - isValid = false; - errMsg = validateItem[checkTitle].errMsg; - break; - }else if(checkTitle == "CheckOldPasswordIsCorrect" && checkValue(inputValue) == false){ - isValid = false; - errMsg = validateItem[checkTitle].errMsg; - break; - }else if(checkTitle == "CheckExist" && checkValue(inputValue) == true){ - isValid = false; - errMsg = validateItem[checkTitle].errMsg; - break; - }else if(checkTitle == "CheckIfNotExist" && checkValue(inputValue) == false){ - isValid = false; - errMsg = validateItem[checkTitle].errMsg; - break; - }else if(checkTitle == "RegExp" && checkValue.test(inputValue) == false){ - isValid = false; - errMsg = validateItem[checkTitle].errMsg; - break; - }else if(checkTitle == "MinLength" && inputValue.length < checkValue){ - isValid = false; - errMsg = validateItem[checkTitle].errMsg; - break; - }else if(checkTitle == "MaxLength" && inputValue.length > checkValue){ - isValid = false; - errMsg = validateItem[checkTitle].errMsg; - break; - }else if(checkTitle == "CompareWith" && $.trim($(checkValue).val()).length > 0 && inputValue != $.trim($(checkValue).val())){ - isValid = false; - errMsg = validateItem[checkTitle].errMsg; - break; - }else if(checkTitle == "IllegalChar"){ - for(var i = 0; i < checkValue.length; i++){ - if(inputValue.indexOf(checkValue[i]) > -1){ - isValid = false; - errMsg = validateItem[checkTitle].errMsg; - } - } - break; - } - } - - if(isValid == false){ - $(target).parent().removeClass("has-success").addClass("has-error"); - $(target).siblings("span").removeClass("glyphicon-ok").addClass("glyphicon-warning-sign"); - $("#divErrMsg").css({"display": "block"}); - $("#divErrMsg").text(errMsg); - }else { - $(target).parent().removeClass("has-error").addClass("has-success"); - $(target).siblings("span").removeClass("glyphicon-warning-sign").addClass("glyphicon-ok"); - $("#divErrMsg").css({"display": "none"}); - } - validateOptions.Result.push(isValid); - return isValid; -} \ No newline at end of file diff --git a/static/vendors/angularjs/angular-cookies.min.js b/static/vendors/angularjs/angular-cookies.min.js new file mode 100644 index 000000000..38eec5bf3 --- /dev/null +++ b/static/vendors/angularjs/angular-cookies.min.js @@ -0,0 +1,9 @@ +/* + AngularJS v1.5.3 + (c) 2010-2016 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(p,c,n){'use strict';function l(b,a,g){var d=g.baseHref(),k=b[0];return function(b,e,f){var g,h;f=f||{};h=f.expires;g=c.isDefined(f.path)?f.path:d;c.isUndefined(e)&&(h="Thu, 01 Jan 1970 00:00:00 GMT",e="");c.isString(h)&&(h=new Date(h));e=encodeURIComponent(b)+"="+encodeURIComponent(e);e=e+(g?";path="+g:"")+(f.domain?";domain="+f.domain:"");e+=h?";expires="+h.toUTCString():"";e+=f.secure?";secure":"";f=e.length+1;4096 4096 bytes)!");k.cookie=e}}c.module("ngCookies",["ng"]).provider("$cookies",[function(){var b=this.defaults={};this.$get=["$$cookieReader","$$cookieWriter",function(a,g){return{get:function(d){return a()[d]},getObject:function(d){return(d=this.get(d))?c.fromJson(d):d},getAll:function(){return a()},put:function(d,a,m){g(d,a,m?c.extend({},b,m):b)},putObject:function(d,b,a){this.put(d,c.toJson(b),a)},remove:function(a,k){g(a,n,k?c.extend({},b,k):b)}}}]}]);c.module("ngCookies").factory("$cookieStore", +["$cookies",function(b){return{get:function(a){return b.getObject(a)},put:function(a,c){b.putObject(a,c)},remove:function(a){b.remove(a)}}}]);l.$inject=["$document","$log","$browser"];c.module("ngCookies").provider("$$cookieWriter",function(){this.$get=l})})(window,window.angular); +//# sourceMappingURL=angular-cookies.min.js.map diff --git a/static/vendors/angularjs/angular-messages.min.js b/static/vendors/angularjs/angular-messages.min.js new file mode 100644 index 000000000..e3094439e --- /dev/null +++ b/static/vendors/angularjs/angular-messages.min.js @@ -0,0 +1,12 @@ +/* + AngularJS v1.5.3 + (c) 2010-2016 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(A,d,B){'use strict';function p(){return["$animate",function(w){return{restrict:"AE",transclude:"element",priority:1,terminal:!0,require:"^^ngMessages",link:function(n,l,a,c,m){var k=l[0],f,q=a.ngMessage||a.when;a=a.ngMessageExp||a.whenExp;var d=function(a){f=a?x(a)?a:a.split(/[\s,]+/):null;c.reRender()};a?(d(n.$eval(a)),n.$watchCollection(a,d)):d(q);var e,r;c.register(k,r={test:function(a){var g=f;a=g?x(g)?0<=g.indexOf(a):g.hasOwnProperty(a):void 0;return a},attach:function(){e||m(n,function(a){w.enter(a, +null,l);e=a;var g=e.$$attachId=c.getAttachId();e.on("$destroy",function(){e&&e.$$attachId===g&&(c.deregister(k),r.detach())})})},detach:function(){if(e){var a=e;e=null;w.leave(a)}}})}}}]}var x=d.isArray,t=d.forEach,y=d.isString,z=d.element;d.module("ngMessages",[]).directive("ngMessages",["$animate",function(d){function n(a,c){return y(c)&&0===c.length||l(a.$eval(c))}function l(a){return y(a)?a.length:!!a}return{require:"ngMessages",restrict:"AE",controller:["$element","$scope","$attrs",function(a, +c,m){function k(a,c){for(var b=c,f=[];b&&b!==a;){var h=b.$$ngMessageNode;if(h&&h.length)return e[h];b.childNodes.length&&-1==f.indexOf(b)?(f.push(b),b=b.childNodes[b.childNodes.length-1]):b.previousSibling?b=b.previousSibling:(b=b.parentNode,f.push(b))}}var f=this,q=0,p=0;this.getAttachId=function(){return p++};var e=this.messages={},r,s;this.render=function(g){g=g||{};r=!1;s=g;for(var e=n(c,m.ngMessagesMultiple)||n(c,m.multiple),b=[],q={},h=f.head,k=!1,p=0;null!=h;){p++;var u=h.message,v=!1;k||t(g, +function(a,b){!v&&l(a)&&u.test(b)&&!q[b]&&(v=q[b]=!0,u.attach())});v?k=!e:b.push(u);h=h.next}t(b,function(a){a.detach()});b.length!==p?d.setClass(a,"ng-active","ng-inactive"):d.setClass(a,"ng-inactive","ng-active")};c.$watchCollection(m.ngMessages||m["for"],f.render);a.on("$destroy",function(){t(e,function(a){a.message.detach()})});this.reRender=function(){r||(r=!0,c.$evalAsync(function(){r&&s&&f.render(s)}))};this.register=function(g,c){var b=q.toString();e[b]={message:c};var d=a[0],h=e[b];f.head? +(d=k(d,g))?(h.next=d.next,d.next=h):(h.next=f.head,f.head=h):f.head=h;g.$$ngMessageNode=b;q++;f.reRender()};this.deregister=function(c){var d=c.$$ngMessageNode;delete c.$$ngMessageNode;var b=e[d];(c=k(a[0],c))?c.next=b.next:f.head=b.next;delete e[d];f.reRender()}}]}}]).directive("ngMessagesInclude",["$templateRequest","$document","$compile",function(d,n,l){return{restrict:"AE",require:"^^ngMessages",link:function(a,c,m){var k=m.ngMessagesInclude||m.src;d(k).then(function(d){l(d)(a,function(a){c.after(a); +a=l.$$createComment?l.$$createComment("ngMessagesInclude",k):n[0].createComment(" ngMessagesInclude: "+k+" ");a=z(a);c.after(a);c.remove()})})}}}]).directive("ngMessage",p()).directive("ngMessageExp",p())})(window,window.angular); +//# sourceMappingURL=angular-messages.min.js.map diff --git a/static/vendors/angularjs/angular.min.js b/static/vendors/angularjs/angular.min.js new file mode 100644 index 000000000..ecdf96736 --- /dev/null +++ b/static/vendors/angularjs/angular.min.js @@ -0,0 +1,311 @@ +/* + AngularJS v1.5.3 + (c) 2010-2016 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(T,P,u){'use strict';function O(a){return function(){var b=arguments[0],d;d="["+(a?a+":":"")+b+"] http://errors.angularjs.org/1.5.3/"+(a?a+"/":"")+b;for(b=1;b").append(a).html();try{return a[0].nodeType===Pa?N(d):d.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+N(b)})}catch(c){return N(d)}}function wc(a){try{return decodeURIComponent(a)}catch(b){}} +function xc(a){var b={};q((a||"").split("&"),function(a){var c,e,f;a&&(e=a=a.replace(/\+/g,"%20"),c=a.indexOf("="),-1!==c&&(e=a.substring(0,c),f=a.substring(c+1)),e=wc(e),A(e)&&(f=A(f)?wc(f):!0,va.call(b,e)?M(b[e])?b[e].push(f):b[e]=[b[e],f]:b[e]=f))});return b}function Sb(a){var b=[];q(a,function(a,c){M(a)?q(a,function(a){b.push(ja(c,!0)+(!0===a?"":"="+ja(a,!0)))}):b.push(ja(c,!0)+(!0===a?"":"="+ja(a,!0)))});return b.length?b.join("&"):""}function rb(a){return ja(a,!0).replace(/%26/gi,"&").replace(/%3D/gi, +"=").replace(/%2B/gi,"+")}function ja(a,b){return encodeURIComponent(a).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%3B/gi,";").replace(/%20/g,b?"%20":"+")}function ce(a,b){var d,c,e=Qa.length;for(c=0;c/,">"));}b=b||[];b.unshift(["$provide",function(b){b.value("$rootElement",a)}]);d.debugInfoEnabled&&b.push(["$compileProvider",function(a){a.debugInfoEnabled(!0)}]);b.unshift("ng");c=eb(b,d.strictDi);c.invoke(["$rootScope", +"$rootElement","$compile","$injector",function(a,b,c,d){a.$apply(function(){b.data("$injector",d);c(b)(a)})}]);return c},e=/^NG_ENABLE_DEBUG_INFO!/,f=/^NG_DEFER_BOOTSTRAP!/;T&&e.test(T.name)&&(d.debugInfoEnabled=!0,T.name=T.name.replace(e,""));if(T&&!f.test(T.name))return c();T.name=T.name.replace(f,"");ea.resumeBootstrap=function(a){q(a,function(a){b.push(a)});return c()};D(ea.resumeDeferredBootstrap)&&ea.resumeDeferredBootstrap()}function ee(){T.name="NG_ENABLE_DEBUG_INFO!"+T.name;T.location.reload()} +function fe(a){a=ea.element(a).injector();if(!a)throw Ba("test");return a.get("$$testability")}function zc(a,b){b=b||"_";return a.replace(ge,function(a,c){return(c?b:"")+a.toLowerCase()})}function he(){var a;if(!Ac){var b=sb();($=z(b)?T.jQuery:b?T[b]:u)&&$.fn.on?(H=$,S($.fn,{scope:Ra.scope,isolateScope:Ra.isolateScope,controller:Ra.controller,injector:Ra.injector,inheritedData:Ra.inheritedData}),a=$.cleanData,$.cleanData=function(b){for(var c,e=0,f;null!=(f=b[e]);e++)(c=$._data(f,"events"))&&c.$destroy&& +$(f).triggerHandler("$destroy");a(b)}):H=U;ea.element=H;Ac=!0}}function tb(a,b,d){if(!a)throw Ba("areq",b||"?",d||"required");return a}function Sa(a,b,d){d&&M(a)&&(a=a[a.length-1]);tb(D(a),b,"not a function, got "+(a&&"object"===typeof a?a.constructor.name||"Object":typeof a));return a}function Ta(a,b){if("hasOwnProperty"===a)throw Ba("badname",b);}function Bc(a,b,d){if(!b)return a;b=b.split(".");for(var c,e=a,f=b.length,g=0;g")+c[2];for(c=c[0];c--;)d=d.lastChild;f=cb(f,d.childNodes);d=e.firstChild;d.textContent=""}else f.push(b.createTextNode(a));e.textContent="";e.innerHTML="";q(f,function(a){e.appendChild(a)});return e}function Mc(a, +b){var d=a.parentNode;d&&d.replaceChild(b,a);b.appendChild(a)}function U(a){if(a instanceof U)return a;var b;y(a)&&(a=W(a),b=!0);if(!(this instanceof U)){if(b&&"<"!=a.charAt(0))throw Vb("nosel");return new U(a)}if(b){b=P;var d;a=(d=Lf.exec(a))?[b.createElement(d[1])]:(d=Lc(a,b))?d.childNodes:[]}Nc(this,a)}function Wb(a){return a.cloneNode(!0)}function xb(a,b){b||gb(a);if(a.querySelectorAll)for(var d=a.querySelectorAll("*"),c=0,e=d.length;c=Da?!1:"function"===typeof a&&/^(?:class\s|constructor\()/.test(Function.prototype.toString.call(a));return d?(c.unshift(null),new (Function.prototype.bind.apply(a,c))):a.apply(b,c)},instantiate:function(a,b,c){var d=M(a)?a[a.length-1]:a;a=e(a,b,c);a.unshift(null);return new (Function.prototype.bind.apply(d,a))},get:d,annotate:eb.$$annotate,has:function(b){return n.hasOwnProperty(b+ +"Provider")||a.hasOwnProperty(b)}}}b=!0===b;var k={},l=[],m=new Ua([],!0),n={$provide:{provider:d(c),factory:d(f),service:d(function(a,b){return f(a,["$injector",function(a){return a.instantiate(b)}])}),value:d(function(a,b){return f(a,da(b),!1)}),constant:d(function(a,b){Ta(a,"constant");n[a]=b;F[a]=b}),decorator:function(a,b){var c=p.get(a+"Provider"),d=c.$get;c.$get=function(){var a=x.invoke(d,c);return x.invoke(b,null,{$delegate:a})}}}},p=n.$injector=h(n,function(a,b){ea.isString(b)&&l.push(b); +throw Ia("unpr",l.join(" <- "));}),F={},L=h(F,function(a,b){var c=p.get(a+"Provider",b);return x.invoke(c.$get,c,u,a)}),x=L;n.$injectorProvider={$get:da(L)};var r=g(a),x=L.get("$injector");x.strictDi=b;q(r,function(a){a&&x.invoke(a)});return x}function Ve(){var a=!0;this.disableAutoScrolling=function(){a=!1};this.$get=["$window","$location","$rootScope",function(b,d,c){function e(a){var b=null;Array.prototype.some.call(a,function(a){if("a"===oa(a))return b=a,!0});return b}function f(a){if(a){a.scrollIntoView(); +var c;c=g.yOffset;D(c)?c=c():Pb(c)?(c=c[0],c="fixed"!==b.getComputedStyle(c).position?0:c.getBoundingClientRect().bottom):R(c)||(c=0);c&&(a=a.getBoundingClientRect().top,b.scrollBy(0,a-c))}else b.scrollTo(0,0)}function g(a){a=y(a)?a:d.hash();var b;a?(b=h.getElementById(a))?f(b):(b=e(h.getElementsByName(a)))?f(b):"top"===a&&f(null):f(null)}var h=b.document;a&&c.$watch(function(){return d.hash()},function(a,b){a===b&&""===a||Nf(function(){c.$evalAsync(g)})});return g}]}function ib(a,b){if(!a&&!b)return""; +if(!a)return b;if(!b)return a;M(a)&&(a=a.join(" "));M(b)&&(b=b.join(" "));return a+" "+b}function Wf(a){y(a)&&(a=a.split(" "));var b=V();q(a,function(a){a.length&&(b[a]=!0)});return b}function Ja(a){return J(a)?a:{}}function Xf(a,b,d,c){function e(a){try{a.apply(null,Aa.call(arguments,1))}finally{if(L--,0===L)for(;x.length;)try{x.pop()()}catch(b){d.error(b)}}}function f(){t=null;g();h()}function g(){r=G();r=z(r)?null:r;na(r,I)&&(r=I);I=r}function h(){if(v!==k.url()||w!==r)v=k.url(),w=r,q(C,function(a){a(k.url(), +r)})}var k=this,l=a.location,m=a.history,n=a.setTimeout,p=a.clearTimeout,F={};k.isMock=!1;var L=0,x=[];k.$$completeOutstandingRequest=e;k.$$incOutstandingRequestCount=function(){L++};k.notifyWhenNoOutstandingRequests=function(a){0===L?a():x.push(a)};var r,w,v=l.href,Q=b.find("base"),t=null,G=c.history?function(){try{return m.state}catch(a){}}:E;g();w=r;k.url=function(b,d,e){z(e)&&(e=null);l!==a.location&&(l=a.location);m!==a.history&&(m=a.history);if(b){var f=w===e;if(v===b&&(!c.history||f))return k; +var h=v&&Ka(v)===Ka(b);v=b;w=e;if(!c.history||h&&f){if(!h||t)t=b;d?l.replace(b):h?(d=l,e=b.indexOf("#"),e=-1===e?"":b.substr(e),d.hash=e):l.href=b;l.href!==b&&(t=b)}else m[d?"replaceState":"pushState"](e,"",b),g(),w=r;return k}return t||l.href.replace(/%27/g,"'")};k.state=function(){return r};var C=[],K=!1,I=null;k.onUrlChange=function(b){if(!K){if(c.history)H(a).on("popstate",f);H(a).on("hashchange",f);K=!0}C.push(b);return b};k.$$applicationDestroyed=function(){H(a).off("hashchange popstate",f)}; +k.$$checkUrlChange=h;k.baseHref=function(){var a=Q.attr("href");return a?a.replace(/^(https?\:)?\/\/[^\/]*/,""):""};k.defer=function(a,b){var c;L++;c=n(function(){delete F[c];e(a)},b||0);F[c]=!0;return c};k.defer.cancel=function(a){return F[a]?(delete F[a],p(a),e(E),!0):!1}}function bf(){this.$get=["$window","$log","$sniffer","$document",function(a,b,d,c){return new Xf(a,c,b,d)}]}function cf(){this.$get=function(){function a(a,c){function e(a){a!=n&&(p?p==a&&(p=a.n):p=a,f(a.n,a.p),f(a,n),n=a,n.n= +null)}function f(a,b){a!=b&&(a&&(a.p=b),b&&(b.n=a))}if(a in b)throw O("$cacheFactory")("iid",a);var g=0,h=S({},c,{id:a}),k=V(),l=c&&c.capacity||Number.MAX_VALUE,m=V(),n=null,p=null;return b[a]={put:function(a,b){if(!z(b)){if(ll&&this.remove(p.key);return b}},get:function(a){if(l";b=la.firstChild.attributes;var d=b[0];b.removeNamedItem(d.name); +d.value=c;a.attributes.setNamedItem(d)}function B(a,b){try{a.addClass(b)}catch(c){}}function ba(a,b,c,d,e){a instanceof H||(a=H(a));for(var f=/\S+/,g=0,h=a.length;g").append(a).html())):c?Ra.clone.call(a):a;if(g)for(var h in g)d.data("$"+h+"Controller",g[h].instance);ba.$$addScopeInfo(d,b);c&&c(d,b);l&&l(b,d,d,f);return d}}function xa(a,b,c,d,e,f){function g(a,c,d,e){var f,k,l,m,n,p,G;if(r)for(G=Array(c.length),m=0;mB.priority)break;if(y=B.scope)B.templateUrl||(J(y)?(X("new/isolated scope",C||G,B,t),C=B):X("new/isolated scope",C,B,t)),G=G||B;L=B.name;if(!Ea&&(B.replace&&(B.templateUrl||B.template)||B.transclude&&!B.$$tlb)){for(y=ra+1;Ea=a[y++];)if(Ea.transclude&&!Ea.$$tlb||Ea.replace&&(Ea.templateUrl||Ea.template)){E=!0;break}Ea=!0}!B.templateUrl&& +B.controller&&(y=B.controller,v=v||V(),X("'"+L+"' controller",v[L],B,t),v[L]=B);if(y=B.transclude)if(K=!0,B.$$tlb||(X("transclusion",I,B,t),I=B),"element"==y)Ca=!0,p=B.priority,Q=t,t=d.$$element=H(ba.$$createComment(L,d[L])),b=t[0],da(f,Aa.call(Q,0),b),Q[0].$$parentNode=Q[0].parentNode,s=Zb(E,Q,e,p,g&&g.name,{nonTlbTranscludeDirective:I});else{var P=V();Q=H(Wb(b)).contents();if(J(y)){Q=[];var Z=V(),Y=V();q(y,function(a,b){var c="?"===a.charAt(0);a=c?a.substring(1):a;Z[a]=b;P[b]=null;Y[b]=c});q(t.contents(), +function(a){var b=Z[ya(oa(a))];b?(Y[b]=!0,P[b]=P[b]||[],P[b].push(a)):Q.push(a)});q(Y,function(a,b){if(!a)throw ga("reqslot",b);});for(var $ in P)P[$]&&(P[$]=Zb(E,P[$],e))}t.empty();s=Zb(E,Q,e,u,u,{needsNewScope:B.$$isolateScope||B.$$newScope});s.$$slots=P}if(B.template)if(x=!0,X("template",w,B,t),w=B,y=D(B.template)?B.template(t,d):B.template,y=ua(y),B.replace){g=B;Q=Ub.test(y)?Xc(ca(B.templateNamespace,W(y))):[];b=Q[0];if(1!=Q.length||1!==b.nodeType)throw ga("tplrt",L,"");da(f,t,b);N={$attr:{}}; +y=A(b,[],N);var ea=a.splice(ra+1,a.length-(ra+1));(C||G)&&Yc(y,C,G);a=a.concat(y).concat(ea);U(d,N);N=a.length}else t.html(y);if(B.templateUrl)x=!0,X("template",w,B,t),w=B,B.replace&&(g=B),n=aa(a.splice(ra,a.length-ra),t,d,f,K&&s,h,k,{controllerDirectives:v,newScopeDirective:G!==B&&G,newIsolateScopeDirective:C,templateDirective:w,nonTlbTranscludeDirective:I}),N=a.length;else if(B.compile)try{xa=B.compile(t,d,s),D(xa)?m(null,xa,R,Fa):xa&&m(xa.pre,xa.post,R,Fa)}catch(fa){c(fa,wa(t))}B.terminal&&(n.terminal= +!0,p=Math.max(p,B.priority))}n.scope=G&&!0===G.scope;n.transcludeOnThisElement=K;n.templateOnThisElement=x;n.transclude=s;l.hasElementTranscludeDirective=Ca;return n}function jb(a,b,c,d){var e;if(y(b)){var f=b.match(k);b=b.substring(f[0].length);var g=f[1]||f[3],f="?"===f[2];"^^"===g?c=c.parent():e=(e=d&&d[b])&&e.instance;if(!e){var h="$"+b+"Controller";e=g?c.inheritedData(h):c.data(h)}if(!e&&!f)throw ga("ctreq",b,a);}else if(M(b))for(e=[],g=0,f=b.length;gn.priority)&&-1!=n.restrict.indexOf(g)){l&&(n=Qb(n,{$$start:l,$$end:m}));if(!n.$$bindings){var v=n,C=n,w=n.name,B={isolateScope:null,bindToController:null};J(C.scope)&&(!0===C.bindToController?(B.bindToController=d(C.scope,w,!0),B.isolateScope={}):B.isolateScope=d(C.scope,w,!1));J(C.bindToController)&&(B.bindToController=d(C.bindToController,w,!0));if(J(B.bindToController)){var I=C.controller,K=C.controllerAs;if(!I)throw ga("noctrl", +w);if(!Uc(I,K))throw ga("noident",w);}var x=v.$$bindings=B;J(x.isolateScope)&&(n.$$isolateBindings=x.isolateScope)}b.push(n);k=n}}catch(t){c(t)}}return k}function R(b){if(e.hasOwnProperty(b))for(var c=a.get(b+"Directive"),d=0,f=c.length;d"+b+"";return c.childNodes[0].childNodes;default:return b}}function ea(a,b){if("srcdoc"==b)return G.HTML;var c=oa(a);if("xlinkHref"==b||"form"==c&&"action"==b||"img"!=c&&("src"==b||"ngSrc"==b))return G.RESOURCE_URL} +function fa(a,c,d,e,f){var g=ea(a,e);f=h[e]||f;var k=b(d,!0,g,f);if(k){if("multiple"===e&&"select"===oa(a))throw ga("selmulti",wa(a));c.push({priority:100,compile:function(){return{pre:function(a,c,h){c=h.$$observers||(h.$$observers=V());if(l.test(e))throw ga("nodomevents");var m=h[e];m!==d&&(k=m&&b(m,!0,g,f),d=m);k&&(h[e]=k(a),(c[e]||(c[e]=[])).$$inter=!0,(h.$$observers&&h.$$observers[e].$$scope||a).$watch(k,function(a,b){"class"===e&&a!=b?h.$updateClass(a,b):h.$set(e,a)}))}}}})}}function da(a,b, +c){var d=b[0],e=b.length,f=d.parentNode,g,h;if(a)for(g=0,h=a.length;g=b)return a;for(;b--;)8===a[b].nodeType&& +Yf.call(a,b,1);return a}function Uc(a,b){if(b&&y(b))return b;if(y(a)){var d=ad.exec(a);if(d)return d[3]}}function df(){var a={},b=!1;this.has=function(b){return a.hasOwnProperty(b)};this.register=function(b,c){Ta(b,"controller");J(b)?S(a,b):a[b]=c};this.allowGlobals=function(){b=!0};this.$get=["$injector","$window",function(d,c){function e(a,b,c,d){if(!a||!J(a.$scope))throw O("$controller")("noscp",d,b);a.$scope[b]=c}return function(f,g,h,k){var l,m,n;h=!0===h;k&&y(k)&&(n=k);if(y(f)){k=f.match(ad); +if(!k)throw Zf("ctrlfmt",f);m=k[1];n=n||k[3];f=a.hasOwnProperty(m)?a[m]:Bc(g.$scope,m,!0)||(b?Bc(c,m,!0):u);Sa(f,m,!0)}if(h)return h=(M(f)?f[f.length-1]:f).prototype,l=Object.create(h||null),n&&e(g,n,l,m||f.name),S(function(){var a=d.invoke(f,l,g,m);a!==l&&(J(a)||D(a))&&(l=a,n&&e(g,n,l,m||f.name));return l},{instance:l,identifier:n});l=d.instantiate(f,g,m);n&&e(g,n,l,m||f.name);return l}}]}function ef(){this.$get=["$window",function(a){return H(a.document)}]}function ff(){this.$get=["$log",function(a){return function(b, +d){a.error.apply(a,arguments)}}]}function $b(a){return J(a)?fa(a)?a.toISOString():db(a):a}function lf(){this.$get=function(){return function(a){if(!a)return"";var b=[];pc(a,function(a,c){null===a||z(a)||(M(a)?q(a,function(a){b.push(ja(c)+"="+ja($b(a)))}):b.push(ja(c)+"="+ja($b(a))))});return b.join("&")}}}function mf(){this.$get=function(){return function(a){function b(a,e,f){null===a||z(a)||(M(a)?q(a,function(a,c){b(a,e+"["+(J(a)?c:"")+"]")}):J(a)&&!fa(a)?pc(a,function(a,c){b(a,e+(f?"":"[")+c+(f? +"":"]"))}):d.push(ja(e)+"="+ja($b(a))))}if(!a)return"";var d=[];b(a,"",!0);return d.join("&")}}}function ac(a,b){if(y(a)){var d=a.replace($f,"").trim();if(d){var c=b("Content-Type");(c=c&&0===c.indexOf(bd))||(c=(c=d.match(ag))&&bg[c[0]].test(d));c&&(a=uc(d))}}return a}function cd(a){var b=V(),d;y(a)?q(a.split("\n"),function(a){d=a.indexOf(":");var e=N(W(a.substr(0,d)));a=W(a.substr(d+1));e&&(b[e]=b[e]?b[e]+", "+a:a)}):J(a)&&q(a,function(a,d){var f=N(d),g=W(a);f&&(b[f]=b[f]?b[f]+", "+g:g)});return b} +function dd(a){var b;return function(d){b||(b=cd(a));return d?(d=b[N(d)],void 0===d&&(d=null),d):b}}function ed(a,b,d,c){if(D(c))return c(a,b,d);q(c,function(c){a=c(a,b,d)});return a}function kf(){var a=this.defaults={transformResponse:[ac],transformRequest:[function(a){return J(a)&&"[object File]"!==ka.call(a)&&"[object Blob]"!==ka.call(a)&&"[object FormData]"!==ka.call(a)?db(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"},post:ia(bc),put:ia(bc),patch:ia(bc)},xsrfCookieName:"XSRF-TOKEN", +xsrfHeaderName:"X-XSRF-TOKEN",paramSerializer:"$httpParamSerializer"},b=!1;this.useApplyAsync=function(a){return A(a)?(b=!!a,this):b};var d=!0;this.useLegacyPromiseExtensions=function(a){return A(a)?(d=!!a,this):d};var c=this.interceptors=[];this.$get=["$httpBackend","$$cookieReader","$cacheFactory","$rootScope","$q","$injector",function(e,f,g,h,k,l){function m(b){function c(a){var b=S({},a);b.data=ed(a.data,a.headers,a.status,f.transformResponse);a=a.status;return 200<=a&&300>a?b:k.reject(b)}function e(a, +b){var c,d={};q(a,function(a,e){D(a)?(c=a(b),null!=c&&(d[e]=c)):d[e]=a});return d}if(!J(b))throw O("$http")("badreq",b);if(!y(b.url))throw O("$http")("badreq",b.url);var f=S({method:"get",transformRequest:a.transformRequest,transformResponse:a.transformResponse,paramSerializer:a.paramSerializer},b);f.headers=function(b){var c=a.headers,d=S({},b.headers),f,g,h,c=S({},c.common,c[N(b.method)]);a:for(f in c){g=N(f);for(h in d)if(N(h)===g)continue a;d[f]=c[f]}return e(d,ia(b))}(b);f.method=vb(f.method); +f.paramSerializer=y(f.paramSerializer)?l.get(f.paramSerializer):f.paramSerializer;var g=[function(b){var d=b.headers,e=ed(b.data,dd(d),u,b.transformRequest);z(e)&&q(d,function(a,b){"content-type"===N(b)&&delete d[b]});z(b.withCredentials)&&!z(a.withCredentials)&&(b.withCredentials=a.withCredentials);return n(b,e).then(c,c)},u],h=k.when(f);for(q(L,function(a){(a.request||a.requestError)&&g.unshift(a.request,a.requestError);(a.response||a.responseError)&&g.push(a.response,a.responseError)});g.length;){b= +g.shift();var m=g.shift(),h=h.then(b,m)}d?(h.success=function(a){Sa(a,"fn");h.then(function(b){a(b.data,b.status,b.headers,f)});return h},h.error=function(a){Sa(a,"fn");h.then(null,function(b){a(b.data,b.status,b.headers,f)});return h}):(h.success=fd("success"),h.error=fd("error"));return h}function n(c,d){function g(a,c,d,e){function f(){l(c,a,d,e)}K&&(200<=a&&300>a?K.put(L,[a,c,cd(d),e]):K.remove(L));b?h.$applyAsync(f):(f(),h.$$phase||h.$apply())}function l(a,b,d,e){b=-1<=b?b:0;(200<=b&&300>b?G.resolve: +G.reject)({data:a,status:b,headers:dd(d),config:c,statusText:e})}function n(a){l(a.data,a.status,ia(a.headers()),a.statusText)}function t(){var a=m.pendingRequests.indexOf(c);-1!==a&&m.pendingRequests.splice(a,1)}var G=k.defer(),C=G.promise,K,I,qa=c.headers,L=p(c.url,c.paramSerializer(c.params));m.pendingRequests.push(c);C.then(t,t);!c.cache&&!a.cache||!1===c.cache||"GET"!==c.method&&"JSONP"!==c.method||(K=J(c.cache)?c.cache:J(a.cache)?a.cache:F);K&&(I=K.get(L),A(I)?I&&D(I.then)?I.then(n,n):M(I)? +l(I[1],I[0],ia(I[2]),I[3]):l(I,200,{},"OK"):K.put(L,C));z(I)&&((I=gd(c.url)?f()[c.xsrfCookieName||a.xsrfCookieName]:u)&&(qa[c.xsrfHeaderName||a.xsrfHeaderName]=I),e(c.method,L,d,g,qa,c.timeout,c.withCredentials,c.responseType));return C}function p(a,b){0=l&&(v.resolve(r),x(Q.$$intervalId),delete g[Q.$$intervalId]);w||a.$apply()},k);g[Q.$$intervalId]=v;return Q}var g={};f.cancel=function(a){return a&&a.$$intervalId in g?(g[a.$$intervalId].reject("canceled"),b.clearInterval(a.$$intervalId), +delete g[a.$$intervalId],!0):!1};return f}]}function cc(a){a=a.split("/");for(var b=a.length;b--;)a[b]=rb(a[b]);return a.join("/")}function hd(a,b){var d=sa(a);b.$$protocol=d.protocol;b.$$host=d.hostname;b.$$port=Y(d.port)||dg[d.protocol]||null}function id(a,b){var d="/"!==a.charAt(0);d&&(a="/"+a);var c=sa(a);b.$$path=decodeURIComponent(d&&"/"===c.pathname.charAt(0)?c.pathname.substring(1):c.pathname);b.$$search=xc(c.search);b.$$hash=decodeURIComponent(c.hash);b.$$path&&"/"!=b.$$path.charAt(0)&&(b.$$path= +"/"+b.$$path)}function la(a,b){if(0===b.indexOf(a))return b.substr(a.length)}function Ka(a){var b=a.indexOf("#");return-1==b?a:a.substr(0,b)}function kb(a){return a.replace(/(#.+)|#$/,"$1")}function dc(a,b,d){this.$$html5=!0;d=d||"";hd(a,this);this.$$parse=function(a){var d=la(b,a);if(!y(d))throw Fb("ipthprfx",a,b);id(d,this);this.$$path||(this.$$path="/");this.$$compose()};this.$$compose=function(){var a=Sb(this.$$search),d=this.$$hash?"#"+rb(this.$$hash):"";this.$$url=cc(this.$$path)+(a?"?"+a:"")+ +d;this.$$absUrl=b+this.$$url.substr(1)};this.$$parseLinkUrl=function(c,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;A(f=la(a,c))?(g=f,g=A(f=la(d,f))?b+(la("/",f)||f):a+g):A(f=la(b,c))?g=b+f:b==c+"/"&&(g=b);g&&this.$$parse(g);return!!g}}function ec(a,b,d){hd(a,this);this.$$parse=function(c){var e=la(a,c)||la(b,c),f;z(e)||"#"!==e.charAt(0)?this.$$html5?f=e:(f="",z(e)&&(a=c,this.replace())):(f=la(d,e),z(f)&&(f=e));id(f,this);c=this.$$path;var e=a,g=/^\/[A-Z]:(\/.*)/;0===f.indexOf(e)&& +(f=f.replace(e,""));g.exec(f)||(c=(f=g.exec(c))?f[1]:c);this.$$path=c;this.$$compose()};this.$$compose=function(){var b=Sb(this.$$search),e=this.$$hash?"#"+rb(this.$$hash):"";this.$$url=cc(this.$$path)+(b?"?"+b:"")+e;this.$$absUrl=a+(this.$$url?d+this.$$url:"")};this.$$parseLinkUrl=function(b,d){return Ka(a)==Ka(b)?(this.$$parse(b),!0):!1}}function jd(a,b,d){this.$$html5=!0;ec.apply(this,arguments);this.$$parseLinkUrl=function(c,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;a==Ka(c)? +f=c:(g=la(b,c))?f=a+d+g:b===c+"/"&&(f=b);f&&this.$$parse(f);return!!f};this.$$compose=function(){var b=Sb(this.$$search),e=this.$$hash?"#"+rb(this.$$hash):"";this.$$url=cc(this.$$path)+(b?"?"+b:"")+e;this.$$absUrl=a+d+this.$$url}}function Gb(a){return function(){return this[a]}}function kd(a,b){return function(d){if(z(d))return this[a];this[a]=b(d);this.$$compose();return this}}function pf(){var a="",b={enabled:!1,requireBase:!0,rewriteLinks:!0};this.hashPrefix=function(b){return A(b)?(a=b,this): +a};this.html5Mode=function(a){return Oa(a)?(b.enabled=a,this):J(a)?(Oa(a.enabled)&&(b.enabled=a.enabled),Oa(a.requireBase)&&(b.requireBase=a.requireBase),Oa(a.rewriteLinks)&&(b.rewriteLinks=a.rewriteLinks),this):b};this.$get=["$rootScope","$browser","$sniffer","$rootElement","$window",function(d,c,e,f,g){function h(a,b,d){var e=l.url(),f=l.$$state;try{c.url(a,b,d),l.$$state=c.state()}catch(g){throw l.url(e),l.$$state=f,g;}}function k(a,b){d.$broadcast("$locationChangeSuccess",l.absUrl(),a,l.$$state, +b)}var l,m;m=c.baseHref();var n=c.url(),p;if(b.enabled){if(!m&&b.requireBase)throw Fb("nobase");p=n.substring(0,n.indexOf("/",n.indexOf("//")+2))+(m||"/");m=e.history?dc:jd}else p=Ka(n),m=ec;var F=p.substr(0,Ka(p).lastIndexOf("/")+1);l=new m(p,F,"#"+a);l.$$parseLinkUrl(n,n);l.$$state=c.state();var q=/^\s*(javascript|mailto):/i;f.on("click",function(a){if(b.rewriteLinks&&!a.ctrlKey&&!a.metaKey&&!a.shiftKey&&2!=a.which&&2!=a.button){for(var e=H(a.target);"a"!==oa(e[0]);)if(e[0]===f[0]||!(e=e.parent())[0])return; +var h=e.prop("href"),k=e.attr("href")||e.attr("xlink:href");J(h)&&"[object SVGAnimatedString]"===h.toString()&&(h=sa(h.animVal).href);q.test(h)||!h||e.attr("target")||a.isDefaultPrevented()||!l.$$parseLinkUrl(h,k)||(a.preventDefault(),l.absUrl()!=c.url()&&(d.$apply(),g.angular["ff-684208-preventDefault"]=!0))}});kb(l.absUrl())!=kb(n)&&c.url(l.absUrl(),!0);var x=!0;c.onUrlChange(function(a,b){z(la(F,a))?g.location.href=a:(d.$evalAsync(function(){var c=l.absUrl(),e=l.$$state,f;a=kb(a);l.$$parse(a); +l.$$state=b;f=d.$broadcast("$locationChangeStart",a,c,b,e).defaultPrevented;l.absUrl()===a&&(f?(l.$$parse(c),l.$$state=e,h(c,!1,e)):(x=!1,k(c,e)))}),d.$$phase||d.$digest())});d.$watch(function(){var a=kb(c.url()),b=kb(l.absUrl()),f=c.state(),g=l.$$replace,m=a!==b||l.$$html5&&e.history&&f!==l.$$state;if(x||m)x=!1,d.$evalAsync(function(){var b=l.absUrl(),c=d.$broadcast("$locationChangeStart",b,a,l.$$state,f).defaultPrevented;l.absUrl()===b&&(c?(l.$$parse(a),l.$$state=f):(m&&h(b,g,f===l.$$state?null: +l.$$state),k(a,f)))});l.$$replace=!1});return l}]}function qf(){var a=!0,b=this;this.debugEnabled=function(b){return A(b)?(a=b,this):a};this.$get=["$window",function(d){function c(a){a instanceof Error&&(a.stack?a=a.message&&-1===a.stack.indexOf(a.message)?"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&&(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b=d.console||{},e=b[a]||b.log||E;a=!1;try{a=!!e.apply}catch(k){}return a?function(){var a=[];q(arguments,function(b){a.push(c(b))}); +return e.apply(b,a)}:function(a,b){e(a,null==b?"":b)}}return{log:e("log"),info:e("info"),warn:e("warn"),error:e("error"),debug:function(){var c=e("debug");return function(){a&&c.apply(b,arguments)}}()}}]}function Wa(a,b){if("__defineGetter__"===a||"__defineSetter__"===a||"__lookupGetter__"===a||"__lookupSetter__"===a||"__proto__"===a)throw ca("isecfld",b);return a}function eg(a){return a+""}function ta(a,b){if(a){if(a.constructor===a)throw ca("isecfn",b);if(a.window===a)throw ca("isecwindow",b);if(a.children&& +(a.nodeName||a.prop&&a.attr&&a.find))throw ca("isecdom",b);if(a===Object)throw ca("isecobj",b);}return a}function ld(a,b){if(a){if(a.constructor===a)throw ca("isecfn",b);if(a===fg||a===gg||a===hg)throw ca("isecff",b);}}function Hb(a,b){if(a&&(a===(0).constructor||a===(!1).constructor||a==="".constructor||a==={}.constructor||a===[].constructor||a===Function.constructor))throw ca("isecaf",b);}function ig(a,b){return"undefined"!==typeof a?a:b}function md(a,b){return"undefined"===typeof a?b:"undefined"=== +typeof b?a:a+b}function aa(a,b){var d,c;switch(a.type){case s.Program:d=!0;q(a.body,function(a){aa(a.expression,b);d=d&&a.expression.constant});a.constant=d;break;case s.Literal:a.constant=!0;a.toWatch=[];break;case s.UnaryExpression:aa(a.argument,b);a.constant=a.argument.constant;a.toWatch=a.argument.toWatch;break;case s.BinaryExpression:aa(a.left,b);aa(a.right,b);a.constant=a.left.constant&&a.right.constant;a.toWatch=a.left.toWatch.concat(a.right.toWatch);break;case s.LogicalExpression:aa(a.left, +b);aa(a.right,b);a.constant=a.left.constant&&a.right.constant;a.toWatch=a.constant?[]:[a];break;case s.ConditionalExpression:aa(a.test,b);aa(a.alternate,b);aa(a.consequent,b);a.constant=a.test.constant&&a.alternate.constant&&a.consequent.constant;a.toWatch=a.constant?[]:[a];break;case s.Identifier:a.constant=!1;a.toWatch=[a];break;case s.MemberExpression:aa(a.object,b);a.computed&&aa(a.property,b);a.constant=a.object.constant&&(!a.computed||a.property.constant);a.toWatch=[a];break;case s.CallExpression:d= +a.filter?!b(a.callee.name).$stateful:!1;c=[];q(a.arguments,function(a){aa(a,b);d=d&&a.constant;a.constant||c.push.apply(c,a.toWatch)});a.constant=d;a.toWatch=a.filter&&!b(a.callee.name).$stateful?c:[a];break;case s.AssignmentExpression:aa(a.left,b);aa(a.right,b);a.constant=a.left.constant&&a.right.constant;a.toWatch=[a];break;case s.ArrayExpression:d=!0;c=[];q(a.elements,function(a){aa(a,b);d=d&&a.constant;a.constant||c.push.apply(c,a.toWatch)});a.constant=d;a.toWatch=c;break;case s.ObjectExpression:d= +!0;c=[];q(a.properties,function(a){aa(a.value,b);d=d&&a.value.constant;a.value.constant||c.push.apply(c,a.value.toWatch)});a.constant=d;a.toWatch=c;break;case s.ThisExpression:a.constant=!1;a.toWatch=[];break;case s.LocalsExpression:a.constant=!1,a.toWatch=[]}}function nd(a){if(1==a.length){a=a[0].expression;var b=a.toWatch;return 1!==b.length?b:b[0]!==a?b:u}}function od(a){return a.type===s.Identifier||a.type===s.MemberExpression}function pd(a){if(1===a.body.length&&od(a.body[0].expression))return{type:s.AssignmentExpression, +left:a.body[0].expression,right:{type:s.NGValueParameter},operator:"="}}function qd(a){return 0===a.body.length||1===a.body.length&&(a.body[0].expression.type===s.Literal||a.body[0].expression.type===s.ArrayExpression||a.body[0].expression.type===s.ObjectExpression)}function rd(a,b){this.astBuilder=a;this.$filter=b}function sd(a,b){this.astBuilder=a;this.$filter=b}function Ib(a){return"constructor"==a}function fc(a){return D(a.valueOf)?a.valueOf():jg.call(a)}function rf(){var a=V(),b=V(),d={"true":!0, +"false":!1,"null":null,undefined:u};this.addLiteral=function(a,b){d[a]=b};this.$get=["$filter",function(c){function e(d,e,g){var p,t,G;g=g||x;switch(typeof d){case "string":G=d=d.trim();var C=g?b:a;p=C[G];if(!p){":"===d.charAt(0)&&":"===d.charAt(1)&&(t=!0,d=d.substring(2));p=g?L:F;var K=new gc(p);p=(new hc(K,c,p)).parse(d);p.constant?p.$$watchDelegate=m:t?p.$$watchDelegate=p.literal?l:k:p.inputs&&(p.$$watchDelegate=h);g&&(p=f(p));C[G]=p}return n(p,e);case "function":return n(d,e);default:return n(E, +e)}}function f(a){function b(c,d,e,f){var g=x;x=!0;try{return a(c,d,e,f)}finally{x=g}}if(!a)return a;b.$$watchDelegate=a.$$watchDelegate;b.assign=f(a.assign);b.constant=a.constant;b.literal=a.literal;for(var c=0;a.inputs&&c=this.promise.$$state.status&& +d&&d.length&&a(function(){for(var a,e,f=0,g=d.length;f +a)for(b in l++,f)va.call(e,b)||(v--,delete f[b])}else f!==e&&(f=e,l++);return l}}c.$stateful=!0;var d=this,e,f,h,k=1t&&(z=4-t,A[z]||(A[z]=[]),A[z].push({msg:D(a.exp)?"fn: "+(a.exp.name||a.exp.toString()):a.exp,newVal:g,oldVal:k}));else if(a===c){q=!1;break a}}catch(H){f(H)}if(!(p=F.$$watchersCount&& +F.$$childHead||F!==this&&F.$$nextSibling))for(;F!==this&&!(p=F.$$nextSibling);)F=F.$parent}while(F=p);if((q||v.length)&&!t--)throw w.$$phase=null,d("infdig",b,A);}while(q||v.length);for(w.$$phase=null;u.length;)try{u.shift()()}catch(J){f(J)}},$destroy:function(){if(!this.$$destroyed){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;this===w&&h.$$applicationDestroyed();p(this,-this.$$watchersCount);for(var b in this.$$listenerCount)F(this,this.$$listenerCount[b],b);a&&a.$$childHead== +this&&(a.$$childHead=this.$$nextSibling);a&&a.$$childTail==this&&(a.$$childTail=this.$$prevSibling);this.$$prevSibling&&(this.$$prevSibling.$$nextSibling=this.$$nextSibling);this.$$nextSibling&&(this.$$nextSibling.$$prevSibling=this.$$prevSibling);this.$destroy=this.$digest=this.$apply=this.$evalAsync=this.$applyAsync=E;this.$on=this.$watch=this.$watchGroup=function(){return E};this.$$listeners={};this.$$nextSibling=null;l(this)}},$eval:function(a,b){return g(a)(this,b)},$evalAsync:function(a,b){w.$$phase|| +v.length||h.defer(function(){v.length&&w.$digest()});v.push({scope:this,expression:g(a),locals:b})},$$postDigest:function(a){u.push(a)},$apply:function(a){try{n("$apply");try{return this.$eval(a)}finally{w.$$phase=null}}catch(b){f(b)}finally{try{w.$digest()}catch(c){throw f(c),c;}}},$applyAsync:function(a){function b(){c.$eval(a)}var c=this;a&&t.push(b);a=g(a);r()},$on:function(a,b){var c=this.$$listeners[a];c||(this.$$listeners[a]=c=[]);c.push(b);var d=this;do d.$$listenerCount[a]||(d.$$listenerCount[a]= +0),d.$$listenerCount[a]++;while(d=d.$parent);var e=this;return function(){var d=c.indexOf(b);-1!==d&&(c[d]=null,F(e,1,a))}},$emit:function(a,b){var c=[],d,e=this,g=!1,h={name:a,targetScope:e,stopPropagation:function(){g=!0},preventDefault:function(){h.defaultPrevented=!0},defaultPrevented:!1},k=cb([h],arguments,1),l,m;do{d=e.$$listeners[a]||c;h.currentScope=e;l=0;for(m=d.length;lDa)throw ua("iequirks");var c=ia(ma);c.isEnabled=function(){return a};c.trustAs=d.trustAs;c.getTrusted=d.getTrusted;c.valueOf=d.valueOf;a||(c.trustAs=c.getTrusted=function(a,b){return b}, +c.valueOf=$a);c.parseAs=function(a,d){var e=b(d);return e.literal&&e.constant?e:b(d,function(b){return c.getTrusted(a,b)})};var e=c.parseAs,f=c.getTrusted,g=c.trustAs;q(ma,function(a,b){var d=N(b);c[fb("parse_as_"+d)]=function(b){return e(a,b)};c[fb("get_trusted_"+d)]=function(b){return f(a,b)};c[fb("trust_as_"+d)]=function(b){return g(a,b)}});return c}]}function xf(){this.$get=["$window","$document",function(a,b){var d={},c=!(a.chrome&&a.chrome.app&&a.chrome.app.runtime)&&a.history&&a.history.pushState, +e=Y((/android (\d+)/.exec(N((a.navigator||{}).userAgent))||[])[1]),f=/Boxee/i.test((a.navigator||{}).userAgent),g=b[0]||{},h,k=/^(Moz|webkit|ms)(?=[A-Z])/,l=g.body&&g.body.style,m=!1,n=!1;if(l){for(var p in l)if(m=k.exec(p)){h=m[0];h=h.substr(0,1).toUpperCase()+h.substr(1);break}h||(h="WebkitOpacity"in l&&"webkit");m=!!("transition"in l||h+"Transition"in l);n=!!("animation"in l||h+"Animation"in l);!e||m&&n||(m=y(l.webkitTransition),n=y(l.webkitAnimation))}return{history:!(!c||4>e||f),hasEvent:function(a){if("input"=== +a&&11>=Da)return!1;if(z(d[a])){var b=g.createElement("div");d[a]="on"+a in b}return d[a]},csp:Ga(),vendorPrefix:h,transitions:m,animations:n,android:e}}]}function zf(){var a;this.httpOptions=function(b){return b?(a=b,this):a};this.$get=["$templateCache","$http","$q","$sce",function(b,d,c,e){function f(g,h){f.totalPendingRequests++;y(g)&&b.get(g)||(g=e.getTrustedResourceUrl(g));var k=d.defaults&&d.defaults.transformResponse;M(k)?k=k.filter(function(a){return a!==ac}):k===ac&&(k=null);return d.get(g, +S({cache:b,transformResponse:k},a))["finally"](function(){f.totalPendingRequests--}).then(function(a){b.put(g,a.data);return a.data},function(a){if(!h)throw lg("tpload",g,a.status,a.statusText);return c.reject(a)})}f.totalPendingRequests=0;return f}]}function Af(){this.$get=["$rootScope","$browser","$location",function(a,b,d){return{findBindings:function(a,b,d){a=a.getElementsByClassName("ng-binding");var g=[];q(a,function(a){var c=ea.element(a).data("$binding");c&&q(c,function(c){d?(new RegExp("(^|\\s)"+ +ud(b)+"(\\s|\\||$)")).test(c)&&g.push(a):-1!=c.indexOf(b)&&g.push(a)})});return g},findModels:function(a,b,d){for(var g=["ng-","data-ng-","ng\\:"],h=0;hc&&(c=e),c+=+a.slice(e+1),a=a.substring(0,e)):0>c&&(c=a.length);for(e=0;a.charAt(e)==jc;e++);if(e==(g=a.length))d=[0],c=1;else{for(g--;a.charAt(g)==jc;)g--;c-=e;d=[];for(f=0;e<=g;e++,f++)d[f]=+a.charAt(e)}c>Ed&&(d=d.splice(0,Ed-1),b=c-1,c=1);return{d:d,e:b,i:c}}function tg(a,b,d,c){var e=a.d,f=e.length-a.i;b=z(b)?Math.min(Math.max(d,f),c):+b;d=b+a.i;c=e[d];if(0d-1){for(c=0;c>d;c--)e.unshift(0),a.i++;e.unshift(1);a.i++}else e[d-1]++;for(;fh;)k.unshift(0),h++;0=b.lgSize&&h.unshift(k.splice(-b.lgSize).join(""));k.length>b.gSize;)h.unshift(k.splice(-b.gSize).join(""));k.length&&h.unshift(k.join(""));k=h.join(d);f.length&&(k+=c+f.join(""));e&&(k+="e+"+e)}return 0>a&&!g?b.negPre+k+b.negSuf:b.posPre+k+b.posSuf}function Jb(a,b,d,c){var e="";if(0>a||c&&0>=a)c?a=-a+1:(a=-a,e="-");for(a=""+a;a.length-d)f+=d;0===f&&-12==d&&(f=12);return Jb(f,b,c,e)}}function lb(a,b,d){return function(c,e){var f=c["get"+a](),g=vb((d?"STANDALONE":"")+(b?"SHORT":"")+a);return e[g][f]}}function Fd(a){var b=(new Date(a,0,1)).getDay();return new Date(a,0,(4>=b?5:12)-b)}function Gd(a){return function(b){var d=Fd(b.getFullYear());b=+new Date(b.getFullYear(),b.getMonth(),b.getDate()+(4-b.getDay()))-+d;b=1+Math.round(b/6048E5);return Jb(b,a)}}function kc(a,b){return 0>=a.getFullYear()? +b.ERAS[0]:b.ERAS[1]}function zd(a){function b(a){var b;if(b=a.match(d)){a=new Date(0);var f=0,g=0,h=b[8]?a.setUTCFullYear:a.setFullYear,k=b[8]?a.setUTCHours:a.setHours;b[9]&&(f=Y(b[9]+b[10]),g=Y(b[9]+b[11]));h.call(a,Y(b[1]),Y(b[2])-1,Y(b[3]));f=Y(b[4]||0)-f;g=Y(b[5]||0)-g;h=Y(b[6]||0);b=Math.round(1E3*parseFloat("0."+(b[7]||0)));k.call(a,f,g,h,b)}return a}var d=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,d,f){var g="",h= +[],k,l;d=d||"mediumDate";d=a.DATETIME_FORMATS[d]||d;y(c)&&(c=ug.test(c)?Y(c):b(c));R(c)&&(c=new Date(c));if(!fa(c)||!isFinite(c.getTime()))return c;for(;d;)(l=vg.exec(d))?(h=cb(h,l,1),d=h.pop()):(h.push(d),d=null);var m=c.getTimezoneOffset();f&&(m=vc(f,m),c=Rb(c,f,!0));q(h,function(b){k=wg[b];g+=k?k(c,a.DATETIME_FORMATS,m):"''"===b?"'":b.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function ng(){return function(a,b){z(b)&&(b=2);return db(a,b)}}function og(){return function(a,b,d){b=Infinity=== +Math.abs(Number(b))?Number(b):Y(b);if(isNaN(b))return a;R(a)&&(a=a.toString());if(!M(a)&&!y(a))return a;d=!d||isNaN(d)?0:Y(d);d=0>d?Math.max(0,a.length+d):d;return 0<=b?a.slice(d,d+b):0===d?a.slice(b,a.length):a.slice(Math.max(0,d+b),d)}}function Bd(a){function b(b,d){d=d?-1:1;return b.map(function(b){var c=1,h=$a;if(D(b))h=b;else if(y(b)){if("+"==b.charAt(0)||"-"==b.charAt(0))c="-"==b.charAt(0)?-1:1,b=b.substring(1);if(""!==b&&(h=a(b),h.constant))var k=h(),h=function(a){return a[k]}}return{get:h, +descending:c*d}})}function d(a){switch(typeof a){case "number":case "boolean":case "string":return!0;default:return!1}}return function(a,e,f){if(null==a)return a;if(!za(a))throw O("orderBy")("notarray",a);M(e)||(e=[e]);0===e.length&&(e=["+"]);var g=b(e,f);g.push({get:function(){return{}},descending:f?-1:1});a=Array.prototype.map.call(a,function(a,b){return{value:a,predicateValues:g.map(function(c){var e=c.get(a);c=typeof e;if(null===e)c="string",e="null";else if("string"===c)e=e.toLowerCase();else if("object"=== +c)a:{if("function"===typeof e.valueOf&&(e=e.valueOf(),d(e)))break a;if(rc(e)&&(e=e.toString(),d(e)))break a;e=b}return{value:e,type:c}})}});a.sort(function(a,b){for(var c=0,d=0,e=g.length;db||37<=b&&40>=b|| +m(a,this,this.value)});if(e.hasEvent("paste"))b.on("paste cut",m)}b.on("change",l);if(Jd[g]&&c.$$hasNativeValidators&&g===d.type)b.on("keydown wheel mousedown",function(a){if(!k){var b=this.validity,c=b.badInput,d=b.typeMismatch;k=f.defer(function(){k=null;b.badInput===c&&b.typeMismatch===d||l(a)})}});c.$render=function(){var a=c.$isEmpty(c.$viewValue)?"":c.$viewValue;b.val()!==a&&b.val(a)}}function Mb(a,b){return function(d,c){var e,f;if(fa(d))return d;if(y(d)){'"'==d.charAt(0)&&'"'==d.charAt(d.length- +1)&&(d=d.substring(1,d.length-1));if(xg.test(d))return new Date(d);a.lastIndex=0;if(e=a.exec(d))return e.shift(),f=c?{yyyy:c.getFullYear(),MM:c.getMonth()+1,dd:c.getDate(),HH:c.getHours(),mm:c.getMinutes(),ss:c.getSeconds(),sss:c.getMilliseconds()/1E3}:{yyyy:1970,MM:1,dd:1,HH:0,mm:0,ss:0,sss:0},q(e,function(a,c){c=x};g.$observe("min",function(a){x= +p(a);h.$validate()})}if(A(g.max)||g.ngMax){var r;h.$validators.max=function(a){return!n(a)||z(r)||d(a)<=r};g.$observe("max",function(a){r=p(a);h.$validate()})}}}function Kd(a,b,d,c){(c.$$hasNativeValidators=J(b[0].validity))&&c.$parsers.push(function(a){var c=b.prop("validity")||{};return c.badInput||c.typeMismatch?u:a})}function Ld(a,b,d,c,e){if(A(c)){a=a(c);if(!a.constant)throw ob("constexpr",d,c);return a(b)}return e}function mc(a,b){a="ngClass"+a;return["$animate",function(d){function c(a,b){var c= +[],d=0;a:for(;d(?:<\/\1>|)$/,Ub=/<|&#?\w+;/,Jf=/<([\w:-]+)/,Kf=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,ha={option:[1,'"],thead:[1,"","
"],col:[2, +"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ha.optgroup=ha.option;ha.tbody=ha.tfoot=ha.colgroup=ha.caption=ha.thead;ha.th=ha.td;var Rf=Node.prototype.contains||function(a){return!!(this.compareDocumentPosition(a)&16)},Ra=U.prototype={ready:function(a){function b(){d||(d=!0,a())}var d=!1;"complete"===P.readyState?setTimeout(b):(this.on("DOMContentLoaded",b),U(T).on("load",b))},toString:function(){var a= +[];q(this,function(b){a.push(""+b)});return"["+a.join(", ")+"]"},eq:function(a){return 0<=a?H(this[a]):H(this[this.length+a])},length:0,push:zg,sort:[].sort,splice:[].splice},Eb={};q("multiple selected checked disabled readOnly required open".split(" "),function(a){Eb[N(a)]=a});var Sc={};q("input select option textarea button form details".split(" "),function(a){Sc[a]=!0});var $c={ngMinlength:"minlength",ngMaxlength:"maxlength",ngMin:"min",ngMax:"max",ngPattern:"pattern"};q({data:Xb,removeData:gb, +hasData:function(a){for(var b in hb[a.ng339])return!0;return!1},cleanData:function(a){for(var b=0,d=a.length;b/,Uf=/^[^\(]*\(\s*([^\)]*)\)/m,Ag=/,/,Bg=/^\s*(_?)(\S+?)\1\s*$/,Sf=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,Ia=O("$injector");eb.$$annotate=function(a,b,d){var c;if("function"===typeof a){if(!(c=a.$inject)){c=[];if(a.length){if(b)throw y(d)&&d||(d=a.name||Vf(a)),Ia("strictdi",d);b=Tc(a);q(b[1].split(Ag),function(a){a.replace(Bg,function(a,b,d){c.push(d)})})}a.$inject=c}}else M(a)? +(b=a.length-1,Sa(a[b],"fn"),c=a.slice(0,b)):Sa(a,"fn",!0);return c};var Pd=O("$animate"),Ye=function(){this.$get=E},Ze=function(){var a=new Ua,b=[];this.$get=["$$AnimateRunner","$rootScope",function(d,c){function e(a,b,c){var d=!1;b&&(b=y(b)?b.split(" "):M(b)?b:[],q(b,function(b){b&&(d=!0,a[b]=c)}));return d}function f(){q(b,function(b){var c=a.get(b);if(c){var d=Wf(b.attr("class")),e="",f="";q(c,function(a,b){a!==!!d[b]&&(a?e+=(e.length?" ":"")+b:f+=(f.length?" ":"")+b)});q(b,function(a){e&&Cb(a, +e);f&&Bb(a,f)});a.remove(b)}});b.length=0}return{enabled:E,on:E,off:E,pin:E,push:function(g,h,k,l){l&&l();k=k||{};k.from&&g.css(k.from);k.to&&g.css(k.to);if(k.addClass||k.removeClass)if(h=k.addClass,l=k.removeClass,k=a.get(g)||{},h=e(k,h,!0),l=e(k,l,!1),h||l)a.put(g,k),b.push(g),1===b.length&&c.$$postDigest(f);g=new d;g.complete();return g}}}]},We=["$provide",function(a){var b=this;this.$$registeredAnimations=Object.create(null);this.register=function(d,c){if(d&&"."!==d.charAt(0))throw Pd("notcsel", +d);var e=d+"-animation";b.$$registeredAnimations[d.substr(1)]=e;a.factory(e,c)};this.classNameFilter=function(a){if(1===arguments.length&&(this.$$classNameFilter=a instanceof RegExp?a:null)&&/(\s+|\/)ng-animate(\s+|\/)/.test(this.$$classNameFilter.toString()))throw Pd("nongcls","ng-animate");return this.$$classNameFilter};this.$get=["$$animateQueue",function(a){function b(a,c,d){if(d){var h;a:{for(h=0;h <= >= && || ! = |".split(" "),function(a){Nb[a]=!0});var Fg={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"'},gc=function(a){this.options=a};gc.prototype={constructor:gc,lex:function(a){this.text=a;this.index=0;for(this.tokens= +[];this.index=a&&"string"===typeof a},isWhitespace:function(a){return" "===a||"\r"===a||"\t"===a||"\n"===a||"\v"===a||"\u00a0"===a},isIdent:function(a){return"a"<=a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isExpOperator:function(a){return"-"=== +a||"+"===a||this.isNumber(a)},throwError:function(a,b,d){d=d||this.index;b=A(b)?"s "+b+"-"+this.index+" ["+this.text.substring(b,d)+"]":" "+d;throw ca("lexerr",a,b,this.text);},readNumber:function(){for(var a="",b=this.index;this.index","<=",">=");)a={type:s.BinaryExpression, +operator:b.text,left:a,right:this.additive()};return a},additive:function(){for(var a=this.multiplicative(),b;b=this.expect("+","-");)a={type:s.BinaryExpression,operator:b.text,left:a,right:this.multiplicative()};return a},multiplicative:function(){for(var a=this.unary(),b;b=this.expect("*","/","%");)a={type:s.BinaryExpression,operator:b.text,left:a,right:this.unary()};return a},unary:function(){var a;return(a=this.expect("+","-","!"))?{type:s.UnaryExpression,operator:a.text,prefix:!0,argument:this.unary()}: +this.primary()},primary:function(){var a;this.expect("(")?(a=this.filterChain(),this.consume(")")):this.expect("[")?a=this.arrayDeclaration():this.expect("{")?a=this.object():this.selfReferential.hasOwnProperty(this.peek().text)?a=pa(this.selfReferential[this.consume().text]):this.options.literals.hasOwnProperty(this.peek().text)?a={type:s.Literal,value:this.options.literals[this.consume().text]}:this.peek().identifier?a=this.identifier():this.peek().constant?a=this.constant():this.throwError("not a primary expression", +this.peek());for(var b;b=this.expect("(","[",".");)"("===b.text?(a={type:s.CallExpression,callee:a,arguments:this.parseArguments()},this.consume(")")):"["===b.text?(a={type:s.MemberExpression,object:a,property:this.expression(),computed:!0},this.consume("]")):"."===b.text?a={type:s.MemberExpression,object:a,property:this.identifier(),computed:!1}:this.throwError("IMPOSSIBLE");return a},filter:function(a){a=[a];for(var b={type:s.CallExpression,callee:this.identifier(),arguments:a,filter:!0};this.expect(":");)a.push(this.expression()); +return b},parseArguments:function(){var a=[];if(")"!==this.peekToken().text){do a.push(this.expression());while(this.expect(","))}return a},identifier:function(){var a=this.consume();a.identifier||this.throwError("is not a valid identifier",a);return{type:s.Identifier,name:a.text}},constant:function(){return{type:s.Literal,value:this.consume().value}},arrayDeclaration:function(){var a=[];if("]"!==this.peekToken().text){do{if(this.peek("]"))break;a.push(this.expression())}while(this.expect(","))}this.consume("]"); +return{type:s.ArrayExpression,elements:a}},object:function(){var a=[],b;if("}"!==this.peekToken().text){do{if(this.peek("}"))break;b={type:s.Property,kind:"init"};this.peek().constant?b.key=this.constant():this.peek().identifier?b.key=this.identifier():this.throwError("invalid key",this.peek());this.consume(":");b.value=this.expression();a.push(b)}while(this.expect(","))}this.consume("}");return{type:s.ObjectExpression,properties:a}},throwError:function(a,b){throw ca("syntax",b.text,a,b.index+1,this.text, +this.text.substring(b.index));},consume:function(a){if(0===this.tokens.length)throw ca("ueoe",this.text);var b=this.expect(a);b||this.throwError("is unexpected, expecting ["+a+"]",this.peek());return b},peekToken:function(){if(0===this.tokens.length)throw ca("ueoe",this.text);return this.tokens[0]},peek:function(a,b,d,c){return this.peekAhead(0,a,b,d,c)},peekAhead:function(a,b,d,c,e){if(this.tokens.length>a){a=this.tokens[a];var f=a.text;if(f===b||f===d||f===c||f===e||!(b||d||c||e))return a}return!1}, +expect:function(a,b,d,c){return(a=this.peek(a,b,d,c))?(this.tokens.shift(),a):!1},selfReferential:{"this":{type:s.ThisExpression},$locals:{type:s.LocalsExpression}}};rd.prototype={compile:function(a,b){var d=this,c=this.astBuilder.ast(a);this.state={nextId:0,filters:{},expensiveChecks:b,fn:{vars:[],body:[],own:{}},assign:{vars:[],body:[],own:{}},inputs:[]};aa(c,d.$filter);var e="",f;this.stage="assign";if(f=pd(c))this.state.computing="assign",e=this.nextId(),this.recurse(f,e),this.return_(e),e="fn.assign="+ +this.generateFunction("assign","s,v,l");f=nd(c.body);d.stage="inputs";q(f,function(a,b){var c="fn"+b;d.state[c]={vars:[],body:[],own:{}};d.state.computing=c;var e=d.nextId();d.recurse(a,e);d.return_(e);d.state.inputs.push(c);a.watchId=b});this.state.computing="fn";this.stage="main";this.recurse(c);e='"'+this.USE+" "+this.STRICT+'";\n'+this.filterPrefix()+"var fn="+this.generateFunction("fn","s,l,a,i")+e+this.watchFns()+"return fn;";e=(new Function("$filter","ensureSafeMemberName","ensureSafeObject", +"ensureSafeFunction","getStringValue","ensureSafeAssignContext","ifDefined","plus","text",e))(this.$filter,Wa,ta,ld,eg,Hb,ig,md,a);this.state=this.stage=u;e.literal=qd(c);e.constant=c.constant;return e},USE:"use",STRICT:"strict",watchFns:function(){var a=[],b=this.state.inputs,d=this;q(b,function(b){a.push("var "+b+"="+d.generateFunction(b,"s"))});b.length&&a.push("fn.inputs=["+b.join(",")+"];");return a.join("")},generateFunction:function(a,b){return"function("+b+"){"+this.varsPrefix(a)+this.body(a)+ +"};"},filterPrefix:function(){var a=[],b=this;q(this.state.filters,function(d,c){a.push(d+"=$filter("+b.escape(c)+")")});return a.length?"var "+a.join(",")+";":""},varsPrefix:function(a){return this.state[a].vars.length?"var "+this.state[a].vars.join(",")+";":""},body:function(a){return this.state[a].body.join("")},recurse:function(a,b,d,c,e,f){var g,h,k=this,l,m;c=c||E;if(!f&&A(a.watchId))b=b||this.nextId(),this.if_("i",this.lazyAssign(b,this.computedMember("i",a.watchId)),this.lazyRecurse(a,b,d, +c,e,!0));else switch(a.type){case s.Program:q(a.body,function(b,c){k.recurse(b.expression,u,u,function(a){h=a});c!==a.body.length-1?k.current().body.push(h,";"):k.return_(h)});break;case s.Literal:m=this.escape(a.value);this.assign(b,m);c(m);break;case s.UnaryExpression:this.recurse(a.argument,u,u,function(a){h=a});m=a.operator+"("+this.ifDefined(h,0)+")";this.assign(b,m);c(m);break;case s.BinaryExpression:this.recurse(a.left,u,u,function(a){g=a});this.recurse(a.right,u,u,function(a){h=a});m="+"=== +a.operator?this.plus(g,h):"-"===a.operator?this.ifDefined(g,0)+a.operator+this.ifDefined(h,0):"("+g+")"+a.operator+"("+h+")";this.assign(b,m);c(m);break;case s.LogicalExpression:b=b||this.nextId();k.recurse(a.left,b);k.if_("&&"===a.operator?b:k.not(b),k.lazyRecurse(a.right,b));c(b);break;case s.ConditionalExpression:b=b||this.nextId();k.recurse(a.test,b);k.if_(b,k.lazyRecurse(a.alternate,b),k.lazyRecurse(a.consequent,b));c(b);break;case s.Identifier:b=b||this.nextId();d&&(d.context="inputs"===k.stage? +"s":this.assign(this.nextId(),this.getHasOwnProperty("l",a.name)+"?l:s"),d.computed=!1,d.name=a.name);Wa(a.name);k.if_("inputs"===k.stage||k.not(k.getHasOwnProperty("l",a.name)),function(){k.if_("inputs"===k.stage||"s",function(){e&&1!==e&&k.if_(k.not(k.nonComputedMember("s",a.name)),k.lazyAssign(k.nonComputedMember("s",a.name),"{}"));k.assign(b,k.nonComputedMember("s",a.name))})},b&&k.lazyAssign(b,k.nonComputedMember("l",a.name)));(k.state.expensiveChecks||Ib(a.name))&&k.addEnsureSafeObject(b);c(b); +break;case s.MemberExpression:g=d&&(d.context=this.nextId())||this.nextId();b=b||this.nextId();k.recurse(a.object,g,u,function(){k.if_(k.notNull(g),function(){e&&1!==e&&k.addEnsureSafeAssignContext(g);if(a.computed)h=k.nextId(),k.recurse(a.property,h),k.getStringValue(h),k.addEnsureSafeMemberName(h),e&&1!==e&&k.if_(k.not(k.computedMember(g,h)),k.lazyAssign(k.computedMember(g,h),"{}")),m=k.ensureSafeObject(k.computedMember(g,h)),k.assign(b,m),d&&(d.computed=!0,d.name=h);else{Wa(a.property.name);e&& +1!==e&&k.if_(k.not(k.nonComputedMember(g,a.property.name)),k.lazyAssign(k.nonComputedMember(g,a.property.name),"{}"));m=k.nonComputedMember(g,a.property.name);if(k.state.expensiveChecks||Ib(a.property.name))m=k.ensureSafeObject(m);k.assign(b,m);d&&(d.computed=!1,d.name=a.property.name)}},function(){k.assign(b,"undefined")});c(b)},!!e);break;case s.CallExpression:b=b||this.nextId();a.filter?(h=k.filter(a.callee.name),l=[],q(a.arguments,function(a){var b=k.nextId();k.recurse(a,b);l.push(b)}),m=h+"("+ +l.join(",")+")",k.assign(b,m),c(b)):(h=k.nextId(),g={},l=[],k.recurse(a.callee,h,g,function(){k.if_(k.notNull(h),function(){k.addEnsureSafeFunction(h);q(a.arguments,function(a){k.recurse(a,k.nextId(),u,function(a){l.push(k.ensureSafeObject(a))})});g.name?(k.state.expensiveChecks||k.addEnsureSafeObject(g.context),m=k.member(g.context,g.name,g.computed)+"("+l.join(",")+")"):m=h+"("+l.join(",")+")";m=k.ensureSafeObject(m);k.assign(b,m)},function(){k.assign(b,"undefined")});c(b)}));break;case s.AssignmentExpression:h= +this.nextId();g={};if(!od(a.left))throw ca("lval");this.recurse(a.left,u,g,function(){k.if_(k.notNull(g.context),function(){k.recurse(a.right,h);k.addEnsureSafeObject(k.member(g.context,g.name,g.computed));k.addEnsureSafeAssignContext(g.context);m=k.member(g.context,g.name,g.computed)+a.operator+h;k.assign(b,m);c(b||m)})},1);break;case s.ArrayExpression:l=[];q(a.elements,function(a){k.recurse(a,k.nextId(),u,function(a){l.push(a)})});m="["+l.join(",")+"]";this.assign(b,m);c(m);break;case s.ObjectExpression:l= +[];q(a.properties,function(a){k.recurse(a.value,k.nextId(),u,function(b){l.push(k.escape(a.key.type===s.Identifier?a.key.name:""+a.key.value)+":"+b)})});m="{"+l.join(",")+"}";this.assign(b,m);c(m);break;case s.ThisExpression:this.assign(b,"s");c("s");break;case s.LocalsExpression:this.assign(b,"l");c("l");break;case s.NGValueParameter:this.assign(b,"v"),c("v")}},getHasOwnProperty:function(a,b){var d=a+"."+b,c=this.current().own;c.hasOwnProperty(d)||(c[d]=this.nextId(!1,a+"&&("+this.escape(b)+" in "+ +a+")"));return c[d]},assign:function(a,b){if(a)return this.current().body.push(a,"=",b,";"),a},filter:function(a){this.state.filters.hasOwnProperty(a)||(this.state.filters[a]=this.nextId(!0));return this.state.filters[a]},ifDefined:function(a,b){return"ifDefined("+a+","+this.escape(b)+")"},plus:function(a,b){return"plus("+a+","+b+")"},return_:function(a){this.current().body.push("return ",a,";")},if_:function(a,b,d){if(!0===a)b();else{var c=this.current().body;c.push("if(",a,"){");b();c.push("}"); +d&&(c.push("else{"),d(),c.push("}"))}},not:function(a){return"!("+a+")"},notNull:function(a){return a+"!=null"},nonComputedMember:function(a,b){return a+"."+b},computedMember:function(a,b){return a+"["+b+"]"},member:function(a,b,d){return d?this.computedMember(a,b):this.nonComputedMember(a,b)},addEnsureSafeObject:function(a){this.current().body.push(this.ensureSafeObject(a),";")},addEnsureSafeMemberName:function(a){this.current().body.push(this.ensureSafeMemberName(a),";")},addEnsureSafeFunction:function(a){this.current().body.push(this.ensureSafeFunction(a), +";")},addEnsureSafeAssignContext:function(a){this.current().body.push(this.ensureSafeAssignContext(a),";")},ensureSafeObject:function(a){return"ensureSafeObject("+a+",text)"},ensureSafeMemberName:function(a){return"ensureSafeMemberName("+a+",text)"},ensureSafeFunction:function(a){return"ensureSafeFunction("+a+",text)"},getStringValue:function(a){this.assign(a,"getStringValue("+a+")")},ensureSafeAssignContext:function(a){return"ensureSafeAssignContext("+a+",text)"},lazyRecurse:function(a,b,d,c,e,f){var g= +this;return function(){g.recurse(a,b,d,c,e,f)}},lazyAssign:function(a,b){var d=this;return function(){d.assign(a,b)}},stringEscapeRegex:/[^ a-zA-Z0-9]/g,stringEscapeFn:function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)},escape:function(a){if(y(a))return"'"+a.replace(this.stringEscapeRegex,this.stringEscapeFn)+"'";if(R(a))return a.toString();if(!0===a)return"true";if(!1===a)return"false";if(null===a)return"null";if("undefined"===typeof a)return"undefined";throw ca("esc");},nextId:function(a, +b){var d="v"+this.state.nextId++;a||this.current().vars.push(d+(b?"="+b:""));return d},current:function(){return this.state[this.state.computing]}};sd.prototype={compile:function(a,b){var d=this,c=this.astBuilder.ast(a);this.expression=a;this.expensiveChecks=b;aa(c,d.$filter);var e,f;if(e=pd(c))f=this.recurse(e);e=nd(c.body);var g;e&&(g=[],q(e,function(a,b){var c=d.recurse(a);a.input=c;g.push(c);a.watchId=b}));var h=[];q(c.body,function(a){h.push(d.recurse(a.expression))});e=0===c.body.length?E:1=== +c.body.length?h[0]:function(a,b){var c;q(h,function(d){c=d(a,b)});return c};f&&(e.assign=function(a,b,c){return f(a,c,b)});g&&(e.inputs=g);e.literal=qd(c);e.constant=c.constant;return e},recurse:function(a,b,d){var c,e,f=this,g;if(a.input)return this.inputs(a.input,a.watchId);switch(a.type){case s.Literal:return this.value(a.value,b);case s.UnaryExpression:return e=this.recurse(a.argument),this["unary"+a.operator](e,b);case s.BinaryExpression:return c=this.recurse(a.left),e=this.recurse(a.right), +this["binary"+a.operator](c,e,b);case s.LogicalExpression:return c=this.recurse(a.left),e=this.recurse(a.right),this["binary"+a.operator](c,e,b);case s.ConditionalExpression:return this["ternary?:"](this.recurse(a.test),this.recurse(a.alternate),this.recurse(a.consequent),b);case s.Identifier:return Wa(a.name,f.expression),f.identifier(a.name,f.expensiveChecks||Ib(a.name),b,d,f.expression);case s.MemberExpression:return c=this.recurse(a.object,!1,!!d),a.computed||(Wa(a.property.name,f.expression), +e=a.property.name),a.computed&&(e=this.recurse(a.property)),a.computed?this.computedMember(c,e,b,d,f.expression):this.nonComputedMember(c,e,f.expensiveChecks,b,d,f.expression);case s.CallExpression:return g=[],q(a.arguments,function(a){g.push(f.recurse(a))}),a.filter&&(e=this.$filter(a.callee.name)),a.filter||(e=this.recurse(a.callee,!0)),a.filter?function(a,c,d,f){for(var n=[],p=0;p":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)>b(c,e,f,g);return d?{value:c}:c}},"binary<=":function(a,b,d){return function(c,e,f,g){c=a(c,e,f, +g)<=b(c,e,f,g);return d?{value:c}:c}},"binary>=":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)>=b(c,e,f,g);return d?{value:c}:c}},"binary&&":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)&&b(c,e,f,g);return d?{value:c}:c}},"binary||":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)||b(c,e,f,g);return d?{value:c}:c}},"ternary?:":function(a,b,d,c){return function(e,f,g,h){e=a(e,f,g,h)?b(e,f,g,h):d(e,f,g,h);return c?{value:e}:e}},value:function(a,b){return function(){return b?{context:u, +name:u,value:a}:a}},identifier:function(a,b,d,c,e){return function(f,g,h,k){f=g&&a in g?g:f;c&&1!==c&&f&&!f[a]&&(f[a]={});g=f?f[a]:u;b&&ta(g,e);return d?{context:f,name:a,value:g}:g}},computedMember:function(a,b,d,c,e){return function(f,g,h,k){var l=a(f,g,h,k),m,n;null!=l&&(m=b(f,g,h,k),m+="",Wa(m,e),c&&1!==c&&(Hb(l),l&&!l[m]&&(l[m]={})),n=l[m],ta(n,e));return d?{context:l,name:m,value:n}:n}},nonComputedMember:function(a,b,d,c,e,f){return function(g,h,k,l){g=a(g,h,k,l);e&&1!==e&&(Hb(g),g&&!g[b]&& +(g[b]={}));h=null!=g?g[b]:u;(d||Ib(b))&&ta(h,f);return c?{context:g,name:b,value:h}:h}},inputs:function(a,b){return function(d,c,e,f){return f?f[b]:a(d,c,e)}}};var hc=function(a,b,d){this.lexer=a;this.$filter=b;this.options=d;this.ast=new s(a,d);this.astCompiler=d.csp?new sd(this.ast,b):new rd(this.ast,b)};hc.prototype={constructor:hc,parse:function(a){return this.astCompiler.compile(a,this.options.expensiveChecks)}};var jg=Object.prototype.valueOf,ua=O("$sce"),ma={HTML:"html",CSS:"css",URL:"url", +RESOURCE_URL:"resourceUrl",JS:"js"},lg=O("$compile"),Z=P.createElement("a"),wd=sa(T.location.href);xd.$inject=["$document"];Jc.$inject=["$provide"];var Ed=22,Dd=".",jc="0";yd.$inject=["$locale"];Ad.$inject=["$locale"];var wg={yyyy:X("FullYear",4,0,!1,!0),yy:X("FullYear",2,0,!0,!0),y:X("FullYear",1,0,!1,!0),MMMM:lb("Month"),MMM:lb("Month",!0),MM:X("Month",2,1),M:X("Month",1,1),LLLL:lb("Month",!1,!0),dd:X("Date",2),d:X("Date",1),HH:X("Hours",2),H:X("Hours",1),hh:X("Hours",2,-12),h:X("Hours",1,-12), +mm:X("Minutes",2),m:X("Minutes",1),ss:X("Seconds",2),s:X("Seconds",1),sss:X("Milliseconds",3),EEEE:lb("Day"),EEE:lb("Day",!0),a:function(a,b){return 12>a.getHours()?b.AMPMS[0]:b.AMPMS[1]},Z:function(a,b,d){a=-1*d;return a=(0<=a?"+":"")+(Jb(Math[0=a.getFullYear()?b.ERANAMES[0]:b.ERANAMES[1]}},vg=/((?:[^yMLdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/, +ug=/^\-?\d+$/;zd.$inject=["$locale"];var pg=da(N),qg=da(vb);Bd.$inject=["$parse"];var me=da({restrict:"E",compile:function(a,b){if(!b.href&&!b.xlinkHref)return function(a,b){if("a"===b[0].nodeName.toLowerCase()){var e="[object SVGAnimatedString]"===ka.call(b.prop("href"))?"xlink:href":"href";b.on("click",function(a){b.attr(e)||a.preventDefault()})}}}}),wb={};q(Eb,function(a,b){function d(a,d,e){a.$watch(e[c],function(a){e.$set(b,!!a)})}if("multiple"!=a){var c=ya("ng-"+b),e=d;"checked"===a&&(e=function(a, +b,e){e.ngModel!==e[c]&&d(a,b,e)});wb[c]=function(){return{restrict:"A",priority:100,link:e}}}});q($c,function(a,b){wb[b]=function(){return{priority:100,link:function(a,c,e){if("ngPattern"===b&&"/"==e.ngPattern.charAt(0)&&(c=e.ngPattern.match(yg))){e.$set("ngPattern",new RegExp(c[1],c[2]));return}a.$watch(e[b],function(a){e.$set(b,a)})}}}});q(["src","srcset","href"],function(a){var b=ya("ng-"+a);wb[b]=function(){return{priority:99,link:function(d,c,e){var f=a,g=a;"href"===a&&"[object SVGAnimatedString]"=== +ka.call(c.prop("href"))&&(g="xlinkHref",e.$attr[g]="xlink:href",f=null);e.$observe(b,function(b){b?(e.$set(g,b),Da&&f&&c.prop(f,e[g])):"href"===a&&e.$set(g,null)})}}}});var Kb={$addControl:E,$$renameControl:function(a,b){a.$name=b},$removeControl:E,$setValidity:E,$setDirty:E,$setPristine:E,$setSubmitted:E};Hd.$inject=["$element","$attrs","$scope","$animate","$interpolate"];var Qd=function(a){return["$timeout","$parse",function(b,d){function c(a){return""===a?d('this[""]').assign:d(a).assign||E}return{name:"form", +restrict:a?"EAC":"E",require:["form","^^?form"],controller:Hd,compile:function(d,f){d.addClass(Xa).addClass(pb);var g=f.name?"name":a&&f.ngForm?"ngForm":!1;return{pre:function(a,d,e,f){var n=f[0];if(!("action"in e)){var p=function(b){a.$apply(function(){n.$commitViewValue();n.$setSubmitted()});b.preventDefault()};d[0].addEventListener("submit",p,!1);d.on("$destroy",function(){b(function(){d[0].removeEventListener("submit",p,!1)},0,!1)})}(f[1]||n.$$parentForm).$addControl(n);var q=g?c(n.$name):E;g&& +(q(a,n),e.$observe(g,function(b){n.$name!==b&&(q(a,u),n.$$parentForm.$$renameControl(n,b),q=c(n.$name),q(a,n))}));d.on("$destroy",function(){n.$$parentForm.$removeControl(n);q(a,u);S(n,Kb)})}}}}}]},ne=Qd(),Ae=Qd(!0),xg=/^\d{4,}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z)$/,Gg=/^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+\])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i,Hg=/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i, +Ig=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/,Rd=/^(\d{4,})-(\d{2})-(\d{2})$/,Sd=/^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,nc=/^(\d{4,})-W(\d\d)$/,Td=/^(\d{4,})-(\d\d)$/,Ud=/^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,Jd=V();q(["date","datetime-local","month","time","week"],function(a){Jd[a]=!0});var Vd={text:function(a,b,d,c,e,f){mb(a,b,d,c,e,f);lc(c)},date:nb("date",Rd,Mb(Rd,["yyyy","MM","dd"]),"yyyy-MM-dd"),"datetime-local":nb("datetimelocal",Sd,Mb(Sd,"yyyy MM dd HH mm ss sss".split(" ")), +"yyyy-MM-ddTHH:mm:ss.sss"),time:nb("time",Ud,Mb(Ud,["HH","mm","ss","sss"]),"HH:mm:ss.sss"),week:nb("week",nc,function(a,b){if(fa(a))return a;if(y(a)){nc.lastIndex=0;var d=nc.exec(a);if(d){var c=+d[1],e=+d[2],f=d=0,g=0,h=0,k=Fd(c),e=7*(e-1);b&&(d=b.getHours(),f=b.getMinutes(),g=b.getSeconds(),h=b.getMilliseconds());return new Date(c,0,k.getDate()+e,d,f,g,h)}}return NaN},"yyyy-Www"),month:nb("month",Td,Mb(Td,["yyyy","MM"]),"yyyy-MM"),number:function(a,b,d,c,e,f){Kd(a,b,d,c);mb(a,b,d,c,e,f);c.$$parserName= +"number";c.$parsers.push(function(a){return c.$isEmpty(a)?null:Ig.test(a)?parseFloat(a):u});c.$formatters.push(function(a){if(!c.$isEmpty(a)){if(!R(a))throw ob("numfmt",a);a=a.toString()}return a});if(A(d.min)||d.ngMin){var g;c.$validators.min=function(a){return c.$isEmpty(a)||z(g)||a>=g};d.$observe("min",function(a){A(a)&&!R(a)&&(a=parseFloat(a,10));g=R(a)&&!isNaN(a)?a:u;c.$validate()})}if(A(d.max)||d.ngMax){var h;c.$validators.max=function(a){return c.$isEmpty(a)||z(h)||a<=h};d.$observe("max",function(a){A(a)&& +!R(a)&&(a=parseFloat(a,10));h=R(a)&&!isNaN(a)?a:u;c.$validate()})}},url:function(a,b,d,c,e,f){mb(a,b,d,c,e,f);lc(c);c.$$parserName="url";c.$validators.url=function(a,b){var d=a||b;return c.$isEmpty(d)||Gg.test(d)}},email:function(a,b,d,c,e,f){mb(a,b,d,c,e,f);lc(c);c.$$parserName="email";c.$validators.email=function(a,b){var d=a||b;return c.$isEmpty(d)||Hg.test(d)}},radio:function(a,b,d,c){z(d.name)&&b.attr("name",++qb);b.on("click",function(a){b[0].checked&&c.$setViewValue(d.value,a&&a.type)});c.$render= +function(){b[0].checked=d.value==c.$viewValue};d.$observe("value",c.$render)},checkbox:function(a,b,d,c,e,f,g,h){var k=Ld(h,a,"ngTrueValue",d.ngTrueValue,!0),l=Ld(h,a,"ngFalseValue",d.ngFalseValue,!1);b.on("click",function(a){c.$setViewValue(b[0].checked,a&&a.type)});c.$render=function(){b[0].checked=c.$viewValue};c.$isEmpty=function(a){return!1===a};c.$formatters.push(function(a){return na(a,k)});c.$parsers.push(function(a){return a?k:l})},hidden:E,button:E,submit:E,reset:E,file:E},Dc=["$browser", +"$sniffer","$filter","$parse",function(a,b,d,c){return{restrict:"E",require:["?ngModel"],link:{pre:function(e,f,g,h){h[0]&&(Vd[N(g.type)]||Vd.text)(e,f,g,h[0],b,a,d,c)}}}}],Jg=/^(true|false|\d+)$/,Se=function(){return{restrict:"A",priority:100,compile:function(a,b){return Jg.test(b.ngValue)?function(a,b,e){e.$set("value",a.$eval(e.ngValue))}:function(a,b,e){a.$watch(e.ngValue,function(a){e.$set("value",a)})}}}},se=["$compile",function(a){return{restrict:"AC",compile:function(b){a.$$addBindingClass(b); +return function(b,c,e){a.$$addBindingInfo(c,e.ngBind);c=c[0];b.$watch(e.ngBind,function(a){c.textContent=z(a)?"":a})}}}}],ue=["$interpolate","$compile",function(a,b){return{compile:function(d){b.$$addBindingClass(d);return function(c,d,f){c=a(d.attr(f.$attr.ngBindTemplate));b.$$addBindingInfo(d,c.expressions);d=d[0];f.$observe("ngBindTemplate",function(a){d.textContent=z(a)?"":a})}}}}],te=["$sce","$parse","$compile",function(a,b,d){return{restrict:"A",compile:function(c,e){var f=b(e.ngBindHtml),g= +b(e.ngBindHtml,function(a){return(a||"").toString()});d.$$addBindingClass(c);return function(b,c,e){d.$$addBindingInfo(c,e.ngBindHtml);b.$watch(g,function(){c.html(a.getTrustedHtml(f(b))||"")})}}}}],Re=da({restrict:"A",require:"ngModel",link:function(a,b,d,c){c.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),ve=mc("",!0),xe=mc("Odd",0),we=mc("Even",1),ye=Na({compile:function(a,b){b.$set("ngCloak",u);a.removeClass("ng-cloak")}}),ze=[function(){return{restrict:"A",scope:!0,controller:"@", +priority:500}}],Ic={},Kg={blur:!0,focus:!0};q("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),function(a){var b=ya("ng-"+a);Ic[b]=["$parse","$rootScope",function(d,c){return{restrict:"A",compile:function(e,f){var g=d(f[b],null,!0);return function(b,d){d.on(a,function(d){var e=function(){g(b,{$event:d})};Kg[a]&&c.$$phase?b.$evalAsync(e):b.$apply(e)})}}}}]});var Ce=["$animate","$compile",function(a, +b){return{multiElement:!0,transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(d,c,e,f,g){var h,k,l;d.$watch(e.ngIf,function(d){d?k||g(function(d,f){k=f;d[d.length++]=b.$$createComment("end ngIf",e.ngIf);h={clone:d};a.enter(d,c.parent(),c)}):(l&&(l.remove(),l=null),k&&(k.$destroy(),k=null),h&&(l=ub(h.clone),a.leave(l).then(function(){l=null}),h=null))})}}}],De=["$templateRequest","$anchorScroll","$animate",function(a,b,d){return{restrict:"ECA",priority:400,terminal:!0, +transclude:"element",controller:ea.noop,compile:function(c,e){var f=e.ngInclude||e.src,g=e.onload||"",h=e.autoscroll;return function(c,e,m,n,p){var q=0,s,x,r,w=function(){x&&(x.remove(),x=null);s&&(s.$destroy(),s=null);r&&(d.leave(r).then(function(){x=null}),x=r,r=null)};c.$watch(f,function(f){var m=function(){!A(h)||h&&!c.$eval(h)||b()},t=++q;f?(a(f,!0).then(function(a){if(!c.$$destroyed&&t===q){var b=c.$new();n.template=a;a=p(b,function(a){w();d.enter(a,null,e).then(m)});s=b;r=a;s.$emit("$includeContentLoaded", +f);c.$eval(g)}},function(){c.$$destroyed||t!==q||(w(),c.$emit("$includeContentError",f))}),c.$emit("$includeContentRequested",f)):(w(),n.template=null)})}}}}],Ue=["$compile",function(a){return{restrict:"ECA",priority:-400,require:"ngInclude",link:function(b,d,c,e){ka.call(d[0]).match(/SVG/)?(d.empty(),a(Lc(e.template,P).childNodes)(b,function(a){d.append(a)},{futureParentElement:d})):(d.html(e.template),a(d.contents())(b))}}}],Ee=Na({priority:450,compile:function(){return{pre:function(a,b,d){a.$eval(d.ngInit)}}}}), +Qe=function(){return{restrict:"A",priority:100,require:"ngModel",link:function(a,b,d,c){var e=b.attr(d.$attr.ngList)||", ",f="false"!==d.ngTrim,g=f?W(e):e;c.$parsers.push(function(a){if(!z(a)){var b=[];a&&q(a.split(g),function(a){a&&b.push(f?W(a):a)});return b}});c.$formatters.push(function(a){return M(a)?a.join(e):u});c.$isEmpty=function(a){return!a||!a.length}}}},pb="ng-valid",Md="ng-invalid",Xa="ng-pristine",Lb="ng-dirty",Od="ng-pending",ob=O("ngModel"),Lg=["$scope","$exceptionHandler","$attrs", +"$element","$parse","$animate","$timeout","$rootScope","$q","$interpolate",function(a,b,d,c,e,f,g,h,k,l){this.$modelValue=this.$viewValue=Number.NaN;this.$$rawModelValue=u;this.$validators={};this.$asyncValidators={};this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$untouched=!0;this.$touched=!1;this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$error={};this.$$success={};this.$pending=u;this.$name=l(d.name||"",!1)(a);this.$$parentForm=Kb;var m=e(d.ngModel), +n=m.assign,p=m,s=n,y=null,x,r=this;this.$$setOptions=function(a){if((r.$options=a)&&a.getterSetter){var b=e(d.ngModel+"()"),f=e(d.ngModel+"($$$p)");p=function(a){var c=m(a);D(c)&&(c=b(a));return c};s=function(a,b){D(m(a))?f(a,{$$$p:b}):n(a,b)}}else if(!m.assign)throw ob("nonassign",d.ngModel,wa(c));};this.$render=E;this.$isEmpty=function(a){return z(a)||""===a||null===a||a!==a};this.$$updateEmptyClasses=function(a){r.$isEmpty(a)?(f.removeClass(c,"ng-not-empty"),f.addClass(c,"ng-empty")):(f.removeClass(c, +"ng-empty"),f.addClass(c,"ng-not-empty"))};var w=0;Id({ctrl:this,$element:c,set:function(a,b){a[b]=!0},unset:function(a,b){delete a[b]},$animate:f});this.$setPristine=function(){r.$dirty=!1;r.$pristine=!0;f.removeClass(c,Lb);f.addClass(c,Xa)};this.$setDirty=function(){r.$dirty=!0;r.$pristine=!1;f.removeClass(c,Xa);f.addClass(c,Lb);r.$$parentForm.$setDirty()};this.$setUntouched=function(){r.$touched=!1;r.$untouched=!0;f.setClass(c,"ng-untouched","ng-touched")};this.$setTouched=function(){r.$touched= +!0;r.$untouched=!1;f.setClass(c,"ng-touched","ng-untouched")};this.$rollbackViewValue=function(){g.cancel(y);r.$viewValue=r.$$lastCommittedViewValue;r.$render()};this.$validate=function(){if(!R(r.$modelValue)||!isNaN(r.$modelValue)){var a=r.$$rawModelValue,b=r.$valid,c=r.$modelValue,d=r.$options&&r.$options.allowInvalid;r.$$runValidators(a,r.$$lastCommittedViewValue,function(e){d||b===e||(r.$modelValue=e?a:u,r.$modelValue!==c&&r.$$writeModelToScope())})}};this.$$runValidators=function(a,b,c){function d(){var c= +!0;q(r.$validators,function(d,e){var g=d(a,b);c=c&&g;f(e,g)});return c?!0:(q(r.$asyncValidators,function(a,b){f(b,null)}),!1)}function e(){var c=[],d=!0;q(r.$asyncValidators,function(e,g){var h=e(a,b);if(!h||!D(h.then))throw ob("nopromise",h);f(g,u);c.push(h.then(function(){f(g,!0)},function(){d=!1;f(g,!1)}))});c.length?k.all(c).then(function(){g(d)},E):g(!0)}function f(a,b){h===w&&r.$setValidity(a,b)}function g(a){h===w&&c(a)}w++;var h=w;(function(){var a=r.$$parserName||"parse";if(z(x))f(a,null); +else return x||(q(r.$validators,function(a,b){f(b,null)}),q(r.$asyncValidators,function(a,b){f(b,null)})),f(a,x),x;return!0})()?d()?e():g(!1):g(!1)};this.$commitViewValue=function(){var a=r.$viewValue;g.cancel(y);if(r.$$lastCommittedViewValue!==a||""===a&&r.$$hasNativeValidators)r.$$updateEmptyClasses(a),r.$$lastCommittedViewValue=a,r.$pristine&&this.$setDirty(),this.$$parseAndValidate()};this.$$parseAndValidate=function(){var b=r.$$lastCommittedViewValue;if(x=z(b)?u:!0)for(var c=0;ce||c.$isEmpty(b)|| +b.length<=e}}}}},Gc=function(){return{restrict:"A",require:"?ngModel",link:function(a,b,d,c){if(c){var e=0;d.$observe("minlength",function(a){e=Y(a)||0;c.$validate()});c.$validators.minlength=function(a,b){return c.$isEmpty(b)||b.length>=e}}}}};T.angular.bootstrap?T.console&&console.log("WARNING: Tried to load angular more than once."):(he(),je(ea),ea.module("ngLocale",[],["$provide",function(a){function b(a){a+="";var b=a.indexOf(".");return-1==b?0:a.length-b-1}a.value("$locale",{DATETIME_FORMATS:{AMPMS:["AM", +"PM"],DAY:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),ERANAMES:["Before Christ","Anno Domini"],ERAS:["BC","AD"],FIRSTDAYOFWEEK:6,MONTH:"January February March April May June July August September October November December".split(" "),SHORTDAY:"Sun Mon Tue Wed Thu Fri Sat".split(" "),SHORTMONTH:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),STANDALONEMONTH:"January February March April May June July August September October November December".split(" "),WEEKENDRANGE:[5, +6],fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",medium:"MMM d, y h:mm:ss a",mediumDate:"MMM d, y",mediumTime:"h:mm:ss a","short":"M/d/yy h:mm a",shortDate:"M/d/yy",shortTime:"h:mm a"},NUMBER_FORMATS:{CURRENCY_SYM:"$",DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{gSize:3,lgSize:3,maxFrac:3,minFrac:0,minInt:1,negPre:"-",negSuf:"",posPre:"",posSuf:""},{gSize:3,lgSize:3,maxFrac:2,minFrac:2,minInt:1,negPre:"-\u00a4",negSuf:"",posPre:"\u00a4",posSuf:""}]},id:"en-us",localeID:"en_US",pluralCat:function(a, +c){var e=a|0,f=c;u===f&&(f=Math.min(b(a),3));Math.pow(10,f);return 1==e&&0==f?"one":"other"}})}]),H(P).ready(function(){de(P,yc)}))})(window,document);!window.angular.$$csp().noInlineStyle&&window.angular.element(document.head).prepend(''); +//# sourceMappingURL=angular.min.js.map diff --git a/static/vendors/spinner.min.js b/static/vendors/spinner.min.js deleted file mode 100644 index bd3ae4f4e..000000000 --- a/static/vendors/spinner.min.js +++ /dev/null @@ -1,2 +0,0 @@ -// http://spin.js.org/#v2.3.2 -!function(a,b){"object"==typeof module&&module.exports?module.exports=b():"function"==typeof define&&define.amd?define(b):a.Spinner=b()}(this,function(){"use strict";function a(a,b){var c,d=document.createElement(a||"div");for(c in b)d[c]=b[c];return d}function b(a){for(var b=1,c=arguments.length;c>b;b++)a.appendChild(arguments[b]);return a}function c(a,b,c,d){var e=["opacity",b,~~(100*a),c,d].join("-"),f=.01+c/d*100,g=Math.max(1-(1-a)/b*(100-f),a),h=j.substring(0,j.indexOf("Animation")).toLowerCase(),i=h&&"-"+h+"-"||"";return m[e]||(k.insertRule("@"+i+"keyframes "+e+"{0%{opacity:"+g+"}"+f+"%{opacity:"+a+"}"+(f+.01)+"%{opacity:1}"+(f+b)%100+"%{opacity:"+a+"}100%{opacity:"+g+"}}",k.cssRules.length),m[e]=1),e}function d(a,b){var c,d,e=a.style;if(b=b.charAt(0).toUpperCase()+b.slice(1),void 0!==e[b])return b;for(d=0;d',c)}k.addRule(".spin-vml","behavior:url(#default#VML)"),h.prototype.lines=function(a,d){function f(){return e(c("group",{coordsize:k+" "+k,coordorigin:-j+" "+-j}),{width:k,height:k})}function h(a,h,i){b(m,b(e(f(),{rotation:360/d.lines*a+"deg",left:~~h}),b(e(c("roundrect",{arcsize:d.corners}),{width:j,height:d.scale*d.width,left:d.scale*d.radius,top:-d.scale*d.width>>1,filter:i}),c("fill",{color:g(d.color,a),opacity:d.opacity}),c("stroke",{opacity:0}))))}var i,j=d.scale*(d.length+d.width),k=2*d.scale*j,l=-(d.width+d.length)*d.scale*2+"px",m=e(f(),{position:"absolute",top:l,left:l});if(d.shadow)for(i=1;i<=d.lines;i++)h(i,-2,"progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)");for(i=1;i<=d.lines;i++)h(i);return b(a,m)},h.prototype.opacity=function(a,b,c,d){var e=a.firstChild;d=d.shadow&&d.lines||0,e&&b+d>1)+"px"})}for(var i,k=0,l=(f.lines-1)*(1-f.direction)/2;k: invalid syntax") +} diff --git a/vendor/github.com/astaxie/beego/config/fake.go b/vendor/github.com/astaxie/beego/config/fake.go index 50aa5d4a4..7e3626087 100644 --- a/vendor/github.com/astaxie/beego/config/fake.go +++ b/vendor/github.com/astaxie/beego/config/fake.go @@ -46,12 +46,16 @@ func (c *fakeConfigContainer) DefaultString(key string, defaultval string) strin } func (c *fakeConfigContainer) Strings(key string) []string { - return strings.Split(c.getData(key), ";") + v := c.getData(key) + if v == "" { + return nil + } + return strings.Split(v, ";") } func (c *fakeConfigContainer) DefaultStrings(key string, defaultval []string) []string { v := c.Strings(key) - if len(v) == 0 { + if v == nil { return defaultval } return v @@ -82,7 +86,7 @@ func (c *fakeConfigContainer) DefaultInt64(key string, defaultval int64) int64 { } func (c *fakeConfigContainer) Bool(key string) (bool, error) { - return strconv.ParseBool(c.getData(key)) + return ParseBool(c.getData(key)) } func (c *fakeConfigContainer) DefaultBool(key string, defaultval bool) bool { diff --git a/vendor/github.com/astaxie/beego/config/ini.go b/vendor/github.com/astaxie/beego/config/ini.go index 59e84e1e5..9c19b9b1b 100644 --- a/vendor/github.com/astaxie/beego/config/ini.go +++ b/vendor/github.com/astaxie/beego/config/ini.go @@ -27,7 +27,6 @@ import ( "strings" "sync" "time" - "unicode" ) var ( @@ -97,9 +96,11 @@ func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) { } if bComment != nil { line = bytes.TrimLeft(line, string(bComment)) - line = bytes.TrimLeftFunc(line, unicode.IsSpace) + // Need append to a new line if multi-line comments. + if comment.Len() > 0 { + comment.WriteByte('\n') + } comment.Write(line) - comment.WriteByte('\n') continue } @@ -194,7 +195,7 @@ type IniConfigContainer struct { // Bool returns the boolean value for a given key. func (c *IniConfigContainer) Bool(key string) (bool, error) { - return strconv.ParseBool(c.getdata(key)) + return ParseBool(c.getdata(key)) } // DefaultBool returns the boolean value for a given key. @@ -268,15 +269,20 @@ func (c *IniConfigContainer) DefaultString(key string, defaultval string) string } // Strings returns the []string value for a given key. +// Return nil if config value does not exist or is empty. func (c *IniConfigContainer) Strings(key string) []string { - return strings.Split(c.String(key), ";") + v := c.String(key) + if v == "" { + return nil + } + return strings.Split(v, ";") } // DefaultStrings returns the []string value for a given key. // if err != nil return defaltval func (c *IniConfigContainer) DefaultStrings(key string, defaultval []string) []string { v := c.Strings(key) - if len(v) == 0 { + if v == nil { return defaultval } return v @@ -299,14 +305,35 @@ func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) { } defer f.Close() + // Get section or key comments. Fixed #1607 + getCommentStr := func(section, key string) string { + comment, ok := "", false + if len(key) == 0 { + comment, ok = c.sectionComment[section] + } else { + comment, ok = c.keyComment[section+"."+key] + } + + if ok { + // Empty comment + if len(comment) == 0 || len(strings.TrimSpace(comment)) == 0 { + return string(bNumComment) + } + prefix := string(bNumComment) + // Add the line head character "#" + return prefix + strings.Replace(comment, lineBreak, lineBreak+prefix, -1) + } + return "" + } + buf := bytes.NewBuffer(nil) // Save default section at first place if dt, ok := c.data[defaultSection]; ok { for key, val := range dt { if key != " " { // Write key comments. - if v, ok := c.keyComment[key]; ok { - if _, err = buf.WriteString(string(bNumComment) + v + lineBreak); err != nil { + if v := getCommentStr(defaultSection, key); len(v) > 0 { + if _, err = buf.WriteString(v + lineBreak); err != nil { return err } } @@ -327,8 +354,8 @@ func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) { for section, dt := range c.data { if section != defaultSection { // Write section comments. - if v, ok := c.sectionComment[section]; ok { - if _, err = buf.WriteString(string(bNumComment) + v + lineBreak); err != nil { + if v := getCommentStr(section, ""); len(v) > 0 { + if _, err = buf.WriteString(v + lineBreak); err != nil { return err } } @@ -341,8 +368,8 @@ func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) { for key, val := range dt { if key != " " { // Write key comments. - if v, ok := c.keyComment[key]; ok { - if _, err = buf.WriteString(string(bNumComment) + v + lineBreak); err != nil { + if v := getCommentStr(section, key); len(v) > 0 { + if _, err = buf.WriteString(v + lineBreak); err != nil { return err } } diff --git a/vendor/github.com/astaxie/beego/config/json.go b/vendor/github.com/astaxie/beego/config/json.go index 65b4ac48a..fce517eb8 100644 --- a/vendor/github.com/astaxie/beego/config/json.go +++ b/vendor/github.com/astaxie/beego/config/json.go @@ -17,6 +17,7 @@ package config import ( "encoding/json" "errors" + "fmt" "io/ioutil" "os" "strings" @@ -70,12 +71,9 @@ type JSONConfigContainer struct { func (c *JSONConfigContainer) Bool(key string) (bool, error) { val := c.getData(key) if val != nil { - if v, ok := val.(bool); ok { - return v, nil - } - return false, errors.New("not bool value") + return ParseBool(val) } - return false, errors.New("not exist key:" + key) + return false, fmt.Errorf("not exist key: %q", key) } // DefaultBool return the bool value if has no error @@ -175,7 +173,7 @@ func (c *JSONConfigContainer) DefaultString(key string, defaultval string) strin func (c *JSONConfigContainer) Strings(key string) []string { stringVal := c.String(key) if stringVal == "" { - return []string{} + return nil } return strings.Split(c.String(key), ";") } @@ -183,7 +181,7 @@ func (c *JSONConfigContainer) Strings(key string) []string { // DefaultStrings returns the []string value for a given key. // if err != nil return defaltval func (c *JSONConfigContainer) DefaultStrings(key string, defaultval []string) []string { - if v := c.Strings(key); len(v) > 0 { + if v := c.Strings(key); v != nil { return v } return defaultval diff --git a/vendor/github.com/astaxie/beego/config/xml/xml.go b/vendor/github.com/astaxie/beego/config/xml/xml.go deleted file mode 100644 index 4d48f7d2c..000000000 --- a/vendor/github.com/astaxie/beego/config/xml/xml.go +++ /dev/null @@ -1,229 +0,0 @@ -// Copyright 2014 beego Author. 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. - -// Package xml for config provider -// -// depend on github.com/beego/x2j -// -// go install github.com/beego/x2j -// -// Usage: -// import( -// _ "github.com/astaxie/beego/config/xml" -// "github.com/astaxie/beego/config" -// ) -// -// cnf, err := config.NewConfig("xml", "config.xml") -// -// more docs http://beego.me/docs/module/config.md -package xml - -import ( - "encoding/xml" - "errors" - "fmt" - "io/ioutil" - "os" - "path" - "strconv" - "strings" - "sync" - "time" - - "github.com/astaxie/beego/config" - "github.com/beego/x2j" -) - -// Config is a xml config parser and implements Config interface. -// xml configurations should be included in tag. -// only support key/value pair as value as each item. -type Config struct{} - -// Parse returns a ConfigContainer with parsed xml config map. -func (xc *Config) Parse(filename string) (config.Configer, error) { - file, err := os.Open(filename) - if err != nil { - return nil, err - } - defer file.Close() - - x := &ConfigContainer{data: make(map[string]interface{})} - content, err := ioutil.ReadAll(file) - if err != nil { - return nil, err - } - - d, err := x2j.DocToMap(string(content)) - if err != nil { - return nil, err - } - - x.data = d["config"].(map[string]interface{}) - return x, nil -} - -// ParseData xml data -func (xc *Config) ParseData(data []byte) (config.Configer, error) { - // Save memory data to temporary file - tmpName := path.Join(os.TempDir(), "beego", fmt.Sprintf("%d", time.Now().Nanosecond())) - os.MkdirAll(path.Dir(tmpName), os.ModePerm) - if err := ioutil.WriteFile(tmpName, data, 0655); err != nil { - return nil, err - } - return xc.Parse(tmpName) -} - -// ConfigContainer A Config represents the xml configuration. -type ConfigContainer struct { - data map[string]interface{} - sync.Mutex -} - -// Bool returns the boolean value for a given key. -func (c *ConfigContainer) Bool(key string) (bool, error) { - return strconv.ParseBool(c.data[key].(string)) -} - -// DefaultBool return the bool value if has no error -// otherwise return the defaultval -func (c *ConfigContainer) DefaultBool(key string, defaultval bool) bool { - v, err := c.Bool(key) - if err != nil { - return defaultval - } - return v -} - -// Int returns the integer value for a given key. -func (c *ConfigContainer) Int(key string) (int, error) { - return strconv.Atoi(c.data[key].(string)) -} - -// DefaultInt returns the integer value for a given key. -// if err != nil return defaltval -func (c *ConfigContainer) DefaultInt(key string, defaultval int) int { - v, err := c.Int(key) - if err != nil { - return defaultval - } - return v -} - -// Int64 returns the int64 value for a given key. -func (c *ConfigContainer) Int64(key string) (int64, error) { - return strconv.ParseInt(c.data[key].(string), 10, 64) -} - -// DefaultInt64 returns the int64 value for a given key. -// if err != nil return defaltval -func (c *ConfigContainer) DefaultInt64(key string, defaultval int64) int64 { - v, err := c.Int64(key) - if err != nil { - return defaultval - } - return v - -} - -// Float returns the float value for a given key. -func (c *ConfigContainer) Float(key string) (float64, error) { - return strconv.ParseFloat(c.data[key].(string), 64) -} - -// DefaultFloat returns the float64 value for a given key. -// if err != nil return defaltval -func (c *ConfigContainer) DefaultFloat(key string, defaultval float64) float64 { - v, err := c.Float(key) - if err != nil { - return defaultval - } - return v -} - -// String returns the string value for a given key. -func (c *ConfigContainer) String(key string) string { - if v, ok := c.data[key].(string); ok { - return v - } - return "" -} - -// DefaultString returns the string value for a given key. -// if err != nil return defaltval -func (c *ConfigContainer) DefaultString(key string, defaultval string) string { - v := c.String(key) - if v == "" { - return defaultval - } - return v -} - -// Strings returns the []string value for a given key. -func (c *ConfigContainer) Strings(key string) []string { - return strings.Split(c.String(key), ";") -} - -// DefaultStrings returns the []string value for a given key. -// if err != nil return defaltval -func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string { - v := c.Strings(key) - if len(v) == 0 { - return defaultval - } - return v -} - -// GetSection returns map for the given section -func (c *ConfigContainer) GetSection(section string) (map[string]string, error) { - if v, ok := c.data[section]; ok { - return v.(map[string]string), nil - } - return nil, errors.New("not exist setction") -} - -// SaveConfigFile save the config into file -func (c *ConfigContainer) SaveConfigFile(filename string) (err error) { - // Write configuration file by filename. - f, err := os.Create(filename) - if err != nil { - return err - } - defer f.Close() - b, err := xml.MarshalIndent(c.data, " ", " ") - if err != nil { - return err - } - _, err = f.Write(b) - return err -} - -// Set writes a new value for key. -func (c *ConfigContainer) Set(key, val string) error { - c.Lock() - defer c.Unlock() - c.data[key] = val - return nil -} - -// DIY returns the raw value by a given key. -func (c *ConfigContainer) DIY(key string) (v interface{}, err error) { - if v, ok := c.data[key]; ok { - return v, nil - } - return nil, errors.New("not exist key") -} - -func init() { - config.Register("xml", &Config{}) -} diff --git a/vendor/github.com/astaxie/beego/config/yaml/yaml.go b/vendor/github.com/astaxie/beego/config/yaml/yaml.go deleted file mode 100644 index f034d3ba3..000000000 --- a/vendor/github.com/astaxie/beego/config/yaml/yaml.go +++ /dev/null @@ -1,266 +0,0 @@ -// Copyright 2014 beego Author. 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. - -// Package yaml for config provider -// -// depend on github.com/beego/goyaml2 -// -// go install github.com/beego/goyaml2 -// -// Usage: -// import( -// _ "github.com/astaxie/beego/config/yaml" -// "github.com/astaxie/beego/config" -// ) -// -// cnf, err := config.NewConfig("yaml", "config.yaml") -// -// more docs http://beego.me/docs/module/config.md -package yaml - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "log" - "os" - "path" - "strings" - "sync" - "time" - - "github.com/astaxie/beego/config" - "github.com/beego/goyaml2" -) - -// Config is a yaml config parser and implements Config interface. -type Config struct{} - -// Parse returns a ConfigContainer with parsed yaml config map. -func (yaml *Config) Parse(filename string) (y config.Configer, err error) { - cnf, err := ReadYmlReader(filename) - if err != nil { - return - } - y = &ConfigContainer{ - data: cnf, - } - return -} - -// ParseData parse yaml data -func (yaml *Config) ParseData(data []byte) (config.Configer, error) { - // Save memory data to temporary file - tmpName := path.Join(os.TempDir(), "beego", fmt.Sprintf("%d", time.Now().Nanosecond())) - os.MkdirAll(path.Dir(tmpName), os.ModePerm) - if err := ioutil.WriteFile(tmpName, data, 0655); err != nil { - return nil, err - } - return yaml.Parse(tmpName) -} - -// ReadYmlReader Read yaml file to map. -// if json like, use json package, unless goyaml2 package. -func ReadYmlReader(path string) (cnf map[string]interface{}, err error) { - f, err := os.Open(path) - if err != nil { - return - } - defer f.Close() - - buf, err := ioutil.ReadAll(f) - if err != nil || len(buf) < 3 { - return - } - - if string(buf[0:1]) == "{" { - log.Println("Look like a Json, try json umarshal") - err = json.Unmarshal(buf, &cnf) - if err == nil { - log.Println("It is Json Map") - return - } - } - - data, err := goyaml2.Read(bytes.NewBuffer(buf)) - if err != nil { - log.Println("Goyaml2 ERR>", string(buf), err) - return - } - - if data == nil { - log.Println("Goyaml2 output nil? Pls report bug\n" + string(buf)) - return - } - cnf, ok := data.(map[string]interface{}) - if !ok { - log.Println("Not a Map? >> ", string(buf), data) - cnf = nil - } - return -} - -// ConfigContainer A Config represents the yaml configuration. -type ConfigContainer struct { - data map[string]interface{} - sync.Mutex -} - -// Bool returns the boolean value for a given key. -func (c *ConfigContainer) Bool(key string) (bool, error) { - if v, ok := c.data[key].(bool); ok { - return v, nil - } - return false, errors.New("not bool value") -} - -// DefaultBool return the bool value if has no error -// otherwise return the defaultval -func (c *ConfigContainer) DefaultBool(key string, defaultval bool) bool { - v, err := c.Bool(key) - if err != nil { - return defaultval - } - return v -} - -// Int returns the integer value for a given key. -func (c *ConfigContainer) Int(key string) (int, error) { - if v, ok := c.data[key].(int64); ok { - return int(v), nil - } - return 0, errors.New("not int value") -} - -// DefaultInt returns the integer value for a given key. -// if err != nil return defaltval -func (c *ConfigContainer) DefaultInt(key string, defaultval int) int { - v, err := c.Int(key) - if err != nil { - return defaultval - } - return v -} - -// Int64 returns the int64 value for a given key. -func (c *ConfigContainer) Int64(key string) (int64, error) { - if v, ok := c.data[key].(int64); ok { - return v, nil - } - return 0, errors.New("not bool value") -} - -// DefaultInt64 returns the int64 value for a given key. -// if err != nil return defaltval -func (c *ConfigContainer) DefaultInt64(key string, defaultval int64) int64 { - v, err := c.Int64(key) - if err != nil { - return defaultval - } - return v -} - -// Float returns the float value for a given key. -func (c *ConfigContainer) Float(key string) (float64, error) { - if v, ok := c.data[key].(float64); ok { - return v, nil - } - return 0.0, errors.New("not float64 value") -} - -// DefaultFloat returns the float64 value for a given key. -// if err != nil return defaltval -func (c *ConfigContainer) DefaultFloat(key string, defaultval float64) float64 { - v, err := c.Float(key) - if err != nil { - return defaultval - } - return v -} - -// String returns the string value for a given key. -func (c *ConfigContainer) String(key string) string { - if v, ok := c.data[key].(string); ok { - return v - } - return "" -} - -// DefaultString returns the string value for a given key. -// if err != nil return defaltval -func (c *ConfigContainer) DefaultString(key string, defaultval string) string { - v := c.String(key) - if v == "" { - return defaultval - } - return v -} - -// Strings returns the []string value for a given key. -func (c *ConfigContainer) Strings(key string) []string { - return strings.Split(c.String(key), ";") -} - -// DefaultStrings returns the []string value for a given key. -// if err != nil return defaltval -func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string { - v := c.Strings(key) - if len(v) == 0 { - return defaultval - } - return v -} - -// GetSection returns map for the given section -func (c *ConfigContainer) GetSection(section string) (map[string]string, error) { - v, ok := c.data[section] - if ok { - return v.(map[string]string), nil - } - return nil, errors.New("not exist setction") -} - -// SaveConfigFile save the config into file -func (c *ConfigContainer) SaveConfigFile(filename string) (err error) { - // Write configuration file by filename. - f, err := os.Create(filename) - if err != nil { - return err - } - defer f.Close() - err = goyaml2.Write(f, c.data) - return err -} - -// Set writes a new value for key. -func (c *ConfigContainer) Set(key, val string) error { - c.Lock() - defer c.Unlock() - c.data[key] = val - return nil -} - -// DIY returns the raw value by a given key. -func (c *ConfigContainer) DIY(key string) (v interface{}, err error) { - if v, ok := c.data[key]; ok { - return v, nil - } - return nil, errors.New("not exist key") -} - -func init() { - config.Register("yaml", &Config{}) -} diff --git a/vendor/github.com/astaxie/beego/context/context.go b/vendor/github.com/astaxie/beego/context/context.go index db790ff2f..63a1313d2 100644 --- a/vendor/github.com/astaxie/beego/context/context.go +++ b/vendor/github.com/astaxie/beego/context/context.go @@ -24,11 +24,13 @@ package context import ( "bufio" + "bytes" "crypto/hmac" "crypto/sha1" "encoding/base64" "errors" "fmt" + "io" "net" "net/http" "strconv" @@ -59,7 +61,10 @@ type Context struct { // Reset init Context, BeegoInput and BeegoOutput func (ctx *Context) Reset(rw http.ResponseWriter, r *http.Request) { ctx.Request = r - ctx.ResponseWriter = &Response{rw, false, 0} + if ctx.ResponseWriter == nil { + ctx.ResponseWriter = &Response{} + } + ctx.ResponseWriter.reset(rw) ctx.Input.Reset(ctx) ctx.Output.Reset(ctx) } @@ -176,25 +181,43 @@ type Response struct { Status int } +func (r *Response) reset(rw http.ResponseWriter) { + r.ResponseWriter = rw + r.Status = 0 + r.Started = false +} + // Write writes the data to the connection as part of an HTTP reply, // and sets `started` to true. // started means the response has sent out. -func (w *Response) Write(p []byte) (int, error) { - w.Started = true - return w.ResponseWriter.Write(p) +func (r *Response) Write(p []byte) (int, error) { + r.Started = true + return r.ResponseWriter.Write(p) +} + +// Copy writes the data to the connection as part of an HTTP reply, +// and sets `started` to true. +// started means the response has sent out. +func (r *Response) Copy(buf *bytes.Buffer) (int64, error) { + r.Started = true + return io.Copy(r.ResponseWriter, buf) } // WriteHeader sends an HTTP response header with status code, // and sets `started` to true. -func (w *Response) WriteHeader(code int) { - w.Status = code - w.Started = true - w.ResponseWriter.WriteHeader(code) +func (r *Response) WriteHeader(code int) { + if r.Status > 0 { + //prevent multiple response.WriteHeader calls + return + } + r.Status = code + r.Started = true + r.ResponseWriter.WriteHeader(code) } // Hijack hijacker for http -func (w *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) { - hj, ok := w.ResponseWriter.(http.Hijacker) +func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) { + hj, ok := r.ResponseWriter.(http.Hijacker) if !ok { return nil, nil, errors.New("webserver doesn't support hijacking") } @@ -202,15 +225,15 @@ func (w *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) { } // Flush http.Flusher -func (w *Response) Flush() { - if f, ok := w.ResponseWriter.(http.Flusher); ok { +func (r *Response) Flush() { + if f, ok := r.ResponseWriter.(http.Flusher); ok { f.Flush() } } // CloseNotify http.CloseNotifier -func (w *Response) CloseNotify() <-chan bool { - if cn, ok := w.ResponseWriter.(http.CloseNotifier); ok { +func (r *Response) CloseNotify() <-chan bool { + if cn, ok := r.ResponseWriter.(http.CloseNotifier); ok { return cn.CloseNotify() } return nil diff --git a/vendor/github.com/astaxie/beego/context/input.go b/vendor/github.com/astaxie/beego/context/input.go index c37204bdc..edfdf5302 100644 --- a/vendor/github.com/astaxie/beego/context/input.go +++ b/vendor/github.com/astaxie/beego/context/input.go @@ -287,6 +287,13 @@ func (input *BeegoInput) Params() map[string]string { // SetParam will set the param with key and value func (input *BeegoInput) SetParam(key, val string) { + // check if already exists + for i, v := range input.pnames { + if v == key && i <= len(input.pvalues) { + input.pvalues[i] = val + return + } + } input.pvalues = append(input.pvalues, val) input.pnames = append(input.pnames, key) } diff --git a/vendor/github.com/astaxie/beego/context/output.go b/vendor/github.com/astaxie/beego/context/output.go index 2d756e271..174047020 100644 --- a/vendor/github.com/astaxie/beego/context/output.go +++ b/vendor/github.com/astaxie/beego/context/output.go @@ -21,7 +21,6 @@ import ( "errors" "fmt" "html/template" - "io" "mime" "net/http" "path/filepath" @@ -57,7 +56,7 @@ func (output *BeegoOutput) Header(key, val string) { // Body sets response body content. // if EnableGzip, compress content string. // it sends out response body directly. -func (output *BeegoOutput) Body(content []byte) { +func (output *BeegoOutput) Body(content []byte) error { var encoding string var buf = &bytes.Buffer{} if output.EnableGzip { @@ -75,7 +74,8 @@ func (output *BeegoOutput) Body(content []byte) { output.Status = 0 } - io.Copy(output.Context.ResponseWriter, buf) + _, err := output.Context.ResponseWriter.Copy(buf) + return err } // Cookie sets cookie value via given key. @@ -97,9 +97,10 @@ func (output *BeegoOutput) Cookie(name string, value string, others ...interface maxAge = v } - if maxAge > 0 { + switch { + case maxAge > 0: fmt.Fprintf(&b, "; Expires=%s; Max-Age=%d", time.Now().Add(time.Duration(maxAge)*time.Second).UTC().Format(time.RFC1123), maxAge) - } else { + case maxAge < 0: fmt.Fprintf(&b, "; Max-Age=0") } } @@ -186,8 +187,7 @@ func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, coding bool) e if coding { content = []byte(stringsToJSON(string(content))) } - output.Body(content) - return nil + return output.Body(content) } // JSONP writes jsonp to response body. @@ -212,8 +212,7 @@ func (output *BeegoOutput) JSONP(data interface{}, hasIndent bool) error { callbackContent.WriteString("(") callbackContent.Write(content) callbackContent.WriteString(");\r\n") - output.Body(callbackContent.Bytes()) - return nil + return output.Body(callbackContent.Bytes()) } // XML writes xml string to response body. @@ -230,8 +229,7 @@ func (output *BeegoOutput) XML(data interface{}, hasIndent bool) error { http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError) return err } - output.Body(content) - return nil + return output.Body(content) } // Download forces response for download file. diff --git a/vendor/github.com/astaxie/beego/controller.go b/vendor/github.com/astaxie/beego/controller.go index a2943d420..dc2758480 100644 --- a/vendor/github.com/astaxie/beego/controller.go +++ b/vendor/github.com/astaxie/beego/controller.go @@ -185,8 +185,7 @@ func (c *Controller) Render() error { return err } c.Ctx.Output.Header("Content-Type", "text/html; charset=utf-8") - c.Ctx.Output.Body(rb) - return nil + return c.Ctx.Output.Body(rb) } // RenderString returns the rendered template string. Do not send out response. @@ -197,33 +196,9 @@ func (c *Controller) RenderString() (string, error) { // RenderBytes returns the bytes of rendered template string. Do not send out response. func (c *Controller) RenderBytes() ([]byte, error) { - //if the controller has set layout, then first get the tplname's content set the content to the layout - var buf bytes.Buffer - if c.Layout != "" { - if c.TplName == "" { - c.TplName = strings.ToLower(c.controllerName) + "/" + strings.ToLower(c.actionName) + "." + c.TplExt - } - - if BConfig.RunMode == DEV { - buildFiles := []string{c.TplName} - if c.LayoutSections != nil { - for _, sectionTpl := range c.LayoutSections { - if sectionTpl == "" { - continue - } - buildFiles = append(buildFiles, sectionTpl) - } - } - BuildTemplate(BConfig.WebConfig.ViewsPath, buildFiles...) - } - if _, ok := BeeTemplates[c.TplName]; !ok { - panic("can't find templatefile in the path:" + c.TplName) - } - err := BeeTemplates[c.TplName].ExecuteTemplate(&buf, c.TplName, c.Data) - if err != nil { - Trace("template Execute err:", err) - return nil, err - } + buf, err := c.renderTemplate() + //if the controller has set layout, then first get the tplName's content set the content to the layout + if err == nil && c.Layout != "" { c.Data["LayoutContent"] = template.HTML(buf.String()) if c.LayoutSections != nil { @@ -232,11 +207,9 @@ func (c *Controller) RenderBytes() ([]byte, error) { c.Data[sectionName] = "" continue } - buf.Reset() - err = BeeTemplates[sectionTpl].ExecuteTemplate(&buf, sectionTpl, c.Data) + err = executeTemplate(&buf, sectionTpl, c.Data) if err != nil { - Trace("template Execute err:", err) return nil, err } c.Data[sectionName] = template.HTML(buf.String()) @@ -244,30 +217,32 @@ func (c *Controller) RenderBytes() ([]byte, error) { } buf.Reset() - err = BeeTemplates[c.Layout].ExecuteTemplate(&buf, c.Layout, c.Data) - if err != nil { - Trace("template Execute err:", err) - return nil, err - } - return buf.Bytes(), nil + executeTemplate(&buf, c.Layout, c.Data) } + return buf.Bytes(), err +} +func (c *Controller) renderTemplate() (bytes.Buffer, error) { + var buf bytes.Buffer if c.TplName == "" { c.TplName = strings.ToLower(c.controllerName) + "/" + strings.ToLower(c.actionName) + "." + c.TplExt } if BConfig.RunMode == DEV { - BuildTemplate(BConfig.WebConfig.ViewsPath, c.TplName) + buildFiles := []string{c.TplName} + if c.Layout != "" { + buildFiles = append(buildFiles, c.Layout) + if c.LayoutSections != nil { + for _, sectionTpl := range c.LayoutSections { + if sectionTpl == "" { + continue + } + buildFiles = append(buildFiles, sectionTpl) + } + } + } + BuildTemplate(BConfig.WebConfig.ViewsPath, buildFiles...) } - if _, ok := BeeTemplates[c.TplName]; !ok { - panic("can't find templatefile in the path:" + c.TplName) - } - buf.Reset() - err := BeeTemplates[c.TplName].ExecuteTemplate(&buf, c.TplName, c.Data) - if err != nil { - Trace("template Execute err:", err) - return nil, err - } - return buf.Bytes(), nil + return buf, executeTemplate(&buf, c.TplName, c.Data) } // Redirect sends the redirection response to url with status code. @@ -287,6 +262,7 @@ func (c *Controller) Abort(code string) { // CustomAbort stops controller handler and show the error data, it's similar Aborts, but support status code and body. func (c *Controller) CustomAbort(status int, body string) { c.Ctx.ResponseWriter.WriteHeader(status) + //c.Ctx.Output.Status = status // first panic from ErrorMaps, is is user defined error functions. if _, ok := ErrorMaps[body]; ok { panic(body) diff --git a/vendor/github.com/astaxie/beego/error.go b/vendor/github.com/astaxie/beego/error.go index 94151dd8b..4f48fab21 100644 --- a/vendor/github.com/astaxie/beego/error.go +++ b/vendor/github.com/astaxie/beego/error.go @@ -424,6 +424,7 @@ func exception(errCode string, ctx *context.Context) { func executeError(err *errorInfo, ctx *context.Context, code int) { if err.errorType == errorTypeHandler { + ctx.ResponseWriter.WriteHeader(code) err.handler(ctx.ResponseWriter, ctx.Request) return } diff --git a/vendor/github.com/astaxie/beego/grace/server.go b/vendor/github.com/astaxie/beego/grace/server.go index f4512ded5..101bda56d 100644 --- a/vendor/github.com/astaxie/beego/grace/server.go +++ b/vendor/github.com/astaxie/beego/grace/server.go @@ -90,16 +90,15 @@ func (srv *Server) ListenAndServeTLS(certFile, keyFile string) (err error) { addr = ":https" } - config := &tls.Config{} - if srv.TLSConfig != nil { - *config = *srv.TLSConfig + if srv.TLSConfig == nil { + srv.TLSConfig = &tls.Config{} } - if config.NextProtos == nil { - config.NextProtos = []string{"http/1.1"} + if srv.TLSConfig.NextProtos == nil { + srv.TLSConfig.NextProtos = []string{"http/1.1"} } - config.Certificates = make([]tls.Certificate, 1) - config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile) + srv.TLSConfig.Certificates = make([]tls.Certificate, 1) + srv.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile) if err != nil { return } @@ -113,7 +112,7 @@ func (srv *Server) ListenAndServeTLS(certFile, keyFile string) (err error) { } srv.tlsInnerListener = newGraceListener(l, srv) - srv.GraceListener = tls.NewListener(srv.tlsInnerListener, config) + srv.GraceListener = tls.NewListener(srv.tlsInnerListener, srv.TLSConfig) if srv.isChild { process, err := os.FindProcess(os.Getppid()) diff --git a/vendor/github.com/astaxie/beego/hooks.go b/vendor/github.com/astaxie/beego/hooks.go index 78abf8ef9..59b10b32f 100644 --- a/vendor/github.com/astaxie/beego/hooks.go +++ b/vendor/github.com/astaxie/beego/hooks.go @@ -68,13 +68,11 @@ func registerSession() error { } func registerTemplate() error { - if BConfig.WebConfig.AutoRender { - if err := BuildTemplate(BConfig.WebConfig.ViewsPath); err != nil { - if BConfig.RunMode == DEV { - Warn(err) - } - return err + if err := BuildTemplate(BConfig.WebConfig.ViewsPath); err != nil { + if BConfig.RunMode == DEV { + Warn(err) } + return err } return nil } diff --git a/vendor/github.com/astaxie/beego/httplib/README.md b/vendor/github.com/astaxie/beego/httplib/README.md deleted file mode 100644 index 6a72cf7cf..000000000 --- a/vendor/github.com/astaxie/beego/httplib/README.md +++ /dev/null @@ -1,97 +0,0 @@ -# httplib -httplib is an libs help you to curl remote url. - -# How to use? - -## GET -you can use Get to crawl data. - - import "github.com/astaxie/beego/httplib" - - str, err := httplib.Get("http://beego.me/").String() - if err != nil { - // error - } - fmt.Println(str) - -## POST -POST data to remote url - - req := httplib.Post("http://beego.me/") - req.Param("username","astaxie") - req.Param("password","123456") - str, err := req.String() - if err != nil { - // error - } - fmt.Println(str) - -## Set timeout - -The default timeout is `60` seconds, function prototype: - - SetTimeout(connectTimeout, readWriteTimeout time.Duration) - -Exmaple: - - // GET - httplib.Get("http://beego.me/").SetTimeout(100 * time.Second, 30 * time.Second) - - // POST - httplib.Post("http://beego.me/").SetTimeout(100 * time.Second, 30 * time.Second) - - -## Debug - -If you want to debug the request info, set the debug on - - httplib.Get("http://beego.me/").Debug(true) - -## Set HTTP Basic Auth - - str, err := Get("http://beego.me/").SetBasicAuth("user", "passwd").String() - if err != nil { - // error - } - fmt.Println(str) - -## Set HTTPS - -If request url is https, You can set the client support TSL: - - httplib.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}) - -More info about the `tls.Config` please visit http://golang.org/pkg/crypto/tls/#Config - -## Set HTTP Version - -some servers need to specify the protocol version of HTTP - - httplib.Get("http://beego.me/").SetProtocolVersion("HTTP/1.1") - -## Set Cookie - -some http request need setcookie. So set it like this: - - cookie := &http.Cookie{} - cookie.Name = "username" - cookie.Value = "astaxie" - httplib.Get("http://beego.me/").SetCookie(cookie) - -## Upload file - -httplib support mutil file upload, use `req.PostFile()` - - req := httplib.Post("http://beego.me/") - req.Param("username","astaxie") - req.PostFile("uploadfile1", "httplib.pdf") - str, err := req.String() - if err != nil { - // error - } - fmt.Println(str) - - -See godoc for further documentation and examples. - -* [godoc.org/github.com/astaxie/beego/httplib](https://godoc.org/github.com/astaxie/beego/httplib) diff --git a/vendor/github.com/astaxie/beego/httplib/httplib.go b/vendor/github.com/astaxie/beego/httplib/httplib.go deleted file mode 100644 index fb64a30a8..000000000 --- a/vendor/github.com/astaxie/beego/httplib/httplib.go +++ /dev/null @@ -1,552 +0,0 @@ -// Copyright 2014 beego Author. 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. - -// Package httplib is used as http.Client -// Usage: -// -// import "github.com/astaxie/beego/httplib" -// -// b := httplib.Post("http://beego.me/") -// b.Param("username","astaxie") -// b.Param("password","123456") -// b.PostFile("uploadfile1", "httplib.pdf") -// b.PostFile("uploadfile2", "httplib.txt") -// str, err := b.String() -// if err != nil { -// t.Fatal(err) -// } -// fmt.Println(str) -// -// more docs http://beego.me/docs/module/httplib.md -package httplib - -import ( - "bytes" - "compress/gzip" - "crypto/tls" - "encoding/json" - "encoding/xml" - "io" - "io/ioutil" - "log" - "mime/multipart" - "net" - "net/http" - "net/http/cookiejar" - "net/http/httputil" - "net/url" - "os" - "strings" - "sync" - "time" -) - -var defaultSetting = BeegoHTTPSettings{ - UserAgent: "beegoServer", - ConnectTimeout: 60 * time.Second, - ReadWriteTimeout: 60 * time.Second, - Gzip: true, - DumpBody: true, -} - -var defaultCookieJar http.CookieJar -var settingMutex sync.Mutex - -// createDefaultCookie creates a global cookiejar to store cookies. -func createDefaultCookie() { - settingMutex.Lock() - defer settingMutex.Unlock() - defaultCookieJar, _ = cookiejar.New(nil) -} - -// SetDefaultSetting Overwrite default settings -func SetDefaultSetting(setting BeegoHTTPSettings) { - settingMutex.Lock() - defer settingMutex.Unlock() - defaultSetting = setting -} - -// NewBeegoRequest return *BeegoHttpRequest with specific method -func NewBeegoRequest(rawurl, method string) *BeegoHTTPRequest { - var resp http.Response - u, err := url.Parse(rawurl) - if err != nil { - log.Println("Httplib:", err) - } - req := http.Request{ - URL: u, - Method: method, - Header: make(http.Header), - Proto: "HTTP/1.1", - ProtoMajor: 1, - ProtoMinor: 1, - } - return &BeegoHTTPRequest{ - url: rawurl, - req: &req, - params: map[string][]string{}, - files: map[string]string{}, - setting: defaultSetting, - resp: &resp, - } -} - -// Get returns *BeegoHttpRequest with GET method. -func Get(url string) *BeegoHTTPRequest { - return NewBeegoRequest(url, "GET") -} - -// Post returns *BeegoHttpRequest with POST method. -func Post(url string) *BeegoHTTPRequest { - return NewBeegoRequest(url, "POST") -} - -// Put returns *BeegoHttpRequest with PUT method. -func Put(url string) *BeegoHTTPRequest { - return NewBeegoRequest(url, "PUT") -} - -// Delete returns *BeegoHttpRequest DELETE method. -func Delete(url string) *BeegoHTTPRequest { - return NewBeegoRequest(url, "DELETE") -} - -// Head returns *BeegoHttpRequest with HEAD method. -func Head(url string) *BeegoHTTPRequest { - return NewBeegoRequest(url, "HEAD") -} - -// BeegoHTTPSettings is the http.Client setting -type BeegoHTTPSettings struct { - ShowDebug bool - UserAgent string - ConnectTimeout time.Duration - ReadWriteTimeout time.Duration - TLSClientConfig *tls.Config - Proxy func(*http.Request) (*url.URL, error) - Transport http.RoundTripper - EnableCookie bool - Gzip bool - DumpBody bool -} - -// BeegoHTTPRequest provides more useful methods for requesting one url than http.Request. -type BeegoHTTPRequest struct { - url string - req *http.Request - params map[string][]string - files map[string]string - setting BeegoHTTPSettings - resp *http.Response - body []byte - dump []byte -} - -// GetRequest return the request object -func (b *BeegoHTTPRequest) GetRequest() *http.Request { - return b.req -} - -// Setting Change request settings -func (b *BeegoHTTPRequest) Setting(setting BeegoHTTPSettings) *BeegoHTTPRequest { - b.setting = setting - return b -} - -// SetBasicAuth sets the request's Authorization header to use HTTP Basic Authentication with the provided username and password. -func (b *BeegoHTTPRequest) SetBasicAuth(username, password string) *BeegoHTTPRequest { - b.req.SetBasicAuth(username, password) - return b -} - -// SetEnableCookie sets enable/disable cookiejar -func (b *BeegoHTTPRequest) SetEnableCookie(enable bool) *BeegoHTTPRequest { - b.setting.EnableCookie = enable - return b -} - -// SetUserAgent sets User-Agent header field -func (b *BeegoHTTPRequest) SetUserAgent(useragent string) *BeegoHTTPRequest { - b.setting.UserAgent = useragent - return b -} - -// Debug sets show debug or not when executing request. -func (b *BeegoHTTPRequest) Debug(isdebug bool) *BeegoHTTPRequest { - b.setting.ShowDebug = isdebug - return b -} - -// DumpBody setting whether need to Dump the Body. -func (b *BeegoHTTPRequest) DumpBody(isdump bool) *BeegoHTTPRequest { - b.setting.DumpBody = isdump - return b -} - -// DumpRequest return the DumpRequest -func (b *BeegoHTTPRequest) DumpRequest() []byte { - return b.dump -} - -// SetTimeout sets connect time out and read-write time out for BeegoRequest. -func (b *BeegoHTTPRequest) SetTimeout(connectTimeout, readWriteTimeout time.Duration) *BeegoHTTPRequest { - b.setting.ConnectTimeout = connectTimeout - b.setting.ReadWriteTimeout = readWriteTimeout - return b -} - -// SetTLSClientConfig sets tls connection configurations if visiting https url. -func (b *BeegoHTTPRequest) SetTLSClientConfig(config *tls.Config) *BeegoHTTPRequest { - b.setting.TLSClientConfig = config - return b -} - -// Header add header item string in request. -func (b *BeegoHTTPRequest) Header(key, value string) *BeegoHTTPRequest { - b.req.Header.Set(key, value) - return b -} - -// SetHost set the request host -func (b *BeegoHTTPRequest) SetHost(host string) *BeegoHTTPRequest { - b.req.Host = host - return b -} - -// SetProtocolVersion Set the protocol version for incoming requests. -// Client requests always use HTTP/1.1. -func (b *BeegoHTTPRequest) SetProtocolVersion(vers string) *BeegoHTTPRequest { - if len(vers) == 0 { - vers = "HTTP/1.1" - } - - major, minor, ok := http.ParseHTTPVersion(vers) - if ok { - b.req.Proto = vers - b.req.ProtoMajor = major - b.req.ProtoMinor = minor - } - - return b -} - -// SetCookie add cookie into request. -func (b *BeegoHTTPRequest) SetCookie(cookie *http.Cookie) *BeegoHTTPRequest { - b.req.Header.Add("Cookie", cookie.String()) - return b -} - -// SetTransport set the setting transport -func (b *BeegoHTTPRequest) SetTransport(transport http.RoundTripper) *BeegoHTTPRequest { - b.setting.Transport = transport - return b -} - -// SetProxy set the http proxy -// example: -// -// func(req *http.Request) (*url.URL, error) { -// u, _ := url.ParseRequestURI("http://127.0.0.1:8118") -// return u, nil -// } -func (b *BeegoHTTPRequest) SetProxy(proxy func(*http.Request) (*url.URL, error)) *BeegoHTTPRequest { - b.setting.Proxy = proxy - return b -} - -// Param adds query param in to request. -// params build query string as ?key1=value1&key2=value2... -func (b *BeegoHTTPRequest) Param(key, value string) *BeegoHTTPRequest { - if param, ok := b.params[key]; ok { - b.params[key] = append(param, value) - } else { - b.params[key] = []string{value} - } - return b -} - -// PostFile add a post file to the request -func (b *BeegoHTTPRequest) PostFile(formname, filename string) *BeegoHTTPRequest { - b.files[formname] = filename - return b -} - -// Body adds request raw body. -// it supports string and []byte. -func (b *BeegoHTTPRequest) Body(data interface{}) *BeegoHTTPRequest { - switch t := data.(type) { - case string: - bf := bytes.NewBufferString(t) - b.req.Body = ioutil.NopCloser(bf) - b.req.ContentLength = int64(len(t)) - case []byte: - bf := bytes.NewBuffer(t) - b.req.Body = ioutil.NopCloser(bf) - b.req.ContentLength = int64(len(t)) - } - return b -} - -// JSONBody adds request raw body encoding by JSON. -func (b *BeegoHTTPRequest) JSONBody(obj interface{}) (*BeegoHTTPRequest, error) { - if b.req.Body == nil && obj != nil { - buf := bytes.NewBuffer(nil) - enc := json.NewEncoder(buf) - if err := enc.Encode(obj); err != nil { - return b, err - } - b.req.Body = ioutil.NopCloser(buf) - b.req.ContentLength = int64(buf.Len()) - b.req.Header.Set("Content-Type", "application/json") - } - return b, nil -} - -func (b *BeegoHTTPRequest) buildURL(paramBody string) { - // build GET url with query string - if b.req.Method == "GET" && len(paramBody) > 0 { - if strings.Index(b.url, "?") != -1 { - b.url += "&" + paramBody - } else { - b.url = b.url + "?" + paramBody - } - return - } - - // build POST/PUT/PATCH url and body - if (b.req.Method == "POST" || b.req.Method == "PUT" || b.req.Method == "PATCH") && b.req.Body == nil { - // with files - if len(b.files) > 0 { - pr, pw := io.Pipe() - bodyWriter := multipart.NewWriter(pw) - go func() { - for formname, filename := range b.files { - fileWriter, err := bodyWriter.CreateFormFile(formname, filename) - if err != nil { - log.Println("Httplib:", err) - } - fh, err := os.Open(filename) - if err != nil { - log.Println("Httplib:", err) - } - //iocopy - _, err = io.Copy(fileWriter, fh) - fh.Close() - if err != nil { - log.Println("Httplib:", err) - } - } - for k, v := range b.params { - for _, vv := range v { - bodyWriter.WriteField(k, vv) - } - } - bodyWriter.Close() - pw.Close() - }() - b.Header("Content-Type", bodyWriter.FormDataContentType()) - b.req.Body = ioutil.NopCloser(pr) - return - } - - // with params - if len(paramBody) > 0 { - b.Header("Content-Type", "application/x-www-form-urlencoded") - b.Body(paramBody) - } - } -} - -func (b *BeegoHTTPRequest) getResponse() (*http.Response, error) { - if b.resp.StatusCode != 0 { - return b.resp, nil - } - resp, err := b.DoRequest() - if err != nil { - return nil, err - } - b.resp = resp - return resp, nil -} - -// DoRequest will do the client.Do -func (b *BeegoHTTPRequest) DoRequest() (*http.Response, error) { - var paramBody string - if len(b.params) > 0 { - var buf bytes.Buffer - for k, v := range b.params { - for _, vv := range v { - buf.WriteString(url.QueryEscape(k)) - buf.WriteByte('=') - buf.WriteString(url.QueryEscape(vv)) - buf.WriteByte('&') - } - } - paramBody = buf.String() - paramBody = paramBody[0 : len(paramBody)-1] - } - - b.buildURL(paramBody) - url, err := url.Parse(b.url) - if err != nil { - return nil, err - } - - b.req.URL = url - - trans := b.setting.Transport - - if trans == nil { - // create default transport - trans = &http.Transport{ - TLSClientConfig: b.setting.TLSClientConfig, - Proxy: b.setting.Proxy, - Dial: TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout), - } - } else { - // if b.transport is *http.Transport then set the settings. - if t, ok := trans.(*http.Transport); ok { - if t.TLSClientConfig == nil { - t.TLSClientConfig = b.setting.TLSClientConfig - } - if t.Proxy == nil { - t.Proxy = b.setting.Proxy - } - if t.Dial == nil { - t.Dial = TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout) - } - } - } - - var jar http.CookieJar - if b.setting.EnableCookie { - if defaultCookieJar == nil { - createDefaultCookie() - } - jar = defaultCookieJar - } - - client := &http.Client{ - Transport: trans, - Jar: jar, - } - - if b.setting.UserAgent != "" && b.req.Header.Get("User-Agent") == "" { - b.req.Header.Set("User-Agent", b.setting.UserAgent) - } - - if b.setting.ShowDebug { - dump, err := httputil.DumpRequest(b.req, b.setting.DumpBody) - if err != nil { - log.Println(err.Error()) - } - b.dump = dump - } - return client.Do(b.req) -} - -// String returns the body string in response. -// it calls Response inner. -func (b *BeegoHTTPRequest) String() (string, error) { - data, err := b.Bytes() - if err != nil { - return "", err - } - - return string(data), nil -} - -// Bytes returns the body []byte in response. -// it calls Response inner. -func (b *BeegoHTTPRequest) Bytes() ([]byte, error) { - if b.body != nil { - return b.body, nil - } - resp, err := b.getResponse() - if err != nil { - return nil, err - } - if resp.Body == nil { - return nil, nil - } - defer resp.Body.Close() - if b.setting.Gzip && resp.Header.Get("Content-Encoding") == "gzip" { - reader, err := gzip.NewReader(resp.Body) - if err != nil { - return nil, err - } - b.body, err = ioutil.ReadAll(reader) - } else { - b.body, err = ioutil.ReadAll(resp.Body) - } - return b.body, err -} - -// ToFile saves the body data in response to one file. -// it calls Response inner. -func (b *BeegoHTTPRequest) ToFile(filename string) error { - f, err := os.Create(filename) - if err != nil { - return err - } - defer f.Close() - - resp, err := b.getResponse() - if err != nil { - return err - } - if resp.Body == nil { - return nil - } - defer resp.Body.Close() - _, err = io.Copy(f, resp.Body) - return err -} - -// ToJSON returns the map that marshals from the body bytes as json in response . -// it calls Response inner. -func (b *BeegoHTTPRequest) ToJSON(v interface{}) error { - data, err := b.Bytes() - if err != nil { - return err - } - return json.Unmarshal(data, v) -} - -// ToXML returns the map that marshals from the body bytes as xml in response . -// it calls Response inner. -func (b *BeegoHTTPRequest) ToXML(v interface{}) error { - data, err := b.Bytes() - if err != nil { - return err - } - return xml.Unmarshal(data, v) -} - -// Response executes request client gets response mannually. -func (b *BeegoHTTPRequest) Response() (*http.Response, error) { - return b.getResponse() -} - -// TimeoutDialer returns functions of connection dialer with timeout settings for http.Transport Dial field. -func TimeoutDialer(cTimeout time.Duration, rwTimeout time.Duration) func(net, addr string) (c net.Conn, err error) { - return func(netw, addr string) (net.Conn, error) { - conn, err := net.DialTimeout(netw, addr, cTimeout) - if err != nil { - return nil, err - } - err = conn.SetDeadline(time.Now().Add(rwTimeout)) - return conn, err - } -} diff --git a/vendor/github.com/astaxie/beego/logs/conn.go b/vendor/github.com/astaxie/beego/logs/conn.go index 3655bf514..1db1a427c 100644 --- a/vendor/github.com/astaxie/beego/logs/conn.go +++ b/vendor/github.com/astaxie/beego/logs/conn.go @@ -17,14 +17,14 @@ package logs import ( "encoding/json" "io" - "log" "net" + "time" ) // connWriter implements LoggerInterface. // it writes messages in keep-live tcp connection. type connWriter struct { - lg *log.Logger + lg *logWriter innerWriter io.WriteCloser ReconnectOnMsg bool `json:"reconnectOnMsg"` Reconnect bool `json:"reconnect"` @@ -42,17 +42,17 @@ func NewConn() Logger { // Init init connection writer with json config. // json config only need key "level". -func (c *connWriter) Init(jsonconfig string) error { - return json.Unmarshal([]byte(jsonconfig), c) +func (c *connWriter) Init(jsonConfig string) error { + return json.Unmarshal([]byte(jsonConfig), c) } // WriteMsg write message in connection. // if connection is down, try to re-connect. -func (c *connWriter) WriteMsg(msg string, level int) error { +func (c *connWriter) WriteMsg(when time.Time, msg string, level int) error { if level > c.Level { return nil } - if c.neddedConnectOnMsg() { + if c.needToConnectOnMsg() { err := c.connect() if err != nil { return err @@ -62,7 +62,8 @@ func (c *connWriter) WriteMsg(msg string, level int) error { if c.ReconnectOnMsg { defer c.innerWriter.Close() } - c.lg.Println(msg) + + c.lg.println(when, msg) return nil } @@ -94,11 +95,11 @@ func (c *connWriter) connect() error { } c.innerWriter = conn - c.lg = log.New(conn, "", log.Ldate|log.Ltime) + c.lg = newLogWriter(conn) return nil } -func (c *connWriter) neddedConnectOnMsg() bool { +func (c *connWriter) needToConnectOnMsg() bool { if c.Reconnect { c.Reconnect = false return true diff --git a/vendor/github.com/astaxie/beego/logs/console.go b/vendor/github.com/astaxie/beego/logs/console.go index 23e8ebcaf..05d08a42e 100644 --- a/vendor/github.com/astaxie/beego/logs/console.go +++ b/vendor/github.com/astaxie/beego/logs/console.go @@ -16,9 +16,9 @@ package logs import ( "encoding/json" - "log" "os" "runtime" + "time" ) // brush is a color join function @@ -34,51 +34,55 @@ func newBrush(color string) brush { } var colors = []brush{ - newBrush("1;37"), // Emergency white - newBrush("1;36"), // Alert cyan - newBrush("1;35"), // Critical magenta - newBrush("1;31"), // Error red - newBrush("1;33"), // Warning yellow - newBrush("1;32"), // Notice green - newBrush("1;34"), // Informational blue - newBrush("1;34"), // Debug blue + newBrush("1;37"), // Emergency white + newBrush("1;36"), // Alert cyan + newBrush("1;35"), // Critical magenta + newBrush("1;31"), // Error red + newBrush("1;33"), // Warning yellow + newBrush("1;32"), // Notice green + newBrush("1;34"), // Informational blue + newBrush("1;34"), // Debug blue } // consoleWriter implements LoggerInterface and writes messages to terminal. type consoleWriter struct { - lg *log.Logger - Level int `json:"level"` + lg *logWriter + Level int `json:"level"` + Colorful bool `json:"color"` //this filed is useful only when system's terminal supports color } // NewConsole create ConsoleWriter returning as LoggerInterface. func NewConsole() Logger { cw := &consoleWriter{ - lg: log.New(os.Stdout, "", log.Ldate|log.Ltime), - Level: LevelDebug, + lg: newLogWriter(os.Stdout), + Level: LevelDebug, + Colorful: true, } return cw } // Init init console logger. -// jsonconfig like '{"level":LevelTrace}'. -func (c *consoleWriter) Init(jsonconfig string) error { - if len(jsonconfig) == 0 { +// jsonConfig like '{"level":LevelTrace}'. +func (c *consoleWriter) Init(jsonConfig string) error { + if len(jsonConfig) == 0 { return nil } - return json.Unmarshal([]byte(jsonconfig), c) + err := json.Unmarshal([]byte(jsonConfig), c) + if runtime.GOOS == "windows" { + c.Colorful = false + } + return err } // WriteMsg write message in console. -func (c *consoleWriter) WriteMsg(msg string, level int) error { +func (c *consoleWriter) WriteMsg(when time.Time, msg string, level int) error { if level > c.Level { return nil } - if goos := runtime.GOOS; goos == "windows" { - c.lg.Println(msg) - return nil + if c.Colorful { + msg = colors[level](msg) } - c.lg.Println(colors[level](msg)) - + c.lg.println(when, msg) return nil } diff --git a/vendor/github.com/astaxie/beego/logs/es/es.go b/vendor/github.com/astaxie/beego/logs/es/es.go deleted file mode 100644 index f8dc5f65b..000000000 --- a/vendor/github.com/astaxie/beego/logs/es/es.go +++ /dev/null @@ -1,80 +0,0 @@ -package es - -import ( - "encoding/json" - "errors" - "fmt" - "net" - "net/url" - "time" - - "github.com/astaxie/beego/logs" - "github.com/belogik/goes" -) - -// NewES return a LoggerInterface -func NewES() logs.Logger { - cw := &esLogger{ - Level: logs.LevelDebug, - } - return cw -} - -type esLogger struct { - *goes.Connection - DSN string `json:"dsn"` - Level int `json:"level"` -} - -// {"dsn":"http://localhost:9200/","level":1} -func (el *esLogger) Init(jsonconfig string) error { - err := json.Unmarshal([]byte(jsonconfig), el) - if err != nil { - return err - } - if el.DSN == "" { - return errors.New("empty dsn") - } else if u, err := url.Parse(el.DSN); err != nil { - return err - } else if u.Path == "" { - return errors.New("missing prefix") - } else if host, port, err := net.SplitHostPort(u.Host); err != nil { - return err - } else { - conn := goes.NewConnection(host, port) - el.Connection = conn - } - return nil -} - -// WriteMsg will write the msg and level into es -func (el *esLogger) WriteMsg(msg string, level int) error { - if level > el.Level { - return nil - } - t := time.Now() - vals := make(map[string]interface{}) - vals["@timestamp"] = t.Format(time.RFC3339) - vals["@msg"] = msg - d := goes.Document{ - Index: fmt.Sprintf("%04d.%02d.%02d", t.Year(), t.Month(), t.Day()), - Type: "logs", - Fields: vals, - } - _, err := el.Index(d, nil) - return err -} - -// Destroy is a empty method -func (el *esLogger) Destroy() { - -} - -// Flush is a empty method -func (el *esLogger) Flush() { - -} - -func init() { - logs.Register("es", NewES) -} diff --git a/vendor/github.com/astaxie/beego/logs/file.go b/vendor/github.com/astaxie/beego/logs/file.go index 0eae734a0..9d3f78a05 100644 --- a/vendor/github.com/astaxie/beego/logs/file.go +++ b/vendor/github.com/astaxie/beego/logs/file.go @@ -53,9 +53,11 @@ type fileLogWriter struct { Level int `json:"level"` Perm os.FileMode `json:"perm"` + + fileNameOnly, suffix string // like "project.log", project is fileNameOnly and .log is suffix } -// NewFileWriter create a FileLogWriter returning as LoggerInterface. +// newFileWriter create a FileLogWriter returning as LoggerInterface. func newFileWriter() Logger { w := &fileLogWriter{ Filename: "", @@ -89,6 +91,11 @@ func (w *fileLogWriter) Init(jsonConfig string) error { if len(w.Filename) == 0 { return errors.New("jsonconfig must have filename") } + w.suffix = filepath.Ext(w.Filename) + w.fileNameOnly = strings.TrimSuffix(w.Filename, w.suffix) + if w.suffix == "" { + w.suffix = ".log" + } err = w.startLogger() return err } @@ -114,59 +121,19 @@ func (w *fileLogWriter) needRotate(size int, day int) bool { } // WriteMsg write logger message into file. -func (w *fileLogWriter) WriteMsg(msg string, level int) error { +func (w *fileLogWriter) WriteMsg(when time.Time, msg string, level int) error { if level > w.Level { return nil } - //2016/01/12 21:34:33 - now := time.Now() - y, mo, d := now.Date() - h, mi, s := now.Clock() - //len(2006/01/02 15:03:04)==19 - var buf [20]byte - t := 3 - for y >= 10 { - p := y / 10 - buf[t] = byte('0' + y - p*10) - y = p - t-- - } - buf[0] = byte('0' + y) - buf[4] = '/' - if mo > 9 { - buf[5] = '1' - buf[6] = byte('0' + mo - 9) - } else { - buf[5] = '0' - buf[6] = byte('0' + mo) - } - buf[7] = '/' - t = d / 10 - buf[8] = byte('0' + t) - buf[9] = byte('0' + d - t*10) - buf[10] = ' ' - t = h / 10 - buf[11] = byte('0' + t) - buf[12] = byte('0' + h - t*10) - buf[13] = ':' - t = mi / 10 - buf[14] = byte('0' + t) - buf[15] = byte('0' + mi - t*10) - buf[16] = ':' - t = s / 10 - buf[17] = byte('0' + t) - buf[18] = byte('0' + s - t*10) - buf[19] = ' ' - msg = string(buf[0:]) + msg + "\n" - + h, d := formatTimeHeader(when) + msg = string(h) + msg + "\n" if w.Rotate { if w.needRotate(len(msg), d) { w.Lock() if w.needRotate(len(msg), d) { - if err := w.doRotate(); err != nil { + if err := w.doRotate(when); err != nil { fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err) } - } w.Unlock() } @@ -235,8 +202,8 @@ func (w *fileLogWriter) lines() (int, error) { } // DoRotate means it need to write file in new file. -// new file name like xx.2013-01-01.2.log -func (w *fileLogWriter) doRotate() error { +// new file name like xx.2013-01-01.log (daily) or xx.001.log (by line or size) +func (w *fileLogWriter) doRotate(logTime time.Time) error { _, err := os.Lstat(w.Filename) if err != nil { return err @@ -245,13 +212,13 @@ func (w *fileLogWriter) doRotate() error { // Find the next available number num := 1 fName := "" - suffix := filepath.Ext(w.Filename) - filenameOnly := strings.TrimSuffix(w.Filename, suffix) - if suffix == "" { - suffix = ".log" - } - for ; err == nil && num <= 999; num++ { - fName = filenameOnly + fmt.Sprintf(".%s.%03d%s", time.Now().Format("2006-01-02"), num, suffix) + if w.MaxLines > 0 || w.MaxSize > 0 { + for ; err == nil && num <= 999; num++ { + fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format("2006-01-02"), num, w.suffix) + _, err = os.Lstat(fName) + } + } else { + fName = fmt.Sprintf("%s.%s%s", w.fileNameOnly, logTime.Format("2006-01-02"), w.suffix) _, err = os.Lstat(fName) } // return error if the last file checked still existed @@ -289,7 +256,8 @@ func (w *fileLogWriter) deleteOldLog() { }() if !info.IsDir() && info.ModTime().Unix() < (time.Now().Unix()-60*60*24*w.MaxDays) { - if strings.HasPrefix(filepath.Base(path), filepath.Base(w.Filename)) { + if strings.HasPrefix(filepath.Base(path), w.fileNameOnly) && + strings.HasSuffix(filepath.Base(path), w.suffix) { os.Remove(path) } } diff --git a/vendor/github.com/astaxie/beego/logs/log.go b/vendor/github.com/astaxie/beego/logs/log.go index ccaaa3ad7..3b3e22081 100644 --- a/vendor/github.com/astaxie/beego/logs/log.go +++ b/vendor/github.com/astaxie/beego/logs/log.go @@ -40,6 +40,7 @@ import ( "runtime" "strconv" "sync" + "time" ) // RFC5424 log message levels. @@ -68,7 +69,7 @@ type loggerType func() Logger // Logger defines the behavior of a log provider. type Logger interface { Init(config string) error - WriteMsg(msg string, level int) error + WriteMsg(when time.Time, msg string, level int) error Destroy() Flush() } @@ -97,6 +98,8 @@ type BeeLogger struct { loggerFuncCallDepth int asynchronous bool msgChan chan *logMsg + signalChan chan string + wg sync.WaitGroup outputs []*nameLogger } @@ -108,6 +111,7 @@ type nameLogger struct { type logMsg struct { level int msg string + when time.Time } var logMsgPool *sync.Pool @@ -120,6 +124,7 @@ func NewLogger(channelLen int64) *BeeLogger { bl.level = LevelDebug bl.loggerFuncCallDepth = 2 bl.msgChan = make(chan *logMsg, channelLen) + bl.signalChan = make(chan string, 1) return bl } @@ -131,6 +136,7 @@ func (bl *BeeLogger) Async() *BeeLogger { return &logMsg{} }, } + bl.wg.Add(1) go bl.startLogger() return bl } @@ -140,17 +146,25 @@ func (bl *BeeLogger) Async() *BeeLogger { func (bl *BeeLogger) SetLogger(adapterName string, config string) error { bl.lock.Lock() defer bl.lock.Unlock() - if log, ok := adapters[adapterName]; ok { - lg := log() - err := lg.Init(config) - if err != nil { - fmt.Fprintln(os.Stderr, "logs.BeeLogger.SetLogger: "+err.Error()) - return err + + for _, l := range bl.outputs { + if l.name == adapterName { + return fmt.Errorf("logs: duplicate adaptername %q (you have set this logger before)", adapterName) } - bl.outputs = append(bl.outputs, &nameLogger{name: adapterName, Logger: lg}) - } else { + } + + log, ok := adapters[adapterName] + if !ok { return fmt.Errorf("logs: unknown adaptername %q (forgotten Register?)", adapterName) } + + lg := log() + err := lg.Init(config) + if err != nil { + fmt.Fprintln(os.Stderr, "logs.BeeLogger.SetLogger: "+err.Error()) + return err + } + bl.outputs = append(bl.outputs, &nameLogger{name: adapterName, Logger: lg}) return nil } @@ -173,9 +187,9 @@ func (bl *BeeLogger) DelLogger(adapterName string) error { return nil } -func (bl *BeeLogger) writeToLoggers(msg string, level int) { +func (bl *BeeLogger) writeToLoggers(when time.Time, msg string, level int) { for _, l := range bl.outputs { - err := l.WriteMsg(msg, level) + err := l.WriteMsg(when, msg, level) if err != nil { fmt.Fprintf(os.Stderr, "unable to WriteMsg to adapter:%v,error:%v\n", l.name, err) } @@ -183,6 +197,7 @@ func (bl *BeeLogger) writeToLoggers(msg string, level int) { } func (bl *BeeLogger) writeMsg(logLevel int, msg string) error { + when := time.Now() if bl.enableFuncCallDepth { _, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth) if !ok { @@ -196,9 +211,10 @@ func (bl *BeeLogger) writeMsg(logLevel int, msg string) error { lm := logMsgPool.Get().(*logMsg) lm.level = logLevel lm.msg = msg + lm.when = when bl.msgChan <- lm } else { - bl.writeToLoggers(msg, logLevel) + bl.writeToLoggers(when, msg, logLevel) } return nil } @@ -228,11 +244,26 @@ func (bl *BeeLogger) EnableFuncCallDepth(b bool) { // start logger chan reading. // when chan is not empty, write logs. func (bl *BeeLogger) startLogger() { + gameOver := false for { select { case bm := <-bl.msgChan: - bl.writeToLoggers(bm.msg, bm.level) + bl.writeToLoggers(bm.when, bm.msg, bm.level) logMsgPool.Put(bm) + case sg := <-bl.signalChan: + // Now should only send "flush" or "close" to bl.signalChan + bl.flush() + if sg == "close" { + for _, l := range bl.outputs { + l.Destroy() + } + bl.outputs = nil + gameOver = true + } + bl.wg.Done() + } + if gameOver { + break } } } @@ -341,17 +372,45 @@ func (bl *BeeLogger) Trace(format string, v ...interface{}) { // Flush flush all chan data. func (bl *BeeLogger) Flush() { - for _, l := range bl.outputs { - l.Flush() + if bl.asynchronous { + bl.signalChan <- "flush" + bl.wg.Wait() + bl.wg.Add(1) + return } + bl.flush() } // Close close logger, flush all chan data and destroy all adapters in BeeLogger. func (bl *BeeLogger) Close() { + if bl.asynchronous { + bl.signalChan <- "close" + bl.wg.Wait() + } else { + bl.flush() + for _, l := range bl.outputs { + l.Destroy() + } + bl.outputs = nil + } + close(bl.msgChan) + close(bl.signalChan) +} + +// Reset close all outputs, and set bl.outputs to nil +func (bl *BeeLogger) Reset() { + bl.Flush() + for _, l := range bl.outputs { + l.Destroy() + } + bl.outputs = nil +} + +func (bl *BeeLogger) flush() { for { if len(bl.msgChan) > 0 { bm := <-bl.msgChan - bl.writeToLoggers(bm.msg, bm.level) + bl.writeToLoggers(bm.when, bm.msg, bm.level) logMsgPool.Put(bm) continue } @@ -359,6 +418,5 @@ func (bl *BeeLogger) Close() { } for _, l := range bl.outputs { l.Flush() - l.Destroy() } } diff --git a/vendor/github.com/astaxie/beego/logs/logger.go b/vendor/github.com/astaxie/beego/logs/logger.go new file mode 100644 index 000000000..323c41c56 --- /dev/null +++ b/vendor/github.com/astaxie/beego/logs/logger.go @@ -0,0 +1,80 @@ +// Copyright 2014 beego Author. 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. + + +package logs + +import ( + "io" + "sync" + "time" +) + +type logWriter struct { + sync.Mutex + writer io.Writer +} + +func newLogWriter(wr io.Writer) *logWriter { + return &logWriter{writer: wr} +} + +func (lg *logWriter) println(when time.Time, msg string) { + lg.Lock() + h, _ := formatTimeHeader(when) + lg.writer.Write(append(append(h, msg...), '\n')) + lg.Unlock() +} + +func formatTimeHeader(when time.Time) ([]byte, int) { + y, mo, d := when.Date() + h, mi, s := when.Clock() + //len(2006/01/02 15:03:04)==19 + var buf [20]byte + t := 3 + for y >= 10 { + p := y / 10 + buf[t] = byte('0' + y - p*10) + y = p + t-- + } + buf[0] = byte('0' + y) + buf[4] = '/' + if mo > 9 { + buf[5] = '1' + buf[6] = byte('0' + mo - 9) + } else { + buf[5] = '0' + buf[6] = byte('0' + mo) + } + buf[7] = '/' + t = d / 10 + buf[8] = byte('0' + t) + buf[9] = byte('0' + d - t*10) + buf[10] = ' ' + t = h / 10 + buf[11] = byte('0' + t) + buf[12] = byte('0' + h - t*10) + buf[13] = ':' + t = mi / 10 + buf[14] = byte('0' + t) + buf[15] = byte('0' + mi - t*10) + buf[16] = ':' + t = s / 10 + buf[17] = byte('0' + t) + buf[18] = byte('0' + s - t*10) + buf[19] = ' ' + + return buf[0:], d +} diff --git a/vendor/github.com/astaxie/beego/logs/multifile.go b/vendor/github.com/astaxie/beego/logs/multifile.go new file mode 100644 index 000000000..b82ba2741 --- /dev/null +++ b/vendor/github.com/astaxie/beego/logs/multifile.go @@ -0,0 +1,116 @@ +// Copyright 2014 beego Author. 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. + +package logs + +import ( + "encoding/json" + "time" +) + +// A filesLogWriter manages several fileLogWriter +// filesLogWriter will write logs to the file in json configuration and write the same level log to correspond file +// means if the file name in configuration is project.log filesLogWriter will create project.error.log/project.debug.log +// and write the error-level logs to project.error.log and write the debug-level logs to project.debug.log +// the rotate attribute also acts like fileLogWriter +type multiFileLogWriter struct { + writers [LevelDebug + 1 + 1]*fileLogWriter // the last one for fullLogWriter + fullLogWriter *fileLogWriter + Separate []string `json:"separate"` +} + +var levelNames = [...]string{"emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"} + +// Init file logger with json config. +// jsonConfig like: +// { +// "filename":"logs/beego.log", +// "maxLines":0, +// "maxsize":0, +// "daily":true, +// "maxDays":15, +// "rotate":true, +// "perm":0600, +// "separate":["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"], +// } + +func (f *multiFileLogWriter) Init(config string) error { + writer := newFileWriter().(*fileLogWriter) + err := writer.Init(config) + if err != nil { + return err + } + f.fullLogWriter = writer + f.writers[LevelDebug+1] = writer + + //unmarshal "separate" field to f.Separate + json.Unmarshal([]byte(config), f) + + jsonMap := map[string]interface{}{} + json.Unmarshal([]byte(config), &jsonMap) + + for i := LevelEmergency; i < LevelDebug+1; i++ { + for _, v := range f.Separate { + if v == levelNames[i] { + jsonMap["filename"] = f.fullLogWriter.fileNameOnly + "." + levelNames[i] + f.fullLogWriter.suffix + jsonMap["level"] = i + bs, _ := json.Marshal(jsonMap) + writer = newFileWriter().(*fileLogWriter) + writer.Init(string(bs)) + f.writers[i] = writer + } + } + } + + return nil +} + +func (f *multiFileLogWriter) Destroy() { + for i := 0; i < len(f.writers); i++ { + if f.writers[i] != nil { + f.writers[i].Destroy() + } + } +} + +func (f *multiFileLogWriter) WriteMsg(when time.Time, msg string, level int) error { + if f.fullLogWriter != nil { + f.fullLogWriter.WriteMsg(when, msg, level) + } + for i := 0; i < len(f.writers)-1; i++ { + if f.writers[i] != nil { + if level == f.writers[i].Level { + f.writers[i].WriteMsg(when, msg, level) + } + } + } + return nil +} + +func (f *multiFileLogWriter) Flush() { + for i := 0; i < len(f.writers); i++ { + if f.writers[i] != nil { + f.writers[i].Flush() + } + } +} + +// newFilesWriter create a FileLogWriter returning as LoggerInterface. +func newFilesWriter() Logger { + return &multiFileLogWriter{} +} + +func init() { + Register("multifile", newFilesWriter) +} diff --git a/vendor/github.com/astaxie/beego/logs/smtp.go b/vendor/github.com/astaxie/beego/logs/smtp.go index 748462f9d..47f5a0c69 100644 --- a/vendor/github.com/astaxie/beego/logs/smtp.go +++ b/vendor/github.com/astaxie/beego/logs/smtp.go @@ -126,7 +126,7 @@ func (s *SMTPWriter) sendMail(hostAddressWithPort string, auth smtp.Auth, fromAd // WriteMsg write message in smtp writer. // it will send an email with subject and only this message. -func (s *SMTPWriter) WriteMsg(msg string, level int) error { +func (s *SMTPWriter) WriteMsg(when time.Time, msg string, level int) error { if level > s.Level { return nil } @@ -140,7 +140,7 @@ func (s *SMTPWriter) WriteMsg(msg string, level int) error { // and send the email all in one step. contentType := "Content-Type: text/plain" + "; charset=UTF-8" mailmsg := []byte("To: " + strings.Join(s.RecipientAddresses, ";") + "\r\nFrom: " + s.FromAddress + "<" + s.FromAddress + - ">\r\nSubject: " + s.Subject + "\r\n" + contentType + "\r\n\r\n" + fmt.Sprintf(".%s", time.Now().Format("2006-01-02 15:04:05")) + msg) + ">\r\nSubject: " + s.Subject + "\r\n" + contentType + "\r\n\r\n" + fmt.Sprintf(".%s", when.Format("2006-01-02 15:04:05")) + msg) return s.sendMail(s.Host, auth, s.FromAddress, s.RecipientAddresses, mailmsg) } diff --git a/vendor/github.com/astaxie/beego/migration/ddl.go b/vendor/github.com/astaxie/beego/migration/ddl.go deleted file mode 100644 index 51243337f..000000000 --- a/vendor/github.com/astaxie/beego/migration/ddl.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2014 beego Author. 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. - -package migration - -// Table store the tablename and Column -type Table struct { - TableName string - Columns []*Column -} - -// Create return the create sql -func (t *Table) Create() string { - return "" -} - -// Drop return the drop sql -func (t *Table) Drop() string { - return "" -} - -// Column define the columns name type and Default -type Column struct { - Name string - Type string - Default interface{} -} - -// Create return create sql with the provided tbname and columns -func Create(tbname string, columns ...Column) string { - return "" -} - -// Drop return the drop sql with the provided tbname and columns -func Drop(tbname string, columns ...Column) string { - return "" -} - -// TableDDL is still in think -func TableDDL(tbname string, columns ...Column) string { - return "" -} diff --git a/vendor/github.com/astaxie/beego/migration/migration.go b/vendor/github.com/astaxie/beego/migration/migration.go deleted file mode 100644 index 1591bc50d..000000000 --- a/vendor/github.com/astaxie/beego/migration/migration.go +++ /dev/null @@ -1,278 +0,0 @@ -// Copyright 2014 beego Author. 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. - -// Package migration is used for migration -// -// The table structure is as follow: -// -// CREATE TABLE `migrations` ( -// `id_migration` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'surrogate key', -// `name` varchar(255) DEFAULT NULL COMMENT 'migration name, unique', -// `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'date migrated or rolled back', -// `statements` longtext COMMENT 'SQL statements for this migration', -// `rollback_statements` longtext, -// `status` enum('update','rollback') DEFAULT NULL COMMENT 'update indicates it is a normal migration while rollback means this migration is rolled back', -// PRIMARY KEY (`id_migration`) -// ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -package migration - -import ( - "errors" - "sort" - "strings" - "time" - - "github.com/astaxie/beego" - "github.com/astaxie/beego/orm" -) - -// const the data format for the bee generate migration datatype -const ( - DateFormat = "20060102_150405" - DBDateFormat = "2006-01-02 15:04:05" -) - -// Migrationer is an interface for all Migration struct -type Migrationer interface { - Up() - Down() - Reset() - Exec(name, status string) error - GetCreated() int64 -} - -var ( - migrationMap map[string]Migrationer -) - -func init() { - migrationMap = make(map[string]Migrationer) -} - -// Migration the basic type which will implement the basic type -type Migration struct { - sqls []string - Created string -} - -// Up implement in the Inheritance struct for upgrade -func (m *Migration) Up() { - -} - -// Down implement in the Inheritance struct for down -func (m *Migration) Down() { - -} - -// SQL add sql want to execute -func (m *Migration) SQL(sql string) { - m.sqls = append(m.sqls, sql) -} - -// Reset the sqls -func (m *Migration) Reset() { - m.sqls = make([]string, 0) -} - -// Exec execute the sql already add in the sql -func (m *Migration) Exec(name, status string) error { - o := orm.NewOrm() - for _, s := range m.sqls { - beego.Info("exec sql:", s) - r := o.Raw(s) - _, err := r.Exec() - if err != nil { - return err - } - } - return m.addOrUpdateRecord(name, status) -} - -func (m *Migration) addOrUpdateRecord(name, status string) error { - o := orm.NewOrm() - if status == "down" { - status = "rollback" - p, err := o.Raw("update migrations set status = ?, rollback_statements = ?, created_at = ? where name = ?").Prepare() - if err != nil { - return nil - } - _, err = p.Exec(status, strings.Join(m.sqls, "; "), time.Now().Format(DBDateFormat), name) - return err - } - status = "update" - p, err := o.Raw("insert into migrations(name, created_at, statements, status) values(?,?,?,?)").Prepare() - if err != nil { - return err - } - _, err = p.Exec(name, time.Now().Format(DBDateFormat), strings.Join(m.sqls, "; "), status) - return err -} - -// GetCreated get the unixtime from the Created -func (m *Migration) GetCreated() int64 { - t, err := time.Parse(DateFormat, m.Created) - if err != nil { - return 0 - } - return t.Unix() -} - -// Register register the Migration in the map -func Register(name string, m Migrationer) error { - if _, ok := migrationMap[name]; ok { - return errors.New("already exist name:" + name) - } - migrationMap[name] = m - return nil -} - -// Upgrade upgrate the migration from lasttime -func Upgrade(lasttime int64) error { - sm := sortMap(migrationMap) - i := 0 - for _, v := range sm { - if v.created > lasttime { - beego.Info("start upgrade", v.name) - v.m.Reset() - v.m.Up() - err := v.m.Exec(v.name, "up") - if err != nil { - beego.Error("execute error:", err) - time.Sleep(2 * time.Second) - return err - } - beego.Info("end upgrade:", v.name) - i++ - } - } - beego.Info("total success upgrade:", i, " migration") - time.Sleep(2 * time.Second) - return nil -} - -// Rollback rollback the migration by the name -func Rollback(name string) error { - if v, ok := migrationMap[name]; ok { - beego.Info("start rollback") - v.Reset() - v.Down() - err := v.Exec(name, "down") - if err != nil { - beego.Error("execute error:", err) - time.Sleep(2 * time.Second) - return err - } - beego.Info("end rollback") - time.Sleep(2 * time.Second) - return nil - } - beego.Error("not exist the migrationMap name:" + name) - time.Sleep(2 * time.Second) - return errors.New("not exist the migrationMap name:" + name) -} - -// Reset reset all migration -// run all migration's down function -func Reset() error { - sm := sortMap(migrationMap) - i := 0 - for j := len(sm) - 1; j >= 0; j-- { - v := sm[j] - if isRollBack(v.name) { - beego.Info("skip the", v.name) - time.Sleep(1 * time.Second) - continue - } - beego.Info("start reset:", v.name) - v.m.Reset() - v.m.Down() - err := v.m.Exec(v.name, "down") - if err != nil { - beego.Error("execute error:", err) - time.Sleep(2 * time.Second) - return err - } - i++ - beego.Info("end reset:", v.name) - } - beego.Info("total success reset:", i, " migration") - time.Sleep(2 * time.Second) - return nil -} - -// Refresh first Reset, then Upgrade -func Refresh() error { - err := Reset() - if err != nil { - beego.Error("execute error:", err) - time.Sleep(2 * time.Second) - return err - } - err = Upgrade(0) - return err -} - -type dataSlice []data - -type data struct { - created int64 - name string - m Migrationer -} - -// Len is part of sort.Interface. -func (d dataSlice) Len() int { - return len(d) -} - -// Swap is part of sort.Interface. -func (d dataSlice) Swap(i, j int) { - d[i], d[j] = d[j], d[i] -} - -// Less is part of sort.Interface. We use count as the value to sort by -func (d dataSlice) Less(i, j int) bool { - return d[i].created < d[j].created -} - -func sortMap(m map[string]Migrationer) dataSlice { - s := make(dataSlice, 0, len(m)) - for k, v := range m { - d := data{} - d.created = v.GetCreated() - d.name = k - d.m = v - s = append(s, d) - } - sort.Sort(s) - return s -} - -func isRollBack(name string) bool { - o := orm.NewOrm() - var maps []orm.Params - num, err := o.Raw("select * from migrations where `name` = ? order by id_migration desc", name).Values(&maps) - if err != nil { - beego.Info("get name has error", err) - return false - } - if num <= 0 { - return false - } - if maps[0]["status"] == "rollback" { - return true - } - return false -} diff --git a/vendor/github.com/astaxie/beego/namespace.go b/vendor/github.com/astaxie/beego/namespace.go index 0dfdd7afd..4007d44cc 100644 --- a/vendor/github.com/astaxie/beego/namespace.go +++ b/vendor/github.com/astaxie/beego/namespace.go @@ -388,3 +388,10 @@ func NSNamespace(prefix string, params ...LinkNamespace) LinkNamespace { ns.Namespace(n) } } + +// NSHandler add handler +func NSHandler(rootpath string, h http.Handler) LinkNamespace { + return func(ns *Namespace) { + ns.Handler(rootpath, h) + } +} diff --git a/vendor/github.com/astaxie/beego/orm/db.go b/vendor/github.com/astaxie/beego/orm/db.go index b62c165bc..314c3535e 100644 --- a/vendor/github.com/astaxie/beego/orm/db.go +++ b/vendor/github.com/astaxie/beego/orm/db.go @@ -113,7 +113,7 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val if fi.pk { _, value, _ = getExistPk(mi, ind) } else { - field := ind.Field(fi.fieldIndex) + field := ind.FieldByIndex(fi.fieldIndex) if fi.isFielder { f := field.Addr().Interface().(Fielder) value = f.RawValue() @@ -517,9 +517,9 @@ func (d *dbBase) Delete(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time. if num > 0 { if mi.fields.pk.auto { if mi.fields.pk.fieldType&IsPostiveIntegerField > 0 { - ind.Field(mi.fields.pk.fieldIndex).SetUint(0) + ind.FieldByIndex(mi.fields.pk.fieldIndex).SetUint(0) } else { - ind.Field(mi.fields.pk.fieldIndex).SetInt(0) + ind.FieldByIndex(mi.fields.pk.fieldIndex).SetInt(0) } } err := d.deleteRels(q, mi, []interface{}{pkValue}, tz) @@ -859,13 +859,13 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi mmi = fi.relModelInfo field := last if last.Kind() != reflect.Invalid { - field = reflect.Indirect(last.Field(fi.fieldIndex)) + field = reflect.Indirect(last.FieldByIndex(fi.fieldIndex)) if field.IsValid() { d.setColsValues(mmi, &field, mmi.fields.dbcols, trefs[:len(mmi.fields.dbcols)], tz) for _, fi := range mmi.fields.fieldsReverse { if fi.inModel && fi.reverseFieldInfo.mi == lastm { if fi.reverseFieldInfo != nil { - f := field.Field(fi.fieldIndex) + f := field.FieldByIndex(fi.fieldIndex) if f.Kind() == reflect.Ptr { f.Set(last.Addr()) } @@ -1014,7 +1014,7 @@ func (d *dbBase) setColsValues(mi *modelInfo, ind *reflect.Value, cols []string, fi := mi.fields.GetByColumn(column) - field := ind.Field(fi.fieldIndex) + field := ind.FieldByIndex(fi.fieldIndex) value, err := d.convertValueFromDB(fi, val, tz) if err != nil { @@ -1350,7 +1350,7 @@ setValue: fieldType = fi.relModelInfo.fields.pk.fieldType mf := reflect.New(fi.relModelInfo.addrField.Elem().Type()) field.Set(mf) - f := mf.Elem().Field(fi.relModelInfo.fields.pk.fieldIndex) + f := mf.Elem().FieldByIndex(fi.relModelInfo.fields.pk.fieldIndex) field = f goto setValue } diff --git a/vendor/github.com/astaxie/beego/orm/db_alias.go b/vendor/github.com/astaxie/beego/orm/db_alias.go index 79576b8e5..b6c833a71 100644 --- a/vendor/github.com/astaxie/beego/orm/db_alias.go +++ b/vendor/github.com/astaxie/beego/orm/db_alias.go @@ -59,6 +59,7 @@ var ( "postgres": DRPostgres, "sqlite3": DRSqlite, "tidb": DRTiDB, + "oracle": DROracle, } dbBasers = map[DriverType]dbBaser{ DRMySQL: newdbBaseMysql(), @@ -151,7 +152,7 @@ func detectTZ(al *alias) { al.Engine = "INNODB" } - case DRSqlite: + case DRSqlite, DROracle: al.TZ = time.UTC case DRPostgres: diff --git a/vendor/github.com/astaxie/beego/orm/db_oracle.go b/vendor/github.com/astaxie/beego/orm/db_oracle.go index 1e385c9a4..deca36ad6 100644 --- a/vendor/github.com/astaxie/beego/orm/db_oracle.go +++ b/vendor/github.com/astaxie/beego/orm/db_oracle.go @@ -14,6 +14,41 @@ package orm +import ( + "fmt" + "strings" +) + +// oracle operators. +var oracleOperators = map[string]string{ + "exact": "= ?", + "gt": "> ?", + "gte": ">= ?", + "lt": "< ?", + "lte": "<= ?", + "//iendswith": "LIKE ?", +} + +// oracle column field types. +var oracleTypes = map[string]string{ + "pk": "NOT NULL PRIMARY KEY", + "bool": "bool", + "string": "VARCHAR2(%d)", + "string-text": "VARCHAR2(%d)", + "time.Time-date": "DATE", + "time.Time": "TIMESTAMP", + "int8": "INTEGER", + "int16": "INTEGER", + "int32": "INTEGER", + "int64": "INTEGER", + "uint8": "INTEGER", + "uint16": "INTEGER", + "uint32": "INTEGER", + "uint64": "INTEGER", + "float64": "NUMBER", + "float64-decimal": "NUMBER(%d, %d)", +} + // oracle dbBaser type dbBaseOracle struct { dbBase @@ -27,3 +62,35 @@ func newdbBaseOracle() dbBaser { b.ins = b return b } + +// OperatorSQL get oracle operator. +func (d *dbBaseOracle) OperatorSQL(operator string) string { + return oracleOperators[operator] +} + +// DbTypes get oracle table field types. +func (d *dbBaseOracle) DbTypes() map[string]string { + return oracleTypes +} + +//ShowTablesQuery show all the tables in database +func (d *dbBaseOracle) ShowTablesQuery() string { + return "SELECT TABLE_NAME FROM USER_TABLES" +} + +// Oracle +func (d *dbBaseOracle) ShowColumnsQuery(table string) string { + return fmt.Sprintf("SELECT COLUMN_NAME FROM ALL_TAB_COLUMNS "+ + "WHERE TABLE_NAME ='%s'", strings.ToUpper(table)) +} + +// check index is exist +func (d *dbBaseOracle) IndexExists(db dbQuerier, table string, name string) bool { + row := db.QueryRow("SELECT COUNT(*) FROM USER_IND_COLUMNS, USER_INDEXES "+ + "WHERE USER_IND_COLUMNS.INDEX_NAME = USER_INDEXES.INDEX_NAME "+ + "AND USER_IND_COLUMNS.TABLE_NAME = ? AND USER_IND_COLUMNS.INDEX_NAME = ?", strings.ToUpper(table), strings.ToUpper(name)) + + var cnt int + row.Scan(&cnt) + return cnt > 0 +} diff --git a/vendor/github.com/astaxie/beego/orm/db_utils.go b/vendor/github.com/astaxie/beego/orm/db_utils.go index ae9b1625d..c97caf361 100644 --- a/vendor/github.com/astaxie/beego/orm/db_utils.go +++ b/vendor/github.com/astaxie/beego/orm/db_utils.go @@ -32,7 +32,7 @@ func getDbAlias(name string) *alias { func getExistPk(mi *modelInfo, ind reflect.Value) (column string, value interface{}, exist bool) { fi := mi.fields.pk - v := ind.Field(fi.fieldIndex) + v := ind.FieldByIndex(fi.fieldIndex) if fi.fieldType&IsPostiveIntegerField > 0 { vu := v.Uint() exist = vu > 0 diff --git a/vendor/github.com/astaxie/beego/orm/models_info_f.go b/vendor/github.com/astaxie/beego/orm/models_info_f.go index 14e1f2c62..996a2f408 100644 --- a/vendor/github.com/astaxie/beego/orm/models_info_f.go +++ b/vendor/github.com/astaxie/beego/orm/models_info_f.go @@ -102,7 +102,7 @@ func newFields() *fields { // single field info type fieldInfo struct { mi *modelInfo - fieldIndex int + fieldIndex []int fieldType int dbcol bool inModel bool @@ -138,7 +138,7 @@ type fieldInfo struct { } // new field info -func newFieldInfo(mi *modelInfo, field reflect.Value, sf reflect.StructField) (fi *fieldInfo, err error) { +func newFieldInfo(mi *modelInfo, field reflect.Value, sf reflect.StructField, mName string) (fi *fieldInfo, err error) { var ( tag string tagValue string @@ -278,7 +278,7 @@ checkType: fi.column = getColumnName(fieldType, addrField, sf, tags["column"]) fi.addrValue = addrField fi.sf = sf - fi.fullName = mi.fullName + "." + sf.Name + fi.fullName = mi.fullName + mName + "." + sf.Name fi.null = attrs["null"] fi.index = attrs["index"] diff --git a/vendor/github.com/astaxie/beego/orm/models_info_m.go b/vendor/github.com/astaxie/beego/orm/models_info_m.go index 2654cdb5b..bbb82444d 100644 --- a/vendor/github.com/astaxie/beego/orm/models_info_m.go +++ b/vendor/github.com/astaxie/beego/orm/models_info_m.go @@ -36,11 +36,6 @@ type modelInfo struct { // new model info func newModelInfo(val reflect.Value) (info *modelInfo) { - var ( - err error - fi *fieldInfo - sf reflect.StructField - ) info = &modelInfo{} info.fields = newFields() @@ -53,13 +48,31 @@ func newModelInfo(val reflect.Value) (info *modelInfo) { info.name = typ.Name() info.fullName = getFullName(typ) + addModelFields(info, ind, "", []int{}) + + return +} + +func addModelFields(info *modelInfo, ind reflect.Value, mName string, index []int) { + var ( + err error + fi *fieldInfo + sf reflect.StructField + ) + for i := 0; i < ind.NumField(); i++ { field := ind.Field(i) sf = ind.Type().Field(i) if sf.PkgPath != "" { continue } - fi, err = newFieldInfo(info, field, sf) + // add anonymous struct fields + if sf.Anonymous { + addModelFields(info, field, mName+"."+sf.Name, append(index, i)) + continue + } + + fi, err = newFieldInfo(info, field, sf, mName) if err != nil { if err == errSkipField { @@ -84,7 +97,7 @@ func newModelInfo(val reflect.Value) (info *modelInfo) { } } - fi.fieldIndex = i + fi.fieldIndex = append(index, i) fi.mi = info fi.inModel = true } @@ -93,8 +106,6 @@ func newModelInfo(val reflect.Value) (info *modelInfo) { fmt.Println(fmt.Errorf("field: %s.%s, %s", ind.Type(), sf.Name, err)) os.Exit(2) } - - return } // combine related model info to new model info. diff --git a/vendor/github.com/astaxie/beego/orm/orm.go b/vendor/github.com/astaxie/beego/orm/orm.go index d00d6d038..0ffb6b869 100644 --- a/vendor/github.com/astaxie/beego/orm/orm.go +++ b/vendor/github.com/astaxie/beego/orm/orm.go @@ -140,7 +140,7 @@ func (o *orm) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, i return (err == nil), id, err } - return false, ind.Field(mi.fields.pk.fieldIndex).Int(), err + return false, ind.FieldByIndex(mi.fields.pk.fieldIndex).Int(), err } // insert model data to database @@ -160,9 +160,9 @@ func (o *orm) Insert(md interface{}) (int64, error) { func (o *orm) setPk(mi *modelInfo, ind reflect.Value, id int64) { if mi.fields.pk.auto { if mi.fields.pk.fieldType&IsPostiveIntegerField > 0 { - ind.Field(mi.fields.pk.fieldIndex).SetUint(uint64(id)) + ind.FieldByIndex(mi.fields.pk.fieldIndex).SetUint(uint64(id)) } else { - ind.Field(mi.fields.pk.fieldIndex).SetInt(id) + ind.FieldByIndex(mi.fields.pk.fieldIndex).SetInt(id) } } } @@ -290,7 +290,7 @@ func (o *orm) LoadRelated(md interface{}, name string, args ...interface{}) (int qs.orders = []string{order} } - find := ind.Field(fi.fieldIndex) + find := ind.FieldByIndex(fi.fieldIndex) var nums int64 var err error diff --git a/vendor/github.com/astaxie/beego/orm/orm_object.go b/vendor/github.com/astaxie/beego/orm/orm_object.go index df5a6600e..8a5d85e28 100644 --- a/vendor/github.com/astaxie/beego/orm/orm_object.go +++ b/vendor/github.com/astaxie/beego/orm/orm_object.go @@ -51,9 +51,9 @@ func (o *insertSet) Insert(md interface{}) (int64, error) { if id > 0 { if o.mi.fields.pk.auto { if o.mi.fields.pk.fieldType&IsPostiveIntegerField > 0 { - ind.Field(o.mi.fields.pk.fieldIndex).SetUint(uint64(id)) + ind.FieldByIndex(o.mi.fields.pk.fieldIndex).SetUint(uint64(id)) } else { - ind.Field(o.mi.fields.pk.fieldIndex).SetInt(id) + ind.FieldByIndex(o.mi.fields.pk.fieldIndex).SetInt(id) } } } diff --git a/vendor/github.com/astaxie/beego/orm/orm_raw.go b/vendor/github.com/astaxie/beego/orm/orm_raw.go index cbb18064f..5f88121cb 100644 --- a/vendor/github.com/astaxie/beego/orm/orm_raw.go +++ b/vendor/github.com/astaxie/beego/orm/orm_raw.go @@ -342,7 +342,7 @@ func (o *rawSet) QueryRow(containers ...interface{}) error { for _, col := range columns { if fi := sMi.fields.GetByColumn(col); fi != nil { value := reflect.ValueOf(columnsMp[col]).Elem().Interface() - o.setFieldValue(ind.FieldByIndex([]int{fi.fieldIndex}), value) + o.setFieldValue(ind.FieldByIndex(fi.fieldIndex), value) } } } else { @@ -480,7 +480,7 @@ func (o *rawSet) QueryRows(containers ...interface{}) (int64, error) { for _, col := range columns { if fi := sMi.fields.GetByColumn(col); fi != nil { value := reflect.ValueOf(columnsMp[col]).Elem().Interface() - o.setFieldValue(ind.FieldByIndex([]int{fi.fieldIndex}), value) + o.setFieldValue(ind.FieldByIndex(fi.fieldIndex), value) } } } else { diff --git a/vendor/github.com/astaxie/beego/orm/types.go b/vendor/github.com/astaxie/beego/orm/types.go index 5fac5fed8..41933dd15 100644 --- a/vendor/github.com/astaxie/beego/orm/types.go +++ b/vendor/github.com/astaxie/beego/orm/types.go @@ -148,6 +148,10 @@ type QuerySeter interface { // add OFFSET value // same as Limit function's args[0] Offset(offset interface{}) QuerySeter + // add GROUP BY expression + // for example: + // qs.GroupBy("id") + GroupBy(exprs ...string) QuerySeter // add ORDER expression. // "column" means ASC, "-column" means DESC. // for example: @@ -162,6 +166,12 @@ type QuerySeter interface { // qs.RelatedSel("profile").One(&user) // user.Profile.Age = 32 RelatedSel(params ...interface{}) QuerySeter + // Set Distinct + // for example: + // o.QueryTable("policy").Filter("Groups__Group__Users__User", user). + // Distinct(). + // All(&permissions) + Distinct() QuerySeter // return QuerySeter execution result number // for example: // num, err = qs.Filter("profile__age__gt", 28).Count() diff --git a/vendor/github.com/astaxie/beego/parser.go b/vendor/github.com/astaxie/beego/parser.go index b14d74b9a..46d023201 100644 --- a/vendor/github.com/astaxie/beego/parser.go +++ b/vendor/github.com/astaxie/beego/parser.go @@ -58,7 +58,7 @@ func parserPkg(pkgRealpath, pkgpath string) error { rep := strings.NewReplacer("/", "_", ".", "_") commentFilename = coomentPrefix + rep.Replace(pkgpath) + ".go" if !compareFile(pkgRealpath) { - Info(pkgRealpath + " has not changed, not reloading") + Info(pkgRealpath + " no changed") return nil } genInfoList = make(map[string][]ControllerComments) @@ -130,7 +130,7 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat } func genRouterCode() { - os.Mkdir("routers", 0755) + os.Mkdir(path.Join(AppPath, "routers"), 0755) Info("generate router from comments") var ( globalinfo string @@ -172,7 +172,7 @@ func genRouterCode() { } } if globalinfo != "" { - f, err := os.Create(path.Join("routers", commentFilename)) + f, err := os.Create(path.Join(AppPath, "routers", commentFilename)) if err != nil { panic(err) } @@ -182,7 +182,7 @@ func genRouterCode() { } func compareFile(pkgRealpath string) bool { - if !utils.FileExists(path.Join("routers", commentFilename)) { + if !utils.FileExists(path.Join(AppPath, "routers", commentFilename)) { return true } if utils.FileExists(lastupdateFilename) { diff --git a/vendor/github.com/astaxie/beego/plugins/apiauth/apiauth.go b/vendor/github.com/astaxie/beego/plugins/apiauth/apiauth.go deleted file mode 100644 index 8af08088d..000000000 --- a/vendor/github.com/astaxie/beego/plugins/apiauth/apiauth.go +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright 2014 beego Author. 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. - -// Package apiauth provides handlers to enable apiauth support. -// -// Simple Usage: -// import( -// "github.com/astaxie/beego" -// "github.com/astaxie/beego/plugins/apiauth" -// ) -// -// func main(){ -// // apiauth every request -// beego.InsertFilter("*", beego.BeforeRouter,apiauth.APIBaiscAuth("appid","appkey")) -// beego.Run() -// } -// -// Advanced Usage: -// -// func getAppSecret(appid string) string { -// // get appsecret by appid -// // maybe store in configure, maybe in database -// } -// -// beego.InsertFilter("*", beego.BeforeRouter,apiauth.APISecretAuth(getAppSecret, 360)) -// -// Infomation: -// -// In the request user should include these params in the query -// -// 1. appid -// -// appid is assigned to the application -// -// 2. signature -// -// get the signature use apiauth.Signature() -// -// when you send to server remember use url.QueryEscape() -// -// 3. timestamp: -// -// send the request time, the format is yyyy-mm-dd HH:ii:ss -// -package apiauth - -import ( - "crypto/hmac" - "crypto/sha256" - "encoding/base64" - "fmt" - "net/url" - "sort" - "time" - - "github.com/astaxie/beego" - "github.com/astaxie/beego/context" -) - -// AppIDToAppSecret is used to get appsecret throw appid -type AppIDToAppSecret func(string) string - -// APIBaiscAuth use the basic appid/appkey as the AppIdToAppSecret -func APIBaiscAuth(appid, appkey string) beego.FilterFunc { - ft := func(aid string) string { - if aid == appid { - return appkey - } - return "" - } - return APISecretAuth(ft, 300) -} - -// APISecretAuth use AppIdToAppSecret verify and -func APISecretAuth(f AppIDToAppSecret, timeout int) beego.FilterFunc { - return func(ctx *context.Context) { - if ctx.Input.Query("appid") == "" { - ctx.ResponseWriter.WriteHeader(403) - ctx.WriteString("miss query param: appid") - return - } - appsecret := f(ctx.Input.Query("appid")) - if appsecret == "" { - ctx.ResponseWriter.WriteHeader(403) - ctx.WriteString("not exist this appid") - return - } - if ctx.Input.Query("signature") == "" { - ctx.ResponseWriter.WriteHeader(403) - ctx.WriteString("miss query param: signature") - return - } - if ctx.Input.Query("timestamp") == "" { - ctx.ResponseWriter.WriteHeader(403) - ctx.WriteString("miss query param: timestamp") - return - } - u, err := time.Parse("2006-01-02 15:04:05", ctx.Input.Query("timestamp")) - if err != nil { - ctx.ResponseWriter.WriteHeader(403) - ctx.WriteString("timestamp format is error, should 2006-01-02 15:04:05") - return - } - t := time.Now() - if t.Sub(u).Seconds() > float64(timeout) { - ctx.ResponseWriter.WriteHeader(403) - ctx.WriteString("timeout! the request time is long ago, please try again") - return - } - if ctx.Input.Query("signature") != - Signature(appsecret, ctx.Input.Method(), ctx.Request.Form, ctx.Input.URI()) { - ctx.ResponseWriter.WriteHeader(403) - ctx.WriteString("auth failed") - } - } -} - -// Signature used to generate signature with the appsecret/method/params/RequestURI -func Signature(appsecret, method string, params url.Values, RequestURI string) (result string) { - var query string - pa := make(map[string]string) - for k, v := range params { - pa[k] = v[0] - } - vs := mapSorter(pa) - vs.Sort() - for i := 0; i < vs.Len(); i++ { - if vs.Keys[i] == "signature" { - continue - } - if vs.Keys[i] != "" && vs.Vals[i] != "" { - query = fmt.Sprintf("%v%v%v", query, vs.Keys[i], vs.Vals[i]) - } - } - stringToSign := fmt.Sprintf("%v\n%v\n%v\n", method, query, RequestURI) - - sha256 := sha256.New - hash := hmac.New(sha256, []byte(appsecret)) - hash.Write([]byte(stringToSign)) - return base64.StdEncoding.EncodeToString(hash.Sum(nil)) -} - -type valSorter struct { - Keys []string - Vals []string -} - -func mapSorter(m map[string]string) *valSorter { - vs := &valSorter{ - Keys: make([]string, 0, len(m)), - Vals: make([]string, 0, len(m)), - } - for k, v := range m { - vs.Keys = append(vs.Keys, k) - vs.Vals = append(vs.Vals, v) - } - return vs -} - -func (vs *valSorter) Sort() { - sort.Sort(vs) -} - -func (vs *valSorter) Len() int { return len(vs.Keys) } -func (vs *valSorter) Less(i, j int) bool { return vs.Keys[i] < vs.Keys[j] } -func (vs *valSorter) Swap(i, j int) { - vs.Vals[i], vs.Vals[j] = vs.Vals[j], vs.Vals[i] - vs.Keys[i], vs.Keys[j] = vs.Keys[j], vs.Keys[i] -} diff --git a/vendor/github.com/astaxie/beego/plugins/auth/basic.go b/vendor/github.com/astaxie/beego/plugins/auth/basic.go deleted file mode 100644 index c478044ab..000000000 --- a/vendor/github.com/astaxie/beego/plugins/auth/basic.go +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright 2014 beego Author. 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. - -// Package auth provides handlers to enable basic auth support. -// Simple Usage: -// import( -// "github.com/astaxie/beego" -// "github.com/astaxie/beego/plugins/auth" -// ) -// -// func main(){ -// // authenticate every request -// beego.InsertFilter("*", beego.BeforeRouter,auth.Basic("username","secretpassword")) -// beego.Run() -// } -// -// -// Advanced Usage: -// -// func SecretAuth(username, password string) bool { -// return username == "astaxie" && password == "helloBeego" -// } -// authPlugin := auth.NewBasicAuthenticator(SecretAuth, "Authorization Required") -// beego.InsertFilter("*", beego.BeforeRouter,authPlugin) -package auth - -import ( - "encoding/base64" - "net/http" - "strings" - - "github.com/astaxie/beego" - "github.com/astaxie/beego/context" -) - -var defaultRealm = "Authorization Required" - -// Basic is the http basic auth -func Basic(username string, password string) beego.FilterFunc { - secrets := func(user, pass string) bool { - return user == username && pass == password - } - return NewBasicAuthenticator(secrets, defaultRealm) -} - -// NewBasicAuthenticator return the BasicAuth -func NewBasicAuthenticator(secrets SecretProvider, Realm string) beego.FilterFunc { - return func(ctx *context.Context) { - a := &BasicAuth{Secrets: secrets, Realm: Realm} - if username := a.CheckAuth(ctx.Request); username == "" { - a.RequireAuth(ctx.ResponseWriter, ctx.Request) - } - } -} - -// SecretProvider is the SecretProvider function -type SecretProvider func(user, pass string) bool - -// BasicAuth store the SecretProvider and Realm -type BasicAuth struct { - Secrets SecretProvider - Realm string -} - -// CheckAuth Checks the username/password combination from the request. Returns -// either an empty string (authentication failed) or the name of the -// authenticated user. -// Supports MD5 and SHA1 password entries -func (a *BasicAuth) CheckAuth(r *http.Request) string { - s := strings.SplitN(r.Header.Get("Authorization"), " ", 2) - if len(s) != 2 || s[0] != "Basic" { - return "" - } - - b, err := base64.StdEncoding.DecodeString(s[1]) - if err != nil { - return "" - } - pair := strings.SplitN(string(b), ":", 2) - if len(pair) != 2 { - return "" - } - - if a.Secrets(pair[0], pair[1]) { - return pair[0] - } - return "" -} - -// RequireAuth http.Handler for BasicAuth which initiates the authentication process -// (or requires reauthentication). -func (a *BasicAuth) RequireAuth(w http.ResponseWriter, r *http.Request) { - w.Header().Set("WWW-Authenticate", `Basic realm="`+a.Realm+`"`) - w.WriteHeader(401) - w.Write([]byte("401 Unauthorized\n")) -} diff --git a/vendor/github.com/astaxie/beego/plugins/cors/cors.go b/vendor/github.com/astaxie/beego/plugins/cors/cors.go deleted file mode 100644 index 1e973a405..000000000 --- a/vendor/github.com/astaxie/beego/plugins/cors/cors.go +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright 2014 beego Author. 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. - -// Package cors provides handlers to enable CORS support. -// Usage -// import ( -// "github.com/astaxie/beego" -// "github.com/astaxie/beego/plugins/cors" -// ) -// -// func main() { -// // CORS for https://foo.* origins, allowing: -// // - PUT and PATCH methods -// // - Origin header -// // - Credentials share -// beego.InsertFilter("*", beego.BeforeRouter, cors.Allow(&cors.Options{ -// AllowOrigins: []string{"https://*.foo.com"}, -// AllowMethods: []string{"PUT", "PATCH"}, -// AllowHeaders: []string{"Origin"}, -// ExposeHeaders: []string{"Content-Length"}, -// AllowCredentials: true, -// })) -// beego.Run() -// } -package cors - -import ( - "regexp" - "strconv" - "strings" - "time" - - "github.com/astaxie/beego" - "github.com/astaxie/beego/context" -) - -const ( - headerAllowOrigin = "Access-Control-Allow-Origin" - headerAllowCredentials = "Access-Control-Allow-Credentials" - headerAllowHeaders = "Access-Control-Allow-Headers" - headerAllowMethods = "Access-Control-Allow-Methods" - headerExposeHeaders = "Access-Control-Expose-Headers" - headerMaxAge = "Access-Control-Max-Age" - - headerOrigin = "Origin" - headerRequestMethod = "Access-Control-Request-Method" - headerRequestHeaders = "Access-Control-Request-Headers" -) - -var ( - defaultAllowHeaders = []string{"Origin", "Accept", "Content-Type", "Authorization"} - // Regex patterns are generated from AllowOrigins. These are used and generated internally. - allowOriginPatterns = []string{} -) - -// Options represents Access Control options. -type Options struct { - // If set, all origins are allowed. - AllowAllOrigins bool - // A list of allowed origins. Wild cards and FQDNs are supported. - AllowOrigins []string - // If set, allows to share auth credentials such as cookies. - AllowCredentials bool - // A list of allowed HTTP methods. - AllowMethods []string - // A list of allowed HTTP headers. - AllowHeaders []string - // A list of exposed HTTP headers. - ExposeHeaders []string - // Max age of the CORS headers. - MaxAge time.Duration -} - -// Header converts options into CORS headers. -func (o *Options) Header(origin string) (headers map[string]string) { - headers = make(map[string]string) - // if origin is not allowed, don't extend the headers - // with CORS headers. - if !o.AllowAllOrigins && !o.IsOriginAllowed(origin) { - return - } - - // add allow origin - if o.AllowAllOrigins { - headers[headerAllowOrigin] = "*" - } else { - headers[headerAllowOrigin] = origin - } - - // add allow credentials - headers[headerAllowCredentials] = strconv.FormatBool(o.AllowCredentials) - - // add allow methods - if len(o.AllowMethods) > 0 { - headers[headerAllowMethods] = strings.Join(o.AllowMethods, ",") - } - - // add allow headers - if len(o.AllowHeaders) > 0 { - headers[headerAllowHeaders] = strings.Join(o.AllowHeaders, ",") - } - - // add exposed header - if len(o.ExposeHeaders) > 0 { - headers[headerExposeHeaders] = strings.Join(o.ExposeHeaders, ",") - } - // add a max age header - if o.MaxAge > time.Duration(0) { - headers[headerMaxAge] = strconv.FormatInt(int64(o.MaxAge/time.Second), 10) - } - return -} - -// PreflightHeader converts options into CORS headers for a preflight response. -func (o *Options) PreflightHeader(origin, rMethod, rHeaders string) (headers map[string]string) { - headers = make(map[string]string) - if !o.AllowAllOrigins && !o.IsOriginAllowed(origin) { - return - } - // verify if requested method is allowed - for _, method := range o.AllowMethods { - if method == rMethod { - headers[headerAllowMethods] = strings.Join(o.AllowMethods, ",") - break - } - } - - // verify if requested headers are allowed - var allowed []string - for _, rHeader := range strings.Split(rHeaders, ",") { - rHeader = strings.TrimSpace(rHeader) - lookupLoop: - for _, allowedHeader := range o.AllowHeaders { - if strings.ToLower(rHeader) == strings.ToLower(allowedHeader) { - allowed = append(allowed, rHeader) - break lookupLoop - } - } - } - - headers[headerAllowCredentials] = strconv.FormatBool(o.AllowCredentials) - // add allow origin - if o.AllowAllOrigins { - headers[headerAllowOrigin] = "*" - } else { - headers[headerAllowOrigin] = origin - } - - // add allowed headers - if len(allowed) > 0 { - headers[headerAllowHeaders] = strings.Join(allowed, ",") - } - - // add exposed headers - if len(o.ExposeHeaders) > 0 { - headers[headerExposeHeaders] = strings.Join(o.ExposeHeaders, ",") - } - // add a max age header - if o.MaxAge > time.Duration(0) { - headers[headerMaxAge] = strconv.FormatInt(int64(o.MaxAge/time.Second), 10) - } - return -} - -// IsOriginAllowed looks up if the origin matches one of the patterns -// generated from Options.AllowOrigins patterns. -func (o *Options) IsOriginAllowed(origin string) (allowed bool) { - for _, pattern := range allowOriginPatterns { - allowed, _ = regexp.MatchString(pattern, origin) - if allowed { - return - } - } - return -} - -// Allow enables CORS for requests those match the provided options. -func Allow(opts *Options) beego.FilterFunc { - // Allow default headers if nothing is specified. - if len(opts.AllowHeaders) == 0 { - opts.AllowHeaders = defaultAllowHeaders - } - - for _, origin := range opts.AllowOrigins { - pattern := regexp.QuoteMeta(origin) - pattern = strings.Replace(pattern, "\\*", ".*", -1) - pattern = strings.Replace(pattern, "\\?", ".", -1) - allowOriginPatterns = append(allowOriginPatterns, "^"+pattern+"$") - } - - return func(ctx *context.Context) { - var ( - origin = ctx.Input.Header(headerOrigin) - requestedMethod = ctx.Input.Header(headerRequestMethod) - requestedHeaders = ctx.Input.Header(headerRequestHeaders) - // additional headers to be added - // to the response. - headers map[string]string - ) - - if ctx.Input.Method() == "OPTIONS" && - (requestedMethod != "" || requestedHeaders != "") { - headers = opts.PreflightHeader(origin, requestedMethod, requestedHeaders) - for key, value := range headers { - ctx.Output.Header(key, value) - } - return - } - headers = opts.Header(origin) - - for key, value := range headers { - ctx.Output.Header(key, value) - } - } -} diff --git a/vendor/github.com/astaxie/beego/router.go b/vendor/github.com/astaxie/beego/router.go index 726936dee..d0bf534f2 100644 --- a/vendor/github.com/astaxie/beego/router.go +++ b/vendor/github.com/astaxie/beego/router.go @@ -62,12 +62,12 @@ var ( } // these beego.Controller's methods shouldn't reflect to AutoRouter exceptMethod = []string{"Init", "Prepare", "Finish", "Render", "RenderString", - "RenderBytes", "Redirect", "Abort", "StopRun", "UrlFor", "ServeJson", "ServeJsonp", - "ServeXml", "Input", "ParseForm", "GetString", "GetStrings", "GetInt", "GetBool", + "RenderBytes", "Redirect", "Abort", "StopRun", "UrlFor", "ServeJSON", "ServeJSONP", + "ServeXML", "Input", "ParseForm", "GetString", "GetStrings", "GetInt", "GetBool", "GetFloat", "GetFile", "SaveToFile", "StartSession", "SetSession", "GetSession", "DelSession", "SessionRegenerateID", "DestroySession", "IsAjax", "GetSecureCookie", "SetSecureCookie", "XsrfToken", "CheckXsrfCookie", "XsrfFormHtml", - "GetControllerAndAction"} + "GetControllerAndAction", "ServeFormatted"} urlPlaceholder = "{{placeholder}}" // DefaultAccessLogFilter will skip the accesslog if return true @@ -607,6 +607,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) ) context := p.pool.Get().(*beecontext.Context) context.Reset(rw, r) + defer p.pool.Put(context) defer p.recoverPanic(context) diff --git a/vendor/github.com/astaxie/beego/session/couchbase/sess_couchbase.go b/vendor/github.com/astaxie/beego/session/couchbase/sess_couchbase.go deleted file mode 100644 index d5be11d0b..000000000 --- a/vendor/github.com/astaxie/beego/session/couchbase/sess_couchbase.go +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright 2014 beego Author. 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. - -// Package couchbase for session provider -// -// depend on github.com/couchbaselabs/go-couchbasee -// -// go install github.com/couchbaselabs/go-couchbase -// -// Usage: -// import( -// _ "github.com/astaxie/beego/session/couchbase" -// "github.com/astaxie/beego/session" -// ) -// -// func init() { -// globalSessions, _ = session.NewManager("couchbase", ``{"cookieName":"gosessionid","gclifetime":3600,"ProviderConfig":"http://host:port/, Pool, Bucket"}``) -// go globalSessions.GC() -// } -// -// more docs: http://beego.me/docs/module/session.md -package couchbase - -import ( - "net/http" - "strings" - "sync" - - couchbase "github.com/couchbase/go-couchbase" - - "github.com/astaxie/beego/session" -) - -var couchbpder = &Provider{} - -// SessionStore store each session -type SessionStore struct { - b *couchbase.Bucket - sid string - lock sync.RWMutex - values map[interface{}]interface{} - maxlifetime int64 -} - -// Provider couchabse provided -type Provider struct { - maxlifetime int64 - savePath string - pool string - bucket string - b *couchbase.Bucket -} - -// Set value to couchabse session -func (cs *SessionStore) Set(key, value interface{}) error { - cs.lock.Lock() - defer cs.lock.Unlock() - cs.values[key] = value - return nil -} - -// Get value from couchabse session -func (cs *SessionStore) Get(key interface{}) interface{} { - cs.lock.RLock() - defer cs.lock.RUnlock() - if v, ok := cs.values[key]; ok { - return v - } - return nil -} - -// Delete value in couchbase session by given key -func (cs *SessionStore) Delete(key interface{}) error { - cs.lock.Lock() - defer cs.lock.Unlock() - delete(cs.values, key) - return nil -} - -// Flush Clean all values in couchbase session -func (cs *SessionStore) Flush() error { - cs.lock.Lock() - defer cs.lock.Unlock() - cs.values = make(map[interface{}]interface{}) - return nil -} - -// SessionID Get couchbase session store id -func (cs *SessionStore) SessionID() string { - return cs.sid -} - -// SessionRelease Write couchbase session with Gob string -func (cs *SessionStore) SessionRelease(w http.ResponseWriter) { - defer cs.b.Close() - - bo, err := session.EncodeGob(cs.values) - if err != nil { - return - } - - cs.b.Set(cs.sid, int(cs.maxlifetime), bo) -} - -func (cp *Provider) getBucket() *couchbase.Bucket { - c, err := couchbase.Connect(cp.savePath) - if err != nil { - return nil - } - - pool, err := c.GetPool(cp.pool) - if err != nil { - return nil - } - - bucket, err := pool.GetBucket(cp.bucket) - if err != nil { - return nil - } - - return bucket -} - -// SessionInit init couchbase session -// savepath like couchbase server REST/JSON URL -// e.g. http://host:port/, Pool, Bucket -func (cp *Provider) SessionInit(maxlifetime int64, savePath string) error { - cp.maxlifetime = maxlifetime - configs := strings.Split(savePath, ",") - if len(configs) > 0 { - cp.savePath = configs[0] - } - if len(configs) > 1 { - cp.pool = configs[1] - } - if len(configs) > 2 { - cp.bucket = configs[2] - } - - return nil -} - -// SessionRead read couchbase session by sid -func (cp *Provider) SessionRead(sid string) (session.Store, error) { - cp.b = cp.getBucket() - - var doc []byte - - err := cp.b.Get(sid, &doc) - var kv map[interface{}]interface{} - if doc == nil { - kv = make(map[interface{}]interface{}) - } else { - kv, err = session.DecodeGob(doc) - if err != nil { - return nil, err - } - } - - cs := &SessionStore{b: cp.b, sid: sid, values: kv, maxlifetime: cp.maxlifetime} - return cs, nil -} - -// SessionExist Check couchbase session exist. -// it checkes sid exist or not. -func (cp *Provider) SessionExist(sid string) bool { - cp.b = cp.getBucket() - defer cp.b.Close() - - var doc []byte - - if err := cp.b.Get(sid, &doc); err != nil || doc == nil { - return false - } - return true -} - -// SessionRegenerate remove oldsid and use sid to generate new session -func (cp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { - cp.b = cp.getBucket() - - var doc []byte - if err := cp.b.Get(oldsid, &doc); err != nil || doc == nil { - cp.b.Set(sid, int(cp.maxlifetime), "") - } else { - err := cp.b.Delete(oldsid) - if err != nil { - return nil, err - } - _, _ = cp.b.Add(sid, int(cp.maxlifetime), doc) - } - - err := cp.b.Get(sid, &doc) - if err != nil { - return nil, err - } - var kv map[interface{}]interface{} - if doc == nil { - kv = make(map[interface{}]interface{}) - } else { - kv, err = session.DecodeGob(doc) - if err != nil { - return nil, err - } - } - - cs := &SessionStore{b: cp.b, sid: sid, values: kv, maxlifetime: cp.maxlifetime} - return cs, nil -} - -// SessionDestroy Remove bucket in this couchbase -func (cp *Provider) SessionDestroy(sid string) error { - cp.b = cp.getBucket() - defer cp.b.Close() - - cp.b.Delete(sid) - return nil -} - -// SessionGC Recycle -func (cp *Provider) SessionGC() { - return -} - -// SessionAll return all active session -func (cp *Provider) SessionAll() int { - return 0 -} - -func init() { - session.Register("couchbase", couchbpder) -} diff --git a/vendor/github.com/astaxie/beego/session/ledis/ledis_session.go b/vendor/github.com/astaxie/beego/session/ledis/ledis_session.go deleted file mode 100644 index 68f37b08c..000000000 --- a/vendor/github.com/astaxie/beego/session/ledis/ledis_session.go +++ /dev/null @@ -1,179 +0,0 @@ -// Package ledis provide session Provider -package ledis - -import ( - "net/http" - "strconv" - "strings" - "sync" - - "github.com/astaxie/beego/session" - "github.com/siddontang/ledisdb/config" - "github.com/siddontang/ledisdb/ledis" -) - -var ledispder = &Provider{} -var c *ledis.DB - -// SessionStore ledis session store -type SessionStore struct { - sid string - lock sync.RWMutex - values map[interface{}]interface{} - maxlifetime int64 -} - -// Set value in ledis session -func (ls *SessionStore) Set(key, value interface{}) error { - ls.lock.Lock() - defer ls.lock.Unlock() - ls.values[key] = value - return nil -} - -// Get value in ledis session -func (ls *SessionStore) Get(key interface{}) interface{} { - ls.lock.RLock() - defer ls.lock.RUnlock() - if v, ok := ls.values[key]; ok { - return v - } - return nil -} - -// Delete value in ledis session -func (ls *SessionStore) Delete(key interface{}) error { - ls.lock.Lock() - defer ls.lock.Unlock() - delete(ls.values, key) - return nil -} - -// Flush clear all values in ledis session -func (ls *SessionStore) Flush() error { - ls.lock.Lock() - defer ls.lock.Unlock() - ls.values = make(map[interface{}]interface{}) - return nil -} - -// SessionID get ledis session id -func (ls *SessionStore) SessionID() string { - return ls.sid -} - -// SessionRelease save session values to ledis -func (ls *SessionStore) SessionRelease(w http.ResponseWriter) { - b, err := session.EncodeGob(ls.values) - if err != nil { - return - } - c.Set([]byte(ls.sid), b) - c.Expire([]byte(ls.sid), ls.maxlifetime) -} - -// Provider ledis session provider -type Provider struct { - maxlifetime int64 - savePath string - db int -} - -// SessionInit init ledis session -// savepath like ledis server saveDataPath,pool size -// e.g. 127.0.0.1:6379,100,astaxie -func (lp *Provider) SessionInit(maxlifetime int64, savePath string) error { - var err error - lp.maxlifetime = maxlifetime - configs := strings.Split(savePath, ",") - if len(configs) == 1 { - lp.savePath = configs[0] - } else if len(configs) == 2 { - lp.savePath = configs[0] - lp.db, err = strconv.Atoi(configs[1]) - if err != nil { - return err - } - } - cfg := new(config.Config) - cfg.DataDir = lp.savePath - nowLedis, err := ledis.Open(cfg) - c, err = nowLedis.Select(lp.db) - if err != nil { - println(err) - return nil - } - return nil -} - -// SessionRead read ledis session by sid -func (lp *Provider) SessionRead(sid string) (session.Store, error) { - kvs, err := c.Get([]byte(sid)) - var kv map[interface{}]interface{} - if len(kvs) == 0 { - kv = make(map[interface{}]interface{}) - } else { - kv, err = session.DecodeGob(kvs) - if err != nil { - return nil, err - } - } - ls := &SessionStore{sid: sid, values: kv, maxlifetime: lp.maxlifetime} - return ls, nil -} - -// SessionExist check ledis session exist by sid -func (lp *Provider) SessionExist(sid string) bool { - count, _ := c.Exists([]byte(sid)) - if count == 0 { - return false - } - return true -} - -// SessionRegenerate generate new sid for ledis session -func (lp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { - count, _ := c.Exists([]byte(sid)) - if count == 0 { - // oldsid doesn't exists, set the new sid directly - // ignore error here, since if it return error - // the existed value will be 0 - c.Set([]byte(sid), []byte("")) - c.Expire([]byte(sid), lp.maxlifetime) - } else { - data, _ := c.Get([]byte(oldsid)) - c.Set([]byte(sid), data) - c.Expire([]byte(sid), lp.maxlifetime) - } - kvs, err := c.Get([]byte(sid)) - var kv map[interface{}]interface{} - if len(kvs) == 0 { - kv = make(map[interface{}]interface{}) - } else { - kv, err = session.DecodeGob([]byte(kvs)) - if err != nil { - return nil, err - } - } - ls := &SessionStore{sid: sid, values: kv, maxlifetime: lp.maxlifetime} - return ls, nil -} - -// SessionDestroy delete ledis session by id -func (lp *Provider) SessionDestroy(sid string) error { - c.Del([]byte(sid)) - return nil -} - -// SessionGC Impelment method, no used. -func (lp *Provider) SessionGC() { - return -} - -// SessionAll return all active session -func (lp *Provider) SessionAll() int { - return 0 -} -func init() { - session.Register("ledis", ledispder) -} diff --git a/vendor/github.com/astaxie/beego/session/memcache/sess_memcache.go b/vendor/github.com/astaxie/beego/session/memcache/sess_memcache.go deleted file mode 100644 index f1069bc93..000000000 --- a/vendor/github.com/astaxie/beego/session/memcache/sess_memcache.go +++ /dev/null @@ -1,232 +0,0 @@ -// Copyright 2014 beego Author. 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. - -// Package memcache for session provider -// -// depend on github.com/bradfitz/gomemcache/memcache -// -// go install github.com/bradfitz/gomemcache/memcache -// -// Usage: -// import( -// _ "github.com/astaxie/beego/session/memcache" -// "github.com/astaxie/beego/session" -// ) -// -// func init() { -// globalSessions, _ = session.NewManager("memcache", ``{"cookieName":"gosessionid","gclifetime":3600,"ProviderConfig":"127.0.0.1:11211"}``) -// go globalSessions.GC() -// } -// -// more docs: http://beego.me/docs/module/session.md -package memcache - -import ( - "net/http" - "strings" - "sync" - - "github.com/astaxie/beego/session" - - "github.com/bradfitz/gomemcache/memcache" -) - -var mempder = &MemProvider{} -var client *memcache.Client - -// SessionStore memcache session store -type SessionStore struct { - sid string - lock sync.RWMutex - values map[interface{}]interface{} - maxlifetime int64 -} - -// Set value in memcache session -func (rs *SessionStore) Set(key, value interface{}) error { - rs.lock.Lock() - defer rs.lock.Unlock() - rs.values[key] = value - return nil -} - -// Get value in memcache session -func (rs *SessionStore) Get(key interface{}) interface{} { - rs.lock.RLock() - defer rs.lock.RUnlock() - if v, ok := rs.values[key]; ok { - return v - } - return nil -} - -// Delete value in memcache session -func (rs *SessionStore) Delete(key interface{}) error { - rs.lock.Lock() - defer rs.lock.Unlock() - delete(rs.values, key) - return nil -} - -// Flush clear all values in memcache session -func (rs *SessionStore) Flush() error { - rs.lock.Lock() - defer rs.lock.Unlock() - rs.values = make(map[interface{}]interface{}) - return nil -} - -// SessionID get memcache session id -func (rs *SessionStore) SessionID() string { - return rs.sid -} - -// SessionRelease save session values to memcache -func (rs *SessionStore) SessionRelease(w http.ResponseWriter) { - b, err := session.EncodeGob(rs.values) - if err != nil { - return - } - item := memcache.Item{Key: rs.sid, Value: b, Expiration: int32(rs.maxlifetime)} - client.Set(&item) -} - -// MemProvider memcache session provider -type MemProvider struct { - maxlifetime int64 - conninfo []string - poolsize int - password string -} - -// SessionInit init memcache session -// savepath like -// e.g. 127.0.0.1:9090 -func (rp *MemProvider) SessionInit(maxlifetime int64, savePath string) error { - rp.maxlifetime = maxlifetime - rp.conninfo = strings.Split(savePath, ";") - client = memcache.New(rp.conninfo...) - return nil -} - -// SessionRead read memcache session by sid -func (rp *MemProvider) SessionRead(sid string) (session.Store, error) { - if client == nil { - if err := rp.connectInit(); err != nil { - return nil, err - } - } - item, err := client.Get(sid) - if err != nil && err == memcache.ErrCacheMiss { - rs := &SessionStore{sid: sid, values: make(map[interface{}]interface{}), maxlifetime: rp.maxlifetime} - return rs, nil - } - var kv map[interface{}]interface{} - if len(item.Value) == 0 { - kv = make(map[interface{}]interface{}) - } else { - kv, err = session.DecodeGob(item.Value) - if err != nil { - return nil, err - } - } - rs := &SessionStore{sid: sid, values: kv, maxlifetime: rp.maxlifetime} - return rs, nil -} - -// SessionExist check memcache session exist by sid -func (rp *MemProvider) SessionExist(sid string) bool { - if client == nil { - if err := rp.connectInit(); err != nil { - return false - } - } - if item, err := client.Get(sid); err != nil || len(item.Value) == 0 { - return false - } - return true -} - -// SessionRegenerate generate new sid for memcache session -func (rp *MemProvider) SessionRegenerate(oldsid, sid string) (session.Store, error) { - if client == nil { - if err := rp.connectInit(); err != nil { - return nil, err - } - } - var contain []byte - if item, err := client.Get(sid); err != nil || len(item.Value) == 0 { - // oldsid doesn't exists, set the new sid directly - // ignore error here, since if it return error - // the existed value will be 0 - item.Key = sid - item.Value = []byte("") - item.Expiration = int32(rp.maxlifetime) - client.Set(item) - } else { - client.Delete(oldsid) - item.Key = sid - item.Expiration = int32(rp.maxlifetime) - client.Set(item) - contain = item.Value - } - - var kv map[interface{}]interface{} - if len(contain) == 0 { - kv = make(map[interface{}]interface{}) - } else { - var err error - kv, err = session.DecodeGob(contain) - if err != nil { - return nil, err - } - } - - rs := &SessionStore{sid: sid, values: kv, maxlifetime: rp.maxlifetime} - return rs, nil -} - -// SessionDestroy delete memcache session by id -func (rp *MemProvider) SessionDestroy(sid string) error { - if client == nil { - if err := rp.connectInit(); err != nil { - return err - } - } - - err := client.Delete(sid) - if err != nil { - return err - } - return nil -} - -func (rp *MemProvider) connectInit() error { - client = memcache.New(rp.conninfo...) - return nil -} - -// SessionGC Impelment method, no used. -func (rp *MemProvider) SessionGC() { - return -} - -// SessionAll return all activeSession -func (rp *MemProvider) SessionAll() int { - return 0 -} - -func init() { - session.Register("memcache", mempder) -} diff --git a/vendor/github.com/astaxie/beego/session/mysql/sess_mysql.go b/vendor/github.com/astaxie/beego/session/mysql/sess_mysql.go deleted file mode 100644 index 969d26c97..000000000 --- a/vendor/github.com/astaxie/beego/session/mysql/sess_mysql.go +++ /dev/null @@ -1,233 +0,0 @@ -// Copyright 2014 beego Author. 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. - -// Package mysql for session provider -// -// depends on github.com/go-sql-driver/mysql: -// -// go install github.com/go-sql-driver/mysql -// -// mysql session support need create table as sql: -// CREATE TABLE `session` ( -// `session_key` char(64) NOT NULL, -// `session_data` blob, -// `session_expiry` int(11) unsigned NOT NULL, -// PRIMARY KEY (`session_key`) -// ) ENGINE=MyISAM DEFAULT CHARSET=utf8; -// -// Usage: -// import( -// _ "github.com/astaxie/beego/session/mysql" -// "github.com/astaxie/beego/session" -// ) -// -// func init() { -// globalSessions, _ = session.NewManager("mysql", ``{"cookieName":"gosessionid","gclifetime":3600,"ProviderConfig":"[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN]"}``) -// go globalSessions.GC() -// } -// -// more docs: http://beego.me/docs/module/session.md -package mysql - -import ( - "database/sql" - "net/http" - "sync" - "time" - - "github.com/astaxie/beego/session" - // import mysql driver - _ "github.com/go-sql-driver/mysql" -) - -var ( - // TableName store the session in MySQL - TableName = "session" - mysqlpder = &Provider{} -) - -// SessionStore mysql session store -type SessionStore struct { - c *sql.DB - sid string - lock sync.RWMutex - values map[interface{}]interface{} -} - -// Set value in mysql session. -// it is temp value in map. -func (st *SessionStore) Set(key, value interface{}) error { - st.lock.Lock() - defer st.lock.Unlock() - st.values[key] = value - return nil -} - -// Get value from mysql session -func (st *SessionStore) Get(key interface{}) interface{} { - st.lock.RLock() - defer st.lock.RUnlock() - if v, ok := st.values[key]; ok { - return v - } - return nil -} - -// Delete value in mysql session -func (st *SessionStore) Delete(key interface{}) error { - st.lock.Lock() - defer st.lock.Unlock() - delete(st.values, key) - return nil -} - -// Flush clear all values in mysql session -func (st *SessionStore) Flush() error { - st.lock.Lock() - defer st.lock.Unlock() - st.values = make(map[interface{}]interface{}) - return nil -} - -// SessionID get session id of this mysql session store -func (st *SessionStore) SessionID() string { - return st.sid -} - -// SessionRelease save mysql session values to database. -// must call this method to save values to database. -func (st *SessionStore) SessionRelease(w http.ResponseWriter) { - defer st.c.Close() - b, err := session.EncodeGob(st.values) - if err != nil { - return - } - st.c.Exec("UPDATE "+TableName+" set `session_data`=?, `session_expiry`=? where session_key=?", - b, time.Now().Unix(), st.sid) - -} - -// Provider mysql session provider -type Provider struct { - maxlifetime int64 - savePath string -} - -// connect to mysql -func (mp *Provider) connectInit() *sql.DB { - db, e := sql.Open("mysql", mp.savePath) - if e != nil { - return nil - } - return db -} - -// SessionInit init mysql session. -// savepath is the connection string of mysql. -func (mp *Provider) SessionInit(maxlifetime int64, savePath string) error { - mp.maxlifetime = maxlifetime - mp.savePath = savePath - return nil -} - -// SessionRead get mysql session by sid -func (mp *Provider) SessionRead(sid string) (session.Store, error) { - c := mp.connectInit() - row := c.QueryRow("select session_data from "+TableName+" where session_key=?", sid) - var sessiondata []byte - err := row.Scan(&sessiondata) - if err == sql.ErrNoRows { - c.Exec("insert into "+TableName+"(`session_key`,`session_data`,`session_expiry`) values(?,?,?)", - sid, "", time.Now().Unix()) - } - var kv map[interface{}]interface{} - if len(sessiondata) == 0 { - kv = make(map[interface{}]interface{}) - } else { - kv, err = session.DecodeGob(sessiondata) - if err != nil { - return nil, err - } - } - rs := &SessionStore{c: c, sid: sid, values: kv} - return rs, nil -} - -// SessionExist check mysql session exist -func (mp *Provider) SessionExist(sid string) bool { - c := mp.connectInit() - defer c.Close() - row := c.QueryRow("select session_data from "+TableName+" where session_key=?", sid) - var sessiondata []byte - err := row.Scan(&sessiondata) - if err == sql.ErrNoRows { - return false - } - return true -} - -// SessionRegenerate generate new sid for mysql session -func (mp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { - c := mp.connectInit() - row := c.QueryRow("select session_data from "+TableName+" where session_key=?", oldsid) - var sessiondata []byte - err := row.Scan(&sessiondata) - if err == sql.ErrNoRows { - c.Exec("insert into "+TableName+"(`session_key`,`session_data`,`session_expiry`) values(?,?,?)", oldsid, "", time.Now().Unix()) - } - c.Exec("update "+TableName+" set `session_key`=? where session_key=?", sid, oldsid) - var kv map[interface{}]interface{} - if len(sessiondata) == 0 { - kv = make(map[interface{}]interface{}) - } else { - kv, err = session.DecodeGob(sessiondata) - if err != nil { - return nil, err - } - } - rs := &SessionStore{c: c, sid: sid, values: kv} - return rs, nil -} - -// SessionDestroy delete mysql session by sid -func (mp *Provider) SessionDestroy(sid string) error { - c := mp.connectInit() - c.Exec("DELETE FROM "+TableName+" where session_key=?", sid) - c.Close() - return nil -} - -// SessionGC delete expired values in mysql session -func (mp *Provider) SessionGC() { - c := mp.connectInit() - c.Exec("DELETE from "+TableName+" where session_expiry < ?", time.Now().Unix()-mp.maxlifetime) - c.Close() - return -} - -// SessionAll count values in mysql session -func (mp *Provider) SessionAll() int { - c := mp.connectInit() - defer c.Close() - var total int - err := c.QueryRow("SELECT count(*) as num from " + TableName).Scan(&total) - if err != nil { - return 0 - } - return total -} - -func init() { - session.Register("mysql", mysqlpder) -} diff --git a/vendor/github.com/astaxie/beego/session/postgres/sess_postgresql.go b/vendor/github.com/astaxie/beego/session/postgres/sess_postgresql.go deleted file mode 100644 index 73f9c13a9..000000000 --- a/vendor/github.com/astaxie/beego/session/postgres/sess_postgresql.go +++ /dev/null @@ -1,248 +0,0 @@ -// Copyright 2014 beego Author. 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. - -// Package postgres for session provider -// -// depends on github.com/lib/pq: -// -// go install github.com/lib/pq -// -// -// needs this table in your database: -// -// CREATE TABLE session ( -// session_key char(64) NOT NULL, -// session_data bytea, -// session_expiry timestamp NOT NULL, -// CONSTRAINT session_key PRIMARY KEY(session_key) -// ); -// -// will be activated with these settings in app.conf: -// -// SessionOn = true -// SessionProvider = postgresql -// SessionSavePath = "user=a password=b dbname=c sslmode=disable" -// SessionName = session -// -// -// Usage: -// import( -// _ "github.com/astaxie/beego/session/postgresql" -// "github.com/astaxie/beego/session" -// ) -// -// func init() { -// globalSessions, _ = session.NewManager("postgresql", ``{"cookieName":"gosessionid","gclifetime":3600,"ProviderConfig":"user=pqgotest dbname=pqgotest sslmode=verify-full"}``) -// go globalSessions.GC() -// } -// -// more docs: http://beego.me/docs/module/session.md -package postgres - -import ( - "database/sql" - "net/http" - "sync" - "time" - - "github.com/astaxie/beego/session" - // import postgresql Driver - _ "github.com/lib/pq" -) - -var postgresqlpder = &Provider{} - -// SessionStore postgresql session store -type SessionStore struct { - c *sql.DB - sid string - lock sync.RWMutex - values map[interface{}]interface{} -} - -// Set value in postgresql session. -// it is temp value in map. -func (st *SessionStore) Set(key, value interface{}) error { - st.lock.Lock() - defer st.lock.Unlock() - st.values[key] = value - return nil -} - -// Get value from postgresql session -func (st *SessionStore) Get(key interface{}) interface{} { - st.lock.RLock() - defer st.lock.RUnlock() - if v, ok := st.values[key]; ok { - return v - } - return nil -} - -// Delete value in postgresql session -func (st *SessionStore) Delete(key interface{}) error { - st.lock.Lock() - defer st.lock.Unlock() - delete(st.values, key) - return nil -} - -// Flush clear all values in postgresql session -func (st *SessionStore) Flush() error { - st.lock.Lock() - defer st.lock.Unlock() - st.values = make(map[interface{}]interface{}) - return nil -} - -// SessionID get session id of this postgresql session store -func (st *SessionStore) SessionID() string { - return st.sid -} - -// SessionRelease save postgresql session values to database. -// must call this method to save values to database. -func (st *SessionStore) SessionRelease(w http.ResponseWriter) { - defer st.c.Close() - b, err := session.EncodeGob(st.values) - if err != nil { - return - } - st.c.Exec("UPDATE session set session_data=$1, session_expiry=$2 where session_key=$3", - b, time.Now().Format(time.RFC3339), st.sid) - -} - -// Provider postgresql session provider -type Provider struct { - maxlifetime int64 - savePath string -} - -// connect to postgresql -func (mp *Provider) connectInit() *sql.DB { - db, e := sql.Open("postgres", mp.savePath) - if e != nil { - return nil - } - return db -} - -// SessionInit init postgresql session. -// savepath is the connection string of postgresql. -func (mp *Provider) SessionInit(maxlifetime int64, savePath string) error { - mp.maxlifetime = maxlifetime - mp.savePath = savePath - return nil -} - -// SessionRead get postgresql session by sid -func (mp *Provider) SessionRead(sid string) (session.Store, error) { - c := mp.connectInit() - row := c.QueryRow("select session_data from session where session_key=$1", sid) - var sessiondata []byte - err := row.Scan(&sessiondata) - if err == sql.ErrNoRows { - _, err = c.Exec("insert into session(session_key,session_data,session_expiry) values($1,$2,$3)", - sid, "", time.Now().Format(time.RFC3339)) - - if err != nil { - return nil, err - } - } else if err != nil { - return nil, err - } - - var kv map[interface{}]interface{} - if len(sessiondata) == 0 { - kv = make(map[interface{}]interface{}) - } else { - kv, err = session.DecodeGob(sessiondata) - if err != nil { - return nil, err - } - } - rs := &SessionStore{c: c, sid: sid, values: kv} - return rs, nil -} - -// SessionExist check postgresql session exist -func (mp *Provider) SessionExist(sid string) bool { - c := mp.connectInit() - defer c.Close() - row := c.QueryRow("select session_data from session where session_key=$1", sid) - var sessiondata []byte - err := row.Scan(&sessiondata) - - if err == sql.ErrNoRows { - return false - } - return true -} - -// SessionRegenerate generate new sid for postgresql session -func (mp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { - c := mp.connectInit() - row := c.QueryRow("select session_data from session where session_key=$1", oldsid) - var sessiondata []byte - err := row.Scan(&sessiondata) - if err == sql.ErrNoRows { - c.Exec("insert into session(session_key,session_data,session_expiry) values($1,$2,$3)", - oldsid, "", time.Now().Format(time.RFC3339)) - } - c.Exec("update session set session_key=$1 where session_key=$2", sid, oldsid) - var kv map[interface{}]interface{} - if len(sessiondata) == 0 { - kv = make(map[interface{}]interface{}) - } else { - kv, err = session.DecodeGob(sessiondata) - if err != nil { - return nil, err - } - } - rs := &SessionStore{c: c, sid: sid, values: kv} - return rs, nil -} - -// SessionDestroy delete postgresql session by sid -func (mp *Provider) SessionDestroy(sid string) error { - c := mp.connectInit() - c.Exec("DELETE FROM session where session_key=$1", sid) - c.Close() - return nil -} - -// SessionGC delete expired values in postgresql session -func (mp *Provider) SessionGC() { - c := mp.connectInit() - c.Exec("DELETE from session where EXTRACT(EPOCH FROM (current_timestamp - session_expiry)) > $1", mp.maxlifetime) - c.Close() - return -} - -// SessionAll count values in postgresql session -func (mp *Provider) SessionAll() int { - c := mp.connectInit() - defer c.Close() - var total int - err := c.QueryRow("SELECT count(*) as num from session").Scan(&total) - if err != nil { - return 0 - } - return total -} - -func init() { - session.Register("postgresql", postgresqlpder) -} diff --git a/vendor/github.com/astaxie/beego/session/redis/sess_redis.go b/vendor/github.com/astaxie/beego/session/redis/sess_redis.go deleted file mode 100644 index c46fa7cdf..000000000 --- a/vendor/github.com/astaxie/beego/session/redis/sess_redis.go +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright 2014 beego Author. 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. - -// Package redis for session provider -// -// depend on github.com/garyburd/redigo/redis -// -// go install github.com/garyburd/redigo/redis -// -// Usage: -// import( -// _ "github.com/astaxie/beego/session/redis" -// "github.com/astaxie/beego/session" -// ) -// -// func init() { -// globalSessions, _ = session.NewManager("redis", ``{"cookieName":"gosessionid","gclifetime":3600,"ProviderConfig":"127.0.0.1:7070"}``) -// go globalSessions.GC() -// } -// -// more docs: http://beego.me/docs/module/session.md -package redis - -import ( - "net/http" - "strconv" - "strings" - "sync" - - "github.com/astaxie/beego/session" - - "github.com/garyburd/redigo/redis" -) - -var redispder = &Provider{} - -// MaxPoolSize redis max pool size -var MaxPoolSize = 100 - -// SessionStore redis session store -type SessionStore struct { - p *redis.Pool - sid string - lock sync.RWMutex - values map[interface{}]interface{} - maxlifetime int64 -} - -// Set value in redis session -func (rs *SessionStore) Set(key, value interface{}) error { - rs.lock.Lock() - defer rs.lock.Unlock() - rs.values[key] = value - return nil -} - -// Get value in redis session -func (rs *SessionStore) Get(key interface{}) interface{} { - rs.lock.RLock() - defer rs.lock.RUnlock() - if v, ok := rs.values[key]; ok { - return v - } - return nil -} - -// Delete value in redis session -func (rs *SessionStore) Delete(key interface{}) error { - rs.lock.Lock() - defer rs.lock.Unlock() - delete(rs.values, key) - return nil -} - -// Flush clear all values in redis session -func (rs *SessionStore) Flush() error { - rs.lock.Lock() - defer rs.lock.Unlock() - rs.values = make(map[interface{}]interface{}) - return nil -} - -// SessionID get redis session id -func (rs *SessionStore) SessionID() string { - return rs.sid -} - -// SessionRelease save session values to redis -func (rs *SessionStore) SessionRelease(w http.ResponseWriter) { - b, err := session.EncodeGob(rs.values) - if err != nil { - return - } - c := rs.p.Get() - defer c.Close() - c.Do("SETEX", rs.sid, rs.maxlifetime, string(b)) -} - -// Provider redis session provider -type Provider struct { - maxlifetime int64 - savePath string - poolsize int - password string - dbNum int - poollist *redis.Pool -} - -// SessionInit init redis session -// savepath like redis server addr,pool size,password,dbnum -// e.g. 127.0.0.1:6379,100,astaxie,0 -func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error { - rp.maxlifetime = maxlifetime - configs := strings.Split(savePath, ",") - if len(configs) > 0 { - rp.savePath = configs[0] - } - if len(configs) > 1 { - poolsize, err := strconv.Atoi(configs[1]) - if err != nil || poolsize <= 0 { - rp.poolsize = MaxPoolSize - } else { - rp.poolsize = poolsize - } - } else { - rp.poolsize = MaxPoolSize - } - if len(configs) > 2 { - rp.password = configs[2] - } - if len(configs) > 3 { - dbnum, err := strconv.Atoi(configs[3]) - if err != nil || dbnum < 0 { - rp.dbNum = 0 - } else { - rp.dbNum = dbnum - } - } else { - rp.dbNum = 0 - } - rp.poollist = redis.NewPool(func() (redis.Conn, error) { - c, err := redis.Dial("tcp", rp.savePath) - if err != nil { - return nil, err - } - if rp.password != "" { - if _, err := c.Do("AUTH", rp.password); err != nil { - c.Close() - return nil, err - } - } - _, err = c.Do("SELECT", rp.dbNum) - if err != nil { - c.Close() - return nil, err - } - return c, err - }, rp.poolsize) - - return rp.poollist.Get().Err() -} - -// SessionRead read redis session by sid -func (rp *Provider) SessionRead(sid string) (session.Store, error) { - c := rp.poollist.Get() - defer c.Close() - - kvs, err := redis.String(c.Do("GET", sid)) - var kv map[interface{}]interface{} - if len(kvs) == 0 { - kv = make(map[interface{}]interface{}) - } else { - kv, err = session.DecodeGob([]byte(kvs)) - if err != nil { - return nil, err - } - } - - rs := &SessionStore{p: rp.poollist, sid: sid, values: kv, maxlifetime: rp.maxlifetime} - return rs, nil -} - -// SessionExist check redis session exist by sid -func (rp *Provider) SessionExist(sid string) bool { - c := rp.poollist.Get() - defer c.Close() - - if existed, err := redis.Int(c.Do("EXISTS", sid)); err != nil || existed == 0 { - return false - } - return true -} - -// SessionRegenerate generate new sid for redis session -func (rp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) { - c := rp.poollist.Get() - defer c.Close() - - if existed, _ := redis.Int(c.Do("EXISTS", oldsid)); existed == 0 { - // oldsid doesn't exists, set the new sid directly - // ignore error here, since if it return error - // the existed value will be 0 - c.Do("SET", sid, "", "EX", rp.maxlifetime) - } else { - c.Do("RENAME", oldsid, sid) - c.Do("EXPIRE", sid, rp.maxlifetime) - } - - kvs, err := redis.String(c.Do("GET", sid)) - var kv map[interface{}]interface{} - if len(kvs) == 0 { - kv = make(map[interface{}]interface{}) - } else { - kv, err = session.DecodeGob([]byte(kvs)) - if err != nil { - return nil, err - } - } - - rs := &SessionStore{p: rp.poollist, sid: sid, values: kv, maxlifetime: rp.maxlifetime} - return rs, nil -} - -// SessionDestroy delete redis session by id -func (rp *Provider) SessionDestroy(sid string) error { - c := rp.poollist.Get() - defer c.Close() - - c.Do("DEL", sid) - return nil -} - -// SessionGC Impelment method, no used. -func (rp *Provider) SessionGC() { - return -} - -// SessionAll return all activeSession -func (rp *Provider) SessionAll() int { - return 0 -} - -func init() { - session.Register("redis", redispder) -} diff --git a/vendor/github.com/astaxie/beego/session/sess_mem.go b/vendor/github.com/astaxie/beego/session/sess_mem.go index dd61ef573..64d8b0561 100644 --- a/vendor/github.com/astaxie/beego/session/sess_mem.go +++ b/vendor/github.com/astaxie/beego/session/sess_mem.go @@ -102,7 +102,7 @@ func (pder *MemProvider) SessionRead(sid string) (Store, error) { pder.lock.RUnlock() pder.lock.Lock() newsess := &MemSessionStore{sid: sid, timeAccessed: time.Now(), value: make(map[interface{}]interface{})} - element := pder.list.PushBack(newsess) + element := pder.list.PushFront(newsess) pder.sessions[sid] = element pder.lock.Unlock() return newsess, nil @@ -134,7 +134,7 @@ func (pder *MemProvider) SessionRegenerate(oldsid, sid string) (Store, error) { pder.lock.RUnlock() pder.lock.Lock() newsess := &MemSessionStore{sid: sid, timeAccessed: time.Now(), value: make(map[interface{}]interface{})} - element := pder.list.PushBack(newsess) + element := pder.list.PushFront(newsess) pder.sessions[sid] = element pder.lock.Unlock() return newsess, nil diff --git a/vendor/github.com/astaxie/beego/session/session.go b/vendor/github.com/astaxie/beego/session/session.go index 39d475fc1..9fe99a174 100644 --- a/vendor/github.com/astaxie/beego/session/session.go +++ b/vendor/github.com/astaxie/beego/session/session.go @@ -201,7 +201,9 @@ func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request) { if err != nil || cookie.Value == "" { return } - manager.provider.SessionDestroy(cookie.Value) + + sid, _ := url.QueryUnescape(cookie.Value) + manager.provider.SessionDestroy(sid) if manager.config.EnableSetCookie { expiration := time.Now() cookie = &http.Cookie{Name: manager.config.CookieName, diff --git a/vendor/github.com/astaxie/beego/staticfile.go b/vendor/github.com/astaxie/beego/staticfile.go index 9534ce914..0aad2c81e 100644 --- a/vendor/github.com/astaxie/beego/staticfile.go +++ b/vendor/github.com/astaxie/beego/staticfile.go @@ -93,12 +93,14 @@ type serveContentHolder struct { var ( staticFileMap = make(map[string]*serveContentHolder) - mapLock sync.Mutex + mapLock sync.RWMutex ) func openFile(filePath string, fi os.FileInfo, acceptEncoding string) (bool, string, *serveContentHolder, error) { mapKey := acceptEncoding + ":" + filePath + mapLock.RLock() mapFile, _ := staticFileMap[mapKey] + mapLock.RUnlock() if isOk(mapFile, fi) { return mapFile.encoding != "", mapFile.encoding, mapFile, nil } diff --git a/vendor/github.com/astaxie/beego/swagger/docs_spec.go b/vendor/github.com/astaxie/beego/swagger/docs_spec.go deleted file mode 100644 index 680324dc0..000000000 --- a/vendor/github.com/astaxie/beego/swagger/docs_spec.go +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright 2014 beego Author. 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. - -// Package swagger struct definition -package swagger - -// SwaggerVersion show the current swagger version -const SwaggerVersion = "1.2" - -// ResourceListing list the resource -type ResourceListing struct { - APIVersion string `json:"apiVersion"` - SwaggerVersion string `json:"swaggerVersion"` // e.g 1.2 - // BasePath string `json:"basePath"` obsolete in 1.1 - APIs []APIRef `json:"apis"` - Info Information `json:"info"` -} - -// APIRef description the api path and description -type APIRef struct { - Path string `json:"path"` // relative or absolute, must start with / - Description string `json:"description"` -} - -// Information show the API Information -type Information struct { - Title string `json:"title,omitempty"` - Description string `json:"description,omitempty"` - Contact string `json:"contact,omitempty"` - TermsOfServiceURL string `json:"termsOfServiceUrl,omitempty"` - License string `json:"license,omitempty"` - LicenseURL string `json:"licenseUrl,omitempty"` -} - -// APIDeclaration see https://github.com/wordnik/swagger-core/blob/scala_2.10-1.3-RC3/schemas/api-declaration-schema.json -type APIDeclaration struct { - APIVersion string `json:"apiVersion"` - SwaggerVersion string `json:"swaggerVersion"` - BasePath string `json:"basePath"` - ResourcePath string `json:"resourcePath"` // must start with / - Consumes []string `json:"consumes,omitempty"` - Produces []string `json:"produces,omitempty"` - APIs []API `json:"apis,omitempty"` - Models map[string]Model `json:"models,omitempty"` -} - -// API show tha API struct -type API struct { - Path string `json:"path"` // relative or absolute, must start with / - Description string `json:"description"` - Operations []Operation `json:"operations,omitempty"` -} - -// Operation desc the Operation -type Operation struct { - HTTPMethod string `json:"httpMethod"` - Nickname string `json:"nickname"` - Type string `json:"type"` // in 1.1 = DataType - // ResponseClass string `json:"responseClass"` obsolete in 1.2 - Summary string `json:"summary,omitempty"` - Notes string `json:"notes,omitempty"` - Parameters []Parameter `json:"parameters,omitempty"` - ResponseMessages []ResponseMessage `json:"responseMessages,omitempty"` // optional - Consumes []string `json:"consumes,omitempty"` - Produces []string `json:"produces,omitempty"` - Authorizations []Authorization `json:"authorizations,omitempty"` - Protocols []Protocol `json:"protocols,omitempty"` -} - -// Protocol support which Protocol -type Protocol struct { -} - -// ResponseMessage Show the -type ResponseMessage struct { - Code int `json:"code"` - Message string `json:"message"` - ResponseModel string `json:"responseModel"` -} - -// Parameter desc the request parameters -type Parameter struct { - ParamType string `json:"paramType"` // path,query,body,header,form - Name string `json:"name"` - Description string `json:"description"` - DataType string `json:"dataType"` // 1.2 needed? - Type string `json:"type"` // integer - Format string `json:"format"` // int64 - AllowMultiple bool `json:"allowMultiple"` - Required bool `json:"required"` - Minimum int `json:"minimum"` - Maximum int `json:"maximum"` -} - -// ErrorResponse desc response -type ErrorResponse struct { - Code int `json:"code"` - Reason string `json:"reason"` -} - -// Model define the data model -type Model struct { - ID string `json:"id"` - Required []string `json:"required,omitempty"` - Properties map[string]ModelProperty `json:"properties"` -} - -// ModelProperty define the properties -type ModelProperty struct { - Type string `json:"type"` - Description string `json:"description"` - Items map[string]string `json:"items,omitempty"` - Format string `json:"format"` -} - -// Authorization see https://github.com/wordnik/swagger-core/wiki/authorizations -type Authorization struct { - LocalOAuth OAuth `json:"local-oauth"` - APIKey APIKey `json:"apiKey"` -} - -// OAuth see https://github.com/wordnik/swagger-core/wiki/authorizations -type OAuth struct { - Type string `json:"type"` // e.g. oauth2 - Scopes []string `json:"scopes"` // e.g. PUBLIC - GrantTypes map[string]GrantType `json:"grantTypes"` -} - -// GrantType see https://github.com/wordnik/swagger-core/wiki/authorizations -type GrantType struct { - LoginEndpoint Endpoint `json:"loginEndpoint"` - TokenName string `json:"tokenName"` // e.g. access_code - TokenRequestEndpoint Endpoint `json:"tokenRequestEndpoint"` - TokenEndpoint Endpoint `json:"tokenEndpoint"` -} - -// Endpoint see https://github.com/wordnik/swagger-core/wiki/authorizations -type Endpoint struct { - URL string `json:"url"` - ClientIDName string `json:"clientIdName"` - ClientSecretName string `json:"clientSecretName"` - TokenName string `json:"tokenName"` -} - -// APIKey see https://github.com/wordnik/swagger-core/wiki/authorizations -type APIKey struct { - Type string `json:"type"` // e.g. apiKey - PassAs string `json:"passAs"` // e.g. header -} diff --git a/vendor/github.com/astaxie/beego/template.go b/vendor/github.com/astaxie/beego/template.go index 363c67541..e6c43f871 100644 --- a/vendor/github.com/astaxie/beego/template.go +++ b/vendor/github.com/astaxie/beego/template.go @@ -18,23 +18,41 @@ import ( "errors" "fmt" "html/template" + "io" "io/ioutil" "os" "path/filepath" "regexp" "strings" + "sync" "github.com/astaxie/beego/utils" ) var ( beegoTplFuncMap = make(template.FuncMap) - // BeeTemplates caching map and supported template file extensions. - BeeTemplates = make(map[string]*template.Template) - // BeeTemplateExt stores the template extension which will build - BeeTemplateExt = []string{"tpl", "html"} + // beeTemplates caching map and supported template file extensions. + beeTemplates = make(map[string]*template.Template) + templatesLock sync.RWMutex + // beeTemplateExt stores the template extension which will build + beeTemplateExt = []string{"tpl", "html"} ) +func executeTemplate(wr io.Writer, name string, data interface{}) error { + if BConfig.RunMode == DEV { + templatesLock.RLock() + defer templatesLock.RUnlock() + } + if t, ok := beeTemplates[name]; ok { + err := t.ExecuteTemplate(wr, name, data) + if err != nil { + Trace("template Execute err:", err) + } + return err + } + panic("can't find templatefile in the path:" + name) +} + func init() { beegoTplFuncMap["dateformat"] = DateFormat beegoTplFuncMap["date"] = Date @@ -53,7 +71,6 @@ func init() { beegoTplFuncMap["config"] = GetConfig beegoTplFuncMap["map_get"] = MapGet - // go1.2 added template funcs // Comparisons beegoTplFuncMap["eq"] = eq // == beegoTplFuncMap["ge"] = ge // >= @@ -62,21 +79,25 @@ func init() { beegoTplFuncMap["lt"] = lt // < beegoTplFuncMap["ne"] = ne // != - beegoTplFuncMap["urlfor"] = URLFor // != + beegoTplFuncMap["urlfor"] = URLFor // build a URL to match a Controller and it's method } // AddFuncMap let user to register a func in the template. -func AddFuncMap(key string, funname interface{}) error { - beegoTplFuncMap[key] = funname +func AddFuncMap(key string, fn interface{}) error { + beegoTplFuncMap[key] = fn return nil } -type templatefile struct { +type templateFile struct { root string files map[string][]string } -func (tf *templatefile) visit(paths string, f os.FileInfo, err error) error { +// visit will make the paths into two part,the first is subDir (without tf.root),the second is full path(without tf.root). +// if tf.root="views" and +// paths is "views/errors/404.html",the subDir will be "errors",the file will be "errors/404.html" +// paths is "views/admin/errors/404.html",the subDir will be "admin/errors",the file will be "admin/errors/404.html" +func (tf *templateFile) visit(paths string, f os.FileInfo, err error) error { if f == nil { return err } @@ -88,24 +109,16 @@ func (tf *templatefile) visit(paths string, f os.FileInfo, err error) error { } replace := strings.NewReplacer("\\", "/") - a := []byte(paths) - a = a[len([]byte(tf.root)):] - file := strings.TrimLeft(replace.Replace(string(a)), "/") - subdir := filepath.Dir(file) - if _, ok := tf.files[subdir]; ok { - tf.files[subdir] = append(tf.files[subdir], file) - } else { - m := make([]string, 1) - m[0] = file - tf.files[subdir] = m - } + file := strings.TrimLeft(replace.Replace(paths[len(tf.root):]), "/") + subDir := filepath.Dir(file) + tf.files[subDir] = append(tf.files[subDir], file) return nil } // HasTemplateExt return this path contains supported template extension of beego or not. func HasTemplateExt(paths string) bool { - for _, v := range BeeTemplateExt { + for _, v := range beeTemplateExt { if strings.HasSuffix(paths, "."+v) { return true } @@ -115,12 +128,12 @@ func HasTemplateExt(paths string) bool { // AddTemplateExt add new extension for template. func AddTemplateExt(ext string) { - for _, v := range BeeTemplateExt { + for _, v := range beeTemplateExt { if v == ext { return } } - BeeTemplateExt = append(BeeTemplateExt, ext) + beeTemplateExt = append(beeTemplateExt, ext) } // BuildTemplate will build all template files in a directory. @@ -132,7 +145,7 @@ func BuildTemplate(dir string, files ...string) error { } return errors.New("dir open err") } - self := &templatefile{ + self := &templateFile{ root: dir, files: make(map[string][]string), } @@ -146,12 +159,14 @@ func BuildTemplate(dir string, files ...string) error { for _, v := range self.files { for _, file := range v { if len(files) == 0 || utils.InSlice(file, files) { + templatesLock.Lock() t, err := getTemplate(self.root, file, v...) if err != nil { Trace("parse template err:", file, err) } else { - BeeTemplates[file] = t + beeTemplates[file] = t } + templatesLock.Unlock() } } } @@ -159,16 +174,16 @@ func BuildTemplate(dir string, files ...string) error { } func getTplDeep(root, file, parent string, t *template.Template) (*template.Template, [][]string, error) { - var fileabspath string + var fileAbsPath string if filepath.HasPrefix(file, "../") { - fileabspath = filepath.Join(root, filepath.Dir(parent), file) + fileAbsPath = filepath.Join(root, filepath.Dir(parent), file) } else { - fileabspath = filepath.Join(root, file) + fileAbsPath = filepath.Join(root, file) } - if e := utils.FileExists(fileabspath); !e { + if e := utils.FileExists(fileAbsPath); !e { panic("can't find template file:" + file) } - data, err := ioutil.ReadFile(fileabspath) + data, err := ioutil.ReadFile(fileAbsPath) if err != nil { return nil, [][]string{}, err } @@ -177,11 +192,11 @@ func getTplDeep(root, file, parent string, t *template.Template) (*template.Temp return nil, [][]string{}, err } reg := regexp.MustCompile(BConfig.WebConfig.TemplateLeft + "[ ]*template[ ]+\"([^\"]+)\"") - allsub := reg.FindAllStringSubmatch(string(data), -1) - for _, m := range allsub { + allSub := reg.FindAllStringSubmatch(string(data), -1) + for _, m := range allSub { if len(m) == 2 { - tlook := t.Lookup(m[1]) - if tlook != nil { + tl := t.Lookup(m[1]) + if tl != nil { continue } if !HasTemplateExt(m[1]) { @@ -193,17 +208,17 @@ func getTplDeep(root, file, parent string, t *template.Template) (*template.Temp } } } - return t, allsub, nil + return t, allSub, nil } func getTemplate(root, file string, others ...string) (t *template.Template, err error) { t = template.New(file).Delims(BConfig.WebConfig.TemplateLeft, BConfig.WebConfig.TemplateRight).Funcs(beegoTplFuncMap) - var submods [][]string - t, submods, err = getTplDeep(root, file, "", t) + var subMods [][]string + t, subMods, err = getTplDeep(root, file, "", t) if err != nil { return nil, err } - t, err = _getTemplate(t, root, submods, others...) + t, err = _getTemplate(t, root, subMods, others...) if err != nil { return nil, err @@ -211,44 +226,44 @@ func getTemplate(root, file string, others ...string) (t *template.Template, err return } -func _getTemplate(t0 *template.Template, root string, submods [][]string, others ...string) (t *template.Template, err error) { +func _getTemplate(t0 *template.Template, root string, subMods [][]string, others ...string) (t *template.Template, err error) { t = t0 - for _, m := range submods { + for _, m := range subMods { if len(m) == 2 { - templ := t.Lookup(m[1]) - if templ != nil { + tpl := t.Lookup(m[1]) + if tpl != nil { continue } //first check filename - for _, otherfile := range others { - if otherfile == m[1] { - var submods1 [][]string - t, submods1, err = getTplDeep(root, otherfile, "", t) + for _, otherFile := range others { + if otherFile == m[1] { + var subMods1 [][]string + t, subMods1, err = getTplDeep(root, otherFile, "", t) if err != nil { Trace("template parse file err:", err) - } else if submods1 != nil && len(submods1) > 0 { - t, err = _getTemplate(t, root, submods1, others...) + } else if subMods1 != nil && len(subMods1) > 0 { + t, err = _getTemplate(t, root, subMods1, others...) } break } } //second check define - for _, otherfile := range others { - fileabspath := filepath.Join(root, otherfile) - data, err := ioutil.ReadFile(fileabspath) + for _, otherFile := range others { + fileAbsPath := filepath.Join(root, otherFile) + data, err := ioutil.ReadFile(fileAbsPath) if err != nil { continue } reg := regexp.MustCompile(BConfig.WebConfig.TemplateLeft + "[ ]*define[ ]+\"([^\"]+)\"") - allsub := reg.FindAllStringSubmatch(string(data), -1) - for _, sub := range allsub { + allSub := reg.FindAllStringSubmatch(string(data), -1) + for _, sub := range allSub { if len(sub) == 2 && sub[1] == m[1] { - var submods1 [][]string - t, submods1, err = getTplDeep(root, otherfile, "", t) + var subMods1 [][]string + t, subMods1, err = getTplDeep(root, otherFile, "", t) if err != nil { Trace("template parse file err:", err) - } else if submods1 != nil && len(submods1) > 0 { - t, err = _getTemplate(t, root, submods1, others...) + } else if subMods1 != nil && len(subMods1) > 0 { + t, err = _getTemplate(t, root, subMods1, others...) } break } @@ -272,7 +287,9 @@ func SetStaticPath(url string, path string) *App { if !strings.HasPrefix(url, "/") { url = "/" + url } - url = strings.TrimRight(url, "/") + if url != "/" { + url = strings.TrimRight(url, "/") + } BConfig.WebConfig.StaticDir[url] = path return BeeApp } @@ -282,7 +299,9 @@ func DelStaticPath(url string) *App { if !strings.HasPrefix(url, "/") { url = "/" + url } - url = strings.TrimRight(url, "/") + if url != "/" { + url = strings.TrimRight(url, "/") + } delete(BConfig.WebConfig.StaticDir, url) return BeeApp } diff --git a/vendor/github.com/astaxie/beego/testing/assertions.go b/vendor/github.com/astaxie/beego/testing/assertions.go deleted file mode 100644 index 96c5d4ddc..000000000 --- a/vendor/github.com/astaxie/beego/testing/assertions.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2014 beego Author. 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. - -package testing diff --git a/vendor/github.com/astaxie/beego/testing/client.go b/vendor/github.com/astaxie/beego/testing/client.go deleted file mode 100644 index c3737e9c6..000000000 --- a/vendor/github.com/astaxie/beego/testing/client.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2014 beego Author. 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. - -package testing - -import ( - "github.com/astaxie/beego/config" - "github.com/astaxie/beego/httplib" -) - -var port = "" -var baseURL = "http://localhost:" - -// TestHTTPRequest beego test request client -type TestHTTPRequest struct { - httplib.BeegoHTTPRequest -} - -func getPort() string { - if port == "" { - config, err := config.NewConfig("ini", "../conf/app.conf") - if err != nil { - return "8080" - } - port = config.String("httpport") - return port - } - return port -} - -// Get returns test client in GET method -func Get(path string) *TestHTTPRequest { - return &TestHTTPRequest{*httplib.Get(baseURL + getPort() + path)} -} - -// Post returns test client in POST method -func Post(path string) *TestHTTPRequest { - return &TestHTTPRequest{*httplib.Post(baseURL + getPort() + path)} -} - -// Put returns test client in PUT method -func Put(path string) *TestHTTPRequest { - return &TestHTTPRequest{*httplib.Put(baseURL + getPort() + path)} -} - -// Delete returns test client in DELETE method -func Delete(path string) *TestHTTPRequest { - return &TestHTTPRequest{*httplib.Delete(baseURL + getPort() + path)} -} - -// Head returns test client in HEAD method -func Head(path string) *TestHTTPRequest { - return &TestHTTPRequest{*httplib.Head(baseURL + getPort() + path)} -} diff --git a/vendor/github.com/astaxie/beego/tree.go b/vendor/github.com/astaxie/beego/tree.go index a6ffe062e..0601099cd 100644 --- a/vendor/github.com/astaxie/beego/tree.go +++ b/vendor/github.com/astaxie/beego/tree.go @@ -141,7 +141,7 @@ func (t *Tree) addtree(segments []string, tree *Tree, wildcards []string, reg st regexpStr = "([^.]+).(.+)" params = params[1:] } else { - for range params { + for _ = range params { regexpStr = "([^/]+)/" + regexpStr } } @@ -254,7 +254,7 @@ func (t *Tree) addseg(segments []string, route interface{}, wildcards []string, regexpStr = "/([^.]+).(.+)" params = params[1:] } else { - for range params { + for _ = range params { regexpStr = "/([^/]+)" + regexpStr } } @@ -265,15 +265,14 @@ func (t *Tree) addseg(segments []string, route interface{}, wildcards []string, } t.wildcard.addseg(segments[1:], route, append(wildcards, params...), reg+regexpStr) } else { - var ok bool var subTree *Tree - for _, subTree = range t.fixrouters { - if t.prefix == seg { - ok = true + for _, sub := range t.fixrouters { + if sub.prefix == seg { + subTree = sub break } } - if !ok { + if subTree == nil { subTree = NewTree() subTree.prefix = seg t.fixrouters = append(t.fixrouters, subTree) @@ -390,7 +389,7 @@ type leafInfo struct { func (leaf *leafInfo) match(wildcardValues []string, ctx *context.Context) (ok bool) { //fmt.Println("Leaf:", wildcardValues, leaf.wildcards, leaf.regexps) if leaf.regexps == nil { - if len(wildcardValues) == 0 { // static path + if len(wildcardValues) == 0 && len(leaf.wildcards) == 0 { // static path return true } // match * @@ -420,7 +419,11 @@ func (leaf *leafInfo) match(wildcardValues []string, ctx *context.Context) (ok b if len(strs) == 2 { ctx.Input.SetParam(":ext", strs[1]) } - ctx.Input.SetParam(":path", path.Join(path.Join(wildcardValues[index:len(wildcardValues)-1]...), strs[0])) + if index > (len(wildcardValues) - 1) { + ctx.Input.SetParam(":path", "") + } else { + ctx.Input.SetParam(":path", path.Join(path.Join(wildcardValues[index:len(wildcardValues)-1]...), strs[0])) + } return true } // match :id @@ -438,7 +441,9 @@ func (leaf *leafInfo) match(wildcardValues []string, ctx *context.Context) (ok b } matches := leaf.regexps.FindStringSubmatch(path.Join(wildcardValues...)) for i, match := range matches[1:] { - ctx.Input.SetParam(leaf.wildcards[i], match) + if i < len(leaf.wildcards) { + ctx.Input.SetParam(leaf.wildcards[i], match) + } } return true } @@ -448,17 +453,11 @@ func (leaf *leafInfo) match(wildcardValues []string, ctx *context.Context) (ok b // "/admin/" -> ["admin"] // "/admin/users" -> ["admin", "users"] func splitPath(key string) []string { + key = strings.Trim(key, "/ ") if key == "" { return []string{} } - elements := strings.Split(key, "/") - if elements[0] == "" { - elements = elements[1:] - } - if elements[len(elements)-1] == "" { - elements = elements[:len(elements)-1] - } - return elements + return strings.Split(key, "/") } // "admin" -> false, nil, "" @@ -542,13 +541,19 @@ func splitSegment(key string) (bool, []string, string) { continue } } - if v == ':' { + // Escape Sequence '\' + if i > 0 && key[i-1] == '\\' { + out = append(out, v) + } else if v == ':' { param = make([]rune, 0) start = true } else if v == '(' { startexp = true start = false - params = append(params, ":"+string(param)) + if len(param) > 0 { + params = append(params, ":"+string(param)) + param = make([]rune, 0) + } paramsNum++ expt = make([]rune, 0) expt = append(expt, '(') diff --git a/vendor/github.com/astaxie/beego/utils/captcha/LICENSE b/vendor/github.com/astaxie/beego/utils/captcha/LICENSE deleted file mode 100644 index 0ad73ae0e..000000000 --- a/vendor/github.com/astaxie/beego/utils/captcha/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2011-2014 Dmitry Chestnykh - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/vendor/github.com/astaxie/beego/utils/captcha/README.md b/vendor/github.com/astaxie/beego/utils/captcha/README.md deleted file mode 100644 index dbc2026b1..000000000 --- a/vendor/github.com/astaxie/beego/utils/captcha/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# Captcha - -an example for use captcha - -``` -package controllers - -import ( - "github.com/astaxie/beego" - "github.com/astaxie/beego/cache" - "github.com/astaxie/beego/utils/captcha" -) - -var cpt *captcha.Captcha - -func init() { - // use beego cache system store the captcha data - store := cache.NewMemoryCache() - cpt = captcha.NewWithFilter("/captcha/", store) -} - -type MainController struct { - beego.Controller -} - -func (this *MainController) Get() { - this.TplName = "index.tpl" -} - -func (this *MainController) Post() { - this.TplName = "index.tpl" - - this.Data["Success"] = cpt.VerifyReq(this.Ctx.Request) -} -``` - -template usage - -``` -{{.Success}} -
- {{create_captcha}} - -
-``` diff --git a/vendor/github.com/astaxie/beego/utils/captcha/captcha.go b/vendor/github.com/astaxie/beego/utils/captcha/captcha.go deleted file mode 100644 index 1a4a6edcb..000000000 --- a/vendor/github.com/astaxie/beego/utils/captcha/captcha.go +++ /dev/null @@ -1,269 +0,0 @@ -// Copyright 2014 beego Author. 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. - -// Package captcha implements generation and verification of image CAPTCHAs. -// an example for use captcha -// -// ``` -// package controllers -// -// import ( -// "github.com/astaxie/beego" -// "github.com/astaxie/beego/cache" -// "github.com/astaxie/beego/utils/captcha" -// ) -// -// var cpt *captcha.Captcha -// -// func init() { -// // use beego cache system store the captcha data -// store := cache.NewMemoryCache() -// cpt = captcha.NewWithFilter("/captcha/", store) -// } -// -// type MainController struct { -// beego.Controller -// } -// -// func (this *MainController) Get() { -// this.TplName = "index.tpl" -// } -// -// func (this *MainController) Post() { -// this.TplName = "index.tpl" -// -// this.Data["Success"] = cpt.VerifyReq(this.Ctx.Request) -// } -// ``` -// -// template usage -// -// ``` -// {{.Success}} -//
-// {{create_captcha}} -// -//
-// ``` -package captcha - -import ( - "fmt" - "html/template" - "net/http" - "path" - "strings" - "time" - - "github.com/astaxie/beego" - "github.com/astaxie/beego/cache" - "github.com/astaxie/beego/context" - "github.com/astaxie/beego/utils" -) - -var ( - defaultChars = []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} -) - -const ( - // default captcha attributes - challengeNums = 6 - expiration = 600 * time.Second - fieldIDName = "captcha_id" - fieldCaptchaName = "captcha" - cachePrefix = "captcha_" - defaultURLPrefix = "/captcha/" -) - -// Captcha struct -type Captcha struct { - // beego cache store - store cache.Cache - - // url prefix for captcha image - URLPrefix string - - // specify captcha id input field name - FieldIDName string - // specify captcha result input field name - FieldCaptchaName string - - // captcha image width and height - StdWidth int - StdHeight int - - // captcha chars nums - ChallengeNums int - - // captcha expiration seconds - Expiration time.Duration - - // cache key prefix - CachePrefix string -} - -// generate key string -func (c *Captcha) key(id string) string { - return c.CachePrefix + id -} - -// generate rand chars with default chars -func (c *Captcha) genRandChars() []byte { - return utils.RandomCreateBytes(c.ChallengeNums, defaultChars...) -} - -// Handler beego filter handler for serve captcha image -func (c *Captcha) Handler(ctx *context.Context) { - var chars []byte - - id := path.Base(ctx.Request.RequestURI) - if i := strings.Index(id, "."); i != -1 { - id = id[:i] - } - - key := c.key(id) - - if len(ctx.Input.Query("reload")) > 0 { - chars = c.genRandChars() - if err := c.store.Put(key, chars, c.Expiration); err != nil { - ctx.Output.SetStatus(500) - ctx.WriteString("captcha reload error") - beego.Error("Reload Create Captcha Error:", err) - return - } - } else { - if v, ok := c.store.Get(key).([]byte); ok { - chars = v - } else { - ctx.Output.SetStatus(404) - ctx.WriteString("captcha not found") - return - } - } - - img := NewImage(chars, c.StdWidth, c.StdHeight) - if _, err := img.WriteTo(ctx.ResponseWriter); err != nil { - beego.Error("Write Captcha Image Error:", err) - } -} - -// CreateCaptchaHTML template func for output html -func (c *Captcha) CreateCaptchaHTML() template.HTML { - value, err := c.CreateCaptcha() - if err != nil { - beego.Error("Create Captcha Error:", err) - return "" - } - - // create html - return template.HTML(fmt.Sprintf(``+ - ``+ - ``+ - ``, c.FieldIDName, value, c.URLPrefix, value, c.URLPrefix, value)) -} - -// CreateCaptcha create a new captcha id -func (c *Captcha) CreateCaptcha() (string, error) { - // generate captcha id - id := string(utils.RandomCreateBytes(15)) - - // get the captcha chars - chars := c.genRandChars() - - // save to store - if err := c.store.Put(c.key(id), chars, c.Expiration); err != nil { - return "", err - } - - return id, nil -} - -// VerifyReq verify from a request -func (c *Captcha) VerifyReq(req *http.Request) bool { - req.ParseForm() - return c.Verify(req.Form.Get(c.FieldIDName), req.Form.Get(c.FieldCaptchaName)) -} - -// Verify direct verify id and challenge string -func (c *Captcha) Verify(id string, challenge string) (success bool) { - if len(challenge) == 0 || len(id) == 0 { - return - } - - var chars []byte - - key := c.key(id) - - if v, ok := c.store.Get(key).([]byte); ok { - chars = v - } else { - return - } - - defer func() { - // finally remove it - c.store.Delete(key) - }() - - if len(chars) != len(challenge) { - return - } - // verify challenge - for i, c := range chars { - if c != challenge[i]-48 { - return - } - } - - return true -} - -// NewCaptcha create a new captcha.Captcha -func NewCaptcha(urlPrefix string, store cache.Cache) *Captcha { - cpt := &Captcha{} - cpt.store = store - cpt.FieldIDName = fieldIDName - cpt.FieldCaptchaName = fieldCaptchaName - cpt.ChallengeNums = challengeNums - cpt.Expiration = expiration - cpt.CachePrefix = cachePrefix - cpt.StdWidth = stdWidth - cpt.StdHeight = stdHeight - - if len(urlPrefix) == 0 { - urlPrefix = defaultURLPrefix - } - - if urlPrefix[len(urlPrefix)-1] != '/' { - urlPrefix += "/" - } - - cpt.URLPrefix = urlPrefix - - return cpt -} - -// NewWithFilter create a new captcha.Captcha and auto AddFilter for serve captacha image -// and add a template func for output html -func NewWithFilter(urlPrefix string, store cache.Cache) *Captcha { - cpt := NewCaptcha(urlPrefix, store) - - // create filter for serve captcha image - beego.InsertFilter(cpt.URLPrefix+"*", beego.BeforeRouter, cpt.Handler) - - // add to template func map - beego.AddFuncMap("create_captcha", cpt.CreateCaptchaHTML) - - return cpt -} diff --git a/vendor/github.com/astaxie/beego/utils/captcha/image.go b/vendor/github.com/astaxie/beego/utils/captcha/image.go deleted file mode 100644 index 1057192aa..000000000 --- a/vendor/github.com/astaxie/beego/utils/captcha/image.go +++ /dev/null @@ -1,498 +0,0 @@ -// Copyright 2014 beego Author. 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. - -package captcha - -import ( - "bytes" - "image" - "image/color" - "image/png" - "io" - "math" -) - -const ( - fontWidth = 11 - fontHeight = 18 - blackChar = 1 - - // Standard width and height of a captcha image. - stdWidth = 240 - stdHeight = 80 - // Maximum absolute skew factor of a single digit. - maxSkew = 0.7 - // Number of background circles. - circleCount = 20 -) - -var font = [][]byte{ - { // 0 - 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, - 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, - 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, - 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, - 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, - 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, - 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, - 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, - 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, - 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, - }, - { // 1 - 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, - 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, - 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, - 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, - 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, - 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, - 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - }, - { // 2 - 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, - 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, - 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, - 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, - 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, - 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, - 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, - 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, - 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, - 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, - 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - }, - { // 3 - 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, - 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, - 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, - 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, - 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, - 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, - 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, - }, - { // 4 - 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, - 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, - 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, - 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, - 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, - 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, - 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, - 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, - 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, - 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, - 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, - }, - { // 5 - 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, - 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, - 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, - 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, - 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, - 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, - }, - { // 6 - 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, - 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, - 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, - 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, - 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, - 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, - 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, - 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, - 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, - 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, - 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, - }, - { // 7 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, - 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, - 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, - 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, - 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, - 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, - 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, - 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, - 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, - 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, - 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, - }, - { // 8 - 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, - 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, - 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, - 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, - 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, - 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, - 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, - 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, - 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, - 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, - 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, - 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, - 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, - 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, - 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, - }, - { // 9 - 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, - 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, - 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, - 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, - 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, - 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, - 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, - 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, - 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, - 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, - 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, - 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, - }, -} - -// Image struct -type Image struct { - *image.Paletted - numWidth int - numHeight int - dotSize int -} - -var prng = &siprng{} - -// randIntn returns a pseudorandom non-negative int in range [0, n). -func randIntn(n int) int { - return prng.Intn(n) -} - -// randInt returns a pseudorandom int in range [from, to]. -func randInt(from, to int) int { - return prng.Intn(to+1-from) + from -} - -// randFloat returns a pseudorandom float64 in range [from, to]. -func randFloat(from, to float64) float64 { - return (to-from)*prng.Float64() + from -} - -func randomPalette() color.Palette { - p := make([]color.Color, circleCount+1) - // Transparent color. - p[0] = color.RGBA{0xFF, 0xFF, 0xFF, 0x00} - // Primary color. - prim := color.RGBA{ - uint8(randIntn(129)), - uint8(randIntn(129)), - uint8(randIntn(129)), - 0xFF, - } - p[1] = prim - // Circle colors. - for i := 2; i <= circleCount; i++ { - p[i] = randomBrightness(prim, 255) - } - return p -} - -// NewImage returns a new captcha image of the given width and height with the -// given digits, where each digit must be in range 0-9. -func NewImage(digits []byte, width, height int) *Image { - m := new(Image) - m.Paletted = image.NewPaletted(image.Rect(0, 0, width, height), randomPalette()) - m.calculateSizes(width, height, len(digits)) - // Randomly position captcha inside the image. - maxx := width - (m.numWidth+m.dotSize)*len(digits) - m.dotSize - maxy := height - m.numHeight - m.dotSize*2 - var border int - if width > height { - border = height / 5 - } else { - border = width / 5 - } - x := randInt(border, maxx-border) - y := randInt(border, maxy-border) - // Draw digits. - for _, n := range digits { - m.drawDigit(font[n], x, y) - x += m.numWidth + m.dotSize - } - // Draw strike-through line. - m.strikeThrough() - // Apply wave distortion. - m.distort(randFloat(5, 10), randFloat(100, 200)) - // Fill image with random circles. - m.fillWithCircles(circleCount, m.dotSize) - return m -} - -// encodedPNG encodes an image to PNG and returns -// the result as a byte slice. -func (m *Image) encodedPNG() []byte { - var buf bytes.Buffer - if err := png.Encode(&buf, m.Paletted); err != nil { - panic(err.Error()) - } - return buf.Bytes() -} - -// WriteTo writes captcha image in PNG format into the given writer. -func (m *Image) WriteTo(w io.Writer) (int64, error) { - n, err := w.Write(m.encodedPNG()) - return int64(n), err -} - -func (m *Image) calculateSizes(width, height, ncount int) { - // Goal: fit all digits inside the image. - var border int - if width > height { - border = height / 4 - } else { - border = width / 4 - } - // Convert everything to floats for calculations. - w := float64(width - border*2) - h := float64(height - border*2) - // fw takes into account 1-dot spacing between digits. - fw := float64(fontWidth + 1) - fh := float64(fontHeight) - nc := float64(ncount) - // Calculate the width of a single digit taking into account only the - // width of the image. - nw := w / nc - // Calculate the height of a digit from this width. - nh := nw * fh / fw - // Digit too high? - if nh > h { - // Fit digits based on height. - nh = h - nw = fw / fh * nh - } - // Calculate dot size. - m.dotSize = int(nh / fh) - // Save everything, making the actual width smaller by 1 dot to account - // for spacing between digits. - m.numWidth = int(nw) - m.dotSize - m.numHeight = int(nh) -} - -func (m *Image) drawHorizLine(fromX, toX, y int, colorIdx uint8) { - for x := fromX; x <= toX; x++ { - m.SetColorIndex(x, y, colorIdx) - } -} - -func (m *Image) drawCircle(x, y, radius int, colorIdx uint8) { - f := 1 - radius - dfx := 1 - dfy := -2 * radius - xo := 0 - yo := radius - - m.SetColorIndex(x, y+radius, colorIdx) - m.SetColorIndex(x, y-radius, colorIdx) - m.drawHorizLine(x-radius, x+radius, y, colorIdx) - - for xo < yo { - if f >= 0 { - yo-- - dfy += 2 - f += dfy - } - xo++ - dfx += 2 - f += dfx - m.drawHorizLine(x-xo, x+xo, y+yo, colorIdx) - m.drawHorizLine(x-xo, x+xo, y-yo, colorIdx) - m.drawHorizLine(x-yo, x+yo, y+xo, colorIdx) - m.drawHorizLine(x-yo, x+yo, y-xo, colorIdx) - } -} - -func (m *Image) fillWithCircles(n, maxradius int) { - maxx := m.Bounds().Max.X - maxy := m.Bounds().Max.Y - for i := 0; i < n; i++ { - colorIdx := uint8(randInt(1, circleCount-1)) - r := randInt(1, maxradius) - m.drawCircle(randInt(r, maxx-r), randInt(r, maxy-r), r, colorIdx) - } -} - -func (m *Image) strikeThrough() { - maxx := m.Bounds().Max.X - maxy := m.Bounds().Max.Y - y := randInt(maxy/3, maxy-maxy/3) - amplitude := randFloat(5, 20) - period := randFloat(80, 180) - dx := 2.0 * math.Pi / period - for x := 0; x < maxx; x++ { - xo := amplitude * math.Cos(float64(y)*dx) - yo := amplitude * math.Sin(float64(x)*dx) - for yn := 0; yn < m.dotSize; yn++ { - r := randInt(0, m.dotSize) - m.drawCircle(x+int(xo), y+int(yo)+(yn*m.dotSize), r/2, 1) - } - } -} - -func (m *Image) drawDigit(digit []byte, x, y int) { - skf := randFloat(-maxSkew, maxSkew) - xs := float64(x) - r := m.dotSize / 2 - y += randInt(-r, r) - for yo := 0; yo < fontHeight; yo++ { - for xo := 0; xo < fontWidth; xo++ { - if digit[yo*fontWidth+xo] != blackChar { - continue - } - m.drawCircle(x+xo*m.dotSize, y+yo*m.dotSize, r, 1) - } - xs += skf - x = int(xs) - } -} - -func (m *Image) distort(amplude float64, period float64) { - w := m.Bounds().Max.X - h := m.Bounds().Max.Y - - oldm := m.Paletted - newm := image.NewPaletted(image.Rect(0, 0, w, h), oldm.Palette) - - dx := 2.0 * math.Pi / period - for x := 0; x < w; x++ { - for y := 0; y < h; y++ { - xo := amplude * math.Sin(float64(y)*dx) - yo := amplude * math.Cos(float64(x)*dx) - newm.SetColorIndex(x, y, oldm.ColorIndexAt(x+int(xo), y+int(yo))) - } - } - m.Paletted = newm -} - -func randomBrightness(c color.RGBA, max uint8) color.RGBA { - minc := min3(c.R, c.G, c.B) - maxc := max3(c.R, c.G, c.B) - if maxc > max { - return c - } - n := randIntn(int(max-maxc)) - int(minc) - return color.RGBA{ - uint8(int(c.R) + n), - uint8(int(c.G) + n), - uint8(int(c.B) + n), - uint8(c.A), - } -} - -func min3(x, y, z uint8) (m uint8) { - m = x - if y < m { - m = y - } - if z < m { - m = z - } - return -} - -func max3(x, y, z uint8) (m uint8) { - m = x - if y > m { - m = y - } - if z > m { - m = z - } - return -} diff --git a/vendor/github.com/astaxie/beego/utils/captcha/siprng.go b/vendor/github.com/astaxie/beego/utils/captcha/siprng.go deleted file mode 100644 index 5e256cf93..000000000 --- a/vendor/github.com/astaxie/beego/utils/captcha/siprng.go +++ /dev/null @@ -1,277 +0,0 @@ -// Copyright 2014 beego Author. 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. - -package captcha - -import ( - "crypto/rand" - "encoding/binary" - "io" - "sync" -) - -// siprng is PRNG based on SipHash-2-4. -type siprng struct { - mu sync.Mutex - k0, k1, ctr uint64 -} - -// siphash implements SipHash-2-4, accepting a uint64 as a message. -func siphash(k0, k1, m uint64) uint64 { - // Initialization. - v0 := k0 ^ 0x736f6d6570736575 - v1 := k1 ^ 0x646f72616e646f6d - v2 := k0 ^ 0x6c7967656e657261 - v3 := k1 ^ 0x7465646279746573 - t := uint64(8) << 56 - - // Compression. - v3 ^= m - - // Round 1. - v0 += v1 - v1 = v1<<13 | v1>>(64-13) - v1 ^= v0 - v0 = v0<<32 | v0>>(64-32) - - v2 += v3 - v3 = v3<<16 | v3>>(64-16) - v3 ^= v2 - - v0 += v3 - v3 = v3<<21 | v3>>(64-21) - v3 ^= v0 - - v2 += v1 - v1 = v1<<17 | v1>>(64-17) - v1 ^= v2 - v2 = v2<<32 | v2>>(64-32) - - // Round 2. - v0 += v1 - v1 = v1<<13 | v1>>(64-13) - v1 ^= v0 - v0 = v0<<32 | v0>>(64-32) - - v2 += v3 - v3 = v3<<16 | v3>>(64-16) - v3 ^= v2 - - v0 += v3 - v3 = v3<<21 | v3>>(64-21) - v3 ^= v0 - - v2 += v1 - v1 = v1<<17 | v1>>(64-17) - v1 ^= v2 - v2 = v2<<32 | v2>>(64-32) - - v0 ^= m - - // Compress last block. - v3 ^= t - - // Round 1. - v0 += v1 - v1 = v1<<13 | v1>>(64-13) - v1 ^= v0 - v0 = v0<<32 | v0>>(64-32) - - v2 += v3 - v3 = v3<<16 | v3>>(64-16) - v3 ^= v2 - - v0 += v3 - v3 = v3<<21 | v3>>(64-21) - v3 ^= v0 - - v2 += v1 - v1 = v1<<17 | v1>>(64-17) - v1 ^= v2 - v2 = v2<<32 | v2>>(64-32) - - // Round 2. - v0 += v1 - v1 = v1<<13 | v1>>(64-13) - v1 ^= v0 - v0 = v0<<32 | v0>>(64-32) - - v2 += v3 - v3 = v3<<16 | v3>>(64-16) - v3 ^= v2 - - v0 += v3 - v3 = v3<<21 | v3>>(64-21) - v3 ^= v0 - - v2 += v1 - v1 = v1<<17 | v1>>(64-17) - v1 ^= v2 - v2 = v2<<32 | v2>>(64-32) - - v0 ^= t - - // Finalization. - v2 ^= 0xff - - // Round 1. - v0 += v1 - v1 = v1<<13 | v1>>(64-13) - v1 ^= v0 - v0 = v0<<32 | v0>>(64-32) - - v2 += v3 - v3 = v3<<16 | v3>>(64-16) - v3 ^= v2 - - v0 += v3 - v3 = v3<<21 | v3>>(64-21) - v3 ^= v0 - - v2 += v1 - v1 = v1<<17 | v1>>(64-17) - v1 ^= v2 - v2 = v2<<32 | v2>>(64-32) - - // Round 2. - v0 += v1 - v1 = v1<<13 | v1>>(64-13) - v1 ^= v0 - v0 = v0<<32 | v0>>(64-32) - - v2 += v3 - v3 = v3<<16 | v3>>(64-16) - v3 ^= v2 - - v0 += v3 - v3 = v3<<21 | v3>>(64-21) - v3 ^= v0 - - v2 += v1 - v1 = v1<<17 | v1>>(64-17) - v1 ^= v2 - v2 = v2<<32 | v2>>(64-32) - - // Round 3. - v0 += v1 - v1 = v1<<13 | v1>>(64-13) - v1 ^= v0 - v0 = v0<<32 | v0>>(64-32) - - v2 += v3 - v3 = v3<<16 | v3>>(64-16) - v3 ^= v2 - - v0 += v3 - v3 = v3<<21 | v3>>(64-21) - v3 ^= v0 - - v2 += v1 - v1 = v1<<17 | v1>>(64-17) - v1 ^= v2 - v2 = v2<<32 | v2>>(64-32) - - // Round 4. - v0 += v1 - v1 = v1<<13 | v1>>(64-13) - v1 ^= v0 - v0 = v0<<32 | v0>>(64-32) - - v2 += v3 - v3 = v3<<16 | v3>>(64-16) - v3 ^= v2 - - v0 += v3 - v3 = v3<<21 | v3>>(64-21) - v3 ^= v0 - - v2 += v1 - v1 = v1<<17 | v1>>(64-17) - v1 ^= v2 - v2 = v2<<32 | v2>>(64-32) - - return v0 ^ v1 ^ v2 ^ v3 -} - -// rekey sets a new PRNG key, which is read from crypto/rand. -func (p *siprng) rekey() { - var k [16]byte - if _, err := io.ReadFull(rand.Reader, k[:]); err != nil { - panic(err.Error()) - } - p.k0 = binary.LittleEndian.Uint64(k[0:8]) - p.k1 = binary.LittleEndian.Uint64(k[8:16]) - p.ctr = 1 -} - -// Uint64 returns a new pseudorandom uint64. -// It rekeys PRNG on the first call and every 64 MB of generated data. -func (p *siprng) Uint64() uint64 { - p.mu.Lock() - if p.ctr == 0 || p.ctr > 8*1024*1024 { - p.rekey() - } - v := siphash(p.k0, p.k1, p.ctr) - p.ctr++ - p.mu.Unlock() - return v -} - -func (p *siprng) Int63() int64 { - return int64(p.Uint64() & 0x7fffffffffffffff) -} - -func (p *siprng) Uint32() uint32 { - return uint32(p.Uint64()) -} - -func (p *siprng) Int31() int32 { - return int32(p.Uint32() & 0x7fffffff) -} - -func (p *siprng) Intn(n int) int { - if n <= 0 { - panic("invalid argument to Intn") - } - if n <= 1<<31-1 { - return int(p.Int31n(int32(n))) - } - return int(p.Int63n(int64(n))) -} - -func (p *siprng) Int63n(n int64) int64 { - if n <= 0 { - panic("invalid argument to Int63n") - } - max := int64((1 << 63) - 1 - (1<<63)%uint64(n)) - v := p.Int63() - for v > max { - v = p.Int63() - } - return v % n -} - -func (p *siprng) Int31n(n int32) int32 { - if n <= 0 { - panic("invalid argument to Int31n") - } - max := int32((1 << 31) - 1 - (1<<31)%uint32(n)) - v := p.Int31() - for v > max { - v = p.Int31() - } - return v % n -} - -func (p *siprng) Float64() float64 { return float64(p.Int63()) / (1 << 63) } diff --git a/vendor/github.com/astaxie/beego/utils/mail.go b/vendor/github.com/astaxie/beego/utils/mail.go index 1d80a0391..10555a0a3 100644 --- a/vendor/github.com/astaxie/beego/utils/mail.go +++ b/vendor/github.com/astaxie/beego/utils/mail.go @@ -31,10 +31,13 @@ import ( "path/filepath" "strconv" "strings" + "sync" ) const ( maxLineLength = 76 + + upperhex = "0123456789ABCDEF" ) // Email is the type used for email messages @@ -74,9 +77,6 @@ func NewEMail(config string) *Email { if err != nil { return nil } - if e.From == "" { - e.From = e.Username - } return e } @@ -228,14 +228,21 @@ func (e *Email) Send() error { to := make([]string, 0, len(e.To)+len(e.Cc)+len(e.Bcc)) to = append(append(append(to, e.To...), e.Cc...), e.Bcc...) // Check to make sure there is at least one recipient and one "From" address - if e.From == "" || len(to) == 0 { - return errors.New("Must specify at least one From address and one To address") + if len(to) == 0 { + return errors.New("Must specify at least one To address") } - from, err := mail.ParseAddress(e.From) + + from, err := mail.ParseAddress(e.Username) if err != nil { return err } - e.From = from.String() + + if len(e.From) == 0 { + e.From = e.Username + } + // use mail's RFC 2047 to encode any string + e.Subject = qEncode("utf-8", e.Subject) + raw, err := e.Bytes() if err != nil { return err @@ -342,3 +349,73 @@ func base64Wrap(w io.Writer, b []byte) { w.Write(out) } } + +// Encode returns the encoded-word form of s. If s is ASCII without special +// characters, it is returned unchanged. The provided charset is the IANA +// charset name of s. It is case insensitive. +// RFC 2047 encoded-word +func qEncode(charset, s string) string { + if !needsEncoding(s) { + return s + } + return encodeWord(charset, s) +} + +func needsEncoding(s string) bool { + for _, b := range s { + if (b < ' ' || b > '~') && b != '\t' { + return true + } + } + return false +} + +// encodeWord encodes a string into an encoded-word. +func encodeWord(charset, s string) string { + buf := getBuffer() + + buf.WriteString("=?") + buf.WriteString(charset) + buf.WriteByte('?') + buf.WriteByte('q') + buf.WriteByte('?') + + enc := make([]byte, 3) + for i := 0; i < len(s); i++ { + b := s[i] + switch { + case b == ' ': + buf.WriteByte('_') + case b <= '~' && b >= '!' && b != '=' && b != '?' && b != '_': + buf.WriteByte(b) + default: + enc[0] = '=' + enc[1] = upperhex[b>>4] + enc[2] = upperhex[b&0x0f] + buf.Write(enc) + } + } + buf.WriteString("?=") + + es := buf.String() + putBuffer(buf) + return es +} + +var bufPool = sync.Pool{ + New: func() interface{} { + return new(bytes.Buffer) + }, +} + +func getBuffer() *bytes.Buffer { + return bufPool.Get().(*bytes.Buffer) +} + +func putBuffer(buf *bytes.Buffer) { + if buf.Len() > 1024 { + return + } + buf.Reset() + bufPool.Put(buf) +} diff --git a/vendor/github.com/astaxie/beego/utils/pagination/controller.go b/vendor/github.com/astaxie/beego/utils/pagination/controller.go deleted file mode 100644 index 1d99cac5c..000000000 --- a/vendor/github.com/astaxie/beego/utils/pagination/controller.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2014 beego Author. 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. - -package pagination - -import ( - "github.com/astaxie/beego/context" -) - -// SetPaginator Instantiates a Paginator and assigns it to context.Input.Data["paginator"]. -func SetPaginator(context *context.Context, per int, nums int64) (paginator *Paginator) { - paginator = NewPaginator(context.Request, per, nums) - context.Input.SetData("paginator", &paginator) - return -} diff --git a/vendor/github.com/astaxie/beego/utils/pagination/doc.go b/vendor/github.com/astaxie/beego/utils/pagination/doc.go deleted file mode 100644 index 9abc6d782..000000000 --- a/vendor/github.com/astaxie/beego/utils/pagination/doc.go +++ /dev/null @@ -1,58 +0,0 @@ -/* -Package pagination provides utilities to setup a paginator within the -context of a http request. - -Usage - -In your beego.Controller: - - package controllers - - import "github.com/astaxie/beego/utils/pagination" - - type PostsController struct { - beego.Controller - } - - func (this *PostsController) ListAllPosts() { - // sets this.Data["paginator"] with the current offset (from the url query param) - postsPerPage := 20 - paginator := pagination.SetPaginator(this.Ctx, postsPerPage, CountPosts()) - - // fetch the next 20 posts - this.Data["posts"] = ListPostsByOffsetAndLimit(paginator.Offset(), postsPerPage) - } - - -In your view templates: - - {{if .paginator.HasPages}} - - {{end}} - -See also - -http://beego.me/docs/mvc/view/page.md - -*/ -package pagination diff --git a/vendor/github.com/astaxie/beego/utils/pagination/paginator.go b/vendor/github.com/astaxie/beego/utils/pagination/paginator.go deleted file mode 100644 index c6db31e08..000000000 --- a/vendor/github.com/astaxie/beego/utils/pagination/paginator.go +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright 2014 beego Author. 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. - -package pagination - -import ( - "math" - "net/http" - "net/url" - "strconv" -) - -// Paginator within the state of a http request. -type Paginator struct { - Request *http.Request - PerPageNums int - MaxPages int - - nums int64 - pageRange []int - pageNums int - page int -} - -// PageNums Returns the total number of pages. -func (p *Paginator) PageNums() int { - if p.pageNums != 0 { - return p.pageNums - } - pageNums := math.Ceil(float64(p.nums) / float64(p.PerPageNums)) - if p.MaxPages > 0 { - pageNums = math.Min(pageNums, float64(p.MaxPages)) - } - p.pageNums = int(pageNums) - return p.pageNums -} - -// Nums Returns the total number of items (e.g. from doing SQL count). -func (p *Paginator) Nums() int64 { - return p.nums -} - -// SetNums Sets the total number of items. -func (p *Paginator) SetNums(nums interface{}) { - p.nums, _ = toInt64(nums) -} - -// Page Returns the current page. -func (p *Paginator) Page() int { - if p.page != 0 { - return p.page - } - if p.Request.Form == nil { - p.Request.ParseForm() - } - p.page, _ = strconv.Atoi(p.Request.Form.Get("p")) - if p.page > p.PageNums() { - p.page = p.PageNums() - } - if p.page <= 0 { - p.page = 1 - } - return p.page -} - -// Pages Returns a list of all pages. -// -// Usage (in a view template): -// -// {{range $index, $page := .paginator.Pages}} -// -// {{$page}} -// -// {{end}} -func (p *Paginator) Pages() []int { - if p.pageRange == nil && p.nums > 0 { - var pages []int - pageNums := p.PageNums() - page := p.Page() - switch { - case page >= pageNums-4 && pageNums > 9: - start := pageNums - 9 + 1 - pages = make([]int, 9) - for i := range pages { - pages[i] = start + i - } - case page >= 5 && pageNums > 9: - start := page - 5 + 1 - pages = make([]int, int(math.Min(9, float64(page+4+1)))) - for i := range pages { - pages[i] = start + i - } - default: - pages = make([]int, int(math.Min(9, float64(pageNums)))) - for i := range pages { - pages[i] = i + 1 - } - } - p.pageRange = pages - } - return p.pageRange -} - -// PageLink Returns URL for a given page index. -func (p *Paginator) PageLink(page int) string { - link, _ := url.ParseRequestURI(p.Request.URL.String()) - values := link.Query() - if page == 1 { - values.Del("p") - } else { - values.Set("p", strconv.Itoa(page)) - } - link.RawQuery = values.Encode() - return link.String() -} - -// PageLinkPrev Returns URL to the previous page. -func (p *Paginator) PageLinkPrev() (link string) { - if p.HasPrev() { - link = p.PageLink(p.Page() - 1) - } - return -} - -// PageLinkNext Returns URL to the next page. -func (p *Paginator) PageLinkNext() (link string) { - if p.HasNext() { - link = p.PageLink(p.Page() + 1) - } - return -} - -// PageLinkFirst Returns URL to the first page. -func (p *Paginator) PageLinkFirst() (link string) { - return p.PageLink(1) -} - -// PageLinkLast Returns URL to the last page. -func (p *Paginator) PageLinkLast() (link string) { - return p.PageLink(p.PageNums()) -} - -// HasPrev Returns true if the current page has a predecessor. -func (p *Paginator) HasPrev() bool { - return p.Page() > 1 -} - -// HasNext Returns true if the current page has a successor. -func (p *Paginator) HasNext() bool { - return p.Page() < p.PageNums() -} - -// IsActive Returns true if the given page index points to the current page. -func (p *Paginator) IsActive(page int) bool { - return p.Page() == page -} - -// Offset Returns the current offset. -func (p *Paginator) Offset() int { - return (p.Page() - 1) * p.PerPageNums -} - -// HasPages Returns true if there is more than one page. -func (p *Paginator) HasPages() bool { - return p.PageNums() > 1 -} - -// NewPaginator Instantiates a paginator struct for the current http request. -func NewPaginator(req *http.Request, per int, nums interface{}) *Paginator { - p := Paginator{} - p.Request = req - if per <= 0 { - per = 10 - } - p.PerPageNums = per - p.SetNums(nums) - return &p -} diff --git a/vendor/github.com/astaxie/beego/utils/pagination/utils.go b/vendor/github.com/astaxie/beego/utils/pagination/utils.go deleted file mode 100644 index 686e68b0d..000000000 --- a/vendor/github.com/astaxie/beego/utils/pagination/utils.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2014 beego Author. 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. - -package pagination - -import ( - "fmt" - "reflect" -) - -// ToInt64 convert any numeric value to int64 -func toInt64(value interface{}) (d int64, err error) { - val := reflect.ValueOf(value) - switch value.(type) { - case int, int8, int16, int32, int64: - d = val.Int() - case uint, uint8, uint16, uint32, uint64: - d = int64(val.Uint()) - default: - err = fmt.Errorf("ToInt64 need numeric not `%T`", value) - } - return -} diff --git a/vendor/github.com/astaxie/beego/validation/validators.go b/vendor/github.com/astaxie/beego/validation/validators.go index ebff2191a..9b04c5cef 100644 --- a/vendor/github.com/astaxie/beego/validation/validators.go +++ b/vendor/github.com/astaxie/beego/validation/validators.go @@ -588,7 +588,7 @@ func (b Base64) GetLimitValue() interface{} { } // just for chinese mobile phone number -var mobilePattern = regexp.MustCompile("^((\\+86)|(86))?(1(([35][0-9])|[8][0-9]|[7][0679]|[4][579]))\\d{8}$") +var mobilePattern = regexp.MustCompile("^((\\+86)|(86))?(1(([35][0-9])|[8][0-9]|[7][06789]|[4][579]))\\d{8}$") // Mobile check struct type Mobile struct { diff --git a/vendor/vendor.json b/vendor/vendor.json new file mode 100644 index 000000000..66393f61c --- /dev/null +++ b/vendor/vendor.json @@ -0,0 +1,89 @@ +{ + "comment": "", + "ignore": "test", + "package": [ + { + "checksumSHA1": "G0MBhApkgOH9++JlIA8FPfU7j9Y=", + "path": "github.com/astaxie/beego", + "revision": "1aeb3d90512734def678c7aa9f612fe6f659e6b5", + "revisionTime": "2016-03-11T03:35:24Z", + "version": "v1.6.1", + "versionExact": "v1.6.1" + }, + { + "checksumSHA1": "gSSu0SO0GZIZrBgQpybXeNMWSJs=", + "path": "github.com/astaxie/beego/cache", + "revision": "88c5dfa6ead42e624c2e7d9e04eab6cb2d07412a", + "revisionTime": "2016-03-23T09:05:40Z" + }, + { + "checksumSHA1": "raJhf500jq7AzIGIclRQBo0B5+s=", + "path": "github.com/astaxie/beego/config", + "revision": "1aeb3d90512734def678c7aa9f612fe6f659e6b5", + "revisionTime": "2016-03-11T03:35:24Z", + "version": "v1.6.1", + "versionExact": "v1.6.1" + }, + { + "checksumSHA1": "52Cnm/rmogm6vPa2jW9D6SuKm3g=", + "path": "github.com/astaxie/beego/context", + "revision": "1aeb3d90512734def678c7aa9f612fe6f659e6b5", + "revisionTime": "2016-03-11T03:35:24Z", + "version": "v1.6.1", + "versionExact": "v1.6.1" + }, + { + "checksumSHA1": "PDNn16w89zWODshT9zlPzSmWZFA=", + "path": "github.com/astaxie/beego/grace", + "revision": "1aeb3d90512734def678c7aa9f612fe6f659e6b5", + "revisionTime": "2016-03-11T03:35:24Z", + "version": "v1.6.1", + "versionExact": "v1.6.1" + }, + { + "checksumSHA1": "xxtx4xJs8UcrvaXNQ9A5QM9+oFg=", + "path": "github.com/astaxie/beego/logs", + "revision": "1aeb3d90512734def678c7aa9f612fe6f659e6b5", + "revisionTime": "2016-03-11T03:35:24Z", + "version": "v1.6.1", + "versionExact": "v1.6.1" + }, + { + "checksumSHA1": "4X7wg7DBwdaRW4W2dBf3U0+fTHc=", + "path": "github.com/astaxie/beego/orm", + "revision": "88c5dfa6ead42e624c2e7d9e04eab6cb2d07412a", + "revisionTime": "2016-03-23T09:05:40Z" + }, + { + "checksumSHA1": "Zeh58fFq41Ym/YNH47O6gdm1Mh8=", + "path": "github.com/astaxie/beego/session", + "revision": "1aeb3d90512734def678c7aa9f612fe6f659e6b5", + "revisionTime": "2016-03-11T03:35:24Z", + "version": "v1.6.1", + "versionExact": "v1.6.1" + }, + { + "checksumSHA1": "Mu2lA5Jq0tRk9MjhWllyzcJD6KU=", + "path": "github.com/astaxie/beego/toolbox", + "revision": "1aeb3d90512734def678c7aa9f612fe6f659e6b5", + "revisionTime": "2016-03-11T03:35:24Z", + "version": "v1.6.1", + "versionExact": "v1.6.1" + }, + { + "checksumSHA1": "x90ZIgNp0dy/saCS+4tBR3gdQ6g=", + "path": "github.com/astaxie/beego/utils", + "revision": "1aeb3d90512734def678c7aa9f612fe6f659e6b5", + "revisionTime": "2016-03-11T03:35:24Z", + "version": "v1.6.1", + "versionExact": "v1.6.1" + }, + { + "checksumSHA1": "pt/trdXvFa8AvhR67iMhfvqNfDU=", + "path": "github.com/astaxie/beego/validation", + "revision": "88c5dfa6ead42e624c2e7d9e04eab6cb2d07412a", + "revisionTime": "2016-03-23T09:05:40Z" + } + ], + "rootPath": "github.com/vmware/harbor" +} diff --git a/views/account-settings.htm b/views/account-settings.htm new file mode 100644 index 000000000..7389ca51a --- /dev/null +++ b/views/account-settings.htm @@ -0,0 +1,83 @@ + +
+ +
+
+
+

// 'account_setting' | tr //

+
+
+
+ +
+ +
+
+
+ +
+ +
+ // 'email_is_required' | tr // + // 'email_content_illegal' | tr // +
+
+
+ * +
+
+
+ +
+ +
+ // 'full_name_is_required' | tr // + // 'full_name_contains_illegal_chars' | tr // + // 'full_name_is_too_long' | tr // +
+
+
+ * +
+
+
+ +
+ +
+ // 'comment_is_too_long' | tr // +
+
+
+ + +
+
+ + +
+
+
+ // vm.errorMessage | tr // +
+
+
+
+
+
+
diff --git a/views/admin-options.htm b/views/admin-options.htm new file mode 100644 index 000000000..68c225e0b --- /dev/null +++ b/views/admin-options.htm @@ -0,0 +1,32 @@ + +
+ +
+
+
+ + + +
+
+
+
\ No newline at end of file diff --git a/views/change-password.htm b/views/change-password.htm new file mode 100644 index 000000000..6526fb7e9 --- /dev/null +++ b/views/change-password.htm @@ -0,0 +1,77 @@ + +
+ +
+
+
+

// 'change_password' | tr //

+
+
+
+
+ +
+ +
+ // 'old_password_is_required' | tr // +
+
+
+ * +
+
+
+ +
+ +
+ // 'password_is_required' | tr // + // 'password_is_invalid' | tr // +
+

// 'password_desc' | tr //

+
+
+ * +
+
+
+ +
+ +
+ // 'password_does_not_match' | tr // +
+
+
+ * +
+
+
+
+
+ + +
+
+
+ // vm.errorMessage | tr // +
+
+
+
+
+
+
diff --git a/views/change-password.tpl b/views/change-password.tpl deleted file mode 100644 index 9dfa50494..000000000 --- a/views/change-password.tpl +++ /dev/null @@ -1,50 +0,0 @@ - -
-
-
- -
- -
- - - -
-
- - - -
{{i18n .Lang "password_description"}}
-
-
- - - -
{{i18n .Lang "password_description"}}
-
-
-
- -
-
-
-
-
-
- - \ No newline at end of file diff --git a/views/dashboard.htm b/views/dashboard.htm new file mode 100644 index 000000000..3affdcea3 --- /dev/null +++ b/views/dashboard.htm @@ -0,0 +1,43 @@ + +
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
+
+
+ + +
+
+
+
+
\ No newline at end of file diff --git a/views/forgot-password.htm b/views/forgot-password.htm new file mode 100644 index 000000000..389c981b1 --- /dev/null +++ b/views/forgot-password.htm @@ -0,0 +1,52 @@ + +
+ +
+
+
+

// 'forgot_password' | tr //

+
+
+
+
+ +
+ +
+
+ // 'email_is_required' | tr // + // 'email_content_illegal' | tr // +
+ // vm.errorMessage | tr // +
+

// 'forgot_password_description' | tr //

+
+
+ * +
+
+
+
+ +
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/views/forgot-password.tpl b/views/forgot-password.tpl deleted file mode 100644 index 0a2738479..000000000 --- a/views/forgot-password.tpl +++ /dev/null @@ -1,40 +0,0 @@ - -
-
-
- -
-
- -
- - - -
{{i18n .Lang "forgot_password_description"}}
-
-
-
- -
-
-
-
-
-
- - \ No newline at end of file diff --git a/views/index.htm b/views/index.htm new file mode 100644 index 000000000..9659b01d2 --- /dev/null +++ b/views/index.htm @@ -0,0 +1,74 @@ + +
+ +
+
+
+
+
+
+   

// 'login_now' | tr //

+
+ +
+
+
+
+
+ +
+
+
+ Step 1 +
// 'icon_label_1' | tr //
+
+
+ Step 2 +
// 'icon_label_2' | tr //
+
+
+ Step 3 +
// 'icon_label_3' | tr //
+
+
+
+
+
+
+
+
+
+
+
+
+ +

+ // 'index_desc' | tr // +

+ +
+
+
+
+
+ +
+
+
+
+
\ No newline at end of file diff --git a/views/index.tpl b/views/index.tpl deleted file mode 100644 index dec019fb1..000000000 --- a/views/index.tpl +++ /dev/null @@ -1,37 +0,0 @@ - - -
-
- Harbor's Logo -

{{i18n .Lang "index_title"}}

-
-
- -
- -
-
-

{{i18n .Lang "index_desc"}}

-

{{i18n .Lang "index_desc_0"}}

-

{{i18n .Lang "index_desc_1"}}

-

{{i18n .Lang "index_desc_2"}}

-

{{i18n .Lang "index_desc_3"}}

-

{{i18n .Lang "index_desc_4"}}

-

{{i18n .Lang "index_desc_5"}}

-
-
-
- \ No newline at end of file diff --git a/views/item-detail.tpl b/views/item-detail.tpl deleted file mode 100644 index cce2c2aa3..000000000 --- a/views/item-detail.tpl +++ /dev/null @@ -1,222 +0,0 @@ - -
- - -
-
- -
- - - - - - - - - -
-
-
-
- -
-
{{i18n .Lang "repo_name"}}:
- - - - -
-
-
-

-

- -
-
-
-
-
-
-
-
- -
-
{{i18n .Lang "username"}}:
- - - - -
-
-
- -
-

-

- - - - - - - - - - -
{{i18n .Lang "username"}}{{i18n .Lang "role"}}{{i18n .Lang "operation"}}
-
-
-
-
-
- -
-
{{i18n .Lang "username"}}:
- - - - -
-
-
-
- -
-
-

-

- -
- -
-
{{i18n .Lang "operation"}}:
- - {{i18n .Lang "all"}} - Create - Pull - Push - Delete - {{i18n .Lang "others"}}: - - -
-
-

-
- -
-
{{i18n .Lang "start_date"}}:
-
- - - - -
-
-
-
-
-
{{i18n .Lang "end_date"}}:
-
- - - - -
-
-
- -
-
- - - - - - - - - - - - -
{{i18n .Lang "username"}}{{i18n .Lang "repo_name"}}{{i18n .Lang "repo_tag"}}{{i18n .Lang "operation"}}{{i18n .Lang "timestamp"}}
-
- -
-
-
-
-
- -
- \ No newline at end of file diff --git a/views/layout.htm b/views/layout.htm new file mode 100644 index 000000000..2aa8ad4b5 --- /dev/null +++ b/views/layout.htm @@ -0,0 +1,27 @@ + + + + + {{.HeaderInclude}} + {{.Title}} + + + {{.HeaderContent}} + {{.LayoutContent}} + {{.FooterContent}} + {{.FooterInclude}} + + \ No newline at end of file diff --git a/views/navigation-detail.htm b/views/navigation-detail.htm new file mode 100644 index 000000000..9fc68cfad --- /dev/null +++ b/views/navigation-detail.htm @@ -0,0 +1,22 @@ + + \ No newline at end of file diff --git a/views/navigation-header.htm b/views/navigation-header.htm new file mode 100644 index 000000000..5a4e9bf08 --- /dev/null +++ b/views/navigation-header.htm @@ -0,0 +1,23 @@ + +{{ if eq .HasLoggedIn true }} + +{{ end }} \ No newline at end of file diff --git a/views/optional-menu.htm b/views/optional-menu.htm new file mode 100644 index 000000000..6950fe206 --- /dev/null +++ b/views/optional-menu.htm @@ -0,0 +1,44 @@ + +{{if eq .HasLoggedIn true }} + +{{ else }} + +{{ end }} \ No newline at end of file diff --git a/views/project.htm b/views/project.htm new file mode 100644 index 000000000..d5bfb3122 --- /dev/null +++ b/views/project.htm @@ -0,0 +1,75 @@ + +
+ +
+
+
+
+ +
+
+
+ + + + +
+ + +
+
+ +
+
+ + + + + + + + +
// 'project_name' | tr //// 'repositories' | tr //// 'role' | tr //// 'creation_time' | tr //// 'publicity' | tr //
+
+
+ + + + + + + + + + + + + +

// 'no_projects_add_new_project' | tr //

//p.name////p.repo_count////vm.getProjectRole(p.current_user_role_id)////p.creation_time | dateL : 'YYYY-MM-DD HH:mm:ss'//
+
+
+
//vm.projects ? vm.projects.length : 0// // 'items' | tr //
+
+
+
+
+
diff --git a/views/project.tpl b/views/project.tpl deleted file mode 100644 index 88783208a..000000000 --- a/views/project.tpl +++ /dev/null @@ -1,115 +0,0 @@ - -
- -
- - -
-
-
- -
-
{{i18n .Lang "project_name"}}:
- - - - -
- -
-
- - - - - - - - - - -
{{i18n .Lang "project_name"}}{{i18n .Lang "creation_time"}}{{i18n .Lang "publicity"}}
-
-
- -
-
-
- -
- - diff --git a/views/register.tpl b/views/register.tpl deleted file mode 100644 index dee0db35b..000000000 --- a/views/register.tpl +++ /dev/null @@ -1,83 +0,0 @@ - -
-
-
- -
- -
- -

*

- - -
{{i18n .Lang "username_description"}}
-
-
- -

*

- - -
{{i18n .Lang "email_description"}}
-
-
- -

*

- - -
{{i18n .Lang "full_name_description"}}
-
-
- -

*

- - -
{{i18n .Lang "password_description"}}
-
-
- -

*

- - -
{{i18n .Lang "password_description"}}
-
-
- - - -
-
-
- -
-
-
-
-
-
- - \ No newline at end of file diff --git a/views/repository.htm b/views/repository.htm new file mode 100644 index 000000000..5248b7430 --- /dev/null +++ b/views/repository.htm @@ -0,0 +1,47 @@ + +
+ +
+
+
+
+ +
+ + + + +
+ + +
+ + + + + +
+
+
+
+
+
+ \ No newline at end of file diff --git a/views/reset-password-mail.tpl b/views/reset-password-mail.tpl index 832000983..019512024 100644 --- a/views/reset-password-mail.tpl +++ b/views/reset-password-mail.tpl @@ -14,8 +14,8 @@ --> - -

{{.Hint}}:

- {{.URL}}/resetPassword?reset_uuid={{.UUID}} - + +

{{.Hint}}:

+ {{.URL}}/reset_password?reset_uuid={{.UUID}} + \ No newline at end of file diff --git a/views/reset-password.htm b/views/reset-password.htm new file mode 100644 index 000000000..3da84e189 --- /dev/null +++ b/views/reset-password.htm @@ -0,0 +1,64 @@ + +
+ +
+
+
+

// 'reset_password' | tr //

+
+
+
+
+ +
+ +
+ // 'password_is_required' | tr // + // 'password_is_invalid' | tr // +
+

// 'password_desc' | tr //

+
+
+ * +
+
+
+ +
+ +
+ // 'password_does_not_match' | tr // +
+
+
+ * +
+
+
+
+ + +
+
+
+ // vm.errorMessage // +
+
+
+
+
+
+
\ No newline at end of file diff --git a/views/reset-password.tpl b/views/reset-password.tpl deleted file mode 100644 index e98a5c27b..000000000 --- a/views/reset-password.tpl +++ /dev/null @@ -1,46 +0,0 @@ - -
- -
-
- -
- -
- - - -
{{i18n .Lang "password_description"}}
-
-
- - - -
{{i18n .Lang "password_description"}}
-
-
-
- -
-
-
-
-
-
- - \ No newline at end of file diff --git a/views/search.htm b/views/search.htm new file mode 100644 index 000000000..7676c4b4e --- /dev/null +++ b/views/search.htm @@ -0,0 +1,40 @@ + +
+ +
+
+
+

// 'search_result' | tr //

+
+
+ +
+ +
+ + +
+
+
+
+
+
\ No newline at end of file diff --git a/views/search.tpl b/views/search.tpl deleted file mode 100644 index e417eec52..000000000 --- a/views/search.tpl +++ /dev/null @@ -1,32 +0,0 @@ - - -
- -
-
{{i18n .Lang "projects"}}
-
-
-
-
-
{{i18n .Lang "repositories"}}
-
-
-
-
- \ No newline at end of file diff --git a/views/sections/footer-content.htm b/views/sections/footer-content.htm new file mode 100644 index 000000000..651a1e08e --- /dev/null +++ b/views/sections/footer-content.htm @@ -0,0 +1,18 @@ + + + diff --git a/views/sections/footer-include.htm b/views/sections/footer-include.htm new file mode 100644 index 000000000..e97a89e74 --- /dev/null +++ b/views/sections/footer-include.htm @@ -0,0 +1,14 @@ + diff --git a/views/sections/header-content.htm b/views/sections/header-content.htm new file mode 100644 index 000000000..7e43e78bf --- /dev/null +++ b/views/sections/header-content.htm @@ -0,0 +1,38 @@ + + \ No newline at end of file diff --git a/views/sections/header-include.htm b/views/sections/header-include.htm new file mode 100644 index 000000000..5e7852aad --- /dev/null +++ b/views/sections/header-include.htm @@ -0,0 +1,260 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +{{ if eq .Lang "zh-CN" }} + +{{ else if eq .Lang "en-US"}} + +{{ end }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/views/segment/header-content.tpl b/views/segment/header-content.tpl deleted file mode 100644 index e112f4ffd..000000000 --- a/views/segment/header-content.tpl +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - diff --git a/views/segment/header-include.tpl b/views/segment/header-include.tpl deleted file mode 100644 index 529d4e716..000000000 --- a/views/segment/header-include.tpl +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/views/segment/modal-dialog.tpl b/views/segment/modal-dialog.tpl deleted file mode 100644 index 7f3da1215..000000000 --- a/views/segment/modal-dialog.tpl +++ /dev/null @@ -1,38 +0,0 @@ - - - - \ No newline at end of file diff --git a/views/sign-in.htm b/views/sign-in.htm new file mode 100644 index 000000000..a4bd94975 --- /dev/null +++ b/views/sign-in.htm @@ -0,0 +1,65 @@ + +{{ if eq .HasLoggedIn true }} +
+

// 'welcome' | tr //

+ +

+

+
+{{ else }} +
+
+
+ +
+
+ // 'username_is_required' | tr // +
+
+
+
+
+
+ +
+
+ // 'password_is_required' | tr // +
+ // vm.errorMessage | tr // +
+
+
+
+
+
+ + {{ if eq .AuthMode "db_auth" }} + + {{ end }} +
+
+
+ {{ if eq .AuthMode "db_auth" }} + + {{ end }} +
+{{ end }} \ No newline at end of file diff --git a/views/sign-in.tpl b/views/sign-in.tpl deleted file mode 100644 index 86723ad01..000000000 --- a/views/sign-in.tpl +++ /dev/null @@ -1,40 +0,0 @@ - -
- -
- - \ No newline at end of file diff --git a/views/sign-up.htm b/views/sign-up.htm new file mode 100644 index 000000000..4bae1a5d0 --- /dev/null +++ b/views/sign-up.htm @@ -0,0 +1,126 @@ + +
+ +
+
+
+

+ {{ if eq .AddNew true }} + // 'add_new_title' | tr // + {{ else }} + // 'sign_up' | tr // + {{ end }} +

+
+
+
+
+ +
+ +
+ // 'username_is_required' | tr // + // 'username_is_too_long' | tr // + // 'username_contains_illegal_chars' | tr // + // 'username_has_been_taken' | tr // +
+
+
+ * +
+
+
+ +
+ +
+ // 'email_is_required' | tr // + // 'email_content_illegal' | tr // + // 'email_has_been_taken' | tr // + // 'email_is_too_long' | tr // +
+

// 'email_desc' | tr //

+
+
+ * +
+
+
+ +
+ +
+ // 'full_name_is_required' | tr // + // 'full_name_contains_illegal_chars' | tr // + // 'full_name_is_too_long' | tr // +
+

// 'full_name_desc' | tr //

+
+
+ * +
+
+
+ +
+ +
+ // 'password_is_required' | tr // + // 'password_is_invalid' | tr // +
+

// 'password_desc' | tr //

+
+
+ * +
+
+
+ +
+ +
+ // 'password_does_not_match' | tr // +
+
+
+ * +
+
+
+ +
+ +
+ // 'comment_is_too_long' | tr // +
+
+
+
+
+ {{ if eq .AddNew true }} + + {{ else }} + + {{ end }} +
+
+
+
+
+
+
+
+
\ No newline at end of file