mirror of
https://github.com/google/nomulus
synced 2026-08-15 11:46:08 +00:00
Compare commits
521
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dfef733360 | ||
|
|
04a0659197 | ||
|
|
70010886b1 | ||
|
|
3cd50dc929 | ||
|
|
03872b508f | ||
|
|
1096f201cd | ||
|
|
9dc3215624 | ||
|
|
af321fb65e | ||
|
|
c5132c04be | ||
|
|
a64dc21f96 | ||
|
|
0381533a35 | ||
|
|
4999a72d96 | ||
|
|
2d072c3844 | ||
|
|
c15dec4419 | ||
|
|
8340125bf4 | ||
|
|
98ba80d94e | ||
|
|
967d04efce | ||
|
|
20fd944e83 | ||
|
|
daa56e6d85 | ||
|
|
ed33c7424d | ||
|
|
04b30f5c04 | ||
|
|
11702bc940 | ||
|
|
2d82646421 | ||
|
|
50260dca5f | ||
|
|
3cc10bfe0d | ||
|
|
5645b2e218 | ||
|
|
2a01c12b14 | ||
|
|
93d77e558f | ||
|
|
92ebd0dedb | ||
|
|
b49e37feee | ||
|
|
bede56598c | ||
|
|
467d9c7bf1 | ||
|
|
e5ebe96c74 | ||
|
|
2ff4d97b0a | ||
|
|
6b0beeb477 | ||
|
|
d2d43f4115 | ||
|
|
12fd206c35 | ||
|
|
a3f510d0db | ||
|
|
fa54c26ee2 | ||
|
|
8896fb94f4 | ||
|
|
6c7bf5e5dd | ||
|
|
ea1e8d5cc5 | ||
|
|
7fb846c5b0 | ||
|
|
5180095cb6 | ||
|
|
9fe64bf9ec | ||
|
|
0f3b62d5ce | ||
|
|
bd4701647b | ||
|
|
fb816d7a2c | ||
|
|
8fbf363195 | ||
|
|
397f800614 | ||
|
|
bcf42bd287 | ||
|
|
ed95d19b93 | ||
|
|
97fc2c0b66 | ||
|
|
00728c40ba | ||
|
|
3f2a42ab8d | ||
|
|
b73e342820 | ||
|
|
df7fec7a3e | ||
|
|
6f7ae1eabc | ||
|
|
eb978ebbd5 | ||
|
|
95831bc8b7 | ||
|
|
538260521b | ||
|
|
612708f0a8 | ||
|
|
e78de98060 | ||
|
|
c918258fb1 | ||
|
|
34103ec815 | ||
|
|
a63812160e | ||
|
|
9aaf7ee36a | ||
|
|
96a864dbd6 | ||
|
|
8a36fb5f1f | ||
|
|
6c138420b0 | ||
|
|
08570511f5 | ||
|
|
e62d970d34 | ||
|
|
067927b735 | ||
|
|
4ec2919ce3 | ||
|
|
19422075fa | ||
|
|
40b6984ffb | ||
|
|
6952e0f653 | ||
|
|
dcb55d27bb | ||
|
|
765bd9834a | ||
|
|
221088e738 | ||
|
|
6649e00df7 | ||
|
|
2ceb52a7c4 | ||
|
|
120bcc33be | ||
|
|
8987fd37c2 | ||
|
|
653e092ad4 | ||
|
|
5e97a8b412 | ||
|
|
229fcf3946 | ||
|
|
b775e4a178 | ||
|
|
e3c386a8a7 | ||
|
|
799f0449ad | ||
|
|
bf025445d5 | ||
|
|
9f22f2e8ae | ||
|
|
45c8b81823 | ||
|
|
4cfcc60655 | ||
|
|
e4ee63b8f3 | ||
|
|
f8407c74bc | ||
|
|
693467a165 | ||
|
|
cea3da01a0 | ||
|
|
c2030e5859 | ||
|
|
1cbbc660d2 | ||
|
|
e0bbff827e | ||
|
|
10925f2447 | ||
|
|
7641b05f12 | ||
|
|
d130e74004 | ||
|
|
c9c61e4f17 | ||
|
|
da8df1f4d9 | ||
|
|
f649d960c1 | ||
|
|
e5ebc5a2bb | ||
|
|
f9d2839590 | ||
|
|
c6a6bc7e25 | ||
|
|
fce126d426 | ||
|
|
8e41278717 | ||
|
|
cb3738d540 | ||
|
|
71afc25110 | ||
|
|
fa377733be | ||
|
|
21950f7d82 | ||
|
|
e66aee0416 | ||
|
|
c7e1fc17d2 | ||
|
|
0c0b0df36e | ||
|
|
304f0002b4 | ||
|
|
15cf3e1bc0 | ||
|
|
eeed166310 | ||
|
|
e54075fea3 | ||
|
|
78cc1b2937 | ||
|
|
35f95bbbe4 | ||
|
|
ae61cd443d | ||
|
|
cc20f7d76d | ||
|
|
5603b91526 | ||
|
|
332f491ac7 | ||
|
|
4bd7c18fe9 | ||
|
|
fdb0664841 | ||
|
|
a9ba770bfa | ||
|
|
4d96e5a6b1 | ||
|
|
1171c5cfcb | ||
|
|
91e241374d | ||
|
|
634202c0e9 | ||
|
|
020ed33003 | ||
|
|
0f61066b1d | ||
|
|
03711481cd | ||
|
|
c32fb2fc71 | ||
|
|
6e77c89cd6 | ||
|
|
5e41e84b8d | ||
|
|
bfd569ee44 | ||
|
|
b13a33347f | ||
|
|
d17a6edf12 | ||
|
|
7255ebff29 | ||
|
|
cacc90097a | ||
|
|
0ef8984767 | ||
|
|
7a4abd93dc | ||
|
|
142c910e3b | ||
|
|
c68d54a5ed | ||
|
|
d17188b820 | ||
|
|
cbe59b6950 | ||
|
|
2b3c6525ff | ||
|
|
72dd8658cf | ||
|
|
c0490f7777 | ||
|
|
a22a38527b | ||
|
|
08203033a2 | ||
|
|
d0482a8f2c | ||
|
|
e6a2db8075 | ||
|
|
7929322e95 | ||
|
|
5c35811eb9 | ||
|
|
4ba0f4a2cd | ||
|
|
e167b4b753 | ||
|
|
c47f821754 | ||
|
|
febdbc0468 | ||
|
|
a988732d65 | ||
|
|
7ee541e1b1 | ||
|
|
b07769bdee | ||
|
|
9db016638e | ||
|
|
c3d164d462 | ||
|
|
352618b3b7 | ||
|
|
0389b0d2d9 | ||
|
|
8906a82e3b | ||
|
|
f6e42896c3 | ||
|
|
4d3dec54cf | ||
|
|
f082ffffe3 | ||
|
|
5f23f2a15a | ||
|
|
7ed7cf3340 | ||
|
|
ab60ac44fd | ||
|
|
d9ad39cdad | ||
|
|
bac4e22bff | ||
|
|
ab5f6cc229 | ||
|
|
1765f4f0b4 | ||
|
|
e88c6e1550 | ||
|
|
1739c6d74f | ||
|
|
66513a114e | ||
|
|
0e808a4c01 | ||
|
|
4e013603be | ||
|
|
730585cd14 | ||
|
|
fd7820759d | ||
|
|
69359bb1e6 | ||
|
|
35b602a76e | ||
|
|
82002d1f75 | ||
|
|
2fd9b062df | ||
|
|
ec3804e87e | ||
|
|
d0d28cc7e6 | ||
|
|
2d1260c01b | ||
|
|
06da6a2cc6 | ||
|
|
858a22f82e | ||
|
|
3c126ddfd4 | ||
|
|
2b98e6f177 | ||
|
|
20036b6a74 | ||
|
|
396cbd6bd3 | ||
|
|
71ea16ff69 | ||
|
|
45331be166 | ||
|
|
beb7c14adb | ||
|
|
d33571dde3 | ||
|
|
53a7d1b66c | ||
|
|
fa721e82ff | ||
|
|
d4faa77ee4 | ||
|
|
96d3d88c2f | ||
|
|
213e06f02e | ||
|
|
d5445dd049 | ||
|
|
af5adcb0ba | ||
|
|
ca238a8578 | ||
|
|
1a8f133d54 | ||
|
|
233ee09efe | ||
|
|
35ff768176 | ||
|
|
c4e5bc913e | ||
|
|
0241937dee | ||
|
|
68b46735cd | ||
|
|
bfeaf4a23e | ||
|
|
5f9f157494 | ||
|
|
c23eed6ec4 | ||
|
|
04a4431d6a | ||
|
|
d9c5d71f40 | ||
|
|
75f09c2fdf | ||
|
|
74f0a8dd7b | ||
|
|
092e3dca47 | ||
|
|
b8a6ac72dd | ||
|
|
b602aac09a | ||
|
|
d86c002132 | ||
|
|
54c5a9450d | ||
|
|
0f0097c15c | ||
|
|
c9437d8c72 | ||
|
|
19819444af | ||
|
|
15df3aea44 | ||
|
|
d000a5dff8 | ||
|
|
34694b4aef | ||
|
|
7ce7b23450 | ||
|
|
a5d1469281 | ||
|
|
a90a85afae | ||
|
|
6e68876a14 | ||
|
|
11231703d5 | ||
|
|
b77a219e19 | ||
|
|
bd8e6354b5 | ||
|
|
361094f537 | ||
|
|
d53177e44c | ||
|
|
e73f646e1f | ||
|
|
1a5dfb0ac2 | ||
|
|
49cb1875d1 | ||
|
|
61eee45ad0 | ||
|
|
e99a18f54f | ||
|
|
0c123e1676 | ||
|
|
81b239c6b3 | ||
|
|
ea8c34bf8b | ||
|
|
b3e67e58b5 | ||
|
|
589041b3ed | ||
|
|
455364ff29 | ||
|
|
d90bc1a3e4 | ||
|
|
0e3875c1ff | ||
|
|
3b565b96b7 | ||
|
|
ec6c77927f | ||
|
|
e88ff77ecb | ||
|
|
0781010b16 | ||
|
|
ab4bac05d1 | ||
|
|
8e22ce7c70 | ||
|
|
d96a5547ce | ||
|
|
05b43965d1 | ||
|
|
43000a5f80 | ||
|
|
a66b9ea749 | ||
|
|
36a660a8ad | ||
|
|
d09bb4ff74 | ||
|
|
6ca3cc230f | ||
|
|
6a5d8ed3b5 | ||
|
|
53dcba1189 | ||
|
|
3e77004274 | ||
|
|
fd21fcdb84 | ||
|
|
ae14e35df7 | ||
|
|
94dc9fd0d5 | ||
|
|
7b34659a8f | ||
|
|
808432e709 | ||
|
|
73d3b76a89 | ||
|
|
ca072b4861 | ||
|
|
54c896cbb9 | ||
|
|
2c7bf2cfdb | ||
|
|
49d2e34e12 | ||
|
|
5511b41f93 | ||
|
|
147cdff555 | ||
|
|
4b6ade0b14 | ||
|
|
570618705e | ||
|
|
e791608098 | ||
|
|
03b358726a | ||
|
|
d121f8f547 | ||
|
|
b27218d799 | ||
|
|
e78ce42dd5 | ||
|
|
55fade497d | ||
|
|
e7501b621a | ||
|
|
9c443bede1 | ||
|
|
6d0a746b76 | ||
|
|
0765e7b209 | ||
|
|
f729802094 | ||
|
|
e809e967a3 | ||
|
|
4de2bd5901 | ||
|
|
b5629ff16f | ||
|
|
91615aef54 | ||
|
|
fa6898167b | ||
|
|
903b7979de | ||
|
|
8721085d14 | ||
|
|
e434528cd3 | ||
|
|
9ca54e4364 | ||
|
|
a16794e2af | ||
|
|
496a781572 | ||
|
|
2df583df1a | ||
|
|
4f1ca920a7 | ||
|
|
96e33f5b4f | ||
|
|
dff2d90325 | ||
|
|
fa344776a1 | ||
|
|
eb164809de | ||
|
|
4ddbeb6d06 | ||
|
|
fa53391395 | ||
|
|
856e70cf8e | ||
|
|
2037611931 | ||
|
|
af1f6e5708 | ||
|
|
0df8372407 | ||
|
|
59f4129ee0 | ||
|
|
de3af34b66 | ||
|
|
5c62dc78ba | ||
|
|
3fdecde6e9 | ||
|
|
e7bf74d91d | ||
|
|
ff211fb4f9 | ||
|
|
3a7c53d895 | ||
|
|
f5b279a288 | ||
|
|
c68583f666 | ||
|
|
6d2eb2e140 | ||
|
|
00a2022292 | ||
|
|
de372c4d47 | ||
|
|
5f9c7de516 | ||
|
|
6e57d93507 | ||
|
|
b9cfa65546 | ||
|
|
9af006836c | ||
|
|
cd95be4776 | ||
|
|
bdc9a1fd1d | ||
|
|
d0b036227a | ||
|
|
0f02858965 | ||
|
|
6acb14c60d | ||
|
|
e881f254f8 | ||
|
|
1fb27fcf8e | ||
|
|
34a8a94083 | ||
|
|
779dc36858 | ||
|
|
40174b825a | ||
|
|
df4e345961 | ||
|
|
1cac9c9684 | ||
|
|
11883812b3 | ||
|
|
742481932e | ||
|
|
37e4607c91 | ||
|
|
c896c022a6 | ||
|
|
805a34be96 | ||
|
|
dcf0412f11 | ||
|
|
fbe0f4e0f2 | ||
|
|
d1f678bba7 | ||
|
|
78c7d44546 | ||
|
|
af2a7540d9 | ||
|
|
f82e8e006d | ||
|
|
bf877f469c | ||
|
|
02fd6d4756 | ||
|
|
a4bd85068b | ||
|
|
15368ee1c6 | ||
|
|
f13fda2c15 | ||
|
|
f72a0d2f16 | ||
|
|
1eef260da9 | ||
|
|
9d0ff74377 | ||
|
|
7a301edab7 | ||
|
|
08bcf579a5 | ||
|
|
7d2330c943 | ||
|
|
670941bec8 | ||
|
|
1f516e34b6 | ||
|
|
70942c87d1 | ||
|
|
406059db72 | ||
|
|
abc1a0ef3d | ||
|
|
7b47ecb1f1 | ||
|
|
469d62703a | ||
|
|
009fda67b7 | ||
|
|
e492936cec | ||
|
|
d1d59c1afd | ||
|
|
7b786eaf1f | ||
|
|
45c5d12743 | ||
|
|
73ab95bd9d | ||
|
|
f85cf57e36 | ||
|
|
5e36cf30c3 | ||
|
|
829be0777b | ||
|
|
c0ac9bdba4 | ||
|
|
58ec0f826d | ||
|
|
f9e0908022 | ||
|
|
b21e1a1935 | ||
|
|
0112b3ae06 | ||
|
|
a4903c27b9 | ||
|
|
2166c28d6d | ||
|
|
891e7c0174 | ||
|
|
64f5971275 | ||
|
|
818944317f | ||
|
|
ea96ed300f | ||
|
|
8415c8bbe4 | ||
|
|
dc48c257b5 | ||
|
|
2bf3867532 | ||
|
|
44f44be643 | ||
|
|
f61579b350 | ||
|
|
c414e38a98 | ||
|
|
2cf2d7e7b1 | ||
|
|
432871add9 | ||
|
|
2621b2d679 | ||
|
|
7a5db3b8fe | ||
|
|
055f9c012c | ||
|
|
14ab9423f8 | ||
|
|
9223b81ab3 | ||
|
|
1dcf34ccc2 | ||
|
|
9273d2bf15 | ||
|
|
036d35c11a | ||
|
|
a8ce34586d | ||
|
|
26fb04f00c | ||
|
|
9d4c38684a | ||
|
|
d7edd27cdd | ||
|
|
265d69051b | ||
|
|
b5d2b56426 | ||
|
|
e79c63142a | ||
|
|
f8ac7afc33 | ||
|
|
e56e751652 | ||
|
|
cfdf12aa7d | ||
|
|
811b385544 | ||
|
|
3f5c9d1246 | ||
|
|
5315752bc0 | ||
|
|
4eee7b8c0d | ||
|
|
ecb39d5899 | ||
|
|
42b508427b | ||
|
|
20b5b43501 | ||
|
|
08285f5de7 | ||
|
|
fb4c5b457d | ||
|
|
781c212275 | ||
|
|
c73f7a6bd3 | ||
|
|
8d793b2349 | ||
|
|
55d5f8c6f8 | ||
|
|
9006312253 | ||
|
|
e5e2370923 | ||
|
|
b3b0efd47e | ||
|
|
e82cbe60a9 | ||
|
|
923bc13e3a | ||
|
|
4893ea307b | ||
|
|
01f868cefc | ||
|
|
1b0919eaff | ||
|
|
92b23bac16 | ||
|
|
cc9b3f5965 | ||
|
|
dd86c56ddc | ||
|
|
08551f7bc7 | ||
|
|
e7171a326b | ||
|
|
c3eae7b76f | ||
|
|
2687181045 | ||
|
|
68750569db | ||
|
|
028e5cc958 | ||
|
|
853e571d01 | ||
|
|
9b79f5af2c | ||
|
|
4195871541 | ||
|
|
504d7ccaac | ||
|
|
36a8908712 | ||
|
|
e42c11051e | ||
|
|
85b588b51f | ||
|
|
572b7101cb | ||
|
|
445825957d | ||
|
|
7ab76f3573 | ||
|
|
9e3c58989a | ||
|
|
cf9c1ec7c3 | ||
|
|
69ea87be31 | ||
|
|
779d0c9d37 | ||
|
|
2855944214 | ||
|
|
992d1c1349 | ||
|
|
f50290ce1d | ||
|
|
e647d4e215 | ||
|
|
08471242df | ||
|
|
cd23fea698 | ||
|
|
ba54208dad | ||
|
|
b5e131ecba | ||
|
|
87e99f59bc | ||
|
|
30accea383 | ||
|
|
72e0101746 | ||
|
|
3090df9a78 | ||
|
|
7332b1fa38 | ||
|
|
9330e3a50d | ||
|
|
1d6b119340 | ||
|
|
8158f761c8 | ||
|
|
08838e091f | ||
|
|
59720a207d | ||
|
|
26bae65e1e | ||
|
|
23a2861b37 | ||
|
|
341238305d | ||
|
|
d210bed744 | ||
|
|
fe710e5510 | ||
|
|
8f8ffe7020 | ||
|
|
16e5018489 | ||
|
|
af303bd26f | ||
|
|
bf3bb5d804 | ||
|
|
dcb16e05bd | ||
|
|
2facedd60f | ||
|
|
b1ec81f054 | ||
|
|
779da518df | ||
|
|
4f53ae0e89 | ||
|
|
da04caeea2 | ||
|
|
a63916b08e | ||
|
|
36bd508bf9 | ||
|
|
bbdbfe85ed | ||
|
|
2a7e9a266a | ||
|
|
bd0d8af7b3 | ||
|
|
2da8ea0185 | ||
|
|
7a84844000 | ||
|
|
1580555d30 | ||
|
|
4fb8a1b50b | ||
|
|
e07f25000d | ||
|
|
cc1777af0c | ||
|
|
87e54c001f | ||
|
|
2dc87d42b4 | ||
|
|
1eed9c82dc | ||
|
|
cf43de7755 |
@@ -6,8 +6,6 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
# The branches below must be a subset of the branches above
|
# The branches below must be a subset of the branches above
|
||||||
branches: [ 'master' ]
|
branches: [ 'master' ]
|
||||||
schedule:
|
|
||||||
- cron: '24 4 * * 2'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
analyze:
|
analyze:
|
||||||
@@ -27,17 +25,17 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set Java version
|
- name: Set Java version
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
java-version: '11'
|
java-version: '21'
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v2
|
uses: github/codeql-action/init@v3
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
@@ -47,11 +45,20 @@ jobs:
|
|||||||
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||||
queries: security-and-quality
|
queries: security-and-quality
|
||||||
|
|
||||||
|
# Build with Gradle
|
||||||
|
- name: Setup Gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v4
|
||||||
|
with:
|
||||||
|
build-scan-publish: true
|
||||||
|
build-scan-terms-of-use-url: "https://gradle.com/terms-of-service"
|
||||||
|
build-scan-terms-of-use-agree: "yes"
|
||||||
|
- name: Execute Gradle build
|
||||||
|
run: ./gradlew --no-daemon --no-build-cache --no-configuration-cache --rerun-tasks clean build -x test -x jIFC
|
||||||
|
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
#- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v2
|
# uses: github/codeql-action/autobuild@v3
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||||
@@ -64,6 +71,6 @@ jobs:
|
|||||||
# ./location_of_script_within_repo/buildscript.sh
|
# ./location_of_script_within_repo/buildscript.sh
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v2
|
uses: github/codeql-action/analyze@v3
|
||||||
with:
|
with:
|
||||||
category: "/language:${{matrix.language}}"
|
category: "/language:${{matrix.language}}"
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
name: Dependency Submission
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ 'master' ]
|
||||||
|
schedule:
|
||||||
|
- cron: '24 3 * * *'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
dependency-submission:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Set Java version
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: 'temurin'
|
||||||
|
java-version: '21'
|
||||||
|
- name: Generate and submit dependency graph
|
||||||
|
uses: gradle/actions/dependency-submission@v3
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
name: "Check labels"
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: ["master"]
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- synchronize
|
||||||
|
- labeled
|
||||||
|
- unlabeled
|
||||||
|
merge_group:
|
||||||
|
branches: ["master"]
|
||||||
|
types: [checks_requested]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
fail-by-label:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Fail if PR is labeled as "do not merge"
|
||||||
|
if: contains(github.event.pull_request.labels.*.name, 'do not merge')
|
||||||
|
run: |
|
||||||
|
echo "This PR is labeled as do not merge!"
|
||||||
|
exit 1
|
||||||
@@ -117,3 +117,6 @@ core/**/registrar_dbg*.css
|
|||||||
# Appengine generated files
|
# Appengine generated files
|
||||||
core/WEB-INF/appengine-generated/*.bin
|
core/WEB-INF/appengine-generated/*.bin
|
||||||
core/WEB-INF/appengine-generated/*.xml
|
core/WEB-INF/appengine-generated/*.xml
|
||||||
|
|
||||||
|
# jEnv
|
||||||
|
.java-version
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
21
|
||||||
@@ -34,6 +34,8 @@ Guy Bensky <guyben@google.com>
|
|||||||
Weimin Yu <weiminyu@google.com>
|
Weimin Yu <weiminyu@google.com>
|
||||||
Shicong Huang <shicong@google.com>
|
Shicong Huang <shicong@google.com>
|
||||||
Gustav Brodman <gbrodman@google.com>
|
Gustav Brodman <gbrodman@google.com>
|
||||||
|
Aman Sanger <sangera@google.com>
|
||||||
Sarah Botwinick <sarahbot@google.com>
|
Sarah Botwinick <sarahbot@google.com>
|
||||||
Legina Chen <legina@google.com>
|
Legina Chen <legina@google.com>
|
||||||
Rachel Guan <rachelguan@google.com>
|
Rachel Guan <rachelguan@google.com>
|
||||||
|
Juan Celhay <jicelhay@google.com>
|
||||||
|
|||||||
@@ -12,16 +12,16 @@ Nomulus is an open source, scalable, cloud-based service for operating
|
|||||||
[top-level domains](https://en.wikipedia.org/wiki/Top-level_domain) (TLDs). It
|
[top-level domains](https://en.wikipedia.org/wiki/Top-level_domain) (TLDs). It
|
||||||
is the authoritative source for the TLDs that it runs, meaning that it is
|
is the authoritative source for the TLDs that it runs, meaning that it is
|
||||||
responsible for tracking domain name ownership and handling registrations,
|
responsible for tracking domain name ownership and handling registrations,
|
||||||
renewals, availability checks, and WHOIS requests. End-user registrants (i.e.
|
renewals, availability checks, and WHOIS requests. End-user registrants (i.e.,
|
||||||
people or companies that want to register a domain name) use an intermediate
|
people or companies that want to register a domain name) use an intermediate
|
||||||
domain name registrar acting on their behalf to interact with the registry.
|
domain name registrar acting on their behalf to interact with the registry.
|
||||||
|
|
||||||
Nomulus runs on [Google App Engine][gae] and is written primarily in Java. It is
|
Nomulus runs on [Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine)
|
||||||
the software that [Google Registry](https://www.registry.google/) uses to
|
and is written primarily in Java. It is the software that
|
||||||
operate TLDs such as .google, .app, .how, .soy, and .みんな. It can run any
|
[Google Registry](https://www.registry.google/) uses to operate TLDs such as .google,
|
||||||
number of TLDs in a single shared registry system using horizontal scaling. Its
|
.app, .how, .soy, and .みんな. It can run any number of TLDs in a single shared registry
|
||||||
source code is publicly available in this repository under the [Apache 2.0 free
|
system using horizontal scaling. Its source code is publicly available in this
|
||||||
and open source license](https://www.apache.org/licenses/LICENSE-2.0).
|
repository under the [Apache 2.0 free and open source license](https://www.apache.org/licenses/LICENSE-2.0).
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
@@ -30,8 +30,8 @@ running system:
|
|||||||
|
|
||||||
* [Install
|
* [Install
|
||||||
guide](https://github.com/google/nomulus/blob/master/docs/install.md)
|
guide](https://github.com/google/nomulus/blob/master/docs/install.md)
|
||||||
* View the source code for the [GAE app](https://github.com/google/nomulus/tree/master/core/src/main/java/google/registry)
|
* View the source code for the [Main HTTP server](https://github.com/google/nomulus/tree/master/core/src/main/java/google/registry)
|
||||||
and for the [GKE proxy](https://github.com/google/nomulus/tree/master/proxy/src/main/java/google/registry)
|
and for the [EPP proxy](https://github.com/google/nomulus/tree/master/proxy/src/main/java/google/registry)
|
||||||
* [Other docs](https://github.com/google/nomulus/tree/master/docs)
|
* [Other docs](https://github.com/google/nomulus/tree/master/docs)
|
||||||
* [Javadoc](https://javadoc.nomulus.foo/)
|
* [Javadoc](https://javadoc.nomulus.foo/)
|
||||||
* [Nomulus discussion
|
* [Nomulus discussion
|
||||||
@@ -54,9 +54,11 @@ Nomulus has the following capabilities:
|
|||||||
checking, updating, and transferring domain names.
|
checking, updating, and transferring domain names.
|
||||||
* **[DNS](https://en.wikipedia.org/wiki/Domain_Name_System) interface**: The
|
* **[DNS](https://en.wikipedia.org/wiki/Domain_Name_System) interface**: The
|
||||||
registry provides a pluggable interface that can be implemented to handle
|
registry provides a pluggable interface that can be implemented to handle
|
||||||
different DNS providers. It includes a sample implementation using Google
|
different DNS providers. It includes a sample implementation using [Google
|
||||||
Cloud DNS as well as an RFC 2136 compliant implementation that works with
|
Cloud DNS](https://cloud.google.com/dns/), as well as an RFC 2136 compliant
|
||||||
BIND.
|
implementation that works with BIND. If you are using Google Cloud DNS, you
|
||||||
|
may need to understand its capabilities and provide your own
|
||||||
|
multi-[AS](https://en.wikipedia.org/wiki/Autonomous_system_\(Internet\)) solution.
|
||||||
* **[WHOIS](https://en.wikipedia.org/wiki/WHOIS)**: A text-based protocol that
|
* **[WHOIS](https://en.wikipedia.org/wiki/WHOIS)**: A text-based protocol that
|
||||||
returns ownership and contact information on registered domain names.
|
returns ownership and contact information on registered domain names.
|
||||||
* **[Registration Data Access Protocol
|
* **[Registration Data Access Protocol
|
||||||
@@ -68,7 +70,7 @@ Nomulus has the following capabilities:
|
|||||||
provider to allow take-over by another registry operator in the event of
|
provider to allow take-over by another registry operator in the event of
|
||||||
serious failure. This is required by ICANN for all [new
|
serious failure. This is required by ICANN for all [new
|
||||||
gTLDs](https://newgtlds.icann.org/).
|
gTLDs](https://newgtlds.icann.org/).
|
||||||
* **Premium pricing**: Communicates prices for premium domain names (i.e.
|
* **Premium pricing**: Communicates prices for premium domain names (i.e.,
|
||||||
those that are highly desirable) and supports configurable premium
|
those that are highly desirable) and supports configurable premium
|
||||||
registration and renewal prices. An extensible interface allows fully
|
registration and renewal prices. An extensible interface allows fully
|
||||||
programmatic pricing.
|
programmatic pricing.
|
||||||
@@ -91,56 +93,50 @@ Nomulus has the following capabilities:
|
|||||||
* **Administrative tool**: Performs the full range of administrative tasks
|
* **Administrative tool**: Performs the full range of administrative tasks
|
||||||
needed to manage a running registry system, including creating and
|
needed to manage a running registry system, including creating and
|
||||||
configuring new TLDs.
|
configuring new TLDs.
|
||||||
* **DNS interface**: An interface for DNS operations is provided so you can
|
* **Secure storage of cryptographic keys**: A keyring interface is
|
||||||
write an implementation for your chosen provider, along with a sample
|
provided for plugging in your own implementation (see [configuration
|
||||||
implementation that uses [Google Cloud DNS](https://cloud.google.com/dns/).
|
doc](https://github.com/google/nomulus/blob/master/docs/configuration.md)
|
||||||
If you are using Google Cloud DNS you may need to understand its
|
for details), and an implementation based on
|
||||||
capabilities and provide your own
|
[Google Cloud Secret Manager](https://cloud.google.com/security/products/secret-manager) is
|
||||||
multi-[AS](https://en.wikipedia.org/wiki/Autonomous_system_\(Internet\))
|
available.
|
||||||
solution.
|
* **TPC Proxy**: Nomulus is built on top of the [Jetty](https://jetty.org/)
|
||||||
* **GAE Proxy**: App Engine Standard only serves HTTP/S traffic. A proxy to
|
container that implements the [Jakarta Servlet](https://jakarta.ee/specifications/servlet/)
|
||||||
forward traffic on EPP and WHOIS ports to App Engine via HTTPS is provided.
|
specification and only serves HTTP/S traffic. A proxy to translate raw TCP traffic (e.g., EPP)
|
||||||
Instructions on setting up the proxy on
|
to and from HTTP is provided.
|
||||||
[Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine/)
|
Instructions on setting up the proxy
|
||||||
is [available](https://github.com/google/nomulus/blob/master/docs/proxy-setup.md).
|
are [available](https://github.com/google/nomulus/blob/master/docs/proxy-setup.md).
|
||||||
Running the proxy on GKE supports IPv4 and IPv6 access, per ICANN's
|
The proxy can either run in a separate cluster and communicate to Nomulus public HTTP
|
||||||
requirements for gTLDs. The proxy can also run as a single jar file, or on
|
endpoints via the Internet, or as a sidecar with the Nomulus image in the same pod and
|
||||||
other Kubernetes providers, with modifications.
|
communicate to it via loopback.
|
||||||
|
|
||||||
## Additional components
|
## Additional components
|
||||||
|
|
||||||
Registry operators interested in deploying Nomulus will likely require some
|
Registry operators interested in deploying Nomulus will likely require some
|
||||||
additional components that are need to be configured separately.
|
additional components that need to be configured separately.
|
||||||
|
|
||||||
* A way to invoice registrars for domain name registrations and accept
|
* A way to invoice registrars for domain name registrations and accept
|
||||||
payments. Nomulus records the information required to generate invoices in
|
payments. Nomulus records the information required to generate invoices in
|
||||||
[billing
|
[billing
|
||||||
events](https://github.com/google/nomulus/blob/master/docs/code-structure.md#billing-events).
|
events](https://github.com/google/nomulus/blob/master/docs/code-structure.md#billing-events).
|
||||||
* Fully automated reporting to meet ICANN's requirements for gTLDs. Nomulus
|
* Fully automated reporting to meet ICANN's requirements for gTLDs. Nomulus
|
||||||
includes substantial reporting functionality but some additional work will
|
includes substantial reporting functionality, but some additional work will
|
||||||
be required by the operator in this area.
|
be required by the operator in this area.
|
||||||
* A secure method for storing cryptographic keys. A keyring interface is
|
|
||||||
provided for plugging in your own implementation (see [configuration
|
|
||||||
doc](https://github.com/google/nomulus/blob/master/docs/configuration.md)
|
|
||||||
for details).
|
|
||||||
* System status and uptime monitoring.
|
* System status and uptime monitoring.
|
||||||
|
|
||||||
## Outside references
|
## Outside references
|
||||||
|
|
||||||
* [Donuts](http://donuts.domains) Registry has helped review the code and
|
* [Identity Digital](http://identity.digital) has helped review the code and
|
||||||
provided valuable feedback
|
provided valuable feedback.
|
||||||
* [CoCCa](http://cocca.org.nz) and [FRED](https://fred.nic.cz) are other
|
* [CoCCa](http://cocca.org.nz) and [FRED](https://fred.nic.cz) are other
|
||||||
open-source registry platforms in use by many TLDs
|
open-source registry platforms in use by many TLDs.
|
||||||
* We are not aware of any fully open source domain registrar projects, but
|
* We are not aware of any fully open source domain registrar projects, but
|
||||||
open source EPP Toolkits (not yet tested with Nomulus; may require
|
open source EPP Toolkits (not yet tested with Nomulus; may require
|
||||||
integration work) include:
|
integration work) include:
|
||||||
* [EPP RTK Project](http://epp-rtk.sourceforge.net/)
|
* [Universal Registry/Registrar Toolkit](https://sourceforge.net/projects/epp-rtk/)
|
||||||
* [CentralNic](https://www.centralnic.com/registry/labs)
|
|
||||||
* [ari-toolkit](https://github.com/AusRegistry/ari-toolkit)
|
* [ari-toolkit](https://github.com/AusRegistry/ari-toolkit)
|
||||||
* [Net::DRI](https://metacpan.org/pod/Net::DRI)
|
* [Net::DRI](https://metacpan.org/pod/Net::DRI)
|
||||||
* Some Open Source DNS Projects that may be useful, but which we have not
|
* Some Open Source DNS Projects that may be useful, but which we have not
|
||||||
tested:
|
tested:
|
||||||
* [AtomiaDNS](http://atomiadns.com/)
|
* [AtomiaDNS](https://github.com/atomia/atomiadns)
|
||||||
* [PowerDNS](https://doc.powerdns.com/md/)
|
* [PowerDNS](https://github.com/PowerDNS/pdns)
|
||||||
|
|
||||||
[gae]:https://cloud.google.com/appengine/docs/about-the-standard-environment
|
|
||||||
|
|||||||
+5
-21
@@ -47,24 +47,9 @@ war {
|
|||||||
|
|
||||||
if (project.path == ":services:default") {
|
if (project.path == ":services:default") {
|
||||||
war {
|
war {
|
||||||
from("${rootDir}/console-webapp/dist/console-webapp") {
|
from("${coreResourcesDir}/google/registry/ui/html") {
|
||||||
include "**/*"
|
include "*.html"
|
||||||
into("console")
|
into("registrar")
|
||||||
}
|
|
||||||
from("${coreResourcesDir}/google/registry/ui") {
|
|
||||||
include "registrar_bin.js"
|
|
||||||
if (environment != "production") {
|
|
||||||
include "registrar_bin.js.map"
|
|
||||||
}
|
|
||||||
into("assets/js")
|
|
||||||
}
|
|
||||||
from("${coreResourcesDir}/google/registry/ui/css") {
|
|
||||||
include "registrar*"
|
|
||||||
into("assets/css")
|
|
||||||
}
|
|
||||||
from("${coreResourcesDir}/google/registry/ui/assets/images") {
|
|
||||||
include "**/*"
|
|
||||||
into("assets/images")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,11 +88,10 @@ explodeWar.doLast {
|
|||||||
file("${it.explodedAppDirectory}/WEB-INF/lib/tools.jar").setWritable(true)
|
file("${it.explodedAppDirectory}/WEB-INF/lib/tools.jar").setWritable(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
appengineDeployAll.mustRunAfter ':console-webapp:deploy'
|
||||||
appengineDeployAll.finalizedBy ':deployCloudSchedulerAndQueue'
|
appengineDeployAll.finalizedBy ':deployCloudSchedulerAndQueue'
|
||||||
rootProject.deploy.dependsOn appengineDeployAll
|
|
||||||
rootProject.stage.dependsOn appengineStage
|
rootProject.stage.dependsOn appengineStage
|
||||||
tasks['war'].dependsOn ':console-webapp:buildConsoleWebappProd'
|
|
||||||
tasks['war'].dependsOn ':core:compileProdJS'
|
|
||||||
tasks['war'].dependsOn ':core:processResources'
|
tasks['war'].dependsOn ':core:processResources'
|
||||||
tasks['war'].dependsOn ':core:jar'
|
tasks['war'].dependsOn ':core:jar'
|
||||||
|
|
||||||
|
|||||||
+82
-108
@@ -29,21 +29,20 @@ buildscript {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.google.cloud.tools:appengine-gradle-plugin:2.4.1'
|
classpath 'com.google.cloud.tools:appengine-gradle-plugin:2.4.1'
|
||||||
classpath 'net.ltgt.gradle:gradle-errorprone-plugin:2.0.2'
|
classpath 'net.ltgt.gradle:gradle-errorprone-plugin:3.1.0'
|
||||||
classpath 'org.sonatype.aether:aether-api:1.13.1'
|
classpath 'org.sonatype.aether:aether-api:1.13.1'
|
||||||
classpath 'org.sonatype.aether:aether-impl:1.13.1'
|
classpath 'org.sonatype.aether:aether-impl:1.13.1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
// Java static analysis plugins. Keep versions consistent with
|
// Java static analysis plugins.
|
||||||
// ./buildSrc/build.gradle.kts
|
|
||||||
|
|
||||||
// Re-enable when compatible with Gradle 8
|
// Re-enable when compatible with Gradle 8
|
||||||
// id 'nebula.lint' version '16.0.2'
|
// id 'nebula.lint' version '16.0.2'
|
||||||
id 'net.ltgt.errorprone' version '2.0.2'
|
id 'net.ltgt.errorprone' version '3.1.0'
|
||||||
id 'checkstyle'
|
id 'checkstyle'
|
||||||
id 'com.github.johnrengelman.shadow' version '5.1.0'
|
id 'com.github.johnrengelman.shadow' version '8.1.1'
|
||||||
|
|
||||||
// NodeJs plugin
|
// NodeJs plugin
|
||||||
id "com.github.node-gradle.node" version "3.0.1"
|
id "com.github.node-gradle.node" version "3.0.1"
|
||||||
@@ -60,53 +59,20 @@ dependencyLocking {
|
|||||||
}
|
}
|
||||||
|
|
||||||
node {
|
node {
|
||||||
download = true
|
download = false
|
||||||
version = "16.14.0"
|
version = "22.7.0"
|
||||||
npmVersion = "6.14.11"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wrapper {
|
wrapper {
|
||||||
distributionType = Wrapper.DistributionType.ALL
|
distributionType = Wrapper.DistributionType.ALL
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: google.registry.gradle.plugin.ReportUploaderPlugin
|
|
||||||
|
|
||||||
reportUploader {
|
|
||||||
// Set the location where we want to upload the build results.
|
|
||||||
// e.g. -P uploaderDestination=gcs://domain-registry-alpha-build-result-test
|
|
||||||
//
|
|
||||||
// If not set - the upload will be skipped
|
|
||||||
destination = uploaderDestination
|
|
||||||
|
|
||||||
// The location of the file containing the OAuth2 Google Cloud credentials.
|
|
||||||
//
|
|
||||||
// The file can contain a Service Account key file in JSON format from the
|
|
||||||
// Google Developers Console or a stored user credential using the format
|
|
||||||
// supported by the Cloud SDK.
|
|
||||||
//
|
|
||||||
// If no file is given - the default credentials are used.
|
|
||||||
credentialsFile = uploaderCredentialsFile
|
|
||||||
|
|
||||||
// If set to 'yes', each file will be uploaded to GCS in a separate thread.
|
|
||||||
// This is MUCH faster.
|
|
||||||
multithreadedUpload = uploaderMultithreadedUpload
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: 'dependencies.gradle'
|
apply from: 'dependencies.gradle'
|
||||||
|
|
||||||
apply from: 'dependency_lic.gradle'
|
apply from: 'dependency_lic.gradle'
|
||||||
|
|
||||||
apply from: 'utils.gradle'
|
apply from: 'utils.gradle'
|
||||||
|
|
||||||
// Custom task to run checkLicense in buildSrc, which is not triggered
|
|
||||||
// by root project tasks. A shell task is used because buildSrc tasks
|
|
||||||
// cannot be referenced in the same way as tasks from a regular included
|
|
||||||
// build.
|
|
||||||
task checkBuildSrcLicense(type:Exec) {
|
|
||||||
workingDir "${rootDir}/buildSrc"
|
|
||||||
commandLine '../gradlew', 'checkLicense'
|
|
||||||
}
|
|
||||||
tasks.checkLicense.dependsOn(tasks.checkBuildSrcLicense)
|
|
||||||
tasks.build.dependsOn(tasks.checkLicense)
|
tasks.build.dependsOn(tasks.checkLicense)
|
||||||
|
|
||||||
// Provide defaults for all of the project properties.
|
// Provide defaults for all of the project properties.
|
||||||
@@ -129,30 +95,27 @@ task stage {
|
|||||||
description = 'Generates application directories for all services.'
|
description = 'Generates application directories for all services.'
|
||||||
}
|
}
|
||||||
|
|
||||||
// App-engine environment configuration. We set up all of the variables in
|
|
||||||
// the root project.
|
|
||||||
|
|
||||||
def environments = ['production', 'sandbox', 'alpha', 'crash', 'qa']
|
|
||||||
|
|
||||||
def gcpProject = null
|
def gcpProject = null
|
||||||
|
|
||||||
apply from: "${rootDir.path}/projects.gradle"
|
apply from: "${rootDir.path}/projects.gradle"
|
||||||
|
|
||||||
if (environment == '') {
|
if (environment == '') {
|
||||||
// Keep the project null, this will prevent deployment. Set the
|
// Keep the project null, this will prevent deployment. Set the
|
||||||
// environment to "alpha" because other code needs this property to
|
// environment to "alpha" because other code needs this property to
|
||||||
// explode the war file.
|
// explode the war file.
|
||||||
environment = 'alpha'
|
environment = 'alpha'
|
||||||
} else if (environment != 'production' && environment != 'sandbox') {
|
} else {
|
||||||
gcpProject = projects[environment]
|
gcpProject = projects[environment]
|
||||||
if (gcpProject == null) {
|
if (gcpProject == null) {
|
||||||
throw new GradleException("-Penvironment must be one of " +
|
throw new GradleException("-Penvironment must be one of " +
|
||||||
"${projects.keySet()}.")
|
"${projects.keySet()}.")
|
||||||
}
|
}
|
||||||
|
project(':console-webapp').setProperty('configuration', environment)
|
||||||
}
|
}
|
||||||
|
|
||||||
rootProject.ext.environment = environment
|
rootProject.ext.environment = environment
|
||||||
rootProject.ext.gcpProject = gcpProject
|
rootProject.ext.gcpProject = gcpProject
|
||||||
|
rootProject.ext.baseDomain = baseDomains[environment]
|
||||||
rootProject.ext.prodOrSandboxEnv = environment in ['production', 'sandbox']
|
rootProject.ext.prodOrSandboxEnv = environment in ['production', 'sandbox']
|
||||||
|
|
||||||
// Function to verify that the deployment parameters have been set.
|
// Function to verify that the deployment parameters have been set.
|
||||||
@@ -212,6 +175,30 @@ allprojects {
|
|||||||
options.fork = true
|
options.fork = true
|
||||||
options.forkOptions.executable =
|
options.forkOptions.executable =
|
||||||
file("${System.env.JAVA_HOME}/bin/javac")
|
file("${System.env.JAVA_HOME}/bin/javac")
|
||||||
|
options.compilerArgs = ["--add-exports",
|
||||||
|
"jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED",
|
||||||
|
"--add-exports",
|
||||||
|
"jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED",
|
||||||
|
"--add-exports",
|
||||||
|
"jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
|
||||||
|
"--add-exports",
|
||||||
|
"jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
|
||||||
|
"--add-exports",
|
||||||
|
"jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
|
||||||
|
"--add-exports",
|
||||||
|
"jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED",
|
||||||
|
"--add-exports",
|
||||||
|
"jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED",
|
||||||
|
"--add-exports",
|
||||||
|
"jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED"]
|
||||||
|
options.forkOptions.jvmArgs = ["-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED",
|
||||||
|
"-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
|
||||||
|
"-J--add-exports=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED",
|
||||||
|
"-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
|
||||||
|
"-J--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
|
||||||
|
"-J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED",
|
||||||
|
"-J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED",
|
||||||
|
"-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -258,6 +245,12 @@ def javadocSource = []
|
|||||||
def javadocClasspath = []
|
def javadocClasspath = []
|
||||||
def javadocDependentTasks = []
|
def javadocDependentTasks = []
|
||||||
|
|
||||||
|
def services = [':services:default',
|
||||||
|
':services:backend',
|
||||||
|
':services:bsa',
|
||||||
|
':services:tools',
|
||||||
|
':services:pubapi']
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
// Skip no-op project
|
// Skip no-op project
|
||||||
if (project.name == 'services') return
|
if (project.name == 'services') return
|
||||||
@@ -278,6 +271,11 @@ subprojects {
|
|||||||
attributes 'Main-Class': mainClass
|
attributes 'Main-Class': mainClass
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Build as a multi-release jar since we've got member jars (e.g., dnsjava
|
||||||
|
// and snakeyaml) that are multi-release.
|
||||||
|
manifest {
|
||||||
|
attributes 'Multi-Release': true
|
||||||
|
}
|
||||||
zip64 = true
|
zip64 = true
|
||||||
archiveClassifier = ''
|
archiveClassifier = ''
|
||||||
archiveVersion = ''
|
archiveVersion = ''
|
||||||
@@ -308,8 +306,7 @@ subprojects {
|
|||||||
|
|
||||||
afterEvaluate {
|
afterEvaluate {
|
||||||
if (rootProject.enableDependencyLocking.toBoolean()
|
if (rootProject.enableDependencyLocking.toBoolean()
|
||||||
&& project.name != 'integration'
|
&& project.name != 'integration') {
|
||||||
&& project.name != 'java8compatibility') {
|
|
||||||
// The ':integration' project runs server/schema integration tests using
|
// The ':integration' project runs server/schema integration tests using
|
||||||
// dynamically specified jars with no transitive dependency. Therefore
|
// dynamically specified jars with no transitive dependency. Therefore
|
||||||
// dependency-locking does not make sense. Furthermore, during
|
// dependency-locking does not make sense. Furthermore, during
|
||||||
@@ -317,9 +314,6 @@ subprojects {
|
|||||||
// immutable. Locking activation would trigger an invalid operation
|
// immutable. Locking activation would trigger an invalid operation
|
||||||
// exception.
|
// exception.
|
||||||
//
|
//
|
||||||
// The ':java8compatibility' project is test-only. Its source does not go
|
|
||||||
// into production.
|
|
||||||
//
|
|
||||||
// For all other projects, due to problem with the gradle-license-report
|
// For all other projects, due to problem with the gradle-license-report
|
||||||
// plugin, the dependencyLicenseReport configuration must opt out of
|
// plugin, the dependencyLicenseReport configuration must opt out of
|
||||||
// dependency-locking. See dependency_lic.gradle for the reason why.
|
// dependency-locking. See dependency_lic.gradle for the reason why.
|
||||||
@@ -335,22 +329,6 @@ subprojects {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 'listenablefuture' is folded into guava since v32. This block is required
|
|
||||||
// until all transitive dependencies have upgraded past guava v32.
|
|
||||||
// TODO(periodically): remove this block and see if build succeeds.
|
|
||||||
configurations.all {
|
|
||||||
resolutionStrategy
|
|
||||||
.capabilitiesResolution
|
|
||||||
.withCapability("com.google.guava:listenablefuture") {
|
|
||||||
select("com.google.guava:guava:0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def services = [':services:default',
|
|
||||||
':services:backend',
|
|
||||||
':services:tools',
|
|
||||||
':services:pubapi']
|
|
||||||
|
|
||||||
// Set up all of the deployment projects.
|
// Set up all of the deployment projects.
|
||||||
if (services.contains(project.path)) {
|
if (services.contains(project.path)) {
|
||||||
|
|
||||||
@@ -362,12 +340,12 @@ subprojects {
|
|||||||
|
|
||||||
apply from: "${rootDir.path}/java_common.gradle"
|
apply from: "${rootDir.path}/java_common.gradle"
|
||||||
|
|
||||||
if (project.name != 'docs') {
|
// When changing Java version here, be sure to update BEAM Java runtime:
|
||||||
compileJava {
|
// search for `flex-template-base-image` and update the parameter value.
|
||||||
// TODO: Remove this once we migrate off AppEngine.
|
// There are at least two instances, one in core/build.gradle, one in
|
||||||
options.release = 8
|
// release/stage_beam_pipeline.sh
|
||||||
}
|
sourceCompatibility = JavaVersion.VERSION_21
|
||||||
}
|
targetCompatibility = JavaVersion.VERSION_21
|
||||||
|
|
||||||
project.tasks.test.dependsOn runPresubmits
|
project.tasks.test.dependsOn runPresubmits
|
||||||
|
|
||||||
@@ -392,9 +370,9 @@ subprojects {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// No need to produce javadoc for the docs subproject, which has no APIs to
|
// No need to produce javadoc for the jetty subproject, which has no APIs to
|
||||||
// expose to users.
|
// expose to users.
|
||||||
if (project.name != 'docs') {
|
if (project.name != 'jetty') {
|
||||||
javadocSource << project.sourceSets.main.allJava
|
javadocSource << project.sourceSets.main.allJava
|
||||||
javadocClasspath << project.sourceSets.main.runtimeClasspath
|
javadocClasspath << project.sourceSets.main.runtimeClasspath
|
||||||
javadocClasspath << "${buildDir}/generated/sources/annotationProcessor/java/main"
|
javadocClasspath << "${buildDir}/generated/sources/annotationProcessor/java/main"
|
||||||
@@ -402,6 +380,17 @@ subprojects {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Force SDK download and deployment to be sequential, otherwise parallel tasks
|
||||||
|
// will fail. For SDK download, they will try to write to the same location to
|
||||||
|
// upgrade gcloud. For deployment, they will try to deploy different services to
|
||||||
|
// the same project at the same time.
|
||||||
|
for (int i = 1; i < services.size(); i++) {
|
||||||
|
project("${services[i]}").downloadCloudSdk
|
||||||
|
.dependsOn(project("${services[i - 1]}").downloadCloudSdk)
|
||||||
|
project("${services[i]}").appengineDeployAll
|
||||||
|
.dependsOn(project("${services[i - 1]}").appengineDeployAll)
|
||||||
|
}
|
||||||
|
|
||||||
// If "-P verboseTestOutput=true" is passed in, configure all subprojects to dump all of their
|
// If "-P verboseTestOutput=true" is passed in, configure all subprojects to dump all of their
|
||||||
// output and final test status (pass/fail, errors) for each test class.
|
// output and final test status (pass/fail, errors) for each test class.
|
||||||
//
|
//
|
||||||
@@ -432,9 +421,6 @@ if (verboseTestOutput.toBoolean()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
task checkDependenciesDotGradle {
|
task checkDependenciesDotGradle {
|
||||||
def buildSrcDepsFile = File.createTempFile('buildSrc', 'deps')
|
|
||||||
buildSrcDepsFile.deleteOnExit()
|
|
||||||
dependsOn createGetBuildSrcDirectDepsTask(buildSrcDepsFile)
|
|
||||||
|
|
||||||
doLast {
|
doLast {
|
||||||
Set<String> depsInUse = []
|
Set<String> depsInUse = []
|
||||||
@@ -447,9 +433,7 @@ task checkDependenciesDotGradle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (buildSrcDepsFile.exists()) {
|
|
||||||
depsInUse.addAll(buildSrcDepsFile.readLines())
|
|
||||||
}
|
|
||||||
def unusedDeps =
|
def unusedDeps =
|
||||||
rootProject.dependencyMap.keySet()
|
rootProject.dependencyMap.keySet()
|
||||||
.findAll { !depsInUse.contains(it) }
|
.findAll { !depsInUse.contains(it) }
|
||||||
@@ -466,34 +450,23 @@ task checkDependenciesDotGradle {
|
|||||||
}
|
}
|
||||||
tasks.build.dependsOn(tasks.checkDependenciesDotGradle)
|
tasks.build.dependsOn(tasks.checkDependenciesDotGradle)
|
||||||
|
|
||||||
def createGetBuildSrcDirectDepsTask(outputFileName) {
|
|
||||||
return tasks
|
|
||||||
.create(
|
|
||||||
"getBuildSrcDeps_${java.util.UUID.randomUUID()}".toString(),
|
|
||||||
Exec) {
|
|
||||||
workingDir "${rootDir}/buildSrc"
|
|
||||||
commandLine '../gradlew', 'exportDependencies',
|
|
||||||
"-PdependencyExportFile=${outputFileName}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rootProject.ext {
|
rootProject.ext {
|
||||||
invokeJavaDiffFormatScript = { action ->
|
invokeJavaDiffFormatScript = { action ->
|
||||||
println("JAVA_HOME=${System.env.JAVA_HOME}")
|
def javaHome = project.findProperty('org.gradle.java.home')
|
||||||
println("PATH=${System.env.PATH}")
|
def javaBin
|
||||||
println(ext.execInBash("type java", "${rootDir}"))
|
if (javaHome != null) {
|
||||||
println(ext.execInBash("java -version", "${rootDir}"))
|
javaBin = "$javaHome/bin/java"
|
||||||
def scriptDir = rootDir.path.endsWith('buildSrc')
|
} else {
|
||||||
? "${rootDir}/../java-format"
|
javaBin = ext.execInBash("which java", rootDir)
|
||||||
: "${rootDir}/java-format"
|
}
|
||||||
def workingDir = rootDir.path.endsWith('buildSrc')
|
println("Running the formatting tool with $javaBin")
|
||||||
? "${rootDir}/.."
|
def scriptDir = "${rootDir}/java-format"
|
||||||
: rootDir
|
def workingDir = rootDir
|
||||||
def formatDiffScript = "${scriptDir}/google-java-format-git-diff.sh"
|
def formatDiffScript = "${scriptDir}/google-java-format-git-diff.sh"
|
||||||
def pythonExe = getPythonExecutable()
|
def pythonExe = getPythonExecutable()
|
||||||
|
|
||||||
return ext.execInBash(
|
return ext.execInBash(
|
||||||
"PYTHON=${pythonExe} ${formatDiffScript} ${action}", "${workingDir}")
|
"JAVA=${javaBin} PYTHON=${pythonExe} ${formatDiffScript} ${action}", "${workingDir}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -548,7 +521,9 @@ task javadoc(type: Javadoc) {
|
|||||||
destinationDir = file("${buildDir}/docs/javadoc")
|
destinationDir = file("${buildDir}/docs/javadoc")
|
||||||
options.encoding = "UTF-8"
|
options.encoding = "UTF-8"
|
||||||
// In a lot of places we don't write @return so suppress warnings about that.
|
// In a lot of places we don't write @return so suppress warnings about that.
|
||||||
options.addBooleanOption('Xdoclint:all,-missing', true)
|
// We don't report HTML lint errors because XJB-generated POJO files have
|
||||||
|
// incorrect tags (like dangling </p> without the corresponding open tag.
|
||||||
|
options.addBooleanOption('Xdoclint:all,-missing,-html', true)
|
||||||
options.addBooleanOption("-allow-script-in-comments",true)
|
options.addBooleanOption("-allow-script-in-comments",true)
|
||||||
options.tags = ["type:a:Generic Type",
|
options.tags = ["type:a:Generic Type",
|
||||||
"error:a:Expected Error",
|
"error:a:Expected Error",
|
||||||
@@ -566,7 +541,6 @@ task coreDev {
|
|||||||
dependsOn 'javadoc'
|
dependsOn 'javadoc'
|
||||||
dependsOn 'checkDependenciesDotGradle'
|
dependsOn 'checkDependenciesDotGradle'
|
||||||
dependsOn 'checkLicense'
|
dependsOn 'checkLicense'
|
||||||
dependsOn ':console-webapp:runConsoleWebappUnitTests'
|
|
||||||
dependsOn ':core:check'
|
dependsOn ':core:check'
|
||||||
dependsOn 'assemble'
|
dependsOn 'assemble'
|
||||||
}
|
}
|
||||||
@@ -583,7 +557,7 @@ task deployCloudSchedulerAndQueue {
|
|||||||
commandLine 'go', 'run',
|
commandLine 'go', 'run',
|
||||||
"./deployCloudSchedulerAndQueue.go",
|
"./deployCloudSchedulerAndQueue.go",
|
||||||
"${rootDir}/core/src/main/java/google/registry/config/files/nomulus-config-${env}.yaml",
|
"${rootDir}/core/src/main/java/google/registry/config/files/nomulus-config-${env}.yaml",
|
||||||
"${rootDir}/core/src/main/java/google/registry/env/${env}/default/WEB-INF/cloud-scheduler-tasks.xml",
|
"${rootDir}/core/src/main/java/google/registry/config/files/tasks/cloud-scheduler-tasks-${env}.xml",
|
||||||
"domain-registry-${env}"
|
"domain-registry-${env}"
|
||||||
}
|
}
|
||||||
exec {
|
exec {
|
||||||
@@ -591,7 +565,7 @@ task deployCloudSchedulerAndQueue {
|
|||||||
commandLine 'go', 'run',
|
commandLine 'go', 'run',
|
||||||
"./deployCloudSchedulerAndQueue.go",
|
"./deployCloudSchedulerAndQueue.go",
|
||||||
"${rootDir}/core/src/main/java/google/registry/config/files/nomulus-config-${env}.yaml",
|
"${rootDir}/core/src/main/java/google/registry/config/files/nomulus-config-${env}.yaml",
|
||||||
"${rootDir}/core/src/main/java/google/registry/env/common/default/WEB-INF/cloud-tasks-queue.xml",
|
"${rootDir}/core/src/main/java/google/registry/config/files/cloud-tasks-queue.xml",
|
||||||
"domain-registry-${env}"
|
"domain-registry-${env}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,131 +0,0 @@
|
|||||||
// Copyright 2019 The Nomulus Authors. 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.
|
|
||||||
|
|
||||||
import java.io.PrintStream;
|
|
||||||
|
|
||||||
val enableDependencyLocking: String by project
|
|
||||||
val allowInsecureProtocol: String by project
|
|
||||||
val allowInsecure = allowInsecureProtocol
|
|
||||||
|
|
||||||
buildscript {
|
|
||||||
// We need to do this again within "buildscript" because setting it in the
|
|
||||||
// main script doesn't affect build dependencies.
|
|
||||||
val enableDependencyLocking: String by project
|
|
||||||
if (enableDependencyLocking.toBoolean()) {
|
|
||||||
// Lock application dependencies.
|
|
||||||
dependencyLocking {
|
|
||||||
lockAllConfigurations()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
// Java static analysis plugins. Keep versions consistent with ../build.gradle
|
|
||||||
|
|
||||||
// We don't anticipate enabling the Gradle lint plugin because they will not support Kotlin
|
|
||||||
// See https://github.com/nebula-plugins/gradle-lint-plugin/issues/166
|
|
||||||
// id 'nebula.lint' version '16.0.2'
|
|
||||||
id("net.ltgt.errorprone") version "2.0.2"
|
|
||||||
checkstyle
|
|
||||||
id("com.diffplug.spotless") version "6.20.0"
|
|
||||||
}
|
|
||||||
|
|
||||||
checkstyle {
|
|
||||||
configDirectory.set(file("../config/checkstyle"))
|
|
||||||
}
|
|
||||||
|
|
||||||
println("enableDependencyLocking is $enableDependencyLocking")
|
|
||||||
if (enableDependencyLocking.toBoolean()) {
|
|
||||||
// Lock application dependencies.
|
|
||||||
dependencyLocking {
|
|
||||||
lockAllConfigurations()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
val mavenUrl = (project.ext.properties.get("mavenUrl") ?: "") as String
|
|
||||||
if (mavenUrl.isEmpty()) {
|
|
||||||
println("Java dependencies: Using Maven central...")
|
|
||||||
mavenCentral()
|
|
||||||
google()
|
|
||||||
} else {
|
|
||||||
maven {
|
|
||||||
println("Java dependencies: Using repo ${mavenUrl}...")
|
|
||||||
url = uri(mavenUrl)
|
|
||||||
isAllowInsecureProtocol = allowInsecureProtocol == "true"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
apply(from = "../dependencies.gradle")
|
|
||||||
apply(from = "../dependency_lic.gradle")
|
|
||||||
apply(from = "../java_common.gradle")
|
|
||||||
|
|
||||||
// 'listenablefuture' is folded into guava since v32. This block is required
|
|
||||||
// until all transitive dependencies have upgraded past guava v32.
|
|
||||||
// TODO(periodically): remove this block and see if build succeeds.
|
|
||||||
configurations.all {
|
|
||||||
resolutionStrategy
|
|
||||||
.capabilitiesResolution
|
|
||||||
.withCapability("com.google.guava:listenablefuture") {
|
|
||||||
select("com.google.guava:guava:0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
project.the<SourceSetContainer>()["main"].java {
|
|
||||||
srcDir("${project.buildDir}/generated/source/apt/main")
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
val deps = project.ext["dependencyMap"] as Map<*, *>
|
|
||||||
val implementation by configurations
|
|
||||||
val testImplementation by configurations
|
|
||||||
val annotationProcessor by configurations
|
|
||||||
implementation(deps["com.google.auth:google-auth-library-credentials"]!!)
|
|
||||||
implementation(deps["com.google.auth:google-auth-library-oauth2-http"]!!)
|
|
||||||
implementation(deps["com.google.auto.value:auto-value-annotations"]!!)
|
|
||||||
// implementation(deps["com.google.common.html.types:types"]!!)
|
|
||||||
implementation(deps["com.google.cloud:google-cloud-core"]!!)
|
|
||||||
implementation(deps["com.google.cloud:google-cloud-storage"]!!)
|
|
||||||
implementation(deps["com.google.guava:guava"]!!)
|
|
||||||
implementation(deps["com.google.protobuf:protobuf-java"]!!)
|
|
||||||
implementation(deps["com.google.template:soy"]!!)
|
|
||||||
implementation(deps["org.apache.commons:commons-text"]!!)
|
|
||||||
annotationProcessor(deps["com.google.auto.value:auto-value"]!!)
|
|
||||||
testImplementation(deps["com.google.truth:truth"]!!)
|
|
||||||
testImplementation(
|
|
||||||
deps["com.google.truth.extensions:truth-java8-extension"]!!)
|
|
||||||
testImplementation(deps["org.junit.jupiter:junit-jupiter-api"]!!)
|
|
||||||
testImplementation(deps["org.junit.jupiter:junit-jupiter-engine"]!!)
|
|
||||||
testImplementation(deps["org.mockito:mockito-core"]!!)
|
|
||||||
}
|
|
||||||
|
|
||||||
gradle.projectsEvaluated {
|
|
||||||
tasks.withType<JavaCompile> {
|
|
||||||
options.compilerArgs.add("-Xlint:unchecked")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.register("exportDependencies") {
|
|
||||||
doLast {
|
|
||||||
project.configurations.forEach {
|
|
||||||
println("dependency is $it")
|
|
||||||
// it.dependencies.findAll {
|
|
||||||
// it.group != null
|
|
||||||
// }.each {
|
|
||||||
// output.println("${it.group}:${it.name}")
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# This is a Gradle generated file for dependency locking.
|
|
||||||
# Manual edits can break the build and are not advised.
|
|
||||||
# This file is expected to be part of source control.
|
|
||||||
com.diffplug.durian:durian-collect:1.2.0=classpath
|
|
||||||
com.diffplug.durian:durian-core:1.2.0=classpath
|
|
||||||
com.diffplug.durian:durian-io:1.2.0=classpath
|
|
||||||
com.diffplug.durian:durian-swt.os:4.2.0=classpath
|
|
||||||
com.diffplug.spotless:com.diffplug.spotless.gradle.plugin:6.20.0=classpath
|
|
||||||
com.diffplug.spotless:spotless-lib-extra:2.40.0=classpath
|
|
||||||
com.diffplug.spotless:spotless-lib:2.40.0=classpath
|
|
||||||
com.diffplug.spotless:spotless-plugin-gradle:6.20.0=classpath
|
|
||||||
com.googlecode.concurrent-trees:concurrent-trees:2.6.1=classpath
|
|
||||||
com.googlecode.javaewah:JavaEWAH:1.2.3=classpath
|
|
||||||
com.squareup.okhttp3:okhttp:4.10.0=classpath
|
|
||||||
com.squareup.okio:okio-jvm:3.0.0=classpath
|
|
||||||
com.squareup.okio:okio:3.0.0=classpath
|
|
||||||
dev.equo.ide:solstice:1.3.1=classpath
|
|
||||||
net.ltgt.errorprone:net.ltgt.errorprone.gradle.plugin:2.0.2=classpath
|
|
||||||
net.ltgt.gradle:gradle-errorprone-plugin:2.0.2=classpath
|
|
||||||
org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r=classpath
|
|
||||||
org.eclipse.platform:org.eclipse.osgi:3.18.300=classpath
|
|
||||||
org.jetbrains.kotlin:kotlin-stdlib-common:1.9.0=classpath
|
|
||||||
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.0=classpath
|
|
||||||
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.0=classpath
|
|
||||||
org.jetbrains.kotlin:kotlin-stdlib:1.9.0=classpath
|
|
||||||
org.jetbrains:annotations:13.0=classpath
|
|
||||||
org.slf4j:slf4j-api:1.7.36=classpath
|
|
||||||
org.tukaani:xz:1.9=classpath
|
|
||||||
empty=
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
# This is a Gradle generated file for dependency locking.
|
|
||||||
# Manual edits can break the build and are not advised.
|
|
||||||
# This file is expected to be part of source control.
|
|
||||||
aopalliance:aopalliance:1.0=buildScriptClasspath,compileClasspath
|
|
||||||
args4j:args4j:2.0.23=buildScriptClasspath,compileClasspath
|
|
||||||
com.fasterxml.jackson.core:jackson-core:2.14.2=buildScriptClasspath,compileClasspath
|
|
||||||
com.fasterxml.jackson:jackson-bom:2.14.2=buildScriptClasspath,compileClasspath
|
|
||||||
com.github.ben-manes.caffeine:caffeine:2.7.0=annotationProcessor
|
|
||||||
com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor
|
|
||||||
com.google.android:annotations:4.1.1.4=buildScriptClasspath
|
|
||||||
com.google.api-client:google-api-client:2.2.0=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.api.grpc:gapic-google-cloud-storage-v2:2.22.6-alpha=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.api.grpc:grpc-google-cloud-storage-v2:2.22.6-alpha=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.api.grpc:proto-google-cloud-storage-v2:2.22.6-alpha=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.api.grpc:proto-google-common-protos:2.22.0=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.api.grpc:proto-google-iam-v1:1.17.0=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.api:api-common:2.14.0=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.api:gax-grpc:2.31.0=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.api:gax-httpjson:2.31.0=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.api:gax:2.31.0=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.apis:google-api-services-storage:v1-rev20230301-2.0.0=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.auth:google-auth-library-credentials:1.19.0=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.auth:google-auth-library-oauth2-http:1.19.0=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.auto.value:auto-value-annotations:1.10.1=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.auto.value:auto-value:1.10.4=annotationProcessor
|
|
||||||
com.google.auto:auto-common:0.10=annotationProcessor
|
|
||||||
com.google.cloud:google-cloud-core-grpc:2.21.0=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.cloud:google-cloud-core-http:2.21.0=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.cloud:google-cloud-core:2.21.0=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.cloud:google-cloud-storage:2.22.6=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.code.findbugs:jFormatString:3.0.0=annotationProcessor
|
|
||||||
com.google.code.findbugs:jsr305:3.0.2=annotationProcessor,buildScriptClasspath,compileClasspath
|
|
||||||
com.google.code.gson:gson:2.10.1=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.common.html.types:types:1.0.6=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.errorprone:error_prone_annotation:2.3.4=annotationProcessor
|
|
||||||
com.google.errorprone:error_prone_annotations:2.18.0=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.errorprone:error_prone_annotations:2.3.4=annotationProcessor
|
|
||||||
com.google.errorprone:error_prone_check_api:2.3.4=annotationProcessor
|
|
||||||
com.google.errorprone:error_prone_core:2.3.4=annotationProcessor
|
|
||||||
com.google.errorprone:error_prone_type_annotations:2.3.4=annotationProcessor
|
|
||||||
com.google.escapevelocity:escapevelocity:0.9.1=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.guava:failureaccess:1.0.1=annotationProcessor,buildScriptClasspath,compileClasspath
|
|
||||||
com.google.guava:guava-parent:32.1.1-jre=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.guava:guava:27.0.1-jre=annotationProcessor
|
|
||||||
com.google.guava:guava:32.1.1-jre=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor
|
|
||||||
com.google.http-client:google-http-client-apache-v2:1.43.3=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.http-client:google-http-client-appengine:1.43.3=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.http-client:google-http-client-gson:1.43.3=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.http-client:google-http-client-jackson2:1.43.3=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.http-client:google-http-client:1.43.3=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.inject.extensions:guice-multibindings:4.1.0=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.inject:guice:4.1.0=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.j2objc:j2objc-annotations:1.1=annotationProcessor
|
|
||||||
com.google.j2objc:j2objc-annotations:2.8=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.jsinterop:jsinterop-annotations:1.0.1=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.oauth-client:google-oauth-client:1.34.1=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.protobuf:protobuf-java-util:3.23.2=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.protobuf:protobuf-java:3.23.2=buildScriptClasspath,compileClasspath
|
|
||||||
com.google.protobuf:protobuf-java:3.4.0=annotationProcessor
|
|
||||||
com.google.re2j:re2j:1.6=buildScriptClasspath
|
|
||||||
com.google.template:soy:2021-02-01=buildScriptClasspath,compileClasspath
|
|
||||||
com.googlecode.java-diff-utils:diffutils:1.3.0=annotationProcessor
|
|
||||||
com.ibm.icu:icu4j:57.1=buildScriptClasspath,compileClasspath
|
|
||||||
commons-codec:commons-codec:1.15=buildScriptClasspath,compileClasspath
|
|
||||||
commons-logging:commons-logging:1.2=buildScriptClasspath,compileClasspath
|
|
||||||
io.grpc:grpc-alts:1.55.3=buildScriptClasspath,compileClasspath
|
|
||||||
io.grpc:grpc-api:1.55.3=buildScriptClasspath,compileClasspath
|
|
||||||
io.grpc:grpc-auth:1.55.3=buildScriptClasspath,compileClasspath
|
|
||||||
io.grpc:grpc-context:1.55.3=buildScriptClasspath,compileClasspath
|
|
||||||
io.grpc:grpc-core:1.55.3=buildScriptClasspath
|
|
||||||
io.grpc:grpc-googleapis:1.55.3=buildScriptClasspath
|
|
||||||
io.grpc:grpc-grpclb:1.55.3=buildScriptClasspath,compileClasspath
|
|
||||||
io.grpc:grpc-netty-shaded:1.55.3=buildScriptClasspath
|
|
||||||
io.grpc:grpc-protobuf-lite:1.55.3=buildScriptClasspath,compileClasspath
|
|
||||||
io.grpc:grpc-protobuf:1.55.3=buildScriptClasspath,compileClasspath
|
|
||||||
io.grpc:grpc-rls:1.55.3=buildScriptClasspath
|
|
||||||
io.grpc:grpc-services:1.55.3=buildScriptClasspath
|
|
||||||
io.grpc:grpc-stub:1.55.3=buildScriptClasspath,compileClasspath
|
|
||||||
io.grpc:grpc-xds:1.55.3=buildScriptClasspath
|
|
||||||
io.opencensus:opencensus-api:0.31.1=buildScriptClasspath,compileClasspath
|
|
||||||
io.opencensus:opencensus-contrib-http-util:0.31.1=buildScriptClasspath,compileClasspath
|
|
||||||
io.opencensus:opencensus-proto:0.2.0=buildScriptClasspath
|
|
||||||
io.perfmark:perfmark-api:0.26.0=buildScriptClasspath
|
|
||||||
javax.annotation:javax.annotation-api:1.3.2=buildScriptClasspath,compileClasspath
|
|
||||||
javax.annotation:jsr250-api:1.0=buildScriptClasspath,compileClasspath
|
|
||||||
javax.inject:javax.inject:1=buildScriptClasspath,compileClasspath
|
|
||||||
org.apache.commons:commons-lang3:3.12.0=buildScriptClasspath,compileClasspath
|
|
||||||
org.apache.commons:commons-text:1.10.0=buildScriptClasspath,compileClasspath
|
|
||||||
org.apache.httpcomponents:httpclient:4.5.14=buildScriptClasspath,compileClasspath
|
|
||||||
org.apache.httpcomponents:httpcore:4.4.16=buildScriptClasspath,compileClasspath
|
|
||||||
org.checkerframework:checker-qual:3.0.0=annotationProcessor
|
|
||||||
org.checkerframework:checker-qual:3.33.0=buildScriptClasspath,compileClasspath
|
|
||||||
org.checkerframework:dataflow:3.0.0=annotationProcessor
|
|
||||||
org.checkerframework:javacutil:3.0.0=annotationProcessor
|
|
||||||
org.codehaus.mojo:animal-sniffer-annotations:1.17=annotationProcessor
|
|
||||||
org.codehaus.mojo:animal-sniffer-annotations:1.23=buildScriptClasspath
|
|
||||||
org.conscrypt:conscrypt-openjdk-uber:2.5.2=buildScriptClasspath,compileClasspath
|
|
||||||
org.json:json:20160212=buildScriptClasspath,compileClasspath
|
|
||||||
org.ow2.asm:asm-analysis:7.0=buildScriptClasspath,compileClasspath
|
|
||||||
org.ow2.asm:asm-commons:7.0=buildScriptClasspath,compileClasspath
|
|
||||||
org.ow2.asm:asm-tree:7.0=buildScriptClasspath,compileClasspath
|
|
||||||
org.ow2.asm:asm-util:7.0=buildScriptClasspath,compileClasspath
|
|
||||||
org.ow2.asm:asm:7.0=buildScriptClasspath,compileClasspath
|
|
||||||
org.pcollections:pcollections:2.1.2=annotationProcessor
|
|
||||||
org.plumelib:plume-util:1.0.6=annotationProcessor
|
|
||||||
org.plumelib:reflection-util:0.0.2=annotationProcessor
|
|
||||||
org.plumelib:require-javadoc:0.1.0=annotationProcessor
|
|
||||||
org.threeten:threetenbp:1.6.8=buildScriptClasspath,compileClasspath
|
|
||||||
empty=
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
pluginsUrl=https://plugins.gradle.org/m2/
|
|
||||||
allowInsecureProtocol=
|
|
||||||
enableDependencyLocking=true
|
|
||||||
@@ -1,211 +0,0 @@
|
|||||||
// Copyright 2019 The Nomulus Authors. 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 google.registry.gradle.plugin;
|
|
||||||
|
|
||||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
|
||||||
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
|
||||||
import static com.google.common.collect.ImmutableSetMultimap.toImmutableSetMultimap;
|
|
||||||
import static com.google.common.io.Resources.getResource;
|
|
||||||
import static google.registry.gradle.plugin.GcsPluginUtils.toByteArraySupplier;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
|
||||||
import com.google.common.collect.ImmutableSetMultimap;
|
|
||||||
import com.google.common.html.types.TrustedResourceUrls;
|
|
||||||
import com.google.template.soy.SoyFileSet;
|
|
||||||
import com.google.template.soy.tofu.SoyTofu;
|
|
||||||
import google.registry.gradle.plugin.ProjectData.TaskData;
|
|
||||||
import java.io.File;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the files for a web-page summary of a given {@Link ProjectData}.
|
|
||||||
*
|
|
||||||
* <p>The main job of this class is rendering a tailored cover page that includes information about
|
|
||||||
* the project and any task that ran.
|
|
||||||
*
|
|
||||||
* <p>It returns all the files that need uploading for the cover page to work. This includes any
|
|
||||||
* report and log files linked to in the ProjectData, as well as a cover page (and associated
|
|
||||||
* resources such as CSS files).
|
|
||||||
*/
|
|
||||||
final class CoverPageGenerator {
|
|
||||||
|
|
||||||
/** List of all resource files that will be uploaded as-is. */
|
|
||||||
private static final ImmutableSet<Path> STATIC_RESOURCE_FILES =
|
|
||||||
ImmutableSet.of(Paths.get("css", "style.css"));
|
|
||||||
/** Name of the entry-point file that will be created. */
|
|
||||||
private static final Path ENTRY_POINT = Paths.get("index.html");
|
|
||||||
|
|
||||||
private final ProjectData projectData;
|
|
||||||
private final ImmutableSetMultimap<TaskData.State, TaskData> tasksByState;
|
|
||||||
/**
|
|
||||||
* The compiled SOY files.
|
|
||||||
*
|
|
||||||
* <p>Will be generated only when actually needed, because it takes a while to compile and we
|
|
||||||
* don't want that to happen unless we actually use it.
|
|
||||||
*/
|
|
||||||
private SoyTofu tofu = null;
|
|
||||||
|
|
||||||
CoverPageGenerator(ProjectData projectData) {
|
|
||||||
this.projectData = projectData;
|
|
||||||
this.tasksByState =
|
|
||||||
projectData.tasks().stream().collect(toImmutableSetMultimap(TaskData::state, task -> task));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all the files that need uploading for the cover page to work.
|
|
||||||
*
|
|
||||||
* <p>This includes all the report files as well, to make sure that the link works.
|
|
||||||
*/
|
|
||||||
FilesWithEntryPoint getFilesToUpload() {
|
|
||||||
ImmutableMap.Builder<Path, Supplier<byte[]>> builder = new ImmutableMap.Builder<>();
|
|
||||||
// Add all the static resource pages
|
|
||||||
STATIC_RESOURCE_FILES.stream().forEach(file -> builder.put(file, resourceLoader(file)));
|
|
||||||
// Create the cover page
|
|
||||||
// Note that the ByteArraySupplier here is lazy - the createCoverPage function is only called
|
|
||||||
// when the resulting Supplier's get function is called.
|
|
||||||
builder.put(ENTRY_POINT, toByteArraySupplier(this::createCoverPage));
|
|
||||||
// Add all the files from the tasks
|
|
||||||
tasksByState.values().stream()
|
|
||||||
.flatMap(task -> task.reports().values().stream())
|
|
||||||
.forEach(reportFiles -> builder.putAll(reportFiles.files()));
|
|
||||||
// Add the logs of every test
|
|
||||||
tasksByState.values().stream()
|
|
||||||
.filter(task -> task.log().isPresent())
|
|
||||||
.forEach(task -> builder.put(getLogPath(task), task.log().get()));
|
|
||||||
|
|
||||||
return FilesWithEntryPoint.create(builder.build(), ENTRY_POINT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Renders the cover page. */
|
|
||||||
private String createCoverPage() {
|
|
||||||
return getTofu()
|
|
||||||
.newRenderer("google.registry.gradle.plugin.coverPage")
|
|
||||||
.setData(getSoyData())
|
|
||||||
.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Converts the projectData and all taskData into all the data the soy template needs. */
|
|
||||||
private ImmutableMap<String, Object> getSoyData() {
|
|
||||||
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
|
|
||||||
|
|
||||||
TaskData.State state =
|
|
||||||
tasksByState.containsKey(TaskData.State.FAILURE)
|
|
||||||
? TaskData.State.FAILURE
|
|
||||||
: TaskData.State.SUCCESS;
|
|
||||||
String title =
|
|
||||||
state != TaskData.State.FAILURE
|
|
||||||
? "Success!"
|
|
||||||
: "Failed: "
|
|
||||||
+ tasksByState.get(state).stream()
|
|
||||||
.map(TaskData::uniqueName)
|
|
||||||
.collect(Collectors.joining(", "));
|
|
||||||
|
|
||||||
builder.put("projectState", state.toString());
|
|
||||||
builder.put("title", title);
|
|
||||||
builder.put("cssFiles", ImmutableSet.of(TrustedResourceUrls.fromConstant("css/style.css")));
|
|
||||||
builder.put("invocation", getInvocation());
|
|
||||||
builder.put("tasksByState", getTasksByStateSoyData());
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a soy-friendly map from the TaskData.State to the task itslef.
|
|
||||||
*
|
|
||||||
* <p>The key order in the resulting map is always the same (the order from the enum definition)
|
|
||||||
* no matter the key order in the original tasksByState map.
|
|
||||||
*/
|
|
||||||
private ImmutableMap<String, Object> getTasksByStateSoyData() {
|
|
||||||
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
|
|
||||||
|
|
||||||
// We go over the States in the order they are defined rather than the order in which they
|
|
||||||
// happen to be in the tasksByState Map.
|
|
||||||
//
|
|
||||||
// That way we guarantee a consistent order.
|
|
||||||
for (TaskData.State state : TaskData.State.values()) {
|
|
||||||
builder.put(
|
|
||||||
state.toString(),
|
|
||||||
tasksByState.get(state).stream()
|
|
||||||
.map(task -> taskDataToSoy(task))
|
|
||||||
.collect(toImmutableList()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** returns a soy-friendly version of the given task data. */
|
|
||||||
static ImmutableMap<String, Object> taskDataToSoy(TaskData task) {
|
|
||||||
// Note that all instances of File.separator are replaced with forward slashes so that we can
|
|
||||||
// generate a valid href on Windows.
|
|
||||||
return new ImmutableMap.Builder<String, Object>()
|
|
||||||
.put("uniqueName", task.uniqueName())
|
|
||||||
.put("description", task.description())
|
|
||||||
.put(
|
|
||||||
"log",
|
|
||||||
task.log().isPresent() ? getLogPath(task).toString().replace(File.separator, "/") : "")
|
|
||||||
.put(
|
|
||||||
"reports",
|
|
||||||
task.reports().entrySet().stream()
|
|
||||||
.collect(
|
|
||||||
toImmutableMap(
|
|
||||||
Map.Entry::getKey,
|
|
||||||
entry ->
|
|
||||||
entry.getValue().files().isEmpty()
|
|
||||||
? ""
|
|
||||||
: entry
|
|
||||||
.getValue()
|
|
||||||
.entryPoint()
|
|
||||||
.toString()
|
|
||||||
.replace(File.separator, "/"))))
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getInvocation() {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
builder.append("./gradlew");
|
|
||||||
projectData.tasksRequested().forEach(task -> builder.append(" ").append(task));
|
|
||||||
projectData
|
|
||||||
.projectProperties()
|
|
||||||
.forEach((key, value) -> builder.append(String.format(" -P %s=%s", key, value)));
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns a lazily created soy renderer */
|
|
||||||
private SoyTofu getTofu() {
|
|
||||||
if (tofu == null) {
|
|
||||||
tofu =
|
|
||||||
SoyFileSet.builder()
|
|
||||||
.add(getResource(CoverPageGenerator.class, "soy/coverpage.soy"))
|
|
||||||
.build()
|
|
||||||
.compileToTofu();
|
|
||||||
}
|
|
||||||
return tofu;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Path getLogPath(TaskData task) {
|
|
||||||
// We replace colons with dashes so that the resulting filename is always valid, even in
|
|
||||||
// Windows. As a dash is not a valid character in Java identifies, a task name cannot include
|
|
||||||
// it, so the uniqueness of the name is perserved.
|
|
||||||
return Paths.get("logs", task.uniqueName().replace(":", "-") + ".log");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Supplier<byte[]> resourceLoader(Path path) {
|
|
||||||
return toByteArraySupplier(getResource(CoverPageGenerator.class, path.toString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
// Copyright 2019 The Nomulus Authors. 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 google.registry.gradle.plugin;
|
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
|
||||||
|
|
||||||
import com.google.auto.value.AutoValue;
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds a set of files with a browser-friendly entry point to those files.
|
|
||||||
*
|
|
||||||
* <p>The file data is lazily generated.
|
|
||||||
*
|
|
||||||
* <p>If there is at least one file, it's guaranteed that the entry point is one of these files.
|
|
||||||
*/
|
|
||||||
@AutoValue
|
|
||||||
abstract class FilesWithEntryPoint {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All files that are part of this report, keyed from their path to a supplier of their content.
|
|
||||||
*
|
|
||||||
* <p>The reason we use a supplier instead of loading the content is in case the content is very
|
|
||||||
* large...
|
|
||||||
*
|
|
||||||
* <p>Also, no point in doing IO before we need it!
|
|
||||||
*/
|
|
||||||
abstract ImmutableMap<Path, Supplier<byte[]>> files();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The file that gives access (links...) to all the data in the report.
|
|
||||||
*
|
|
||||||
* <p>Guaranteed to be a key in {@link #files} if and only if files isn't empty.
|
|
||||||
*/
|
|
||||||
abstract Path entryPoint();
|
|
||||||
|
|
||||||
static FilesWithEntryPoint create(ImmutableMap<Path, Supplier<byte[]>> files, Path entryPoint) {
|
|
||||||
checkArgument(files.isEmpty() || files.containsKey(entryPoint));
|
|
||||||
return new AutoValue_FilesWithEntryPoint(files, entryPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
static FilesWithEntryPoint createSingleFile(Path path, Supplier<byte[]> data) {
|
|
||||||
return create(ImmutableMap.of(path, data), path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,221 +0,0 @@
|
|||||||
// Copyright 2019 The Nomulus Authors. 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 google.registry.gradle.plugin;
|
|
||||||
|
|
||||||
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
|
||||||
import static com.google.common.collect.Iterables.getOnlyElement;
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
|
||||||
|
|
||||||
import com.google.cloud.storage.BlobInfo;
|
|
||||||
import com.google.cloud.storage.Storage;
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import com.google.common.collect.Streams;
|
|
||||||
import com.google.common.io.Files;
|
|
||||||
import com.google.common.io.Resources;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.UncheckedIOException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
import java.util.zip.ZipEntry;
|
|
||||||
import java.util.zip.ZipOutputStream;
|
|
||||||
|
|
||||||
/** Utility functions used in the GCS plugin. */
|
|
||||||
final class GcsPluginUtils {
|
|
||||||
|
|
||||||
private static final ImmutableMap<String, String> EXTENSION_TO_CONTENT_TYPE =
|
|
||||||
new ImmutableMap.Builder<String, String>()
|
|
||||||
.put("html", "text/html")
|
|
||||||
.put("htm", "text/html")
|
|
||||||
.put("log", "text/plain")
|
|
||||||
.put("txt", "text/plain")
|
|
||||||
.put("css", "text/css")
|
|
||||||
.put("xml", "text/xml")
|
|
||||||
.put("zip", "application/zip")
|
|
||||||
.put("js", "text/javascript")
|
|
||||||
.build();
|
|
||||||
|
|
||||||
private static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";
|
|
||||||
|
|
||||||
static Path toNormalizedPath(File file) {
|
|
||||||
return file.toPath().toAbsolutePath().normalize();
|
|
||||||
}
|
|
||||||
|
|
||||||
static Path toNormalizedPath(Path file) {
|
|
||||||
return file.toAbsolutePath().normalize();
|
|
||||||
}
|
|
||||||
|
|
||||||
static String getContentType(String fileName) {
|
|
||||||
return EXTENSION_TO_CONTENT_TYPE.getOrDefault(
|
|
||||||
Files.getFileExtension(fileName), DEFAULT_CONTENT_TYPE);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void uploadFileToGcs(
|
|
||||||
Storage storage, String bucket, Path path, Supplier<byte[]> dataSupplier) {
|
|
||||||
// Replace Windows file separators with forward slashes.
|
|
||||||
String filename = path.toString().replace(File.separator, "/");
|
|
||||||
storage.create(
|
|
||||||
BlobInfo.newBuilder(bucket, filename).setContentType(getContentType(filename)).build(),
|
|
||||||
dataSupplier.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
static void uploadFilesToGcsMultithread(
|
|
||||||
Storage storage, String bucket, Path folder, Map<Path, Supplier<byte[]>> files) {
|
|
||||||
ImmutableMap.Builder<Path, Thread> threads = new ImmutableMap.Builder<>();
|
|
||||||
files.forEach(
|
|
||||||
(path, dataSupplier) -> {
|
|
||||||
Thread thread =
|
|
||||||
new Thread(
|
|
||||||
() -> uploadFileToGcs(storage, bucket, folder.resolve(path), dataSupplier));
|
|
||||||
thread.start();
|
|
||||||
threads.put(path, thread);
|
|
||||||
});
|
|
||||||
threads
|
|
||||||
.build()
|
|
||||||
.forEach(
|
|
||||||
(path, thread) -> {
|
|
||||||
try {
|
|
||||||
thread.join();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
System.out.format("Upload of %s interrupted", path);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static Supplier<byte[]> toByteArraySupplier(String data) {
|
|
||||||
return () -> data.getBytes(UTF_8);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Supplier<byte[]> toByteArraySupplier(Supplier<String> dataSupplier) {
|
|
||||||
return () -> dataSupplier.get().getBytes(UTF_8);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Supplier<byte[]> toByteArraySupplier(File file) {
|
|
||||||
return () -> {
|
|
||||||
try {
|
|
||||||
return Files.toByteArray(file);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new UncheckedIOException(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static Supplier<byte[]> toByteArraySupplier(URL url) {
|
|
||||||
return () -> {
|
|
||||||
try {
|
|
||||||
return Resources.toByteArray(url);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new UncheckedIOException(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads all the files generated by a Report into a FilesWithEntryPoint object.
|
|
||||||
*
|
|
||||||
* <p>Every FilesWithEntryPoint must have a single link "entry point" that gives users access to
|
|
||||||
* all the files. If the report generated just one file - we will just link to that file.
|
|
||||||
*
|
|
||||||
* <p>However, if the report generated more than one file - the only thing we can safely do is to
|
|
||||||
* zip all the files and link to the zip file.
|
|
||||||
*
|
|
||||||
* <p>As an alternative to using a zip file, we allow the caller to supply an optional "entry
|
|
||||||
* point" file that will link to all the other files. If that file is given and is "appropriate"
|
|
||||||
* (exists and is in the correct location) - we will upload all the report files "as is" and link
|
|
||||||
* to the entry file.
|
|
||||||
*
|
|
||||||
* @param destination the location of the output. Either a file or a directory. If a directory -
|
|
||||||
* then all the files inside that directory are the outputs we're looking for.
|
|
||||||
* @param entryPointHint If present - a hint to what the entry point to this directory tree is.
|
|
||||||
* Will only be used if all of the following apply: (a) {@code
|
|
||||||
* destination.isDirectory()==true}, (b) there are 2 or more files in the {@code destination}
|
|
||||||
* directory, and (c) {@code entryPointHint.get()} is one of the files nested inside of the
|
|
||||||
* {@code destination} directory.
|
|
||||||
*/
|
|
||||||
static FilesWithEntryPoint readFilesWithEntryPoint(
|
|
||||||
File destination, Optional<File> entryPointHint, Path rootDir) {
|
|
||||||
|
|
||||||
Path destinationPath = rootDir.relativize(toNormalizedPath(destination));
|
|
||||||
|
|
||||||
if (destination.isFile()) {
|
|
||||||
// The destination is a single file - find its root, and add this single file to the
|
|
||||||
// FilesWithEntryPoint.
|
|
||||||
return FilesWithEntryPoint.createSingleFile(
|
|
||||||
destinationPath, toByteArraySupplier(destination));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!destination.isDirectory()) {
|
|
||||||
// This isn't a file nor a directory - so it doesn't exist! Return empty FilesWithEntryPoint
|
|
||||||
return FilesWithEntryPoint.create(ImmutableMap.of(), destinationPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The destination is a directory - find all the actual files first
|
|
||||||
ImmutableMap<Path, Supplier<byte[]>> files =
|
|
||||||
Streams.stream(Files.fileTraverser().depthFirstPreOrder(destination))
|
|
||||||
.filter(File::isFile)
|
|
||||||
.collect(
|
|
||||||
toImmutableMap(
|
|
||||||
file -> rootDir.relativize(toNormalizedPath(file)),
|
|
||||||
GcsPluginUtils::toByteArraySupplier));
|
|
||||||
|
|
||||||
if (files.isEmpty()) {
|
|
||||||
// The directory exists, but is empty. Return empty FilesWithEntryPoint
|
|
||||||
return FilesWithEntryPoint.create(ImmutableMap.of(), destinationPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (files.size() == 1) {
|
|
||||||
// We got a directory, but it only has a single file. We can link to that.
|
|
||||||
return FilesWithEntryPoint.create(files, getOnlyElement(files.keySet()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// There are multiple files in the report! We need to check the entryPointHint
|
|
||||||
Optional<Path> entryPointPath =
|
|
||||||
entryPointHint.map(file -> rootDir.relativize(toNormalizedPath(file)));
|
|
||||||
|
|
||||||
if (entryPointPath.isPresent() && files.containsKey(entryPointPath.get())) {
|
|
||||||
// We were given the entry point! Use it!
|
|
||||||
return FilesWithEntryPoint.create(files, entryPointPath.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
// We weren't given an appropriate entry point. But we still need a single link to all this data
|
|
||||||
// - so we'll zip it and just host a single file.
|
|
||||||
Path zipFilePath = destinationPath.resolve(destinationPath.getFileName().toString() + ".zip");
|
|
||||||
return FilesWithEntryPoint.createSingleFile(zipFilePath, createZippedByteArraySupplier(files));
|
|
||||||
}
|
|
||||||
|
|
||||||
static Supplier<byte[]> createZippedByteArraySupplier(Map<Path, Supplier<byte[]>> files) {
|
|
||||||
return () -> zipFiles(files);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] zipFiles(Map<Path, Supplier<byte[]>> files) {
|
|
||||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
|
||||||
try (ZipOutputStream zip = new ZipOutputStream(output)) {
|
|
||||||
for (Path path : files.keySet()) {
|
|
||||||
zip.putNextEntry(new ZipEntry(path.toString()));
|
|
||||||
zip.write(files.get(path).get());
|
|
||||||
zip.closeEntry();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new UncheckedIOException(e);
|
|
||||||
}
|
|
||||||
return output.toByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private GcsPluginUtils() {}
|
|
||||||
}
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
// Copyright 2019 The Nomulus Authors. 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 google.registry.gradle.plugin;
|
|
||||||
|
|
||||||
import com.google.auto.value.AutoValue;
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All the data of a root Gradle project.
|
|
||||||
*
|
|
||||||
* <p>This is basically all the "relevant" data from a Gradle Project, arranged in an immutable and
|
|
||||||
* more convenient way.
|
|
||||||
*/
|
|
||||||
@AutoValue
|
|
||||||
abstract class ProjectData {
|
|
||||||
|
|
||||||
abstract String name();
|
|
||||||
|
|
||||||
abstract String description();
|
|
||||||
|
|
||||||
abstract String gradleVersion();
|
|
||||||
|
|
||||||
abstract ImmutableMap<String, String> projectProperties();
|
|
||||||
|
|
||||||
abstract ImmutableMap<String, String> systemProperties();
|
|
||||||
|
|
||||||
abstract ImmutableSet<String> tasksRequested();
|
|
||||||
|
|
||||||
abstract ImmutableSet<TaskData> tasks();
|
|
||||||
|
|
||||||
abstract Builder toBuilder();
|
|
||||||
|
|
||||||
static Builder builder() {
|
|
||||||
return new AutoValue_ProjectData.Builder();
|
|
||||||
}
|
|
||||||
|
|
||||||
@AutoValue.Builder
|
|
||||||
abstract static class Builder {
|
|
||||||
abstract Builder setName(String name);
|
|
||||||
|
|
||||||
abstract Builder setDescription(String description);
|
|
||||||
|
|
||||||
abstract Builder setGradleVersion(String gradleVersion);
|
|
||||||
|
|
||||||
abstract Builder setProjectProperties(Map<String, String> projectProperties);
|
|
||||||
|
|
||||||
abstract Builder setSystemProperties(Map<String, String> systemProperties);
|
|
||||||
|
|
||||||
abstract Builder setTasksRequested(Iterable<String> tasksRequested);
|
|
||||||
|
|
||||||
abstract ImmutableSet.Builder<TaskData> tasksBuilder();
|
|
||||||
|
|
||||||
Builder addTask(TaskData task) {
|
|
||||||
tasksBuilder().add(task);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract ProjectData build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Relevant data to a single Task's.
|
|
||||||
*
|
|
||||||
* <p>Some Tasks are also "Reporting", meaning they create file outputs we want to upload in
|
|
||||||
* various formats. The format that interests us the most is "html", as that's nicely browsable,
|
|
||||||
* but they might also have other formats.
|
|
||||||
*/
|
|
||||||
@AutoValue
|
|
||||||
abstract static class TaskData {
|
|
||||||
|
|
||||||
enum State {
|
|
||||||
/** The task has failed for some reason. */
|
|
||||||
FAILURE,
|
|
||||||
/** The task was actually run and has finished successfully. */
|
|
||||||
SUCCESS,
|
|
||||||
/** The task was up-to-date and successful, and hence didn't need to run again. */
|
|
||||||
UP_TO_DATE
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract String uniqueName();
|
|
||||||
|
|
||||||
abstract String description();
|
|
||||||
|
|
||||||
abstract State state();
|
|
||||||
|
|
||||||
abstract Optional<Supplier<byte[]>> log();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the FilesWithEntryPoint for every report, keyed on the report type.
|
|
||||||
*
|
|
||||||
* <p>The "html" report type is the most interesting, but there are other report formats.
|
|
||||||
*/
|
|
||||||
abstract ImmutableMap<String, FilesWithEntryPoint> reports();
|
|
||||||
|
|
||||||
abstract Builder toBuilder();
|
|
||||||
|
|
||||||
static Builder builder() {
|
|
||||||
return new AutoValue_ProjectData_TaskData.Builder();
|
|
||||||
}
|
|
||||||
|
|
||||||
@AutoValue.Builder
|
|
||||||
abstract static class Builder {
|
|
||||||
abstract Builder setUniqueName(String name);
|
|
||||||
|
|
||||||
abstract Builder setDescription(String description);
|
|
||||||
|
|
||||||
abstract Builder setState(State state);
|
|
||||||
|
|
||||||
abstract Builder setLog(Supplier<byte[]> log);
|
|
||||||
|
|
||||||
abstract ImmutableMap.Builder<String, FilesWithEntryPoint> reportsBuilder();
|
|
||||||
|
|
||||||
Builder putReport(String type, FilesWithEntryPoint reportFiles) {
|
|
||||||
reportsBuilder().put(type, reportFiles);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract TaskData build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,311 +0,0 @@
|
|||||||
// Copyright 2019 The Nomulus Authors. 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 google.registry.gradle.plugin;
|
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
|
||||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
|
||||||
import static google.registry.gradle.plugin.GcsPluginUtils.readFilesWithEntryPoint;
|
|
||||||
import static google.registry.gradle.plugin.GcsPluginUtils.toByteArraySupplier;
|
|
||||||
import static google.registry.gradle.plugin.GcsPluginUtils.toNormalizedPath;
|
|
||||||
import static google.registry.gradle.plugin.GcsPluginUtils.uploadFileToGcs;
|
|
||||||
import static google.registry.gradle.plugin.GcsPluginUtils.uploadFilesToGcsMultithread;
|
|
||||||
|
|
||||||
import com.google.auth.oauth2.GoogleCredentials;
|
|
||||||
import com.google.cloud.storage.Storage;
|
|
||||||
import com.google.cloud.storage.StorageOptions;
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import com.google.common.io.Files;
|
|
||||||
import google.registry.gradle.plugin.ProjectData.TaskData;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.UncheckedIOException;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.function.BiConsumer;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
import org.gradle.api.DefaultTask;
|
|
||||||
import org.gradle.api.Project;
|
|
||||||
import org.gradle.api.Task;
|
|
||||||
import org.gradle.api.reporting.DirectoryReport;
|
|
||||||
import org.gradle.api.reporting.Report;
|
|
||||||
import org.gradle.api.reporting.ReportContainer;
|
|
||||||
import org.gradle.api.reporting.Reporting;
|
|
||||||
import org.gradle.api.tasks.TaskAction;
|
|
||||||
|
|
||||||
/** A task that uploads the Reports generated by other tasks to GCS. */
|
|
||||||
public class ReportUploader extends DefaultTask {
|
|
||||||
|
|
||||||
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
|
|
||||||
private static final ImmutableMap<String, BiConsumer<ReportUploader, String>> UPLOAD_FUNCTIONS =
|
|
||||||
ImmutableMap.of(
|
|
||||||
"file://", ReportUploader::saveResultsToLocalFolder,
|
|
||||||
"gcs://", ReportUploader::uploadResultsToGcs);
|
|
||||||
|
|
||||||
private final ArrayList<Task> tasks = new ArrayList<>();
|
|
||||||
private final HashMap<String, StringBuilder> logs = new HashMap<>();
|
|
||||||
private Project project;
|
|
||||||
|
|
||||||
private String destination = null;
|
|
||||||
private String credentialsFile = null;
|
|
||||||
private String multithreadedUpload = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the destination of the reports.
|
|
||||||
*
|
|
||||||
* <p>Currently supports two types of destinations:
|
|
||||||
*
|
|
||||||
* <ul>
|
|
||||||
* <li>file://[absulute local path], e.g. file:///tmp/buildOutputs/
|
|
||||||
* <li>gcs://[bucket name]/[optional path], e.g. gcs://my-bucket/buildOutputs/
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
public void setDestination(String destination) {
|
|
||||||
this.destination = destination;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCredentialsFile(String credentialsFile) {
|
|
||||||
this.credentialsFile = credentialsFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMultithreadedUpload(String multithreadedUpload) {
|
|
||||||
this.multithreadedUpload = multithreadedUpload;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Converts the given Gradle Project into a ProjectData. */
|
|
||||||
private ProjectData createProjectData() {
|
|
||||||
ProjectData.Builder builder =
|
|
||||||
ProjectData.builder()
|
|
||||||
.setName(project.getPath() + project.getName())
|
|
||||||
.setDescription(
|
|
||||||
Optional.ofNullable(project.getDescription()).orElse("[No description available]"))
|
|
||||||
.setGradleVersion(project.getGradle().getGradleVersion())
|
|
||||||
.setProjectProperties(project.getGradle().getStartParameter().getProjectProperties())
|
|
||||||
.setSystemProperties(project.getGradle().getStartParameter().getSystemPropertiesArgs())
|
|
||||||
.setTasksRequested(project.getGradle().getStartParameter().getTaskNames());
|
|
||||||
|
|
||||||
Path rootDir = toNormalizedPath(project.getRootDir());
|
|
||||||
tasks.stream()
|
|
||||||
.filter(task -> task.getState().getExecuted() || task.getState().getUpToDate())
|
|
||||||
.map(task -> createTaskData(task, rootDir))
|
|
||||||
.forEach(builder.tasksBuilder()::add);
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a Gradle Task into a TaskData.
|
|
||||||
*
|
|
||||||
* @param rootDir the root directory of the main Project - used to get the relative path of any
|
|
||||||
* Task files.
|
|
||||||
*/
|
|
||||||
private TaskData createTaskData(Task task, Path rootDir) {
|
|
||||||
TaskData.State state =
|
|
||||||
task.getState().getFailure() != null
|
|
||||||
? TaskData.State.FAILURE
|
|
||||||
: task.getState().getUpToDate() ? TaskData.State.UP_TO_DATE : TaskData.State.SUCCESS;
|
|
||||||
String log = logs.get(task.getPath()).toString();
|
|
||||||
|
|
||||||
TaskData.Builder builder =
|
|
||||||
TaskData.builder()
|
|
||||||
.setState(state)
|
|
||||||
.setUniqueName(task.getPath())
|
|
||||||
.setDescription(
|
|
||||||
Optional.ofNullable(task.getDescription()).orElse("[No description available]"));
|
|
||||||
if (!log.isEmpty()) {
|
|
||||||
builder.setLog(toByteArraySupplier(log));
|
|
||||||
}
|
|
||||||
|
|
||||||
Reporting<? extends ReportContainer<? extends Report>> reporting = asReporting(task);
|
|
||||||
|
|
||||||
if (reporting != null) {
|
|
||||||
// This Task is also a Reporting task! It has a destination file/directory for every supported
|
|
||||||
// format.
|
|
||||||
// Add the files for each of the formats into the ReportData.
|
|
||||||
reporting
|
|
||||||
.getReports()
|
|
||||||
.getAsMap()
|
|
||||||
.forEach(
|
|
||||||
(type, report) -> {
|
|
||||||
File destination = report.getOutputLocation().get().getAsFile();
|
|
||||||
// The destination could be a file, or a directory. If it's a directory - the Report
|
|
||||||
// could have created multiple files - and we need to know to which one of those to
|
|
||||||
// link.
|
|
||||||
//
|
|
||||||
// If we're lucky, whoever implemented the Report made sure to extend
|
|
||||||
// DirectoryReport, which gives us the entry point to all the files.
|
|
||||||
//
|
|
||||||
// This isn't guaranteed though, as it depends on the implementer.
|
|
||||||
Optional<File> entryPointHint =
|
|
||||||
destination.isDirectory() && (report instanceof DirectoryReport)
|
|
||||||
? Optional.ofNullable(((DirectoryReport) report).getEntryPoint())
|
|
||||||
: Optional.empty();
|
|
||||||
builder
|
|
||||||
.reportsBuilder()
|
|
||||||
.put(type, readFilesWithEntryPoint(destination, entryPointHint, rootDir));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private FilesWithEntryPoint generateFilesToUpload() {
|
|
||||||
ProjectData projectData = createProjectData();
|
|
||||||
CoverPageGenerator coverPageGenerator = new CoverPageGenerator(projectData);
|
|
||||||
return coverPageGenerator.getFilesToUpload();
|
|
||||||
}
|
|
||||||
|
|
||||||
@TaskAction
|
|
||||||
void uploadResults() {
|
|
||||||
System.out.format("ReportUploader: destination= '%s'\n", destination);
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
if (isNullOrEmpty(destination)) {
|
|
||||||
System.out.format("ReportUploader: no destination given, skipping...\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (String key : UPLOAD_FUNCTIONS.keySet()) {
|
|
||||||
if (destination.startsWith(key)) {
|
|
||||||
UPLOAD_FUNCTIONS.get(key).accept(this, destination.substring(key.length()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
System.out.format(
|
|
||||||
"ReportUploader: given destination '%s' doesn't start with one of %s."
|
|
||||||
+ " Defaulting to saving in /tmp\n",
|
|
||||||
destination, UPLOAD_FUNCTIONS.keySet());
|
|
||||||
saveResultsToLocalFolder("/tmp/");
|
|
||||||
} catch (Throwable e) {
|
|
||||||
System.out.format("ReportUploader: Encountered error %s\n", e);
|
|
||||||
e.printStackTrace(System.out);
|
|
||||||
System.out.format("ReportUploader: skipping upload\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveResultsToLocalFolder(String absoluteFolderName) {
|
|
||||||
Path folder = Paths.get(absoluteFolderName, createUniqueFolderName());
|
|
||||||
checkArgument(
|
|
||||||
folder.isAbsolute(),
|
|
||||||
"Local files destination must be an absolute path, but is %s",
|
|
||||||
absoluteFolderName);
|
|
||||||
FilesWithEntryPoint filesToUpload = generateFilesToUpload();
|
|
||||||
System.out.format(
|
|
||||||
"ReportUploader: going to save %s files to %s\n", filesToUpload.files().size(), folder);
|
|
||||||
filesToUpload
|
|
||||||
.files()
|
|
||||||
.forEach((path, dataSupplier) -> saveFile(folder.resolve(path), dataSupplier));
|
|
||||||
System.out.format(
|
|
||||||
"ReportUploader: report saved to file://%s\n", folder.resolve(filesToUpload.entryPoint()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveFile(Path path, Supplier<byte[]> dataSupplier) {
|
|
||||||
File dir = path.getParent().toFile();
|
|
||||||
if (!dir.isDirectory()) {
|
|
||||||
checkState(dir.mkdirs(), "Couldn't create directory %s", dir);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
Files.write(dataSupplier.get(), path.toFile());
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new UncheckedIOException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void uploadResultsToGcs(String destination) {
|
|
||||||
checkArgument(
|
|
||||||
!destination.isEmpty(), "destination must include at least the bucket name, but is empty");
|
|
||||||
Path bucketWithFolder = Paths.get(destination, createUniqueFolderName());
|
|
||||||
String bucket = bucketWithFolder.getName(0).toString();
|
|
||||||
Path folder = bucketWithFolder.subpath(1, bucketWithFolder.getNameCount());
|
|
||||||
|
|
||||||
StorageOptions.Builder storageOptions = StorageOptions.newBuilder();
|
|
||||||
if (!isNullOrEmpty(credentialsFile)) {
|
|
||||||
try {
|
|
||||||
storageOptions.setCredentials(
|
|
||||||
GoogleCredentials.fromStream(new FileInputStream(credentialsFile)));
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new UncheckedIOException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Storage storage = storageOptions.build().getService();
|
|
||||||
|
|
||||||
FilesWithEntryPoint filesToUpload = generateFilesToUpload();
|
|
||||||
|
|
||||||
System.out.format(
|
|
||||||
"ReportUploader: going to upload %s files to %s/%s\n",
|
|
||||||
filesToUpload.files().size(), bucket, folder);
|
|
||||||
if ("yes".equals(multithreadedUpload)) {
|
|
||||||
System.out.format("ReportUploader: multi-threaded upload\n");
|
|
||||||
uploadFilesToGcsMultithread(storage, bucket, folder, filesToUpload.files());
|
|
||||||
} else {
|
|
||||||
System.out.format("ReportUploader: single threaded upload\n");
|
|
||||||
filesToUpload
|
|
||||||
.files()
|
|
||||||
.forEach(
|
|
||||||
(path, dataSupplier) -> {
|
|
||||||
System.out.format("ReportUploader: Uploading %s\n", path);
|
|
||||||
uploadFileToGcs(storage, bucket, folder.resolve(path), dataSupplier);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
System.out.format(
|
|
||||||
"ReportUploader: report uploaded to https://storage.googleapis.com/%s/%s\n",
|
|
||||||
bucket, folder.resolve(filesToUpload.entryPoint()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void setProject(Project project) {
|
|
||||||
this.project = project;
|
|
||||||
|
|
||||||
for (Project subProject : project.getAllprojects()) {
|
|
||||||
subProject.getTasks().all(this::addTask);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addTask(Task task) {
|
|
||||||
if (task instanceof ReportUploader) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
tasks.add(task);
|
|
||||||
StringBuilder log = new StringBuilder();
|
|
||||||
checkArgument(
|
|
||||||
!logs.containsKey(task.getPath()),
|
|
||||||
"Multiple tasks with the same .getPath()=%s",
|
|
||||||
task.getPath());
|
|
||||||
logs.put(task.getPath(), log);
|
|
||||||
task.getLogging().addStandardOutputListener(output -> log.append(output));
|
|
||||||
task.getLogging().addStandardErrorListener(output -> log.append(output));
|
|
||||||
task.finalizedBy(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private static Reporting<? extends ReportContainer<? extends Report>> asReporting(Task task) {
|
|
||||||
if (task instanceof Reporting) {
|
|
||||||
return (Reporting<? extends ReportContainer<? extends Report>>) task;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String createUniqueFolderName() {
|
|
||||||
return String.format(
|
|
||||||
"%h-%h-%h-%h",
|
|
||||||
SECURE_RANDOM.nextInt(),
|
|
||||||
SECURE_RANDOM.nextInt(),
|
|
||||||
SECURE_RANDOM.nextInt(),
|
|
||||||
SECURE_RANDOM.nextInt());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
// Copyright 2019 The Nomulus Authors. 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 google.registry.gradle.plugin;
|
|
||||||
|
|
||||||
import org.gradle.api.Plugin;
|
|
||||||
import org.gradle.api.Project;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Plugin setting up the ReportUploader task.
|
|
||||||
*
|
|
||||||
* <p>It goes over all the tasks in a project and pass them on to the ReportUploader task for set
|
|
||||||
* up.
|
|
||||||
*
|
|
||||||
* <p>Note that since we're passing in all the projects' tasks - this includes the ReportUploader
|
|
||||||
* itself! It's up to the ReportUploader to take care of not having "infinite loops" caused by
|
|
||||||
* waiting for itself to end before finishing.
|
|
||||||
*/
|
|
||||||
public class ReportUploaderPlugin implements Plugin<Project> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void apply(Project project) {
|
|
||||||
ReportUploader reportUploader =
|
|
||||||
project
|
|
||||||
.getTasks()
|
|
||||||
.create(
|
|
||||||
"reportUploader",
|
|
||||||
ReportUploader.class,
|
|
||||||
task -> {
|
|
||||||
task.setDescription("Uploads the reports to GCS bucket");
|
|
||||||
task.setGroup("uploads");
|
|
||||||
});
|
|
||||||
|
|
||||||
reportUploader.setProject(project);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
body {
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
.task_state_SUCCESS {
|
|
||||||
color: green;
|
|
||||||
}
|
|
||||||
.task_state_FAILURE {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
.task_name {
|
|
||||||
display: block;
|
|
||||||
font-size: larger;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.task_description {
|
|
||||||
display: block;
|
|
||||||
margin-left: 1em;
|
|
||||||
color: gray;
|
|
||||||
}
|
|
||||||
.report_links {
|
|
||||||
margin-left: 1em;
|
|
||||||
}
|
|
||||||
.report_link_broken {
|
|
||||||
text-decoration: line-through;
|
|
||||||
color: gray;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
// Copyright 2019 The Nomulus Authors. 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.
|
|
||||||
|
|
||||||
{namespace google.registry.gradle.plugin}
|
|
||||||
|
|
||||||
{template .coverPage}
|
|
||||||
{@param title: string}
|
|
||||||
{@param cssFiles: list<trusted_resource_uri>}
|
|
||||||
{@param projectState: string}
|
|
||||||
{@param invocation: string}
|
|
||||||
{@param tasksByState: map<string, list<[uniqueName: string, description: string, log: string, reports: map<string, string>]>>}
|
|
||||||
|
|
||||||
<title>{$title}</title>
|
|
||||||
{for $cssFile in $cssFiles}
|
|
||||||
<link rel="stylesheet" type="text/css" href="{$cssFile}">
|
|
||||||
{/for}
|
|
||||||
<body>
|
|
||||||
<div class="project">
|
|
||||||
<h1 class="project_title {'task_state_' + $projectState}">{$title}</h1>
|
|
||||||
<span class="project_subtitle">
|
|
||||||
Build results for <span class="project_invocation">{$invocation}</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{for $taskState in mapKeys($tasksByState)}
|
|
||||||
{if length($tasksByState[$taskState]) > 0}
|
|
||||||
{call .tasksOfState}
|
|
||||||
{param state: $taskState /}
|
|
||||||
{param tasks: $tasksByState[$taskState] /}
|
|
||||||
{/call}
|
|
||||||
{/if}
|
|
||||||
{/for}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
{/template}
|
|
||||||
|
|
||||||
{template .tasksOfState}
|
|
||||||
{@param state: string}
|
|
||||||
{@param tasks: list<[uniqueName: string, description: string, log: string, reports: map<string, string>]>}
|
|
||||||
|
|
||||||
<div class="{'task_state_' + $state}">
|
|
||||||
<p>{$state}</p>
|
|
||||||
// Place the tasks with actual reports first, since those are more likely to be useful
|
|
||||||
{for $task in $tasks}
|
|
||||||
{if length(mapKeys($task.reports)) > 0}
|
|
||||||
{call .task}
|
|
||||||
{param task: $task /}
|
|
||||||
{/call}
|
|
||||||
{/if}
|
|
||||||
{/for}
|
|
||||||
// Followup with reports without links
|
|
||||||
{for $task in $tasks}
|
|
||||||
{if length(mapKeys($task.reports)) == 0}
|
|
||||||
{call .task}
|
|
||||||
{param task: $task /}
|
|
||||||
{/call}
|
|
||||||
{/if}
|
|
||||||
{/for}
|
|
||||||
</div>
|
|
||||||
{/template}
|
|
||||||
|
|
||||||
{template .task}
|
|
||||||
{@param task: [uniqueName: string, description: string, log: string, reports: map<string, string>]}
|
|
||||||
{call .taskInternal}
|
|
||||||
{param uniqueName: $task.uniqueName /}
|
|
||||||
{param description: $task.description /}
|
|
||||||
{param log: $task.log /}
|
|
||||||
{param reports: $task.reports /}
|
|
||||||
{/call}
|
|
||||||
{/template}
|
|
||||||
|
|
||||||
{template .taskInternal}
|
|
||||||
{@param uniqueName: string}
|
|
||||||
{@param description: string}
|
|
||||||
{@param log: string}
|
|
||||||
{@param reports: map<string, string>}
|
|
||||||
|
|
||||||
<div class="task">
|
|
||||||
<span class="task_name">{$uniqueName}</span>
|
|
||||||
<span class="task_description">{$description}</span>
|
|
||||||
<span class="report_links">
|
|
||||||
{if $log}
|
|
||||||
<a href="{$log}">[log]</a>
|
|
||||||
{else}
|
|
||||||
<span class="report_link_broken">[log]</span>
|
|
||||||
{/if}
|
|
||||||
{for $type in mapKeys($reports)}
|
|
||||||
{if $reports[$type]}
|
|
||||||
<a href="{$reports[$type]}">[{$type}]</a>
|
|
||||||
{else}
|
|
||||||
<span class="report_link_broken">[{$type}]</span>
|
|
||||||
{/if}
|
|
||||||
{/for}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{/template}
|
|
||||||
@@ -1,279 +0,0 @@
|
|||||||
// Copyright 2019 The Nomulus Authors. 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 google.registry.gradle.plugin;
|
|
||||||
|
|
||||||
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
import static com.google.common.truth.Truth8.assertThat;
|
|
||||||
import static google.registry.gradle.plugin.GcsPluginUtils.toByteArraySupplier;
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
|
||||||
|
|
||||||
import com.google.common.base.Joiner;
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
|
||||||
import google.registry.gradle.plugin.ProjectData.TaskData;
|
|
||||||
import java.io.File;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
/** Tests for {@link CoverPageGenerator} */
|
|
||||||
final class CoverPageGeneratorTest {
|
|
||||||
|
|
||||||
private static final ProjectData EMPTY_PROJECT =
|
|
||||||
ProjectData.builder()
|
|
||||||
.setName("project-name")
|
|
||||||
.setDescription("project-description")
|
|
||||||
.setGradleVersion("gradle-version")
|
|
||||||
.setProjectProperties(ImmutableMap.of("key", "value"))
|
|
||||||
.setSystemProperties(ImmutableMap.of())
|
|
||||||
.setTasksRequested(ImmutableSet.of(":a:task1", ":a:task2"))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
private static final TaskData EMPTY_TASK_SUCCESS =
|
|
||||||
TaskData.builder()
|
|
||||||
.setUniqueName("task-success")
|
|
||||||
.setDescription("a successful task")
|
|
||||||
.setState(TaskData.State.SUCCESS)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
private static final TaskData EMPTY_TASK_FAILURE =
|
|
||||||
TaskData.builder()
|
|
||||||
.setUniqueName("task-failure")
|
|
||||||
.setDescription("a failed task")
|
|
||||||
.setState(TaskData.State.FAILURE)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
private static final TaskData EMPTY_TASK_UP_TO_DATE =
|
|
||||||
TaskData.builder()
|
|
||||||
.setUniqueName("task-up-to-date")
|
|
||||||
.setDescription("an up-to-date task")
|
|
||||||
.setState(TaskData.State.UP_TO_DATE)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
private static final Joiner filenameJoiner = Joiner.on(File.separator);
|
|
||||||
|
|
||||||
private ImmutableMap<String, String> getGeneratedFiles(ProjectData project) {
|
|
||||||
CoverPageGenerator coverPageGenerator = new CoverPageGenerator(project);
|
|
||||||
FilesWithEntryPoint files = coverPageGenerator.getFilesToUpload();
|
|
||||||
return files.files().entrySet().stream()
|
|
||||||
.collect(
|
|
||||||
toImmutableMap(
|
|
||||||
entry -> entry.getKey().toString(),
|
|
||||||
entry -> new String(entry.getValue().get(), UTF_8)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getContentOfGeneratedFile(ProjectData project, String expectedPath) {
|
|
||||||
ImmutableMap<String, String> files = getGeneratedFiles(project);
|
|
||||||
assertThat(files).containsKey(expectedPath);
|
|
||||||
return files.get(expectedPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getCoverPage(ProjectData project) {
|
|
||||||
return getContentOfGeneratedFile(project, "index.html");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testGetFilesToUpload_entryPoint_isIndexHtml() {
|
|
||||||
CoverPageGenerator coverPageGenerator = new CoverPageGenerator(EMPTY_PROJECT);
|
|
||||||
assertThat(coverPageGenerator.getFilesToUpload().entryPoint())
|
|
||||||
.isEqualTo(Paths.get("index.html"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testGetFilesToUpload_containsEntryFile() {
|
|
||||||
String content = getContentOfGeneratedFile(EMPTY_PROJECT, "index.html");
|
|
||||||
assertThat(content)
|
|
||||||
.contains(
|
|
||||||
"<span class=\"project_invocation\">./gradlew :a:task1 :a:task2 -P key=value</span>");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testCoverPage_showsFailedTask() {
|
|
||||||
String content = getCoverPage(EMPTY_PROJECT.toBuilder().addTask(EMPTY_TASK_FAILURE).build());
|
|
||||||
assertThat(content).contains("task-failure");
|
|
||||||
assertThat(content).contains("<p>FAILURE</p>");
|
|
||||||
assertThat(content).doesNotContain("<p>SUCCESS</p>");
|
|
||||||
assertThat(content).doesNotContain("<p>UP_TO_DATE</p>");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testCoverPage_showsSuccessfulTask() {
|
|
||||||
String content = getCoverPage(EMPTY_PROJECT.toBuilder().addTask(EMPTY_TASK_SUCCESS).build());
|
|
||||||
assertThat(content).contains("task-success");
|
|
||||||
assertThat(content).doesNotContain("<p>FAILURE</p>");
|
|
||||||
assertThat(content).contains("<p>SUCCESS</p>");
|
|
||||||
assertThat(content).doesNotContain("<p>UP_TO_DATE</p>");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testCoverPage_showsUpToDateTask() {
|
|
||||||
String content = getCoverPage(EMPTY_PROJECT.toBuilder().addTask(EMPTY_TASK_UP_TO_DATE).build());
|
|
||||||
assertThat(content).contains("task-up-to-date");
|
|
||||||
assertThat(content).doesNotContain("<p>FAILURE</p>");
|
|
||||||
assertThat(content).doesNotContain("<p>SUCCESS</p>");
|
|
||||||
assertThat(content).contains("<p>UP_TO_DATE</p>");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testCoverPage_failedAreFirst() {
|
|
||||||
String content =
|
|
||||||
getCoverPage(
|
|
||||||
EMPTY_PROJECT.toBuilder()
|
|
||||||
.addTask(EMPTY_TASK_UP_TO_DATE)
|
|
||||||
.addTask(EMPTY_TASK_FAILURE)
|
|
||||||
.addTask(EMPTY_TASK_SUCCESS)
|
|
||||||
.build());
|
|
||||||
assertThat(content).contains("<p>FAILURE</p>");
|
|
||||||
assertThat(content).contains("<p>SUCCESS</p>");
|
|
||||||
assertThat(content).contains("<p>UP_TO_DATE</p>");
|
|
||||||
assertThat(content).containsMatch("(?s)<p>FAILURE</p>.*<p>SUCCESS</p>");
|
|
||||||
assertThat(content).containsMatch("(?s)<p>FAILURE</p>.*<p>UP_TO_DATE</p>");
|
|
||||||
assertThat(content).doesNotContainMatch("(?s)<p>SUCCESS</p>.*<p>FAILURE</p>");
|
|
||||||
assertThat(content).doesNotContainMatch("(?s)<p>UP_TO_DATE</p>.*<p>FAILURE</p>");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testCoverPage_failingTask_statusIsFailure() {
|
|
||||||
String content =
|
|
||||||
getCoverPage(
|
|
||||||
EMPTY_PROJECT.toBuilder()
|
|
||||||
.addTask(EMPTY_TASK_UP_TO_DATE)
|
|
||||||
.addTask(EMPTY_TASK_FAILURE)
|
|
||||||
.addTask(EMPTY_TASK_SUCCESS)
|
|
||||||
.build());
|
|
||||||
assertThat(content).contains("<title>Failed: task-failure</title>");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testCoverPage_noFailingTask_statusIsSuccess() {
|
|
||||||
String content =
|
|
||||||
getCoverPage(
|
|
||||||
EMPTY_PROJECT.toBuilder()
|
|
||||||
.addTask(EMPTY_TASK_UP_TO_DATE)
|
|
||||||
.addTask(EMPTY_TASK_SUCCESS)
|
|
||||||
.build());
|
|
||||||
assertThat(content).contains("<title>Success!</title>");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testGetFilesToUpload_containsCssFile() {
|
|
||||||
ImmutableMap<String, String> files = getGeneratedFiles(EMPTY_PROJECT);
|
|
||||||
assertThat(files).containsKey(filenameJoiner.join("css", "style.css"));
|
|
||||||
assertThat(files.get(filenameJoiner.join("css", "style.css"))).contains("body {");
|
|
||||||
assertThat(files.get("index.html"))
|
|
||||||
.contains("<link rel=\"stylesheet\" type=\"text/css\" href=\"css/style.css\">");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testCreateReportFiles_taskWithLog() {
|
|
||||||
ImmutableMap<String, String> files =
|
|
||||||
getGeneratedFiles(
|
|
||||||
EMPTY_PROJECT.toBuilder()
|
|
||||||
.addTask(
|
|
||||||
EMPTY_TASK_SUCCESS.toBuilder()
|
|
||||||
.setUniqueName("my:name")
|
|
||||||
.setLog(toByteArraySupplier("my log data"))
|
|
||||||
.build())
|
|
||||||
.build());
|
|
||||||
assertThat(files).containsEntry(filenameJoiner.join("logs", "my-name.log"), "my log data");
|
|
||||||
assertThat(files.get("index.html")).contains("<a href=\"logs/my-name.log\">[log]</a>");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testCreateReportFiles_taskWithoutLog() {
|
|
||||||
ImmutableMap<String, String> files =
|
|
||||||
getGeneratedFiles(
|
|
||||||
EMPTY_PROJECT.toBuilder()
|
|
||||||
.addTask(EMPTY_TASK_SUCCESS.toBuilder().setUniqueName("my:name").build())
|
|
||||||
.build());
|
|
||||||
assertThat(files).doesNotContainKey("logs/my-name.log");
|
|
||||||
assertThat(files.get("index.html")).contains("<span class=\"report_link_broken\">[log]</span>");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testCreateReportFiles_taskWithFilledReport() {
|
|
||||||
ImmutableMap<String, String> files =
|
|
||||||
getGeneratedFiles(
|
|
||||||
EMPTY_PROJECT.toBuilder()
|
|
||||||
.addTask(
|
|
||||||
EMPTY_TASK_SUCCESS.toBuilder()
|
|
||||||
.putReport(
|
|
||||||
"someReport",
|
|
||||||
FilesWithEntryPoint.create(
|
|
||||||
ImmutableMap.of(
|
|
||||||
Paths.get("path", "report.txt"),
|
|
||||||
toByteArraySupplier("report content")),
|
|
||||||
Paths.get("path", "report.txt")))
|
|
||||||
.build())
|
|
||||||
.build());
|
|
||||||
assertThat(files).containsEntry(filenameJoiner.join("path", "report.txt"), "report content");
|
|
||||||
assertThat(files.get("index.html")).contains("<a href=\"path/report.txt\">[someReport]</a>");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testCreateReportFiles_taskWithEmptyReport() {
|
|
||||||
ImmutableMap<String, String> files =
|
|
||||||
getGeneratedFiles(
|
|
||||||
EMPTY_PROJECT.toBuilder()
|
|
||||||
.addTask(
|
|
||||||
EMPTY_TASK_SUCCESS.toBuilder()
|
|
||||||
.putReport(
|
|
||||||
"someReport",
|
|
||||||
FilesWithEntryPoint.create(
|
|
||||||
ImmutableMap.of(), Paths.get("path", "report.txt")))
|
|
||||||
.build())
|
|
||||||
.build());
|
|
||||||
assertThat(files).doesNotContainKey(filenameJoiner.join("path", "report.txt"));
|
|
||||||
assertThat(files.get("index.html"))
|
|
||||||
.contains("<span class=\"report_link_broken\">[someReport]</span>");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testCreateReportFiles_taskWithLogAndMultipleReports() {
|
|
||||||
ImmutableMap<String, String> files =
|
|
||||||
getGeneratedFiles(
|
|
||||||
EMPTY_PROJECT.toBuilder()
|
|
||||||
.addTask(
|
|
||||||
EMPTY_TASK_SUCCESS.toBuilder()
|
|
||||||
.setUniqueName("my:name")
|
|
||||||
.setLog(toByteArraySupplier("log data"))
|
|
||||||
.putReport(
|
|
||||||
"filledReport",
|
|
||||||
FilesWithEntryPoint.create(
|
|
||||||
ImmutableMap.of(
|
|
||||||
Paths.get("path-filled", "report.txt"),
|
|
||||||
toByteArraySupplier("report content"),
|
|
||||||
Paths.get("path-filled", "other", "file.txt"),
|
|
||||||
toByteArraySupplier("some other content")),
|
|
||||||
Paths.get("path-filled", "report.txt")))
|
|
||||||
.putReport(
|
|
||||||
"emptyReport",
|
|
||||||
FilesWithEntryPoint.create(
|
|
||||||
ImmutableMap.of(), Paths.get("path-empty", "report.txt")))
|
|
||||||
.build())
|
|
||||||
.build());
|
|
||||||
assertThat(files)
|
|
||||||
.containsEntry(filenameJoiner.join("path-filled", "report.txt"), "report content");
|
|
||||||
assertThat(files)
|
|
||||||
.containsEntry(
|
|
||||||
filenameJoiner.join("path-filled", "other", "file.txt"), "some other content");
|
|
||||||
assertThat(files).containsEntry(filenameJoiner.join("logs", "my-name.log"), "log data");
|
|
||||||
assertThat(files.get("index.html"))
|
|
||||||
.contains("<a href=\"path-filled/report.txt\">[filledReport]</a>");
|
|
||||||
assertThat(files.get("index.html")).contains("<a href=\"logs/my-name.log\">[log]</a>");
|
|
||||||
assertThat(files.get("index.html"))
|
|
||||||
.contains("<span class=\"report_link_broken\">[emptyReport]</span>");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,298 +0,0 @@
|
|||||||
// Copyright 2019 The Nomulus Authors. 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 google.registry.gradle.plugin;
|
|
||||||
|
|
||||||
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
import static google.registry.gradle.plugin.GcsPluginUtils.getContentType;
|
|
||||||
import static google.registry.gradle.plugin.GcsPluginUtils.readFilesWithEntryPoint;
|
|
||||||
import static google.registry.gradle.plugin.GcsPluginUtils.toByteArraySupplier;
|
|
||||||
import static google.registry.gradle.plugin.GcsPluginUtils.toNormalizedPath;
|
|
||||||
import static google.registry.gradle.plugin.GcsPluginUtils.uploadFileToGcs;
|
|
||||||
import static google.registry.gradle.plugin.GcsPluginUtils.uploadFilesToGcsMultithread;
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
|
||||||
import static java.nio.file.Files.createDirectories;
|
|
||||||
import static java.nio.file.Files.createDirectory;
|
|
||||||
import static java.nio.file.Files.createFile;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
|
||||||
|
|
||||||
import com.google.cloud.storage.BlobInfo;
|
|
||||||
import com.google.cloud.storage.Storage;
|
|
||||||
import com.google.common.base.Joiner;
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import java.io.File;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.Optional;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
|
||||||
|
|
||||||
/** Tests for {@link GcsPluginUtilsTest} */
|
|
||||||
final class GcsPluginUtilsTest {
|
|
||||||
|
|
||||||
private static final Joiner filenameJoiner = Joiner.on(File.separator);
|
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
|
||||||
@TempDir
|
|
||||||
Path tmpDir;
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testGetContentType_knownTypes() {
|
|
||||||
assertThat(getContentType("path/to/file.html")).isEqualTo("text/html");
|
|
||||||
assertThat(getContentType("path/to/file.htm")).isEqualTo("text/html");
|
|
||||||
assertThat(getContentType("path/to/file.log")).isEqualTo("text/plain");
|
|
||||||
assertThat(getContentType("path/to/file.txt")).isEqualTo("text/plain");
|
|
||||||
assertThat(getContentType("path/to/file.css")).isEqualTo("text/css");
|
|
||||||
assertThat(getContentType("path/to/file.xml")).isEqualTo("text/xml");
|
|
||||||
assertThat(getContentType("path/to/file.zip")).isEqualTo("application/zip");
|
|
||||||
assertThat(getContentType("path/to/file.js")).isEqualTo("text/javascript");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testGetContentType_unknownTypes() {
|
|
||||||
assertThat(getContentType("path/to/file.unknown")).isEqualTo("application/octet-stream");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testUploadFileToGcs() {
|
|
||||||
Storage storage = mock(Storage.class);
|
|
||||||
uploadFileToGcs(
|
|
||||||
storage, "my-bucket", Paths.get("my", "filename.txt"), toByteArraySupplier("my data"));
|
|
||||||
verify(storage)
|
|
||||||
.create(
|
|
||||||
BlobInfo.newBuilder("my-bucket", "my/filename.txt")
|
|
||||||
.setContentType("text/plain")
|
|
||||||
.build(),
|
|
||||||
"my data".getBytes(UTF_8));
|
|
||||||
verifyNoMoreInteractions(storage);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testUploadFilesToGcsMultithread() {
|
|
||||||
Storage storage = mock(Storage.class);
|
|
||||||
uploadFilesToGcsMultithread(
|
|
||||||
storage,
|
|
||||||
"my-bucket",
|
|
||||||
Paths.get("my", "folder"),
|
|
||||||
ImmutableMap.of(
|
|
||||||
Paths.get("some", "index.html"), toByteArraySupplier("some web page"),
|
|
||||||
Paths.get("some", "style.css"), toByteArraySupplier("some style"),
|
|
||||||
Paths.get("other", "index.html"), toByteArraySupplier("other web page"),
|
|
||||||
Paths.get("other", "style.css"), toByteArraySupplier("other style")));
|
|
||||||
verify(storage)
|
|
||||||
.create(
|
|
||||||
BlobInfo.newBuilder("my-bucket", "my/folder/some/index.html")
|
|
||||||
.setContentType("text/html")
|
|
||||||
.build(),
|
|
||||||
"some web page".getBytes(UTF_8));
|
|
||||||
verify(storage)
|
|
||||||
.create(
|
|
||||||
BlobInfo.newBuilder("my-bucket", "my/folder/some/style.css")
|
|
||||||
.setContentType("text/css")
|
|
||||||
.build(),
|
|
||||||
"some style".getBytes(UTF_8));
|
|
||||||
verify(storage)
|
|
||||||
.create(
|
|
||||||
BlobInfo.newBuilder("my-bucket", "my/folder/other/index.html")
|
|
||||||
.setContentType("text/html")
|
|
||||||
.build(),
|
|
||||||
"other web page".getBytes(UTF_8));
|
|
||||||
verify(storage)
|
|
||||||
.create(
|
|
||||||
BlobInfo.newBuilder("my-bucket", "my/folder/other/style.css")
|
|
||||||
.setContentType("text/css")
|
|
||||||
.build(),
|
|
||||||
"other style".getBytes(UTF_8));
|
|
||||||
verifyNoMoreInteractions(storage);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testToByteArraySupplier_string() {
|
|
||||||
assertThat(toByteArraySupplier("my string").get()).isEqualTo("my string".getBytes(UTF_8));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testToByteArraySupplier_stringSupplier() {
|
|
||||||
assertThat(toByteArraySupplier(() -> "my string").get()).isEqualTo("my string".getBytes(UTF_8));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testToByteArraySupplier_file() throws Exception {
|
|
||||||
Path dir = createDirectory(tmpDir.resolve("arbitrary"));
|
|
||||||
Path file = createFile(dir.resolve("file.txt"));
|
|
||||||
Files.write(file, "some data".getBytes(UTF_8));
|
|
||||||
assertThat(toByteArraySupplier(file.toFile()).get()).isEqualTo("some data".getBytes(UTF_8));
|
|
||||||
}
|
|
||||||
|
|
||||||
private ImmutableMap<String, String> readAllFiles(FilesWithEntryPoint reportFiles) {
|
|
||||||
return reportFiles.files().entrySet().stream()
|
|
||||||
.collect(
|
|
||||||
toImmutableMap(
|
|
||||||
entry -> entry.getKey().toString(),
|
|
||||||
entry -> new String(entry.getValue().get(), UTF_8)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testCreateReportFiles_destinationIsFile() throws Exception {
|
|
||||||
Path root = toNormalizedPath(createDirectories(tmpDir.resolve("my/root")).toAbsolutePath());
|
|
||||||
Path somePath = createDirectories(root.resolve("some/path"));
|
|
||||||
Path destination = createFile(somePath.resolve("file.txt"));
|
|
||||||
Files.write(destination, "some data".getBytes(UTF_8));
|
|
||||||
// Since the entry point is obvious here - any hint given is just ignored.
|
|
||||||
File ignoredHint = createFile(root.resolve("ignored.txt")).toFile();
|
|
||||||
|
|
||||||
FilesWithEntryPoint files =
|
|
||||||
readFilesWithEntryPoint(destination.toFile(), Optional.of(ignoredHint), root);
|
|
||||||
|
|
||||||
assertThat(files.entryPoint().toString())
|
|
||||||
.isEqualTo(filenameJoiner.join("some", "path", "file.txt"));
|
|
||||||
assertThat(readAllFiles(files))
|
|
||||||
.containsExactly(filenameJoiner.join("some", "path", "file.txt"), "some data");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testCreateReportFiles_destinationDoesntExist() throws Exception {
|
|
||||||
Path root = toNormalizedPath(createDirectories(tmpDir.resolve("my/root")).toAbsolutePath());
|
|
||||||
File destination = root.resolve("non/existing.txt").toFile();
|
|
||||||
assertThat(destination.isFile()).isFalse();
|
|
||||||
assertThat(destination.isDirectory()).isFalse();
|
|
||||||
// Since there are no files, any hint given is obviously wrong and will be ignored.
|
|
||||||
File ignoredHint = createFile(root.resolve("ignored.txt")).toFile();
|
|
||||||
|
|
||||||
FilesWithEntryPoint files =
|
|
||||||
readFilesWithEntryPoint(destination, Optional.of(ignoredHint), root);
|
|
||||||
|
|
||||||
assertThat(files.entryPoint().toString()).isEqualTo(filenameJoiner.join("non", "existing.txt"));
|
|
||||||
assertThat(files.files()).isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testCreateReportFiles_noFiles() throws Exception {
|
|
||||||
Path root = toNormalizedPath(createDirectories(tmpDir.resolve("my/root")).toAbsolutePath());
|
|
||||||
Path destination = createDirectories(root.resolve("some/path"));
|
|
||||||
createDirectories(destination.resolve("a/b"));
|
|
||||||
createDirectory(destination.resolve("c"));
|
|
||||||
// Since there are not files, any hint given is obviously wrong and will be ignored.
|
|
||||||
File ignoredHint = createFile(root.resolve("ignored.txt")).toFile();
|
|
||||||
|
|
||||||
FilesWithEntryPoint files =
|
|
||||||
readFilesWithEntryPoint(destination.toFile(), Optional.of(ignoredHint), root);
|
|
||||||
|
|
||||||
assertThat(files.entryPoint().toString()).isEqualTo(filenameJoiner.join("some", "path"));
|
|
||||||
assertThat(files.files()).isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testCreateReportFiles_oneFile() throws Exception {
|
|
||||||
Path root = toNormalizedPath(createDirectories(tmpDir.resolve("my/root")).toAbsolutePath());
|
|
||||||
Path destination = createDirectories(root.resolve("some/path"));
|
|
||||||
createDirectories(destination.resolve("a/b"));
|
|
||||||
createDirectory(destination.resolve("c"));
|
|
||||||
Files.write(createFile(destination.resolve("a/file.txt")), "some data".getBytes(UTF_8));
|
|
||||||
// Since the entry point is obvious here - any hint given is just ignored.
|
|
||||||
File ignoredHint = createFile(root.resolve("ignored.txt")).toFile();
|
|
||||||
|
|
||||||
FilesWithEntryPoint files =
|
|
||||||
readFilesWithEntryPoint(destination.toFile(), Optional.of(ignoredHint), root);
|
|
||||||
|
|
||||||
assertThat(files.entryPoint().toString())
|
|
||||||
.isEqualTo(filenameJoiner.join("some", "path", "a", "file.txt"));
|
|
||||||
assertThat(readAllFiles(files))
|
|
||||||
.containsExactly(filenameJoiner.join("some", "path", "a", "file.txt"), "some data");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Currently tests the "unimplemented" behavior.
|
|
||||||
*
|
|
||||||
* <p>TODO(guyben): switch to checking zip file instead.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
void testCreateReportFiles_multipleFiles_noHint() throws Exception {
|
|
||||||
Path root = toNormalizedPath(createDirectories(tmpDir.resolve("my/root")).toAbsolutePath());
|
|
||||||
Path destination = createDirectories(root.resolve("some/path"));
|
|
||||||
createDirectories(destination.resolve("a/b"));
|
|
||||||
createDirectory(destination.resolve("c"));
|
|
||||||
|
|
||||||
Files.write(createFile(destination.resolve("index.html")), "some data".getBytes(UTF_8));
|
|
||||||
Files.write(createFile(destination.resolve("a/index.html")), "wrong index".getBytes(UTF_8));
|
|
||||||
Files.write(createFile(destination.resolve("c/style.css")), "css file".getBytes(UTF_8));
|
|
||||||
Files.write(createFile(destination.resolve("my_image.png")), "images".getBytes(UTF_8));
|
|
||||||
|
|
||||||
FilesWithEntryPoint files =
|
|
||||||
readFilesWithEntryPoint(destination.toFile(), Optional.empty(), root);
|
|
||||||
|
|
||||||
assertThat(files.entryPoint().toString())
|
|
||||||
.isEqualTo(filenameJoiner.join("some", "path", "path.zip"));
|
|
||||||
assertThat(readAllFiles(files).keySet())
|
|
||||||
.containsExactly(filenameJoiner.join("some", "path", "path.zip"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Currently tests the "unimplemented" behavior.
|
|
||||||
*
|
|
||||||
* <p>TODO(guyben): switch to checking zip file instead.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
void testCreateReportFiles_multipleFiles_withBadHint() throws Exception {
|
|
||||||
Path root = toNormalizedPath(createDirectories(tmpDir.resolve("my/root")).toAbsolutePath());
|
|
||||||
Path destination = createDirectories(root.resolve("some/path"));
|
|
||||||
// This entry point points to a directory, which isn't an appropriate entry point
|
|
||||||
File badEntryPoint = createDirectories(destination.resolve("a/b")).toFile();
|
|
||||||
createDirectory(destination.resolve("c"));
|
|
||||||
|
|
||||||
Files.write(createFile(destination.resolve("index.html")), "some data".getBytes(UTF_8));
|
|
||||||
Files.write(createFile(destination.resolve("a/index.html")), "wrong index".getBytes(UTF_8));
|
|
||||||
Files.write(createFile(destination.resolve("c/style.css")), "css file".getBytes(UTF_8));
|
|
||||||
Files.write(createFile(destination.resolve("my_image.png")), "images".getBytes(UTF_8));
|
|
||||||
|
|
||||||
FilesWithEntryPoint files =
|
|
||||||
readFilesWithEntryPoint(destination.toFile(), Optional.of(badEntryPoint), root);
|
|
||||||
|
|
||||||
assertThat(files.entryPoint().toString())
|
|
||||||
.isEqualTo(filenameJoiner.join("some", "path", "path.zip"));
|
|
||||||
assertThat(readAllFiles(files).keySet())
|
|
||||||
.containsExactly(filenameJoiner.join("some", "path", "path.zip"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testCreateReportFiles_multipleFiles_withGoodHint() throws Exception {
|
|
||||||
Path root = toNormalizedPath(createDirectories(tmpDir.resolve("my/root")).toAbsolutePath());
|
|
||||||
Path destination = createDirectories(root.resolve("some/path"));
|
|
||||||
createDirectories(destination.resolve("a/b"));
|
|
||||||
createDirectory(destination.resolve("c"));
|
|
||||||
// The hint is an actual file nested in the destination directory!
|
|
||||||
Path goodEntryPoint = createFile(destination.resolve("index.html"));
|
|
||||||
|
|
||||||
Files.write(goodEntryPoint, "some data".getBytes(UTF_8));
|
|
||||||
Files.write(createFile(destination.resolve("a/index.html")), "wrong index".getBytes(UTF_8));
|
|
||||||
Files.write(createFile(destination.resolve("c/style.css")), "css file".getBytes(UTF_8));
|
|
||||||
Files.write(createFile(destination.resolve("my_image.png")), "images".getBytes(UTF_8));
|
|
||||||
|
|
||||||
FilesWithEntryPoint files =
|
|
||||||
readFilesWithEntryPoint(destination.toFile(), Optional.of(goodEntryPoint.toFile()), root);
|
|
||||||
|
|
||||||
assertThat(files.entryPoint().toString())
|
|
||||||
.isEqualTo(filenameJoiner.join("some", "path", "index.html"));
|
|
||||||
assertThat(readAllFiles(files))
|
|
||||||
.containsExactly(
|
|
||||||
filenameJoiner.join("some", "path", "index.html"), "some data",
|
|
||||||
filenameJoiner.join("some", "path", "a", "index.html"), "wrong index",
|
|
||||||
filenameJoiner.join("some", "path", "c", "style.css"), "css file",
|
|
||||||
filenameJoiner.join("some", "path", "my_image.png"), "images");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
mock-maker-inline
|
|
||||||
+13
-14
@@ -11,8 +11,8 @@ com.diffplug.spotless:spotless-lib:2.40.0=classpath
|
|||||||
com.diffplug.spotless:spotless-plugin-gradle:6.20.0=classpath
|
com.diffplug.spotless:spotless-plugin-gradle:6.20.0=classpath
|
||||||
com.dorongold.plugins:task-tree:2.1.0=classpath
|
com.dorongold.plugins:task-tree:2.1.0=classpath
|
||||||
com.dorongold.task-tree:com.dorongold.task-tree.gradle.plugin:2.1.0=classpath
|
com.dorongold.task-tree:com.dorongold.task-tree.gradle.plugin:2.1.0=classpath
|
||||||
com.github.jengelman.gradle.plugins:shadow:5.1.0=classpath
|
com.github.johnrengelman.shadow:com.github.johnrengelman.shadow.gradle.plugin:8.1.1=classpath
|
||||||
com.github.johnrengelman.shadow:com.github.johnrengelman.shadow.gradle.plugin:5.1.0=classpath
|
com.github.johnrengelman:shadow:8.1.1=classpath
|
||||||
com.github.node-gradle.node:com.github.node-gradle.node.gradle.plugin:3.0.1=classpath
|
com.github.node-gradle.node:com.github.node-gradle.node.gradle.plugin:3.0.1=classpath
|
||||||
com.github.node-gradle:gradle-node-plugin:3.0.1=classpath
|
com.github.node-gradle:gradle-node-plugin:3.0.1=classpath
|
||||||
com.google.cloud.tools:appengine-gradle-plugin:2.4.1=classpath
|
com.google.cloud.tools:appengine-gradle-plugin:2.4.1=classpath
|
||||||
@@ -29,35 +29,34 @@ com.googlecode.javaewah:JavaEWAH:1.2.3=classpath
|
|||||||
com.squareup.okhttp3:okhttp:4.10.0=classpath
|
com.squareup.okhttp3:okhttp:4.10.0=classpath
|
||||||
com.squareup.okio:okio-jvm:3.0.0=classpath
|
com.squareup.okio:okio-jvm:3.0.0=classpath
|
||||||
com.squareup.okio:okio:3.0.0=classpath
|
com.squareup.okio:okio:3.0.0=classpath
|
||||||
commons-io:commons-io:2.6=classpath
|
commons-io:commons-io:2.11.0=classpath
|
||||||
dev.equo.ide:solstice:1.3.1=classpath
|
dev.equo.ide:solstice:1.3.1=classpath
|
||||||
net.ltgt.errorprone:net.ltgt.errorprone.gradle.plugin:2.0.2=classpath
|
net.ltgt.errorprone:net.ltgt.errorprone.gradle.plugin:3.1.0=classpath
|
||||||
net.ltgt.gradle:gradle-errorprone-plugin:2.0.2=classpath
|
net.ltgt.gradle:gradle-errorprone-plugin:3.1.0=classpath
|
||||||
org.apache.ant:ant-launcher:1.9.7=classpath
|
org.apache.ant:ant-launcher:1.10.13=classpath
|
||||||
org.apache.ant:ant:1.9.7=classpath
|
org.apache.ant:ant:1.10.13=classpath
|
||||||
org.apache.commons:commons-compress:1.20=classpath
|
org.apache.commons:commons-compress:1.20=classpath
|
||||||
org.apache.commons:commons-lang3:3.5=classpath
|
org.apache.commons:commons-lang3:3.5=classpath
|
||||||
org.checkerframework:checker-qual:2.10.0=classpath
|
org.checkerframework:checker-qual:2.10.0=classpath
|
||||||
org.codehaus.plexus:plexus-utils:3.0.24=classpath
|
org.codehaus.plexus:plexus-utils:3.5.1=classpath
|
||||||
org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r=classpath
|
org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r=classpath
|
||||||
org.eclipse.platform:org.eclipse.osgi:3.18.300=classpath
|
org.eclipse.platform:org.eclipse.osgi:3.18.300=classpath
|
||||||
org.glassfish:javax.json:1.0.4=classpath
|
org.glassfish:javax.json:1.0.4=classpath
|
||||||
org.jdom:jdom2:2.0.6=classpath
|
org.jdom:jdom2:2.0.6.1=classpath
|
||||||
org.jetbrains.kotlin:kotlin-stdlib-common:1.6.20=classpath
|
org.jetbrains.kotlin:kotlin-stdlib-common:1.6.20=classpath
|
||||||
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.31=classpath
|
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.31=classpath
|
||||||
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.31=classpath
|
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.31=classpath
|
||||||
org.jetbrains.kotlin:kotlin-stdlib:1.6.20=classpath
|
org.jetbrains.kotlin:kotlin-stdlib:1.6.20=classpath
|
||||||
org.jetbrains:annotations:13.0=classpath
|
org.jetbrains:annotations:13.0=classpath
|
||||||
org.ow2.asm:asm-analysis:7.0-beta=classpath
|
org.ow2.asm:asm-commons:9.4=classpath
|
||||||
org.ow2.asm:asm-commons:7.0-beta=classpath
|
org.ow2.asm:asm-tree:9.4=classpath
|
||||||
org.ow2.asm:asm-tree:7.0-beta=classpath
|
org.ow2.asm:asm:9.4=classpath
|
||||||
org.ow2.asm:asm:7.0-beta=classpath
|
|
||||||
org.slf4j:slf4j-api:1.7.36=classpath
|
org.slf4j:slf4j-api:1.7.36=classpath
|
||||||
org.sonatype.aether:aether-api:1.13.1=classpath
|
org.sonatype.aether:aether-api:1.13.1=classpath
|
||||||
org.sonatype.aether:aether-impl:1.13.1=classpath
|
org.sonatype.aether:aether-impl:1.13.1=classpath
|
||||||
org.sonatype.aether:aether-spi:1.13.1=classpath
|
org.sonatype.aether:aether-spi:1.13.1=classpath
|
||||||
org.sonatype.aether:aether-util:1.13.1=classpath
|
org.sonatype.aether:aether-util:1.13.1=classpath
|
||||||
org.tukaani:xz:1.9=classpath
|
org.tukaani:xz:1.9=classpath
|
||||||
org.vafer:jdependency:2.1.1=classpath
|
org.vafer:jdependency:2.8.0=classpath
|
||||||
org.yaml:snakeyaml:1.21=classpath
|
org.yaml:snakeyaml:1.21=classpath
|
||||||
empty=
|
empty=
|
||||||
|
|||||||
+2
-1
@@ -57,7 +57,7 @@ dependencies {
|
|||||||
implementation deps['com.github.ben-manes.caffeine:caffeine']
|
implementation deps['com.github.ben-manes.caffeine:caffeine']
|
||||||
implementation deps['com.google.code.findbugs:jsr305']
|
implementation deps['com.google.code.findbugs:jsr305']
|
||||||
implementation deps['com.google.guava:guava']
|
implementation deps['com.google.guava:guava']
|
||||||
implementation deps['javax.inject:javax.inject']
|
implementation deps['jakarta.inject:jakarta.inject-api']
|
||||||
implementation deps['joda-time:joda-time']
|
implementation deps['joda-time:joda-time']
|
||||||
implementation deps['com.google.flogger:flogger']
|
implementation deps['com.google.flogger:flogger']
|
||||||
implementation deps['io.github.java-diff-utils:java-diff-utils']
|
implementation deps['io.github.java-diff-utils:java-diff-utils']
|
||||||
@@ -65,4 +65,5 @@ dependencies {
|
|||||||
|
|
||||||
testImplementation deps['org.junit.jupiter:junit-jupiter-api']
|
testImplementation deps['org.junit.jupiter:junit-jupiter-api']
|
||||||
testImplementation deps['org.junit.jupiter:junit-jupiter-engine']
|
testImplementation deps['org.junit.jupiter:junit-jupiter-engine']
|
||||||
|
testImplementation deps['org.junit.platform:junit-platform-launcher']
|
||||||
}
|
}
|
||||||
|
|||||||
+49
-47
@@ -1,69 +1,71 @@
|
|||||||
# This is a Gradle generated file for dependency locking.
|
# This is a Gradle generated file for dependency locking.
|
||||||
# Manual edits can break the build and are not advised.
|
# Manual edits can break the build and are not advised.
|
||||||
# This file is expected to be part of source control.
|
# This file is expected to be part of source control.
|
||||||
com.github.ben-manes.caffeine:caffeine:2.7.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
aopalliance:aopalliance:1.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
||||||
com.github.ben-manes.caffeine:caffeine:2.9.3=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
com.github.ben-manes.caffeine:caffeine:3.0.5=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
||||||
|
com.github.ben-manes.caffeine:caffeine:3.2.0=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
||||||
com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
||||||
com.google.auto.value:auto-value-annotations:1.10.1=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
||||||
com.google.auto:auto-common:0.10=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
com.google.auto.value:auto-value-annotations:1.11.0=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
||||||
com.google.code.findbugs:jFormatString:3.0.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
||||||
|
com.google.auto:auto-common:1.2.1=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
||||||
com.google.code.findbugs:jsr305:3.0.2=annotationProcessor,checkstyle,compileClasspath,deploy_jar,errorprone,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath,testing,testingAnnotationProcessor,testingCompileClasspath
|
com.google.code.findbugs:jsr305:3.0.2=annotationProcessor,checkstyle,compileClasspath,deploy_jar,errorprone,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath,testing,testingAnnotationProcessor,testingCompileClasspath
|
||||||
com.google.errorprone:error_prone_annotation:2.3.4=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
com.google.errorprone:error_prone_annotation:2.23.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
||||||
com.google.errorprone:error_prone_annotations:2.19.1=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
com.google.errorprone:error_prone_annotations:2.23.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
||||||
com.google.errorprone:error_prone_annotations:2.3.4=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
com.google.errorprone:error_prone_annotations:2.36.0=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
||||||
com.google.errorprone:error_prone_annotations:2.7.1=checkstyle
|
com.google.errorprone:error_prone_annotations:2.7.1=checkstyle
|
||||||
com.google.errorprone:error_prone_check_api:2.3.4=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
com.google.errorprone:error_prone_check_api:2.23.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
||||||
com.google.errorprone:error_prone_core:2.3.4=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
com.google.errorprone:error_prone_core:2.23.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
||||||
com.google.errorprone:error_prone_type_annotations:2.3.4=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
com.google.errorprone:error_prone_type_annotations:2.23.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
||||||
com.google.flogger:flogger:0.7.4=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
com.google.errorprone:javac:9+181-r4173-1=errorproneJavac
|
||||||
com.google.guava:failureaccess:1.0.1=annotationProcessor,checkstyle,compileClasspath,deploy_jar,errorprone,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath,testing,testingAnnotationProcessor,testingCompileClasspath
|
com.google.flogger:flogger:0.8=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
||||||
com.google.guava:guava-parent:32.1.2-jre=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
com.google.guava:failureaccess:1.0.1=annotationProcessor,checkstyle,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
||||||
com.google.guava:guava:27.0.1-jre=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
com.google.guava:failureaccess:1.0.2=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
||||||
|
com.google.guava:guava-parent:32.1.1-jre=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
||||||
com.google.guava:guava:31.0.1-jre=checkstyle
|
com.google.guava:guava:31.0.1-jre=checkstyle
|
||||||
com.google.guava:guava:32.1.2-jre=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
com.google.guava:guava:32.1.1-jre=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
||||||
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,compileClasspath,deploy_jar,errorprone,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath,testing,testingAnnotationProcessor,testingCompileClasspath
|
com.google.guava:guava:33.2.1-android=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
||||||
com.google.j2objc:j2objc-annotations:1.1=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=checkstyle,compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
||||||
|
com.google.inject:guice:5.1.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
||||||
com.google.j2objc:j2objc-annotations:1.3=checkstyle
|
com.google.j2objc:j2objc-annotations:1.3=checkstyle
|
||||||
com.google.j2objc:j2objc-annotations:2.8=compileClasspath,testCompileClasspath,testingCompileClasspath
|
com.google.j2objc:j2objc-annotations:3.0.0=compileClasspath,testCompileClasspath,testingCompileClasspath
|
||||||
com.google.protobuf:protobuf-java:3.4.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
com.google.protobuf:protobuf-java:3.19.6=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
||||||
com.google.truth:truth:1.1.5=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
com.google.truth:truth:1.4.4=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
||||||
com.googlecode.java-diff-utils:diffutils:1.3.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
|
||||||
com.puppycrawl.tools:checkstyle:9.3=checkstyle
|
com.puppycrawl.tools:checkstyle:9.3=checkstyle
|
||||||
commons-beanutils:commons-beanutils:1.9.4=checkstyle
|
commons-beanutils:commons-beanutils:1.9.4=checkstyle
|
||||||
commons-collections:commons-collections:3.2.2=checkstyle
|
commons-collections:commons-collections:3.2.2=checkstyle
|
||||||
info.picocli:picocli:4.6.2=checkstyle
|
info.picocli:picocli:4.6.2=checkstyle
|
||||||
io.github.java-diff-utils:java-diff-utils:4.12=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
io.github.eisop:dataflow-errorprone:3.34.0-eisop1=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
||||||
javax.inject:javax.inject:1=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
||||||
joda-time:joda-time:2.12.5=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
io.github.java-diff-utils:java-diff-utils:4.15=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
||||||
|
jakarta.inject:jakarta.inject-api:2.0.1=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
||||||
|
javax.inject:javax.inject:1=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
||||||
|
joda-time:joda-time:2.13.1=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
||||||
junit:junit:4.13.2=testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
junit:junit:4.13.2=testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
||||||
net.sf.saxon:Saxon-HE:10.6=checkstyle
|
net.sf.saxon:Saxon-HE:10.6=checkstyle
|
||||||
org.antlr:antlr4-runtime:4.9.3=checkstyle
|
org.antlr:antlr4-runtime:4.9.3=checkstyle
|
||||||
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath
|
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath
|
||||||
org.checkerframework:checker-compat-qual:2.5.3=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
org.checkerframework:checker-compat-qual:2.5.3=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
||||||
org.checkerframework:checker-qual:3.0.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
|
||||||
org.checkerframework:checker-qual:3.12.0=checkstyle
|
org.checkerframework:checker-qual:3.12.0=checkstyle
|
||||||
org.checkerframework:checker-qual:3.35.0=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
org.checkerframework:checker-qual:3.33.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
||||||
org.checkerframework:dataflow:3.0.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
org.checkerframework:checker-qual:3.42.0=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
||||||
org.checkerframework:javacutil:3.0.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
|
||||||
org.codehaus.mojo:animal-sniffer-annotations:1.17=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
|
||||||
org.hamcrest:hamcrest-core:1.3=testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
org.hamcrest:hamcrest-core:1.3=testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
||||||
org.jacoco:org.jacoco.agent:0.8.9=jacocoAgent,jacocoAnt
|
org.jacoco:org.jacoco.agent:0.8.12=jacocoAgent,jacocoAnt
|
||||||
org.jacoco:org.jacoco.ant:0.8.9=jacocoAnt
|
org.jacoco:org.jacoco.ant:0.8.12=jacocoAnt
|
||||||
org.jacoco:org.jacoco.core:0.8.9=jacocoAnt
|
org.jacoco:org.jacoco.core:0.8.12=jacocoAnt
|
||||||
org.jacoco:org.jacoco.report:0.8.9=jacocoAnt
|
org.jacoco:org.jacoco.report:0.8.12=jacocoAnt
|
||||||
org.javassist:javassist:3.28.0-GA=checkstyle
|
org.javassist:javassist:3.28.0-GA=checkstyle
|
||||||
org.junit.jupiter:junit-jupiter-api:5.10.0=testCompileClasspath,testRuntimeClasspath
|
org.jspecify:jspecify:1.0.0=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
||||||
org.junit.jupiter:junit-jupiter-engine:5.10.0=testCompileClasspath,testRuntimeClasspath
|
org.junit.jupiter:junit-jupiter-api:5.12.1=testCompileClasspath,testRuntimeClasspath
|
||||||
org.junit.platform:junit-platform-commons:1.10.0=testCompileClasspath,testRuntimeClasspath
|
org.junit.jupiter:junit-jupiter-engine:5.12.1=testCompileClasspath,testRuntimeClasspath
|
||||||
org.junit.platform:junit-platform-engine:1.10.0=testCompileClasspath,testRuntimeClasspath
|
org.junit.platform:junit-platform-commons:1.12.1=testCompileClasspath,testRuntimeClasspath
|
||||||
org.junit:junit-bom:5.10.0=testCompileClasspath,testRuntimeClasspath
|
org.junit.platform:junit-platform-engine:1.12.1=testCompileClasspath,testRuntimeClasspath
|
||||||
|
org.junit.platform:junit-platform-launcher:1.12.1=testCompileClasspath,testRuntimeClasspath
|
||||||
|
org.junit:junit-bom:5.12.1=testCompileClasspath,testRuntimeClasspath
|
||||||
org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testRuntimeClasspath
|
org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testRuntimeClasspath
|
||||||
org.ow2.asm:asm-commons:9.5=jacocoAnt
|
org.ow2.asm:asm-commons:9.7=jacocoAnt
|
||||||
org.ow2.asm:asm-tree:9.5=jacocoAnt
|
org.ow2.asm:asm-tree:9.7=jacocoAnt
|
||||||
org.ow2.asm:asm:9.5=compileClasspath,deploy_jar,jacocoAnt,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
org.ow2.asm:asm:9.7=compileClasspath,deploy_jar,jacocoAnt,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
|
||||||
org.pcollections:pcollections:2.1.2=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
org.pcollections:pcollections:3.1.4=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
||||||
org.plumelib:plume-util:1.0.6=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
|
||||||
org.plumelib:reflection-util:0.0.2=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
|
||||||
org.plumelib:require-javadoc:0.1.0=annotationProcessor,errorprone,testAnnotationProcessor,testingAnnotationProcessor
|
|
||||||
org.reflections:reflections:0.10.2=checkstyle
|
org.reflections:reflections:0.10.2=checkstyle
|
||||||
empty=errorproneJavac,testingCompile,testingRuntime,testingRuntimeClasspath
|
empty=testingCompile,testingRuntime,testingRuntimeClasspath
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// Copyright 2023 The Nomulus Authors. 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 google.registry.util;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
import static com.google.common.collect.Iterators.partition;
|
||||||
|
import static com.google.common.collect.Iterators.transform;
|
||||||
|
import static com.google.common.collect.Streams.stream;
|
||||||
|
import static java.lang.Math.min;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/** Utilities for breaking up a {@link Stream} into batches. */
|
||||||
|
public final class BatchedStreams {
|
||||||
|
|
||||||
|
static final int MAX_BATCH = 1024 * 1024;
|
||||||
|
|
||||||
|
private BatchedStreams() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform a flat {@link Stream} into a {@code Stream} of batches.
|
||||||
|
*
|
||||||
|
* <p>Closing the returned stream does not close the original stream.
|
||||||
|
*/
|
||||||
|
public static <T> Stream<ImmutableList<T>> toBatches(Stream<T> stream, int batchSize) {
|
||||||
|
checkArgument(batchSize > 0, "batchSize must be a positive integer.");
|
||||||
|
return stream(
|
||||||
|
transform(partition(stream.iterator(), min(MAX_BATCH, batchSize)), ImmutableList::copyOf));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,8 +16,8 @@ package google.registry.util;
|
|||||||
|
|
||||||
import static org.joda.time.DateTimeZone.UTC;
|
import static org.joda.time.DateTimeZone.UTC;
|
||||||
|
|
||||||
|
import jakarta.inject.Inject;
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
import javax.inject.Inject;
|
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
|
|
||||||
/** Clock implementation that proxies to the real system clock. */
|
/** Clock implementation that proxies to the real system clock. */
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ package google.registry.util;
|
|||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.Uninterruptibles;
|
import com.google.common.util.concurrent.Uninterruptibles;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
import javax.inject.Inject;
|
|
||||||
import org.joda.time.ReadableDuration;
|
import org.joda.time.ReadableDuration;
|
||||||
|
|
||||||
/** Implementation of {@link Sleeper} for production use. */
|
/** Implementation of {@link Sleeper} for production use. */
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
// Copyright 2023 The Nomulus Authors. 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 google.registry.util;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static google.registry.util.BatchedStreams.toBatches;
|
||||||
|
import static java.util.stream.Collectors.counting;
|
||||||
|
import static java.util.stream.Collectors.groupingBy;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/** Unit tests for {@link BatchedStreams}. */
|
||||||
|
public class BatchedStreamsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void invalidBatchSize() {
|
||||||
|
assertThat(assertThrows(IllegalArgumentException.class, () -> toBatches(Stream.of(), 0)))
|
||||||
|
.hasMessageThat()
|
||||||
|
.contains("must be a positive integer");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void batch_success() {
|
||||||
|
// 900_002 elements -> 900 1K-batches + 1 2-element-batch
|
||||||
|
Stream<Integer> data = IntStream.rangeClosed(0, 900_001).boxed();
|
||||||
|
assertThat(
|
||||||
|
toBatches(data, 1000).map(ImmutableList::size).collect(groupingBy(x -> x, counting())))
|
||||||
|
.containsExactly(1000, 900L, 2, 1L);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void batch_partialBatch() {
|
||||||
|
Stream<Integer> data = Stream.of(1, 2, 3);
|
||||||
|
assertThat(
|
||||||
|
toBatches(data, 1000).map(ImmutableList::size).collect(groupingBy(x -> x, counting())))
|
||||||
|
.containsExactly(3, 1L);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void batch_truncateBatchSize() {
|
||||||
|
// 2M elements -> 2 1M-batches despite the user-specified 2M batch size.
|
||||||
|
Stream<Integer> data = IntStream.range(0, 1024 * 2048).boxed();
|
||||||
|
assertThat(
|
||||||
|
toBatches(data, 2_000_000)
|
||||||
|
.map(ImmutableList::size)
|
||||||
|
.collect(groupingBy(x -> x, counting())))
|
||||||
|
.containsExactly(1024 * 1024, 2L);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,6 @@
|
|||||||
|
|
||||||
package google.registry.testing;
|
package google.registry.testing;
|
||||||
|
|
||||||
import com.github.benmanes.caffeine.cache.CacheLoader;
|
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||||
import com.google.common.flogger.FluentLogger;
|
import com.google.common.flogger.FluentLogger;
|
||||||
@@ -31,31 +30,26 @@ public final class SystemInfo {
|
|||||||
private static final LoadingCache<String, Boolean> hasCommandCache =
|
private static final LoadingCache<String, Boolean> hasCommandCache =
|
||||||
Caffeine.newBuilder()
|
Caffeine.newBuilder()
|
||||||
.build(
|
.build(
|
||||||
new CacheLoader<String, Boolean>() {
|
cmd -> {
|
||||||
@Override
|
try {
|
||||||
public Boolean load(String cmd) throws InterruptedException {
|
Process pid = Runtime.getRuntime().exec(cmd.split(" "));
|
||||||
try {
|
pid.getOutputStream().close();
|
||||||
Process pid = Runtime.getRuntime().exec(cmd);
|
pid.waitFor();
|
||||||
pid.getOutputStream().close();
|
} catch (IOException e) {
|
||||||
pid.waitFor();
|
logger.atWarning().withCause(e).log("%s command not available.", cmd);
|
||||||
} catch (IOException e) {
|
return false;
|
||||||
logger.atWarning().withCause(e).log("%s command not available.", cmd);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns {@code true} if system command can be run from path.
|
* Returns {@code true} if system command can be run from the path.
|
||||||
*
|
*
|
||||||
* <p><b>Warning:</b> The command is actually run! So there could be side-effects. You might
|
* <p><b>Warning:</b> The command is actually run! So there could be side effects. You might need
|
||||||
* need to specify a version flag or something. Return code is ignored.
|
* to specify a version flag or something. Return code is ignored.
|
||||||
*
|
*
|
||||||
* <p>This result is a memoized. If multiple therads try to get the same result at once, the
|
* <p>This result is a memoized. If multiple threads try to get the same result at once, the heavy
|
||||||
* heavy lifting will only be performed by the first thread and the rest will wait.
|
* lifting will only be performed by the first thread and the rest will wait.
|
||||||
*
|
|
||||||
* @throws ExecutionException
|
|
||||||
*/
|
*/
|
||||||
public static boolean hasCommand(String cmd) throws ExecutionException {
|
public static boolean hasCommand(String cmd) throws ExecutionException {
|
||||||
return hasCommandCache.get(cmd);
|
return hasCommandCache.get(cmd);
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ public class TextDiffSubject extends Subject {
|
|||||||
|
|
||||||
private final ImmutableList<String> actual;
|
private final ImmutableList<String> actual;
|
||||||
private DiffFormat diffFormat = DiffFormat.SIDE_BY_SIDE_MARKDOWN;
|
private DiffFormat diffFormat = DiffFormat.SIDE_BY_SIDE_MARKDOWN;
|
||||||
|
private ImmutableList<String> comments = ImmutableList.of();
|
||||||
|
|
||||||
protected TextDiffSubject(FailureMetadata metadata, List<String> actual) {
|
protected TextDiffSubject(FailureMetadata metadata, List<String> actual) {
|
||||||
super(metadata, actual);
|
super(metadata, actual);
|
||||||
@@ -83,10 +84,22 @@ public class TextDiffSubject extends Subject {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** If set, ignore lines that start with the given string. */
|
||||||
|
public TextDiffSubject ignoringLinesStartingWith(String... comments) {
|
||||||
|
this.comments = ImmutableList.copyOf(comments);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImmutableList<String> filterComments(List<String> lines) {
|
||||||
|
return lines.stream()
|
||||||
|
.filter(line -> comments.stream().noneMatch(line::startsWith))
|
||||||
|
.collect(ImmutableList.toImmutableList());
|
||||||
|
}
|
||||||
|
|
||||||
public void hasSameContentAs(List<String> expectedContent) {
|
public void hasSameContentAs(List<String> expectedContent) {
|
||||||
checkNotNull(expectedContent, "expectedContent");
|
checkNotNull(expectedContent, "expectedContent");
|
||||||
ImmutableList<String> expected = ImmutableList.copyOf(expectedContent);
|
ImmutableList<String> expected = filterComments(expectedContent);
|
||||||
if (expected.equals(actual)) {
|
if (filterComments(expected).equals(filterComments(actual))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String diffString = diffFormat.generateDiff(expected, actual);
|
String diffString = diffFormat.generateDiff(expected, actual);
|
||||||
@@ -175,14 +188,7 @@ public class TextDiffSubject extends Subject {
|
|||||||
.orElse(0);
|
.orElse(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class SideBySideRowFormatter {
|
private record SideBySideRowFormatter(int maxExpectedLineLength, int maxActualLineLength) {
|
||||||
private final int maxExpectedLineLength;
|
|
||||||
private final int maxActualLineLength;
|
|
||||||
|
|
||||||
private SideBySideRowFormatter(int maxExpectedLineLength, int maxActualLineLength) {
|
|
||||||
this.maxExpectedLineLength = maxExpectedLineLength;
|
|
||||||
this.maxActualLineLength = maxActualLineLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String formatRow(String expected, String actual, char padChar) {
|
public String formatRow(String expected, String actual, char padChar) {
|
||||||
return String.format(
|
return String.format(
|
||||||
|
|||||||
@@ -27,6 +27,9 @@
|
|||||||
{
|
{
|
||||||
"moduleLicense": "Apache License V2.0"
|
"moduleLicense": "Apache License V2.0"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"moduleLicense": "Apache License Version 2.0"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"moduleLicense": "Apache License, Version 2.0"
|
"moduleLicense": "Apache License, Version 2.0"
|
||||||
},
|
},
|
||||||
@@ -102,6 +105,12 @@
|
|||||||
{
|
{
|
||||||
"moduleLicense": "BSD-2-Clause"
|
"moduleLicense": "BSD-2-Clause"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"moduleLicense": "BSD 3-Clause \"New\" or \"Revised\" License (BSD-3-Clause)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleLicense": "BSD licence"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"moduleLicense": "New BSD License"
|
"moduleLicense": "New BSD License"
|
||||||
},
|
},
|
||||||
@@ -141,6 +150,9 @@
|
|||||||
{
|
{
|
||||||
"moduleLicense": "\\n Dual license consisting of the CDDL v1.1 and GPL v2\\n "
|
"moduleLicense": "\\n Dual license consisting of the CDDL v1.1 and GPL v2\\n "
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"moduleLicense": "EPL-2.0"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"moduleLicense": "Eclipse Distribution License (New BSD License)"
|
"moduleLicense": "Eclipse Distribution License (New BSD License)"
|
||||||
},
|
},
|
||||||
@@ -192,6 +204,9 @@
|
|||||||
{
|
{
|
||||||
"moduleLicense": "GNU GENERAL PUBLIC LICENSE, Version 2 + Classpath Exception"
|
"moduleLicense": "GNU GENERAL PUBLIC LICENSE, Version 2 + Classpath Exception"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"moduleLicense": "(GPL-2.0-only WITH Classpath-exception-2.0)"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"moduleLicense": "GNU Library General Public License v2.1 or later"
|
"moduleLicense": "GNU Library General Public License v2.1 or later"
|
||||||
},
|
},
|
||||||
@@ -262,6 +277,12 @@
|
|||||||
{
|
{
|
||||||
"moduleLicense": "Unicode/ICU License"
|
"moduleLicense": "Unicode/ICU License"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"moduleLicense": "Unicode-3.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleLicense": "The W3C Software License"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"moduleLicense": "Public Domain",
|
"moduleLicense": "Public Domain",
|
||||||
"moduleName": "aopalliance:aopalliance"
|
"moduleName": "aopalliance:aopalliance"
|
||||||
@@ -274,12 +295,6 @@
|
|||||||
"moduleLicense": "Public Domain",
|
"moduleLicense": "Public Domain",
|
||||||
"moduleName": "org.json:json"
|
"moduleName": "org.json:json"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
// "Apache License, Version 2.0".
|
|
||||||
"moduleLicense": null,
|
|
||||||
"moduleVersion": "2.10.0",
|
|
||||||
"moduleName": "com.google.gwt:gwt-user"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
// "Apache License, Version 2.0". The plugin is able to parse up to
|
// "Apache License, Version 2.0". The plugin is able to parse up to
|
||||||
// 2.11.3 correctly but then something changed with 2.12.* and it no
|
// 2.11.3 correctly but then something changed with 2.12.* and it no
|
||||||
@@ -287,12 +302,63 @@
|
|||||||
"moduleLicense": null,
|
"moduleLicense": null,
|
||||||
"moduleName": "com.fasterxml.jackson:jackson-bom"
|
"moduleName": "com.fasterxml.jackson:jackson-bom"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// "Apache License, Version 2.0".
|
||||||
|
"moduleLicense": null,
|
||||||
|
"moduleName": "com.google.cloud:libraries-bom"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// Part of Guava with "Apache License, Version 2.0". The plugin is unable
|
// Part of Guava with "Apache License, Version 2.0". The plugin is unable
|
||||||
// to parse its license for unknown reason.
|
// to parse its license for unknown reason.
|
||||||
"moduleLicense": null,
|
"moduleLicense": null,
|
||||||
"moduleName": "com.google.guava:guava-parent"
|
"moduleName": "com.google.guava:guava-parent"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// "Apache License, Version 2.0".
|
||||||
|
"moduleLicense": null,
|
||||||
|
"moduleVersion": "2.10.0",
|
||||||
|
"moduleName": "com.google.gwt:gwt-user"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// "Apache License, Version 2.0".
|
||||||
|
"moduleLicense": null,
|
||||||
|
"moduleName": "com.squareup.okhttp3:okhttp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// "Apache License, Version 2.0".
|
||||||
|
"moduleLicense": null,
|
||||||
|
"moduleVersion": "1.15.1",
|
||||||
|
"moduleName": "com.squareup:kotlinpoet"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// "Apache License, Version 2.0".
|
||||||
|
"moduleLicense": null,
|
||||||
|
"moduleName": "com.squareup.okio:okio"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// "Apache License, Version 2.0".
|
||||||
|
"moduleLicense": null,
|
||||||
|
"moduleVersion": "3.0.0",
|
||||||
|
"moduleName": "com.squareup.okio:okio-bom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// "Apache License, Version 2.0".
|
||||||
|
"moduleLicense": null,
|
||||||
|
"moduleVersion": "3.4.0",
|
||||||
|
"moduleName": "com.squareup.okio:okio-fakefilesystem"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// "Apache License, Version 2.0".
|
||||||
|
"moduleLicense": null,
|
||||||
|
"moduleVersion": "4.9.3",
|
||||||
|
"moduleName": "com.squareup.wire:wire-runtime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// "Apache License, Version 2.0".
|
||||||
|
"moduleLicense": null,
|
||||||
|
"moduleVersion": "4.8.0",
|
||||||
|
"moduleName": "com.squareup.wire:wire-schema"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// "Apache License, Version 2.0". The plugin is able to parse up to
|
// "Apache License, Version 2.0". The plugin is able to parse up to
|
||||||
// 2.0.33.Final but not this verson.
|
// 2.0.33.Final but not this verson.
|
||||||
@@ -300,13 +366,51 @@
|
|||||||
"moduleVersion": "2.0.46.Final",
|
"moduleVersion": "2.0.46.Final",
|
||||||
"moduleName": "io.netty:netty-tcnative-classes"
|
"moduleName": "io.netty:netty-tcnative-classes"
|
||||||
},
|
},
|
||||||
|
// "Apache License, Version 2.0".
|
||||||
|
{
|
||||||
|
"moduleLicense": null,
|
||||||
|
"moduleName": "io.opentelemetry:opentelemetry-bom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// "Apache License, Version 2.0".
|
||||||
|
"moduleLicense": null,
|
||||||
|
"moduleVersion": "1.4",
|
||||||
|
"moduleName": "jakarta-regexp:jakarta-regexp"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// Actually Eclipse Public License v2.0
|
// Actually Eclipse Public License v2.0
|
||||||
"moduleLicense": null,
|
"moduleLicense": null,
|
||||||
"moduleName": "org.junit:junit-bom"
|
"moduleName": "org.junit:junit-bom"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"moduleLicense": "The W3C Software License"
|
// "Apache License, Version 2.0".
|
||||||
|
"moduleLicense": null,
|
||||||
|
"moduleVersion": "1.4.0",
|
||||||
|
"moduleName": "org.jetbrains.kotlin:kotlin-bom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// "Apache License, Version 2.0".
|
||||||
|
"moduleLicense": null,
|
||||||
|
"moduleVersion": "1.9.20",
|
||||||
|
"moduleName": "org.jetbrains.kotlin:kotlin-stdlib-common"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// "Apache License, Version 2.0".
|
||||||
|
"moduleLicense": null,
|
||||||
|
"moduleVersion": "1.5.2",
|
||||||
|
"moduleName": "org.jetbrains.kotlinx:kotlinx-coroutines-core"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// "Apache License, Version 2.0".
|
||||||
|
"moduleLicense": null,
|
||||||
|
"moduleVersion": "0.4.0",
|
||||||
|
"moduleName": "org.jetbrains.kotlinx:kotlinx-datetime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// "Apache License, Version 2.0".
|
||||||
|
"moduleLicense": null,
|
||||||
|
"moduleVersion": "1.0.1",
|
||||||
|
"moduleName": "org.jetbrains.kotlinx:kotlinx-serialization-core"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-12
@@ -47,7 +47,7 @@ class GradleFlag:
|
|||||||
|
|
||||||
|
|
||||||
PROPERTIES_HEADER = """\
|
PROPERTIES_HEADER = """\
|
||||||
# This file defines properties used by the gradle build. It must be kept in
|
# This file defines properties used by the gradle build. It must be kept in
|
||||||
# sync with config/nom_build.py.
|
# sync with config/nom_build.py.
|
||||||
#
|
#
|
||||||
# To regenerate, run ./nom_build --generate-gradle-properties
|
# To regenerate, run ./nom_build --generate-gradle-properties
|
||||||
@@ -58,6 +58,7 @@ PROPERTIES_HEADER = """\
|
|||||||
# DO NOT EDIT THIS FILE BY HAND
|
# DO NOT EDIT THIS FILE BY HAND
|
||||||
org.gradle.jvmargs=-Xmx1024m
|
org.gradle.jvmargs=-Xmx1024m
|
||||||
org.gradle.caching=true
|
org.gradle.caching=true
|
||||||
|
org.gradle.parallel=true
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Help text to be displayed (in addition to the synopsis and flag help, which
|
# Help text to be displayed (in addition to the synopsis and flag help, which
|
||||||
@@ -88,19 +89,10 @@ PROPERTIES = [
|
|||||||
'Allow connecting to plain HTTP repositories. This is provided '
|
'Allow connecting to plain HTTP repositories. This is provided '
|
||||||
'to allow us to communicate to a local proxy when doing '
|
'to allow us to communicate to a local proxy when doing '
|
||||||
'dependency updates.'),
|
'dependency updates.'),
|
||||||
Property('uploaderDestination',
|
|
||||||
'Location to upload test reports to. Normally this should be a '
|
|
||||||
'GCS url (see also uploaderCredentialsFile)'),
|
|
||||||
Property('uploaderCredentialsFile',
|
|
||||||
'json credentials file to use to upload test reports.'),
|
|
||||||
Property('uploaderMultithreadedUpload',
|
|
||||||
'Whether to enable multithread upload.'),
|
|
||||||
Property('verboseTestOutput',
|
Property('verboseTestOutput',
|
||||||
'If true, show all test output in near-realtime.',
|
'If true, show all test output in near-realtime.',
|
||||||
'false',
|
'false',
|
||||||
bool),
|
bool),
|
||||||
Property('flowDocsFile',
|
|
||||||
'Output filename for the flowDocsTool command.'),
|
|
||||||
Property('enableDependencyLocking',
|
Property('enableDependencyLocking',
|
||||||
'Enables dependency locking.',
|
'Enables dependency locking.',
|
||||||
'true',
|
'true',
|
||||||
@@ -278,8 +270,7 @@ def generate_gradle_properties() -> str:
|
|||||||
def get_root() -> str:
|
def get_root() -> str:
|
||||||
"""Returns the root of the nomulus build tree."""
|
"""Returns the root of the nomulus build tree."""
|
||||||
cur_dir = os.getcwd()
|
cur_dir = os.getcwd()
|
||||||
if not os.path.exists(os.path.join(cur_dir, 'buildSrc')) or \
|
if not os.path.exists(os.path.join(cur_dir, 'core')) or \
|
||||||
not os.path.exists(os.path.join(cur_dir, 'core')) or \
|
|
||||||
not os.path.exists(os.path.join(cur_dir, 'gradle.properties')):
|
not os.path.exists(os.path.join(cur_dir, 'gradle.properties')):
|
||||||
raise Exception('You must run this script from the root directory')
|
raise Exception('You must run this script from the root directory')
|
||||||
return cur_dir
|
return cur_dir
|
||||||
|
|||||||
+51
-6
@@ -25,7 +25,11 @@ import textwrap
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
# We should never analyze any generated files
|
# We should never analyze any generated files
|
||||||
UNIVERSALLY_SKIPPED_PATTERNS = {"/build/", "cloudbuild-caches", "/out/", ".git/", ".gradle/", "/dist/", "karma.conf.js", "polyfills.ts", "test.ts", "/docs/console-endpoints/"}
|
UNIVERSALLY_SKIPPED_PATTERNS = {"/build/", "cloudbuild-caches", "/out/", ".git/",
|
||||||
|
".gradle/", "/dist/", "/console-alpha/", "/console-crash/", "/console-qa",
|
||||||
|
"/console-sandbox", "/console-production", "karma.conf.js", "polyfills.ts",
|
||||||
|
"test.ts", "/docs/console-endpoints/", "/bin/generated-sources/",
|
||||||
|
"/bin/generated-test-sources/", "src/main/generated", "src/test/generated"}
|
||||||
# We can't rely on CI to have the Enum package installed so we do this instead.
|
# We can't rely on CI to have the Enum package installed so we do this instead.
|
||||||
FORBIDDEN = 1
|
FORBIDDEN = 1
|
||||||
REQUIRED = 2
|
REQUIRED = 2
|
||||||
@@ -87,8 +91,7 @@ PRESUBMITS = {
|
|||||||
PresubmitCheck(
|
PresubmitCheck(
|
||||||
r".*Copyright 20\d{2} The Nomulus Authors\. All Rights Reserved\.",
|
r".*Copyright 20\d{2} The Nomulus Authors\. All Rights Reserved\.",
|
||||||
("java", "js", "soy", "sql", "py", "sh", "gradle", "ts"), {
|
("java", "js", "soy", "sql", "py", "sh", "gradle", "ts"), {
|
||||||
".git", "/build/", "/generated/", "/generated_tests/",
|
".git", "/build/", "node_modules/", "LoggerConfig.java", "registrar_bin.",
|
||||||
"node_modules/", "LoggerConfig.java", "registrar_bin.",
|
|
||||||
"registrar_dbg.", "google-java-format-diff.py",
|
"registrar_dbg.", "google-java-format-diff.py",
|
||||||
"nomulus.golden.sql", "soyutils_usegoog.js", "javascript/checks.js"
|
"nomulus.golden.sql", "soyutils_usegoog.js", "javascript/checks.js"
|
||||||
}, REQUIRED):
|
}, REQUIRED):
|
||||||
@@ -99,12 +102,12 @@ PRESUBMITS = {
|
|||||||
{"node_modules/"}, REQUIRED):
|
{"node_modules/"}, REQUIRED):
|
||||||
"Source files must end in a newline.",
|
"Source files must end in a newline.",
|
||||||
|
|
||||||
# System.(out|err).println should only appear in tools/
|
# System.(out|err).println should only appear in tools/ or load-testing/
|
||||||
PresubmitCheck(
|
PresubmitCheck(
|
||||||
r".*\bSystem\.(out|err)\.print", "java", {
|
r".*\bSystem\.(out|err)\.print", "java", {
|
||||||
"StackdriverDashboardBuilder.java", "/tools/", "/example/",
|
"StackdriverDashboardBuilder.java", "/tools/", "/example/",
|
||||||
"RegistryTestServerMain.java", "TestServerExtension.java",
|
"/load-testing/", "RegistryTestServerMain.java",
|
||||||
"FlowDocumentationTool.java"
|
"TestServerExtension.java", "FlowDocumentationTool.java"
|
||||||
}):
|
}):
|
||||||
"System.(out|err).println is only allowed in tools/ packages. Please "
|
"System.(out|err).println is only allowed in tools/ packages. Please "
|
||||||
"use a logger instead.",
|
"use a logger instead.",
|
||||||
@@ -171,6 +174,48 @@ PRESUBMITS = {
|
|||||||
{"/node_modules/", "google/registry/ui/js/util.js", "registrar_bin."},
|
{"/node_modules/", "google/registry/ui/js/util.js", "registrar_bin."},
|
||||||
):
|
):
|
||||||
"JavaScript files should not include console logging.",
|
"JavaScript files should not include console logging.",
|
||||||
|
PresubmitCheck(
|
||||||
|
r".*\nimport (static )?.*\.shaded\..*",
|
||||||
|
"java",
|
||||||
|
{"/node_modules/"},
|
||||||
|
):
|
||||||
|
"Do not use shaded dependencies",
|
||||||
|
PresubmitCheck(
|
||||||
|
r".*com\.google\.common\.truth\.Truth8.*",
|
||||||
|
"java",
|
||||||
|
{"/node_modules/"},
|
||||||
|
):
|
||||||
|
"Truth8 is deprecated. Use Truth instead.",
|
||||||
|
PresubmitCheck(
|
||||||
|
r".*java\.util\.Date.*",
|
||||||
|
"java",
|
||||||
|
{"/node_modules/", "JpaTransactionManagerImpl.java"},
|
||||||
|
):
|
||||||
|
"Do not use java.util.Date. Use classes in java.time package instead.",
|
||||||
|
PresubmitCheck(
|
||||||
|
r".*com\.google\.api\.client\.http\.HttpStatusCodes.*",
|
||||||
|
"java",
|
||||||
|
{"/node_modules/"},
|
||||||
|
):
|
||||||
|
"Use status code from jakarta.servlet.http.HttpServletResponse.",
|
||||||
|
PresubmitCheck(
|
||||||
|
r".*mock\(Response\.class\).*",
|
||||||
|
"java",
|
||||||
|
{"/node_modules/"},
|
||||||
|
):
|
||||||
|
"Do not mock Response, use FakeResponse.",
|
||||||
|
PresubmitCheck(
|
||||||
|
r".*javax\.servlet\..*",
|
||||||
|
"java",
|
||||||
|
{"/node_modules/"},
|
||||||
|
):
|
||||||
|
"Do not use javax.servlet.* Use jakarta.servlet.* instead.",
|
||||||
|
PresubmitCheck(
|
||||||
|
r".*javax\.inject\..*",
|
||||||
|
"java",
|
||||||
|
{"/node_modules/"},
|
||||||
|
):
|
||||||
|
"Do not use javax.inject.* Use jakarta.inject.* instead.",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Note that this regex only works for one kind of Flyway file. If we want to
|
# Note that this regex only works for one kind of Flyway file. If we want to
|
||||||
|
|||||||
@@ -31,7 +31,10 @@
|
|||||||
"style": "kebab-case"
|
"style": "kebab-case"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"eol-last": ["error", "always"]
|
"eol-last": [
|
||||||
|
"error",
|
||||||
|
"always"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -36,7 +36,12 @@ yarn-error.log
|
|||||||
/libpeerconnection.log
|
/libpeerconnection.log
|
||||||
testem.log
|
testem.log
|
||||||
/typings
|
/typings
|
||||||
|
.nx/
|
||||||
|
|
||||||
# System files
|
# System files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
|
# Build artifact
|
||||||
|
/staged/dist
|
||||||
|
/staged/console-*
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ Webapp is deployed with the nomulus default service war to Google App Engine.
|
|||||||
During nomulus default service war build task, gradle script triggers the
|
During nomulus default service war build task, gradle script triggers the
|
||||||
following:
|
following:
|
||||||
|
|
||||||
1) Console webapp build script `buildConsoleWebappProd`, which installs
|
1) Console webapp build script `buildConsoleWebapp`, which installs
|
||||||
dependencies, assembles a compiled ts -> js, minified, optimized static
|
dependencies, assembles a compiled ts -> js, minified, optimized static
|
||||||
artifact (html, css, js)
|
artifact (html, css, js)
|
||||||
2) Artifact assembled in step 1 then gets copied to core project web artifact
|
2) Artifact assembled in step 1 then gets copied to core project web artifact
|
||||||
|
|||||||
+68
-11
@@ -15,12 +15,16 @@
|
|||||||
"prefix": "app",
|
"prefix": "app",
|
||||||
"architect": {
|
"architect": {
|
||||||
"build": {
|
"build": {
|
||||||
"builder": "@angular-devkit/build-angular:browser",
|
"builder": "@angular-devkit/build-angular:application",
|
||||||
"options": {
|
"options": {
|
||||||
"outputPath": "dist/console-webapp",
|
"outputPath": {
|
||||||
|
"base": "staged/dist/",
|
||||||
|
"browser": ""
|
||||||
|
},
|
||||||
"index": "src/index.html",
|
"index": "src/index.html",
|
||||||
"main": "src/main.ts",
|
"polyfills": [
|
||||||
"polyfills": "src/polyfills.ts",
|
"src/polyfills.ts"
|
||||||
|
],
|
||||||
"tsConfig": "tsconfig.app.json",
|
"tsConfig": "tsconfig.app.json",
|
||||||
"inlineStyleLanguage": "scss",
|
"inlineStyleLanguage": "scss",
|
||||||
"assets": [
|
"assets": [
|
||||||
@@ -34,7 +38,8 @@
|
|||||||
"stylePreprocessorOptions": {
|
"stylePreprocessorOptions": {
|
||||||
"includePaths": ["node_modules/"]
|
"includePaths": ["node_modules/"]
|
||||||
},
|
},
|
||||||
"scripts": []
|
"scripts": [],
|
||||||
|
"browser": "src/main.ts"
|
||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"production": {
|
"production": {
|
||||||
@@ -58,13 +63,50 @@
|
|||||||
],
|
],
|
||||||
"outputHashing": "all"
|
"outputHashing": "all"
|
||||||
},
|
},
|
||||||
"development": {
|
"sandbox": {
|
||||||
"buildOptimizer": false,
|
"budgets": [
|
||||||
|
{
|
||||||
|
"type": "initial",
|
||||||
|
"maximumWarning": "2mb",
|
||||||
|
"maximumError": "5mb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "anyComponentStyle",
|
||||||
|
"maximumWarning": "2kb",
|
||||||
|
"maximumError": "4kb"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fileReplacements": [
|
||||||
|
{
|
||||||
|
"replace": "src/environments/environment.ts",
|
||||||
|
"with": "src/environments/environment.sandbox.ts"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputHashing": "all"
|
||||||
|
},
|
||||||
|
"crash": {
|
||||||
"optimization": false,
|
"optimization": false,
|
||||||
"vendorChunk": true,
|
|
||||||
"extractLicenses": false,
|
"extractLicenses": false,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"namedChunks": true
|
"namedChunks": true
|
||||||
|
},
|
||||||
|
"alpha": {
|
||||||
|
"optimization": false,
|
||||||
|
"extractLicenses": false,
|
||||||
|
"sourceMap": true,
|
||||||
|
"namedChunks": true
|
||||||
|
},
|
||||||
|
"qa": {
|
||||||
|
"optimization": false,
|
||||||
|
"extractLicenses": false,
|
||||||
|
"sourceMap": true,
|
||||||
|
"namedChunks": true
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"optimization": false,
|
||||||
|
"extractLicenses": false,
|
||||||
|
"sourceMap": true,
|
||||||
|
"namedChunks": true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"defaultConfiguration": "production"
|
"defaultConfiguration": "production"
|
||||||
@@ -73,10 +115,22 @@
|
|||||||
"builder": "@angular-devkit/build-angular:dev-server",
|
"builder": "@angular-devkit/build-angular:dev-server",
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"production": {
|
"production": {
|
||||||
"browserTarget": "console-webapp:build:production"
|
"buildTarget": "console-webapp:build:production"
|
||||||
|
},
|
||||||
|
"alpha": {
|
||||||
|
"buildTarget": "console-webapp:build:alpha"
|
||||||
|
},
|
||||||
|
"crash": {
|
||||||
|
"buildTarget": "console-webapp:build:crash"
|
||||||
|
},
|
||||||
|
"sandbox": {
|
||||||
|
"buildTarget": "console-webapp:build:sandbox"
|
||||||
|
},
|
||||||
|
"qa": {
|
||||||
|
"buildTarget": "console-webapp:build:qa"
|
||||||
},
|
},
|
||||||
"development": {
|
"development": {
|
||||||
"browserTarget": "console-webapp:build:development"
|
"buildTarget": "console-webapp:build:development"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"defaultConfiguration": "development"
|
"defaultConfiguration": "development"
|
||||||
@@ -84,7 +138,7 @@
|
|||||||
"extract-i18n": {
|
"extract-i18n": {
|
||||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||||
"options": {
|
"options": {
|
||||||
"browserTarget": "console-webapp:build"
|
"buildTarget": "console-webapp:build"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"test": {
|
"test": {
|
||||||
@@ -122,6 +176,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cli": {
|
"cli": {
|
||||||
|
"cache": {
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
"analytics": false,
|
"analytics": false,
|
||||||
"schematicCollections": [
|
"schematicCollections": [
|
||||||
"@angular-eslint/schematics"
|
"@angular-eslint/schematics"
|
||||||
|
|||||||
+51
-11
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
|
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -11,12 +11,12 @@
|
|||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
def consoleDir = "${rootDir}/console-webapp"
|
def consoleDir = "${rootDir}/console-webapp"
|
||||||
|
def projectParam = "--project=${rootProject.gcpProject}"
|
||||||
|
|
||||||
clean {
|
clean {
|
||||||
delete "${consoleDir}/node_modules"
|
delete "${consoleDir}/node_modules"
|
||||||
delete "${consoleDir}/dist"
|
delete "${consoleDir}/staged/dist"
|
||||||
}
|
}
|
||||||
|
|
||||||
task npmInstallDeps(type: Exec) {
|
task npmInstallDeps(type: Exec) {
|
||||||
@@ -37,19 +37,58 @@ task runConsoleWebappUnitTests(type: Exec) {
|
|||||||
args 'run', 'test'
|
args 'run', 'test'
|
||||||
}
|
}
|
||||||
|
|
||||||
task buildConsoleWebappNonProd(type: Exec) {
|
task buildConsoleWebapp(type: Exec) {
|
||||||
workingDir "${consoleDir}/"
|
workingDir "${consoleDir}/"
|
||||||
executable 'npm'
|
executable 'npm'
|
||||||
args 'run', 'build'
|
def configuration = project.getProperty('configuration')
|
||||||
|
args 'run', "build", "--configuration=${configuration}"
|
||||||
|
doFirst {
|
||||||
|
println "Building console for environment: ${configuration}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keeping the same as non prod for now before we figure out optimization we want to include
|
task buildConsoleForAll() {}
|
||||||
task buildConsoleWebappProd(type: Exec) {
|
|
||||||
workingDir "${consoleDir}/"
|
def createConsoleTask = { env ->
|
||||||
executable 'npm'
|
project.tasks.register("buildConsoleFor${env.capitalize()}", Exec) {
|
||||||
args 'run', 'build'
|
workingDir "${consoleDir}/"
|
||||||
|
executable 'npm'
|
||||||
|
args 'run', 'build', "--configuration=${env}"
|
||||||
|
doFirst {
|
||||||
|
println "Building console for environment: ${env}"
|
||||||
|
}
|
||||||
|
doLast {
|
||||||
|
copy {
|
||||||
|
from "${consoleDir}/staged/dist/"
|
||||||
|
into "${consoleDir}/staged/console-${env}"
|
||||||
|
}
|
||||||
|
delete "${consoleDir}/staged/dist"
|
||||||
|
}
|
||||||
|
dependsOn(tasks.npmInstallDeps)
|
||||||
|
}
|
||||||
|
project.tasks.register("deleteConsoleFor${env.capitalize()}", Delete) {
|
||||||
|
delete "${consoleDir}/staged/console-${env}"
|
||||||
|
}
|
||||||
|
tasks.named('clean') {
|
||||||
|
dependsOn(tasks.named("deleteConsoleFor${env.capitalize()}"))
|
||||||
|
}
|
||||||
|
tasks.named('buildConsoleForAll') {
|
||||||
|
dependsOn(tasks.named("buildConsoleFor${env.capitalize()}"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
['alpha', 'crash', 'qa', 'sandbox', 'production'].forEach {env ->
|
||||||
|
createConsoleTask(env)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force an order so we don't run these tasks in parallel.
|
||||||
|
tasks.buildConsoleForCrash.mustRunAfter(tasks.buildConsoleForAlpha)
|
||||||
|
tasks.buildConsoleForQa.mustRunAfter(tasks.buildConsoleForCrash)
|
||||||
|
tasks.buildConsoleForSandbox.mustRunAfter(tasks.buildConsoleForQa)
|
||||||
|
tasks.buildConsoleForProduction.mustRunAfter(tasks.buildConsoleForSandbox)
|
||||||
|
// This task must run last, otherwise the previous tasks will have deleted the "dist" folder.
|
||||||
|
tasks.buildConsoleWebapp.mustRunAfter(tasks.buildConsoleForProduction)
|
||||||
|
|
||||||
task applyFormatting(type: Exec) {
|
task applyFormatting(type: Exec) {
|
||||||
workingDir "${consoleDir}/"
|
workingDir "${consoleDir}/"
|
||||||
executable 'npm'
|
executable 'npm'
|
||||||
@@ -62,8 +101,9 @@ task checkFormatting(type: Exec) {
|
|||||||
args 'run', 'prettify:check'
|
args 'run', 'prettify:check'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.buildConsoleWebapp.dependsOn(tasks.npmInstallDeps)
|
||||||
tasks.runConsoleWebappUnitTests.dependsOn(tasks.npmInstallDeps)
|
tasks.runConsoleWebappUnitTests.dependsOn(tasks.npmInstallDeps)
|
||||||
tasks.buildConsoleWebappProd.dependsOn(tasks.npmInstallDeps)
|
|
||||||
tasks.applyFormatting.dependsOn(tasks.npmInstallDeps)
|
tasks.applyFormatting.dependsOn(tasks.npmInstallDeps)
|
||||||
tasks.checkFormatting.dependsOn(tasks.npmInstallDeps)
|
tasks.checkFormatting.dependsOn(tasks.npmInstallDeps)
|
||||||
tasks.build.dependsOn(tasks.checkFormatting)
|
tasks.build.dependsOn(tasks.checkFormatting)
|
||||||
|
tasks.build.dependsOn(tasks.runConsoleWebappUnitTests)
|
||||||
|
|||||||
@@ -1,47 +1,47 @@
|
|||||||
# This is a Gradle generated file for dependency locking.
|
# This is a Gradle generated file for dependency locking.
|
||||||
# Manual edits can break the build and are not advised.
|
# Manual edits can break the build and are not advised.
|
||||||
# This file is expected to be part of source control.
|
# This file is expected to be part of source control.
|
||||||
com.github.ben-manes.caffeine:caffeine:2.7.0=annotationProcessor,errorprone,testAnnotationProcessor
|
aopalliance:aopalliance:1.0=annotationProcessor,errorprone,testAnnotationProcessor
|
||||||
|
com.github.ben-manes.caffeine:caffeine:3.0.5=annotationProcessor,errorprone,testAnnotationProcessor
|
||||||
com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,errorprone,testAnnotationProcessor
|
com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,errorprone,testAnnotationProcessor
|
||||||
com.google.auto:auto-common:0.10=annotationProcessor,errorprone,testAnnotationProcessor
|
com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,errorprone,testAnnotationProcessor
|
||||||
com.google.code.findbugs:jFormatString:3.0.0=annotationProcessor,errorprone,testAnnotationProcessor
|
com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,errorprone,testAnnotationProcessor
|
||||||
|
com.google.auto:auto-common:1.2.1=annotationProcessor,errorprone,testAnnotationProcessor
|
||||||
com.google.code.findbugs:jsr305:3.0.2=annotationProcessor,checkstyle,errorprone,testAnnotationProcessor
|
com.google.code.findbugs:jsr305:3.0.2=annotationProcessor,checkstyle,errorprone,testAnnotationProcessor
|
||||||
com.google.errorprone:error_prone_annotation:2.3.4=annotationProcessor,errorprone,testAnnotationProcessor
|
com.google.errorprone:error_prone_annotation:2.23.0=annotationProcessor,errorprone,testAnnotationProcessor
|
||||||
com.google.errorprone:error_prone_annotations:2.3.4=annotationProcessor,errorprone,testAnnotationProcessor
|
com.google.errorprone:error_prone_annotations:2.23.0=annotationProcessor,errorprone,testAnnotationProcessor
|
||||||
com.google.errorprone:error_prone_annotations:2.7.1=checkstyle
|
com.google.errorprone:error_prone_annotations:2.7.1=checkstyle
|
||||||
com.google.errorprone:error_prone_check_api:2.3.4=annotationProcessor,errorprone,testAnnotationProcessor
|
com.google.errorprone:error_prone_check_api:2.23.0=annotationProcessor,errorprone,testAnnotationProcessor
|
||||||
com.google.errorprone:error_prone_core:2.3.4=annotationProcessor,errorprone,testAnnotationProcessor
|
com.google.errorprone:error_prone_core:2.23.0=annotationProcessor,errorprone,testAnnotationProcessor
|
||||||
com.google.errorprone:error_prone_type_annotations:2.3.4=annotationProcessor,errorprone,testAnnotationProcessor
|
com.google.errorprone:error_prone_type_annotations:2.23.0=annotationProcessor,errorprone,testAnnotationProcessor
|
||||||
|
com.google.errorprone:javac:9+181-r4173-1=errorproneJavac
|
||||||
com.google.guava:failureaccess:1.0.1=annotationProcessor,checkstyle,errorprone,testAnnotationProcessor
|
com.google.guava:failureaccess:1.0.1=annotationProcessor,checkstyle,errorprone,testAnnotationProcessor
|
||||||
com.google.guava:guava:27.0.1-jre=annotationProcessor,errorprone,testAnnotationProcessor
|
com.google.guava:guava-parent:32.1.1-jre=annotationProcessor,errorprone,testAnnotationProcessor
|
||||||
com.google.guava:guava:31.0.1-jre=checkstyle
|
com.google.guava:guava:31.0.1-jre=checkstyle
|
||||||
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,errorprone,testAnnotationProcessor
|
com.google.guava:guava:32.1.1-jre=annotationProcessor,errorprone,testAnnotationProcessor
|
||||||
com.google.j2objc:j2objc-annotations:1.1=annotationProcessor,errorprone,testAnnotationProcessor
|
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=checkstyle
|
||||||
|
com.google.inject:guice:5.1.0=annotationProcessor,errorprone,testAnnotationProcessor
|
||||||
com.google.j2objc:j2objc-annotations:1.3=checkstyle
|
com.google.j2objc:j2objc-annotations:1.3=checkstyle
|
||||||
com.google.protobuf:protobuf-java:3.4.0=annotationProcessor,errorprone,testAnnotationProcessor
|
com.google.protobuf:protobuf-java:3.19.6=annotationProcessor,errorprone,testAnnotationProcessor
|
||||||
com.googlecode.java-diff-utils:diffutils:1.3.0=annotationProcessor,errorprone,testAnnotationProcessor
|
|
||||||
com.puppycrawl.tools:checkstyle:9.3=checkstyle
|
com.puppycrawl.tools:checkstyle:9.3=checkstyle
|
||||||
commons-beanutils:commons-beanutils:1.9.4=checkstyle
|
commons-beanutils:commons-beanutils:1.9.4=checkstyle
|
||||||
commons-collections:commons-collections:3.2.2=checkstyle
|
commons-collections:commons-collections:3.2.2=checkstyle
|
||||||
info.picocli:picocli:4.6.2=checkstyle
|
info.picocli:picocli:4.6.2=checkstyle
|
||||||
|
io.github.eisop:dataflow-errorprone:3.34.0-eisop1=annotationProcessor,errorprone,testAnnotationProcessor
|
||||||
|
io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,errorprone,testAnnotationProcessor
|
||||||
|
javax.inject:javax.inject:1=annotationProcessor,errorprone,testAnnotationProcessor
|
||||||
net.sf.saxon:Saxon-HE:10.6=checkstyle
|
net.sf.saxon:Saxon-HE:10.6=checkstyle
|
||||||
org.antlr:antlr4-runtime:4.9.3=checkstyle
|
org.antlr:antlr4-runtime:4.9.3=checkstyle
|
||||||
org.checkerframework:checker-qual:3.0.0=annotationProcessor,errorprone,testAnnotationProcessor
|
|
||||||
org.checkerframework:checker-qual:3.12.0=checkstyle
|
org.checkerframework:checker-qual:3.12.0=checkstyle
|
||||||
org.checkerframework:dataflow:3.0.0=annotationProcessor,errorprone,testAnnotationProcessor
|
org.checkerframework:checker-qual:3.33.0=annotationProcessor,errorprone,testAnnotationProcessor
|
||||||
org.checkerframework:javacutil:3.0.0=annotationProcessor,errorprone,testAnnotationProcessor
|
org.jacoco:org.jacoco.agent:0.8.12=jacocoAgent,jacocoAnt
|
||||||
org.codehaus.mojo:animal-sniffer-annotations:1.17=annotationProcessor,errorprone,testAnnotationProcessor
|
org.jacoco:org.jacoco.ant:0.8.12=jacocoAnt
|
||||||
org.jacoco:org.jacoco.agent:0.8.9=jacocoAgent,jacocoAnt
|
org.jacoco:org.jacoco.core:0.8.12=jacocoAnt
|
||||||
org.jacoco:org.jacoco.ant:0.8.9=jacocoAnt
|
org.jacoco:org.jacoco.report:0.8.12=jacocoAnt
|
||||||
org.jacoco:org.jacoco.core:0.8.9=jacocoAnt
|
|
||||||
org.jacoco:org.jacoco.report:0.8.9=jacocoAnt
|
|
||||||
org.javassist:javassist:3.28.0-GA=checkstyle
|
org.javassist:javassist:3.28.0-GA=checkstyle
|
||||||
org.ow2.asm:asm-commons:9.5=jacocoAnt
|
org.ow2.asm:asm-commons:9.7=jacocoAnt
|
||||||
org.ow2.asm:asm-tree:9.5=jacocoAnt
|
org.ow2.asm:asm-tree:9.7=jacocoAnt
|
||||||
org.ow2.asm:asm:9.5=jacocoAnt
|
org.ow2.asm:asm:9.7=jacocoAnt
|
||||||
org.pcollections:pcollections:2.1.2=annotationProcessor,errorprone,testAnnotationProcessor
|
org.pcollections:pcollections:3.1.4=annotationProcessor,errorprone,testAnnotationProcessor
|
||||||
org.plumelib:plume-util:1.0.6=annotationProcessor,errorprone,testAnnotationProcessor
|
|
||||||
org.plumelib:reflection-util:0.0.2=annotationProcessor,errorprone,testAnnotationProcessor
|
|
||||||
org.plumelib:require-javadoc:0.1.0=annotationProcessor,errorprone,testAnnotationProcessor
|
|
||||||
org.reflections:reflections:0.10.2=checkstyle
|
org.reflections:reflections:0.10.2=checkstyle
|
||||||
empty=compileClasspath,deploy_jar,errorproneJavac,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
empty=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
configuration=production
|
||||||
@@ -3,6 +3,17 @@
|
|||||||
|
|
||||||
module.exports = function (config) {
|
module.exports = function (config) {
|
||||||
config.set({
|
config.set({
|
||||||
|
customLaunchers: {
|
||||||
|
ChromeHeadless: {
|
||||||
|
base: 'Chrome',
|
||||||
|
flags: [
|
||||||
|
'--no-sandbox',
|
||||||
|
'--disable-gpu',
|
||||||
|
'--headless',
|
||||||
|
'--remote-debugging-port=9222'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
basePath: '',
|
basePath: '',
|
||||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|||||||
Generated
+8188
-17300
File diff suppressed because it is too large
Load Diff
+26
-26
@@ -4,9 +4,9 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
"start": "ng serve --proxy-config dev-proxy.config.json",
|
"start": "ng serve --proxy-config dev-proxy.config.json",
|
||||||
"build": "ng build --base-href=/console/",
|
"build": "ng build --base-href=/console/ --configuration=$npm_config_configuration",
|
||||||
"build:local": "ng build --base-href=/default/console/",
|
"build:local": "ng build --base-href=/default/console/",
|
||||||
"watch": "ng build --watch --configuration development",
|
"watch": "ng build --watch --configuration=development",
|
||||||
"test": "ng test --browsers=ChromeHeadless --watch=false",
|
"test": "ng test --browsers=ChromeHeadless --watch=false",
|
||||||
"run:dev": "",
|
"run:dev": "",
|
||||||
"prettify": "npx prettier --write ./src/",
|
"prettify": "npx prettier --write ./src/",
|
||||||
@@ -16,35 +16,35 @@
|
|||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^15.2.2",
|
"@angular/animations": "^19.1.4",
|
||||||
"@angular/cdk": "^15.2.2",
|
"@angular/cdk": "^19.1.2",
|
||||||
"@angular/common": "^15.2.2",
|
"@angular/common": "^19.1.4",
|
||||||
"@angular/compiler": "^15.2.2",
|
"@angular/compiler": "^19.1.4",
|
||||||
"@angular/core": "^15.2.2",
|
"@angular/core": "^19.1.4",
|
||||||
"@angular/forms": "^15.2.2",
|
"@angular/forms": "^19.1.4",
|
||||||
"@angular/material": "^15.2.2",
|
"@angular/material": "^19.1.2",
|
||||||
"@angular/platform-browser": "^15.2.2",
|
"@angular/platform-browser": "^19.1.4",
|
||||||
"@angular/platform-browser-dynamic": "^15.2.2",
|
"@angular/platform-browser-dynamic": "^19.1.4",
|
||||||
"@angular/router": "^15.2.2",
|
"@angular/router": "^19.1.4",
|
||||||
"rxjs": "~7.5.0",
|
"rxjs": "~7.5.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"zone.js": "~0.11.4"
|
"zone.js": "~0.15.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/build-angular": "^15.2.4",
|
"@angular-devkit/build-angular": "^19.1.5",
|
||||||
"@angular-eslint/builder": "15.2.1",
|
"@angular-eslint/builder": "19.0.2",
|
||||||
"@angular-eslint/eslint-plugin": "15.2.1",
|
"@angular-eslint/eslint-plugin": "19.0.2",
|
||||||
"@angular-eslint/eslint-plugin-template": "15.2.1",
|
"@angular-eslint/eslint-plugin-template": "19.0.2",
|
||||||
"@angular-eslint/schematics": "15.2.1",
|
"@angular-eslint/schematics": "19.0.2",
|
||||||
"@angular-eslint/template-parser": "15.2.1",
|
"@angular-eslint/template-parser": "19.0.2",
|
||||||
"@angular/cli": "~15.2.4",
|
"@angular/cli": "~19.1.5",
|
||||||
"@angular/compiler-cli": "^15.2.2",
|
"@angular/compiler-cli": "^19.1.4",
|
||||||
"@types/jasmine": "~4.0.0",
|
"@types/jasmine": "~4.0.0",
|
||||||
"@types/node": "^18.11.18",
|
"@types/node": "^18.19.74",
|
||||||
"@typescript-eslint/eslint-plugin": "5.48.2",
|
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
||||||
"@typescript-eslint/parser": "5.48.2",
|
"@typescript-eslint/parser": "^7.2.0",
|
||||||
"concurrently": "^7.6.0",
|
"concurrently": "^7.6.0",
|
||||||
"eslint": "^8.33.0",
|
"eslint": "^8.57.0",
|
||||||
"jasmine-core": "~4.3.0",
|
"jasmine-core": "~4.3.0",
|
||||||
"karma": "~6.4.0",
|
"karma": "~6.4.0",
|
||||||
"karma-chrome-launcher": "~3.1.0",
|
"karma-chrome-launcher": "~3.1.0",
|
||||||
@@ -52,6 +52,6 @@
|
|||||||
"karma-jasmine": "~5.1.0",
|
"karma-jasmine": "~5.1.0",
|
||||||
"karma-jasmine-html-reporter": "~2.0.0",
|
"karma-jasmine-html-reporter": "~2.0.0",
|
||||||
"prettier": "2.8.7",
|
"prettier": "2.8.7",
|
||||||
"typescript": "~4.9.4"
|
"typescript": "^5.7.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 The Nomulus Authors. All Rights Reserved.
|
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -13,63 +13,128 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
import { Route, RouterModule } from '@angular/router';
|
||||||
import { TldsComponent } from './tlds/tlds.component';
|
import { BillingInfoComponent } from './billingInfo/billingInfo.component';
|
||||||
|
import { DomainListComponent } from './domains/domainList.component';
|
||||||
import { HomeComponent } from './home/home.component';
|
import { HomeComponent } from './home/home.component';
|
||||||
import { SettingsComponent } from './settings/settings.component';
|
import { RegistryLockVerifyComponent } from './lock/registryLockVerify.component';
|
||||||
import SettingsContactComponent from './settings/contact/contact.component';
|
import { RegistrarDetailsComponent } from './registrar/registrarDetails.component';
|
||||||
import SettingsWhoisComponent from './settings/whois/whois.component';
|
|
||||||
import SettingsUsersComponent from './settings/users/users.component';
|
|
||||||
import SettingsSecurityComponent from './settings/security/security.component';
|
|
||||||
import { RegistrarGuard } from './registrar/registrar.guard';
|
|
||||||
import { RegistrarComponent } from './registrar/registrarsTable.component';
|
import { RegistrarComponent } from './registrar/registrarsTable.component';
|
||||||
import { EmptyRegistrar } from './registrar/emptyRegistrar.component';
|
import { ResourcesComponent } from './resources/resources.component';
|
||||||
import ContactComponent from './settings/contact/contact.component';
|
import ContactComponent from './settings/contact/contact.component';
|
||||||
import WhoisComponent from './settings/whois/whois.component';
|
|
||||||
import SecurityComponent from './settings/security/security.component';
|
import SecurityComponent from './settings/security/security.component';
|
||||||
import UsersComponent from './settings/users/users.component';
|
import { SettingsComponent } from './settings/settings.component';
|
||||||
|
import { SupportComponent } from './support/support.component';
|
||||||
|
import RdapComponent from './settings/rdap/rdap.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
export interface RouteWithIcon extends Route {
|
||||||
|
iconName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PATHS = {
|
||||||
|
NewOteComponent: 'new-ote',
|
||||||
|
OteStatusComponent: 'ote-status/:registrarId',
|
||||||
|
UsersComponent: 'users',
|
||||||
|
};
|
||||||
|
export const routes: RouteWithIcon[] = [
|
||||||
{ path: '', redirectTo: '/home', pathMatch: 'full' },
|
{ path: '', redirectTo: '/home', pathMatch: 'full' },
|
||||||
|
{
|
||||||
|
path: RegistryLockVerifyComponent.PATH,
|
||||||
|
component: RegistryLockVerifyComponent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: PATHS.NewOteComponent,
|
||||||
|
loadComponent: () =>
|
||||||
|
import('./ote/newOte.component').then((mod) => mod.NewOteComponent),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: PATHS.OteStatusComponent,
|
||||||
|
loadComponent: () =>
|
||||||
|
import('./ote/oteStatus.component').then((mod) => mod.OteStatusComponent),
|
||||||
|
},
|
||||||
{ path: 'registrars', component: RegistrarComponent },
|
{ path: 'registrars', component: RegistrarComponent },
|
||||||
{ path: 'empty-registrar', component: EmptyRegistrar },
|
{
|
||||||
{ path: 'home', component: HomeComponent, canActivate: [RegistrarGuard] },
|
path: 'home',
|
||||||
{ path: 'tlds', component: TldsComponent, canActivate: [RegistrarGuard] },
|
component: HomeComponent,
|
||||||
|
title: 'Dashboard',
|
||||||
|
iconName: 'view_comfy_alt',
|
||||||
|
},
|
||||||
|
// { path: 'tlds', component: TldsComponent, title: "TLDs", iconName: "event_list" },
|
||||||
|
{
|
||||||
|
path: DomainListComponent.PATH,
|
||||||
|
component: DomainListComponent,
|
||||||
|
title: 'Domains',
|
||||||
|
iconName: 'view_list',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: SettingsComponent.PATH,
|
path: SettingsComponent.PATH,
|
||||||
component: SettingsComponent,
|
component: SettingsComponent,
|
||||||
|
title: 'Settings',
|
||||||
|
iconName: 'settings',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
redirectTo: 'registrars',
|
redirectTo: ContactComponent.PATH,
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: ContactComponent.PATH,
|
path: ContactComponent.PATH,
|
||||||
component: SettingsContactComponent,
|
component: ContactComponent,
|
||||||
canActivate: [RegistrarGuard],
|
title: 'Contacts',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: WhoisComponent.PATH,
|
path: RdapComponent.PATH,
|
||||||
component: SettingsWhoisComponent,
|
component: RdapComponent,
|
||||||
canActivate: [RegistrarGuard],
|
title: 'RDAP Info',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: SecurityComponent.PATH,
|
path: SecurityComponent.PATH,
|
||||||
component: SettingsSecurityComponent,
|
component: SecurityComponent,
|
||||||
canActivate: [RegistrarGuard],
|
title: 'Security',
|
||||||
},
|
|
||||||
{
|
|
||||||
path: UsersComponent.PATH,
|
|
||||||
component: SettingsUsersComponent,
|
|
||||||
canActivate: [RegistrarGuard],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: RegistrarComponent.PATH,
|
|
||||||
component: RegistrarComponent,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// path: EppConsole.PATH,
|
||||||
|
// component: EppConsoleComponent,
|
||||||
|
// title: "EPP Console",
|
||||||
|
// iconName: "upgrade"
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
path: RegistrarComponent.PATH,
|
||||||
|
component: RegistrarComponent,
|
||||||
|
title: 'Registrars',
|
||||||
|
iconName: 'account_circle',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: RegistrarDetailsComponent.PATH,
|
||||||
|
component: RegistrarDetailsComponent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: BillingInfoComponent.PATH,
|
||||||
|
component: BillingInfoComponent,
|
||||||
|
title: 'Billing Info',
|
||||||
|
iconName: 'credit_card',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ResourcesComponent.PATH,
|
||||||
|
component: ResourcesComponent,
|
||||||
|
title: 'Resources',
|
||||||
|
iconName: 'description',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: PATHS.UsersComponent,
|
||||||
|
title: 'Users',
|
||||||
|
iconName: 'manage_accounts',
|
||||||
|
loadComponent: () =>
|
||||||
|
import('./users/users.component').then((mod) => mod.UsersComponent),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: SupportComponent.PATH,
|
||||||
|
component: SupportComponent,
|
||||||
|
title: 'Support',
|
||||||
|
iconName: 'help',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|||||||
@@ -1,26 +1,25 @@
|
|||||||
<div class="console-app">
|
<div class="console-app mat-typography">
|
||||||
<app-header (toggleNavOpen)="sidenav.toggle()"></app-header>
|
<app-header (toggleNavOpen)="toggleSidenav()"></app-header>
|
||||||
|
<div class="console-app__global-spinner">
|
||||||
|
<mat-progress-bar
|
||||||
|
mode="indeterminate"
|
||||||
|
*ngIf="globalLoader.isLoading"
|
||||||
|
></mat-progress-bar>
|
||||||
|
</div>
|
||||||
<mat-sidenav-container class="console-app__container">
|
<mat-sidenav-container class="console-app__container">
|
||||||
<mat-sidenav #sidenav class="console-app__sidebar">
|
|
||||||
<mat-nav-list>
|
|
||||||
<a mat-list-item [routerLink]="'/home'" routerLinkActive="active">
|
|
||||||
Home page
|
|
||||||
</a>
|
|
||||||
<a mat-list-item [routerLink]="'/tlds'" routerLinkActive="active">
|
|
||||||
TLDS
|
|
||||||
</a>
|
|
||||||
<a mat-list-item [routerLink]="'/settings'" routerLinkActive="active">
|
|
||||||
Settings
|
|
||||||
</a>
|
|
||||||
</mat-nav-list>
|
|
||||||
</mat-sidenav>
|
|
||||||
<mat-sidenav-content class="console-app__content-wrapper">
|
<mat-sidenav-content class="console-app__content-wrapper">
|
||||||
<div *ngIf="globalLoader.isLoading" class="console-app__global-spinner">
|
<div class="console-app__content" role="main">
|
||||||
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
|
|
||||||
</div>
|
|
||||||
<div class="console-app__content" *ngIf="renderRouter">
|
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
</div>
|
</div>
|
||||||
</mat-sidenav-content>
|
</mat-sidenav-content>
|
||||||
|
<mat-sidenav
|
||||||
|
[mode]="breakpointObserver.isMobileView() ? 'over' : 'side'"
|
||||||
|
[opened]="!breakpointObserver.isMobileView()"
|
||||||
|
[disableClose]="!breakpointObserver.isMobileView()"
|
||||||
|
#sidenav
|
||||||
|
class="console-app__sidebar"
|
||||||
|
>
|
||||||
|
<app-navigation />
|
||||||
|
</mat-sidenav>
|
||||||
</mat-sidenav-container>
|
</mat-sidenav-container>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 The Nomulus Authors. All Rights Reserved.
|
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -13,8 +13,8 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
font-family: Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji",
|
font-family: "Google Sans", Roboto, Helvetica, Arial, sans-serif,
|
||||||
"Segoe UI Emoji", "Segoe UI Symbol" !important;
|
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !important;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #333;
|
color: #333;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@@ -26,28 +26,21 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
background: #fff;
|
||||||
&__container {
|
&__container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
margin-top: -12px;
|
|
||||||
padding-bottom: 36px;
|
padding-bottom: 36px;
|
||||||
|
background: transparent;
|
||||||
}
|
}
|
||||||
&__sidebar {
|
&__sidebar {
|
||||||
min-width: 300px;
|
min-width: 240px;
|
||||||
a::before {
|
border: 0;
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
.active {
|
|
||||||
background: #eae1e1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&__content-wrapper {
|
|
||||||
margin: 12px 24px;
|
|
||||||
}
|
}
|
||||||
&__content {
|
&__content {
|
||||||
max-width: 1340px;
|
padding: 0 16px;
|
||||||
margin: 0 auto;
|
|
||||||
}
|
}
|
||||||
&__global-spinner {
|
&__global-spinner {
|
||||||
margin-bottom: 2rem;
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
|
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -12,22 +12,37 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { provideHttpClient } from '@angular/common/http';
|
||||||
|
import { provideHttpClientTesting } from '@angular/common/http/testing';
|
||||||
import { TestBed } from '@angular/core/testing';
|
import { TestBed } from '@angular/core/testing';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
|
||||||
import { AppComponent } from './app.component';
|
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
import { MaterialModule } from './material.module';
|
import { MaterialModule } from './material.module';
|
||||||
|
import { BackendService } from './shared/services/backend.service';
|
||||||
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
|
import { AppModule } from './app.module';
|
||||||
|
|
||||||
describe('AppComponent', () => {
|
describe('AppComponent', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [RouterTestingModule, MaterialModule, BrowserAnimationsModule],
|
|
||||||
declarations: [AppComponent],
|
declarations: [AppComponent],
|
||||||
|
imports: [
|
||||||
|
MaterialModule,
|
||||||
|
BrowserAnimationsModule,
|
||||||
|
AppRoutingModule,
|
||||||
|
AppModule,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
BackendService,
|
||||||
|
provideHttpClient(),
|
||||||
|
provideHttpClientTesting(),
|
||||||
|
],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create the app', () => {
|
it('should create the app', () => {
|
||||||
const fixture = TestBed.createComponent(AppComponent);
|
const fixture = TestBed.createComponent(AppComponent);
|
||||||
|
fixture.detectChanges();
|
||||||
const app = fixture.componentInstance;
|
const app = fixture.componentInstance;
|
||||||
expect(app).toBeTruthy();
|
expect(app).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
|
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -12,43 +12,47 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, ViewChild } from '@angular/core';
|
import { AfterViewInit, Component, ViewChild } from '@angular/core';
|
||||||
import { RegistrarService } from './registrar/registrar.service';
|
|
||||||
import { UserDataService } from './shared/services/userData.service';
|
|
||||||
import { GlobalLoaderService } from './shared/services/globalLoader.service';
|
|
||||||
import { NavigationEnd, Router } from '@angular/router';
|
|
||||||
import { MatSidenav } from '@angular/material/sidenav';
|
import { MatSidenav } from '@angular/material/sidenav';
|
||||||
|
import { NavigationEnd, Router } from '@angular/router';
|
||||||
|
import { RegistrarService } from './registrar/registrar.service';
|
||||||
|
import { BreakPointObserverService } from './shared/services/breakPoint.service';
|
||||||
|
import { GlobalLoaderService } from './shared/services/globalLoader.service';
|
||||||
|
import { UserDataService } from './shared/services/userData.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styleUrls: ['./app.component.scss'],
|
styleUrls: ['./app.component.scss'],
|
||||||
|
standalone: false,
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent implements AfterViewInit {
|
||||||
renderRouter: boolean = true;
|
@ViewChild(MatSidenav)
|
||||||
|
|
||||||
@ViewChild('sidenav')
|
|
||||||
sidenav!: MatSidenav;
|
sidenav!: MatSidenav;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected registrarService: RegistrarService,
|
protected registrarService: RegistrarService,
|
||||||
protected userDataService: UserDataService,
|
protected userDataService: UserDataService,
|
||||||
protected globalLoader: GlobalLoaderService,
|
protected globalLoader: GlobalLoaderService,
|
||||||
protected router: Router
|
protected breakpointObserver: BreakPointObserverService,
|
||||||
) {
|
private router: Router
|
||||||
registrarService.activeRegistrarIdChange.subscribe(() => {
|
) {}
|
||||||
this.renderRouter = false;
|
|
||||||
setTimeout(() => {
|
|
||||||
this.renderRouter = true;
|
|
||||||
}, 400);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ngAfterViewInit() {
|
ngAfterViewInit() {
|
||||||
this.router.events.subscribe((event) => {
|
this.router.events.subscribe((event) => {
|
||||||
if (event instanceof NavigationEnd) {
|
if (event instanceof NavigationEnd) {
|
||||||
this.sidenav.close();
|
if (this.breakpointObserver.isMobileView()) {
|
||||||
|
this.sidenav.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleSidenav() {
|
||||||
|
if (this.sidenav.opened) {
|
||||||
|
this.sidenav.close();
|
||||||
|
} else {
|
||||||
|
this.sidenav.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 The Nomulus Authors. All Rights Reserved.
|
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -13,77 +13,108 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
|
||||||
import { MaterialModule } from './material.module';
|
import { MaterialModule } from './material.module';
|
||||||
|
|
||||||
import { BackendService } from './shared/services/backend.service';
|
import { BackendService } from './shared/services/backend.service';
|
||||||
|
|
||||||
import { HomeComponent } from './home/home.component';
|
import { provideHttpClient } from '@angular/common/http';
|
||||||
import { TldsComponent } from './tlds/tlds.component';
|
|
||||||
import { HeaderComponent } from './header/header.component';
|
|
||||||
import { SettingsComponent } from './settings/settings.component';
|
|
||||||
import SettingsContactComponent, {
|
|
||||||
ContactDetailsDialogComponent,
|
|
||||||
} from './settings/contact/contact.component';
|
|
||||||
import { HttpClientModule } from '@angular/common/http';
|
|
||||||
import { RegistrarComponent } from './registrar/registrarsTable.component';
|
|
||||||
import { RegistrarGuard } from './registrar/registrar.guard';
|
|
||||||
import SecurityComponent from './settings/security/security.component';
|
|
||||||
import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
|
import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
|
||||||
import { EmptyRegistrar } from './registrar/emptyRegistrar.component';
|
import { BillingInfoComponent } from './billingInfo/billingInfo.component';
|
||||||
import { RegistrarSelectorComponent } from './registrar/registrar-selector.component';
|
import {
|
||||||
|
DomainListComponent,
|
||||||
|
ReasonDialogComponent,
|
||||||
|
ResponseDialogComponent,
|
||||||
|
} from './domains/domainList.component';
|
||||||
|
import { RegistryLockComponent } from './domains/registryLock.component';
|
||||||
|
import { HeaderComponent } from './header/header.component';
|
||||||
|
import { HomeComponent } from './home/home.component';
|
||||||
|
import { RegistryLockVerifyComponent } from './lock/registryLockVerify.component';
|
||||||
|
import { NavigationComponent } from './navigation/navigation.component';
|
||||||
|
import NewRegistrarComponent from './registrar/newRegistrar.component';
|
||||||
|
import { RegistrarDetailsComponent } from './registrar/registrarDetails.component';
|
||||||
|
import { RegistrarSelectorComponent } from './registrar/registrarSelector.component';
|
||||||
|
import { RegistrarComponent } from './registrar/registrarsTable.component';
|
||||||
|
import { ResourcesComponent } from './resources/resources.component';
|
||||||
|
import SettingsContactComponent from './settings/contact/contact.component';
|
||||||
|
import { ContactDetailsComponent } from './settings/contact/contactDetails.component';
|
||||||
|
import EppPasswordEditComponent from './settings/security/eppPasswordEdit.component';
|
||||||
|
import SecurityComponent from './settings/security/security.component';
|
||||||
|
import SecurityEditComponent from './settings/security/securityEdit.component';
|
||||||
|
import { SettingsComponent } from './settings/settings.component';
|
||||||
|
import { NotificationsComponent } from './shared/components/notifications/notifications.component';
|
||||||
|
import { SelectedRegistrarWrapper } from './shared/components/selectedRegistrarWrapper/selectedRegistrarWrapper.component';
|
||||||
|
import { LocationBackDirective } from './shared/directives/locationBack.directive';
|
||||||
|
import { UserLevelVisibility } from './shared/directives/userLevelVisiblity.directive';
|
||||||
|
import { BreakPointObserverService } from './shared/services/breakPoint.service';
|
||||||
import { GlobalLoaderService } from './shared/services/globalLoader.service';
|
import { GlobalLoaderService } from './shared/services/globalLoader.service';
|
||||||
import { ContactWidgetComponent } from './home/widgets/contact-widget.component';
|
|
||||||
import { PromotionsWidgetComponent } from './home/widgets/promotions-widget.component';
|
|
||||||
import { TldsWidgetComponent } from './home/widgets/tlds-widget.component';
|
|
||||||
import { ResourcesWidgetComponent } from './home/widgets/resources-widget.component';
|
|
||||||
import { EppWidgetComponent } from './home/widgets/epp-widget.component';
|
|
||||||
import { BillingWidgetComponent } from './home/widgets/billing-widget.component';
|
|
||||||
import { DomainsWidgetComponent } from './home/widgets/domains-widget.component';
|
|
||||||
import { SettingsWidgetComponent } from './home/widgets/settings-widget.component';
|
|
||||||
import { UserDataService } from './shared/services/userData.service';
|
import { UserDataService } from './shared/services/userData.service';
|
||||||
import WhoisComponent from './settings/whois/whois.component';
|
import { SnackBarModule } from './snackbar.module';
|
||||||
|
import { SupportComponent } from './support/support.component';
|
||||||
|
import { TldsComponent } from './tlds/tlds.component';
|
||||||
|
import { ForceFocusDirective } from './shared/directives/forceFocus.directive';
|
||||||
|
import RdapComponent from './settings/rdap/rdap.component';
|
||||||
|
import RdapEditComponent from './settings/rdap/rdapEdit.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [SelectedRegistrarWrapper],
|
||||||
|
imports: [MaterialModule],
|
||||||
|
exports: [SelectedRegistrarWrapper],
|
||||||
|
providers: [],
|
||||||
|
})
|
||||||
|
export class SelectedRegistrarModule {}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent,
|
AppComponent,
|
||||||
BillingWidgetComponent,
|
BillingInfoComponent,
|
||||||
ContactDetailsDialogComponent,
|
ContactDetailsComponent,
|
||||||
ContactWidgetComponent,
|
DomainListComponent,
|
||||||
DomainsWidgetComponent,
|
EppPasswordEditComponent,
|
||||||
EmptyRegistrar,
|
ForceFocusDirective,
|
||||||
EppWidgetComponent,
|
|
||||||
HeaderComponent,
|
HeaderComponent,
|
||||||
HomeComponent,
|
HomeComponent,
|
||||||
PromotionsWidgetComponent,
|
LocationBackDirective,
|
||||||
|
NavigationComponent,
|
||||||
|
NewRegistrarComponent,
|
||||||
|
NotificationsComponent,
|
||||||
|
RdapComponent,
|
||||||
|
RdapEditComponent,
|
||||||
|
ReasonDialogComponent,
|
||||||
RegistrarComponent,
|
RegistrarComponent,
|
||||||
|
RegistrarDetailsComponent,
|
||||||
RegistrarSelectorComponent,
|
RegistrarSelectorComponent,
|
||||||
ResourcesWidgetComponent,
|
RegistryLockComponent,
|
||||||
|
RegistryLockVerifyComponent,
|
||||||
|
ResourcesComponent,
|
||||||
|
ResponseDialogComponent,
|
||||||
SecurityComponent,
|
SecurityComponent,
|
||||||
|
SecurityEditComponent,
|
||||||
SettingsComponent,
|
SettingsComponent,
|
||||||
SettingsContactComponent,
|
SettingsContactComponent,
|
||||||
SettingsWidgetComponent,
|
SupportComponent,
|
||||||
TldsComponent,
|
TldsComponent,
|
||||||
TldsWidgetComponent,
|
UserLevelVisibility,
|
||||||
WhoisComponent,
|
|
||||||
],
|
],
|
||||||
|
bootstrap: [AppComponent],
|
||||||
imports: [
|
imports: [
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
HttpClientModule,
|
|
||||||
MaterialModule,
|
MaterialModule,
|
||||||
|
SelectedRegistrarModule,
|
||||||
|
SnackBarModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
BackendService,
|
BackendService,
|
||||||
|
BreakPointObserverService,
|
||||||
GlobalLoaderService,
|
GlobalLoaderService,
|
||||||
RegistrarGuard,
|
|
||||||
UserDataService,
|
UserDataService,
|
||||||
{
|
{
|
||||||
provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,
|
provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,
|
||||||
@@ -91,7 +122,7 @@ import WhoisComponent from './settings/whois/whois.component';
|
|||||||
subscriptSizing: 'dynamic',
|
subscriptSizing: 'dynamic',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
provideHttpClient(),
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent],
|
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<app-selected-registrar-wrapper>
|
||||||
|
<h1 class="mat-headline-4" forceFocus>Billing Info</h1>
|
||||||
|
<div class="console-app__billing">
|
||||||
|
<div>
|
||||||
|
<div class="console-app__billing-subhead">
|
||||||
|
Billing records and information
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
class="text-l"
|
||||||
|
href="{{ driveFolderUrl() }}"
|
||||||
|
target="_blank"
|
||||||
|
aria-label="View billing records on Google Drive"
|
||||||
|
>View on Google Drive</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<img src="./assets/billing.png" alt="Generic billing image" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</app-selected-registrar-wrapper>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
.console-app__billing {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap-reverse;
|
||||||
|
> div {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: left;
|
||||||
|
max-width: 300px;
|
||||||
|
min-width: 200px;
|
||||||
|
margin-top: -40px;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
&-subhead {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
// Copyright 2024 The Nomulus Authors. 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.
|
||||||
|
|
||||||
|
import { Component, computed } from '@angular/core';
|
||||||
|
import { RegistrarService } from '../registrar/registrar.service';
|
||||||
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-billingInfo',
|
||||||
|
templateUrl: './billingInfo.component.html',
|
||||||
|
styleUrls: ['./billingInfo.component.scss'],
|
||||||
|
standalone: false,
|
||||||
|
})
|
||||||
|
export class BillingInfoComponent {
|
||||||
|
public static PATH = 'billingInfo';
|
||||||
|
constructor(
|
||||||
|
public registrarService: RegistrarService,
|
||||||
|
private _snackBar: MatSnackBar
|
||||||
|
) {}
|
||||||
|
|
||||||
|
driveFolderUrl = computed<string>(() => {
|
||||||
|
if (this.registrarService.registrar()?.driveFolderId) {
|
||||||
|
return (
|
||||||
|
'https://drive.google.com/drive/folders/' +
|
||||||
|
this.registrarService.registrar()?.driveFolderId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
|
openBillingDetails(e: MouseEvent) {
|
||||||
|
if (!this.registrarService.registrar()?.driveFolderId) {
|
||||||
|
e.preventDefault();
|
||||||
|
this._snackBar.open('Billing Folder ID has not been assigned');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,230 @@
|
|||||||
|
<app-selected-registrar-wrapper>
|
||||||
|
<div class="console-app-domains">
|
||||||
|
<h1 class="mat-headline-4" forceFocus>Domains</h1>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="console-app-domains__actions-wrapper"
|
||||||
|
[hidden]="!domainListService.activeActionComponent"
|
||||||
|
>
|
||||||
|
<ng-container
|
||||||
|
v-if="domainListService.activeActionComponent"
|
||||||
|
*ngComponentOutlet="domainListService.activeActionComponent"
|
||||||
|
>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (!isLoading && totalResults == 0) {
|
||||||
|
<div class="console-app__empty-domains">
|
||||||
|
<h1>
|
||||||
|
<mat-icon class="console-app__empty-domains-icon secondary-text"
|
||||||
|
>apps_outage</mat-icon
|
||||||
|
>
|
||||||
|
</h1>
|
||||||
|
<h1>No domains found</h1>
|
||||||
|
</div>
|
||||||
|
} @else {
|
||||||
|
<mat-menu #actions="matMenu">
|
||||||
|
<ng-template
|
||||||
|
matMenuContent
|
||||||
|
let-domainName="domainName"
|
||||||
|
let-domain="domain"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
mat-menu-item
|
||||||
|
(click)="openRegistryLock(domainName)"
|
||||||
|
aria-label="Access registry lock for domain"
|
||||||
|
>
|
||||||
|
<mat-icon>key</mat-icon>
|
||||||
|
<span>Registry Lock</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
mat-menu-item
|
||||||
|
(click)="onSuspendClick(domainName)"
|
||||||
|
[elementId]="getElementIdForSuspendUnsuspend()"
|
||||||
|
[disabled]="isDomainUnsuspendable(domain)"
|
||||||
|
>
|
||||||
|
<mat-icon>lock_clock</mat-icon>
|
||||||
|
<span>Suspend</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
mat-menu-item
|
||||||
|
(click)="onUnsuspendClick(domainName)"
|
||||||
|
[elementId]="getElementIdForSuspendUnsuspend()"
|
||||||
|
[disabled]="!isDomainUnsuspendable(domain)"
|
||||||
|
>
|
||||||
|
<mat-icon>lock_open</mat-icon>
|
||||||
|
<span>Unsuspend</span>
|
||||||
|
</button>
|
||||||
|
</ng-template>
|
||||||
|
</mat-menu>
|
||||||
|
<div
|
||||||
|
class="console-app__domains-table-parent"
|
||||||
|
[hidden]="domainListService.activeActionComponent"
|
||||||
|
>
|
||||||
|
<div class="console-app__scrollable-wrapper">
|
||||||
|
<div class="console-app__scrollable">
|
||||||
|
@if (isLoading) {
|
||||||
|
<div class="console-app__domains-spinner">
|
||||||
|
<mat-spinner />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<a
|
||||||
|
mat-stroked-button
|
||||||
|
color="primary"
|
||||||
|
href="/console-api/dum-download?registrarId={{
|
||||||
|
registrarService.registrarId()
|
||||||
|
}}"
|
||||||
|
class="console-app-domains__download"
|
||||||
|
>
|
||||||
|
<mat-icon>download</mat-icon>
|
||||||
|
Download domains (.csv)
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<mat-form-field class="console-app__domains-filter">
|
||||||
|
<mat-label>Filter</mat-label>
|
||||||
|
<input
|
||||||
|
type="search"
|
||||||
|
matInput
|
||||||
|
[(ngModel)]="searchTerm"
|
||||||
|
(ngModelChange)="sendInput()"
|
||||||
|
#input
|
||||||
|
/>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="console-app__domains-selection"
|
||||||
|
[elementId]="getElementIdForBulkDelete()"
|
||||||
|
[ngClass]="{ active: selection.hasValue() }"
|
||||||
|
>
|
||||||
|
<div class="console-app__domains-selection-text">
|
||||||
|
{{ selection.selected.length }} Selected
|
||||||
|
</div>
|
||||||
|
<div class="console-app__domains-selection-actions">
|
||||||
|
<button
|
||||||
|
mat-flat-button
|
||||||
|
aria-label="Delete Selected Domains"
|
||||||
|
[attr.aria-hidden]="!selection.hasValue()"
|
||||||
|
(click)="deleteSelectedDomains()"
|
||||||
|
>
|
||||||
|
Delete Selected Domains
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<mat-table
|
||||||
|
[dataSource]="dataSource"
|
||||||
|
class="mat-elevation-z0"
|
||||||
|
class="console-app__domains-table"
|
||||||
|
>
|
||||||
|
<!-- Checkbox Column -->
|
||||||
|
<ng-container matColumnDef="select">
|
||||||
|
<mat-header-cell *matHeaderCellDef>
|
||||||
|
<mat-checkbox
|
||||||
|
(change)="$event ? toggleAllRows() : null"
|
||||||
|
[checked]="selection.hasValue() && isAllSelected"
|
||||||
|
[indeterminate]="selection.hasValue() && !isAllSelected"
|
||||||
|
[aria-label]="checkboxLabel()"
|
||||||
|
[elementId]="getElementIdForBulkDelete()"
|
||||||
|
>
|
||||||
|
</mat-checkbox>
|
||||||
|
</mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let row">
|
||||||
|
<mat-checkbox
|
||||||
|
(click)="$event.stopPropagation()"
|
||||||
|
(change)="$event ? selection.toggle(row) : null"
|
||||||
|
[checked]="selection.isSelected(row)"
|
||||||
|
[aria-label]="checkboxLabel(row)"
|
||||||
|
[elementId]="getElementIdForBulkDelete()"
|
||||||
|
>
|
||||||
|
</mat-checkbox>
|
||||||
|
</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="domainName">
|
||||||
|
<mat-header-cell *matHeaderCellDef>Domain Name</mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let element">
|
||||||
|
<mat-icon
|
||||||
|
*ngIf="getOperationMessage(element.domainName)"
|
||||||
|
[matTooltip]="getOperationMessage(element.domainName)"
|
||||||
|
matTooltipPosition="above"
|
||||||
|
class="primary-text"
|
||||||
|
>info</mat-icon
|
||||||
|
>
|
||||||
|
<span>{{ element.domainName }}</span>
|
||||||
|
</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="creationTime">
|
||||||
|
<mat-header-cell *matHeaderCellDef>Creation Time</mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let element">
|
||||||
|
{{ element.creationTime.creationTime }}
|
||||||
|
</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="registrationExpirationTime">
|
||||||
|
<mat-header-cell *matHeaderCellDef
|
||||||
|
>Expiration Time</mat-header-cell
|
||||||
|
>
|
||||||
|
<mat-cell *matCellDef="let element">
|
||||||
|
{{ element.registrationExpirationTime }}
|
||||||
|
</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="statuses">
|
||||||
|
<mat-header-cell *matHeaderCellDef>Statuses</mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let element">
|
||||||
|
<span>{{ element.statuses?.join(", ") }}</span>
|
||||||
|
</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="registryLock">
|
||||||
|
<mat-header-cell *matHeaderCellDef
|
||||||
|
>Registry-Locked</mat-header-cell
|
||||||
|
>
|
||||||
|
<mat-cell *matCellDef="let element">{{
|
||||||
|
isDomainLocked(element.domainName)
|
||||||
|
}}</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="actions">
|
||||||
|
<mat-header-cell *matHeaderCellDef>Actions</mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let element">
|
||||||
|
<button
|
||||||
|
mat-icon-button
|
||||||
|
[matMenuTriggerFor]="actions"
|
||||||
|
[matMenuTriggerData]="{
|
||||||
|
domainName: element.domainName,
|
||||||
|
domain: element
|
||||||
|
}"
|
||||||
|
aria-label="Domain actions"
|
||||||
|
>
|
||||||
|
<mat-icon>more_horiz</mat-icon>
|
||||||
|
</button>
|
||||||
|
</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<mat-header-row
|
||||||
|
*matHeaderRowDef="displayedColumns"
|
||||||
|
></mat-header-row>
|
||||||
|
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
|
||||||
|
|
||||||
|
<!-- Row shown when there is no matching data. -->
|
||||||
|
<mat-row *matNoDataRow>
|
||||||
|
<mat-cell colspan="6">No domains found</mat-cell>
|
||||||
|
</mat-row>
|
||||||
|
</mat-table>
|
||||||
|
<mat-paginator
|
||||||
|
[length]="totalResults"
|
||||||
|
[pageIndex]="pageNumber"
|
||||||
|
[pageSize]="resultsPerPage"
|
||||||
|
[pageSizeOptions]="[10, 25, 50, 100, 500]"
|
||||||
|
(page)="onPageChange($event)"
|
||||||
|
aria-label="Select page of domain results"
|
||||||
|
showFirstLastButtons
|
||||||
|
></mat-paginator>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</app-selected-registrar-wrapper>
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
.console-app {
|
||||||
|
$min-width: 756px;
|
||||||
|
|
||||||
|
&__empty-domains {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
&-icon {
|
||||||
|
transform: scale(3);
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__domains-selection {
|
||||||
|
height: 60px;
|
||||||
|
max-height: 0;
|
||||||
|
transition: max-height 0.2s linear;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
gap: 20px;
|
||||||
|
&-text {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
&.active {
|
||||||
|
max-height: 60px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-domains__download {
|
||||||
|
position: absolute;
|
||||||
|
top: -55px;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__domains-filter {
|
||||||
|
min-width: $min-width !important;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__domains-table-parent {
|
||||||
|
position: relative;
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__domains-table {
|
||||||
|
min-width: $min-width !important;
|
||||||
|
.mat-column-actions {
|
||||||
|
max-width: 100px;
|
||||||
|
}
|
||||||
|
.mat-column-registryLock {
|
||||||
|
max-width: 150px;
|
||||||
|
}
|
||||||
|
.mat-column-statuses span {
|
||||||
|
padding: 10px 0;
|
||||||
|
overflow: hidden;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
.mat-column-select {
|
||||||
|
max-width: 60px;
|
||||||
|
padding-left: 15px;
|
||||||
|
}
|
||||||
|
.mat-column-domainName {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 25px;
|
||||||
|
mat-icon {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mat-cell:has([style*="display: none"]),
|
||||||
|
mat-header-cell:has([style*="display: none"]) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__domains-spinner {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background: rgba(255, 255, 255, 0.6);
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-mdc-paginator {
|
||||||
|
min-width: $min-width !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
// Copyright 2024 The Nomulus Authors. 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.
|
||||||
|
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { provideHttpClient } from '@angular/common/http';
|
||||||
|
import { provideHttpClientTesting } from '@angular/common/http/testing';
|
||||||
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { MaterialModule } from '../material.module';
|
||||||
|
import { BackendService } from '../shared/services/backend.service';
|
||||||
|
import { DomainListComponent } from './domainList.component';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { AppModule } from '../app.module';
|
||||||
|
|
||||||
|
describe('DomainListComponent', () => {
|
||||||
|
let component: DomainListComponent;
|
||||||
|
let fixture: ComponentFixture<DomainListComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [DomainListComponent],
|
||||||
|
imports: [
|
||||||
|
MaterialModule,
|
||||||
|
BrowserAnimationsModule,
|
||||||
|
FormsModule,
|
||||||
|
AppModule,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
BackendService,
|
||||||
|
provideHttpClient(),
|
||||||
|
provideHttpClientTesting(),
|
||||||
|
],
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(DomainListComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,419 @@
|
|||||||
|
// Copyright 2024 The Nomulus Authors. 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.
|
||||||
|
|
||||||
|
import { SelectionModel } from '@angular/cdk/collections';
|
||||||
|
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
|
||||||
|
import { Component, effect, Inject, ViewChild } from '@angular/core';
|
||||||
|
import { MatPaginator, PageEvent } from '@angular/material/paginator';
|
||||||
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
|
import { MatTableDataSource } from '@angular/material/table';
|
||||||
|
import { debounceTime, filter, Subject, take } from 'rxjs';
|
||||||
|
import { RegistrarService } from '../registrar/registrar.service';
|
||||||
|
import {
|
||||||
|
BULK_ACTION_NAME,
|
||||||
|
Domain,
|
||||||
|
DomainListService,
|
||||||
|
} from './domainList.service';
|
||||||
|
import { RegistryLockComponent } from './registryLock.component';
|
||||||
|
import { RegistryLockService } from './registryLock.service';
|
||||||
|
import {
|
||||||
|
MAT_DIALOG_DATA,
|
||||||
|
MatDialog,
|
||||||
|
MatDialogRef,
|
||||||
|
} from '@angular/material/dialog';
|
||||||
|
import { RESTRICTED_ELEMENTS } from '../shared/directives/userLevelVisiblity.directive';
|
||||||
|
|
||||||
|
interface DomainResponse {
|
||||||
|
message: string;
|
||||||
|
responseCode: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DomainData {
|
||||||
|
[domain: string]: DomainResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-response-dialog',
|
||||||
|
template: `
|
||||||
|
<h2 mat-dialog-title>{{ data.title }}</h2>
|
||||||
|
<mat-dialog-content [innerHTML]="data.content" />
|
||||||
|
<mat-dialog-actions>
|
||||||
|
<button mat-button (click)="onClose()">Close</button>
|
||||||
|
</mat-dialog-actions>
|
||||||
|
`,
|
||||||
|
standalone: false,
|
||||||
|
})
|
||||||
|
export class ResponseDialogComponent {
|
||||||
|
constructor(
|
||||||
|
public dialogRef: MatDialogRef<ReasonDialogComponent>,
|
||||||
|
@Inject(MAT_DIALOG_DATA)
|
||||||
|
public data: { title: string; content: string }
|
||||||
|
) {}
|
||||||
|
|
||||||
|
onClose(): void {
|
||||||
|
this.dialogRef.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Operation {
|
||||||
|
deleting = 'deleting',
|
||||||
|
suspending = 'suspending',
|
||||||
|
unsuspending = 'unsuspending',
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-reason-dialog',
|
||||||
|
template: `
|
||||||
|
<h2 mat-dialog-title>
|
||||||
|
Please provide the (EPP) reason for {{ data.operation }} the domain(s):
|
||||||
|
</h2>
|
||||||
|
<mat-dialog-content>
|
||||||
|
<mat-form-field appearance="outline" style="width:100%">
|
||||||
|
<textarea matInput [(ngModel)]="reason" rows="4"></textarea>
|
||||||
|
</mat-form-field>
|
||||||
|
</mat-dialog-content>
|
||||||
|
<mat-dialog-actions>
|
||||||
|
<button mat-button (click)="onCancel()">Cancel</button>
|
||||||
|
<button mat-button color="warn" (click)="onSave()" [disabled]="!reason">
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</mat-dialog-actions>
|
||||||
|
`,
|
||||||
|
standalone: false,
|
||||||
|
})
|
||||||
|
export class ReasonDialogComponent {
|
||||||
|
reason: string = '';
|
||||||
|
constructor(
|
||||||
|
public dialogRef: MatDialogRef<ReasonDialogComponent>,
|
||||||
|
@Inject(MAT_DIALOG_DATA)
|
||||||
|
public data: { operation: Operation }
|
||||||
|
) {}
|
||||||
|
|
||||||
|
onSave(): void {
|
||||||
|
this.dialogRef.close(this.reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
onCancel(): void {
|
||||||
|
this.dialogRef.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-domain-list',
|
||||||
|
templateUrl: './domainList.component.html',
|
||||||
|
styleUrls: ['./domainList.component.scss'],
|
||||||
|
standalone: false,
|
||||||
|
})
|
||||||
|
export class DomainListComponent {
|
||||||
|
public static PATH = 'domain-list';
|
||||||
|
private static SUSPENDED_STATUSES = [
|
||||||
|
'SERVER_RENEW_PROHIBITED',
|
||||||
|
'SERVER_TRANSFER_PROHIBITED',
|
||||||
|
'SERVER_UPDATE_PROHIBITED',
|
||||||
|
'SERVER_DELETE_PROHIBITED',
|
||||||
|
'SERVER_HOLD',
|
||||||
|
];
|
||||||
|
private readonly DEBOUNCE_MS = 500;
|
||||||
|
isAllSelected = false;
|
||||||
|
|
||||||
|
displayedColumns: string[] = [
|
||||||
|
'select',
|
||||||
|
'domainName',
|
||||||
|
'creationTime',
|
||||||
|
'registrationExpirationTime',
|
||||||
|
'statuses',
|
||||||
|
'registryLock',
|
||||||
|
'actions',
|
||||||
|
];
|
||||||
|
|
||||||
|
dataSource: MatTableDataSource<Domain> = new MatTableDataSource();
|
||||||
|
selection = new SelectionModel<Domain>(true, [], undefined, this.isChecked());
|
||||||
|
isLoading = true;
|
||||||
|
|
||||||
|
searchTermSubject = new Subject<string>();
|
||||||
|
searchTerm?: string;
|
||||||
|
|
||||||
|
pageNumber?: number;
|
||||||
|
resultsPerPage = 50;
|
||||||
|
totalResults?: number = 0;
|
||||||
|
|
||||||
|
reason: string = '';
|
||||||
|
|
||||||
|
operationResult: DomainData | undefined;
|
||||||
|
|
||||||
|
@ViewChild(MatPaginator, { static: true }) paginator!: MatPaginator;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected domainListService: DomainListService,
|
||||||
|
protected registrarService: RegistrarService,
|
||||||
|
protected registryLockService: RegistryLockService,
|
||||||
|
private _snackBar: MatSnackBar,
|
||||||
|
private dialog: MatDialog
|
||||||
|
) {
|
||||||
|
effect(() => {
|
||||||
|
this.pageNumber = 0;
|
||||||
|
this.totalResults = 0;
|
||||||
|
if (this.registrarService.registrarId()) {
|
||||||
|
this.loadLocks();
|
||||||
|
this.reloadData();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.dataSource.paginator = this.paginator;
|
||||||
|
// Don't spam the server unnecessarily while the user is typing
|
||||||
|
this.searchTermSubject
|
||||||
|
.pipe(debounceTime(this.DEBOUNCE_MS))
|
||||||
|
.subscribe((searchTermValue) => {
|
||||||
|
this.reloadData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.searchTermSubject.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
openRegistryLock(domainName: string) {
|
||||||
|
this.domainListService.selectedDomain = domainName;
|
||||||
|
this.domainListService.activeActionComponent = RegistryLockComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadLocks() {
|
||||||
|
this.registryLockService.retrieveLocks().subscribe({
|
||||||
|
error: (err: HttpErrorResponse) => {
|
||||||
|
if (err.status !== HttpStatusCode.Forbidden) {
|
||||||
|
// Some users may not have registry lock permissions and that's OK
|
||||||
|
this._snackBar.open(err.message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
isDomainLocked(domainName: string) {
|
||||||
|
return this.registryLockService.domainsLocks.some(
|
||||||
|
(d) => d.domainName === domainName
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadData() {
|
||||||
|
this.isLoading = true;
|
||||||
|
this.domainListService
|
||||||
|
.retrieveDomains(
|
||||||
|
this.pageNumber,
|
||||||
|
this.resultsPerPage,
|
||||||
|
this.totalResults,
|
||||||
|
this.searchTerm
|
||||||
|
)
|
||||||
|
.subscribe({
|
||||||
|
error: (err: HttpErrorResponse) => {
|
||||||
|
this._snackBar.open(err.message);
|
||||||
|
this.isLoading = false;
|
||||||
|
},
|
||||||
|
next: (domainListResult) => {
|
||||||
|
this.dataSource.data = this.domainListService.domainsList;
|
||||||
|
this.totalResults = (domainListResult || {}).totalResults || 0;
|
||||||
|
this.isLoading = false;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sendInput() {
|
||||||
|
this.searchTermSubject.next(this.searchTerm!);
|
||||||
|
}
|
||||||
|
|
||||||
|
onPageChange(event: PageEvent) {
|
||||||
|
this.pageNumber = event.pageIndex;
|
||||||
|
this.resultsPerPage = event.pageSize;
|
||||||
|
this.selection.clear();
|
||||||
|
this.reloadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleAllRows() {
|
||||||
|
if (this.isAllSelected) {
|
||||||
|
this.selection.clear();
|
||||||
|
this.isAllSelected = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selection.select(...this.dataSource.data);
|
||||||
|
this.isAllSelected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkboxLabel(row?: Domain): string {
|
||||||
|
if (!row) {
|
||||||
|
return `${this.isAllSelected ? 'deselect' : 'select'} all`;
|
||||||
|
}
|
||||||
|
return `${this.selection.isSelected(row) ? 'deselect' : 'select'} row ${
|
||||||
|
row.domainName
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private isChecked(): ((o1: Domain, o2: Domain) => boolean) | undefined {
|
||||||
|
return (o1: Domain, o2: Domain) => {
|
||||||
|
if (!o1.domainName || !o2.domainName) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.isAllSelected || o1.domainName === o2.domainName;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getElementIdForBulkDelete() {
|
||||||
|
return RESTRICTED_ELEMENTS.BULK_DELETE;
|
||||||
|
}
|
||||||
|
|
||||||
|
getElementIdForSuspendUnsuspend() {
|
||||||
|
return RESTRICTED_ELEMENTS.SUSPEND;
|
||||||
|
}
|
||||||
|
|
||||||
|
getOperationMessage(domain: string) {
|
||||||
|
if (this.operationResult && this.operationResult[domain])
|
||||||
|
return this.operationResult[domain].message;
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
isDomainUnsuspendable(domain: Domain) {
|
||||||
|
return DomainListComponent.SUSPENDED_STATUSES.every((s) =>
|
||||||
|
domain.statuses.includes(s)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendDeleteRequest(reason: string) {
|
||||||
|
this.isLoading = true;
|
||||||
|
this.domainListService
|
||||||
|
.bulkDomainAction(
|
||||||
|
this.selection.selected.map((d) => d.domainName),
|
||||||
|
reason,
|
||||||
|
this.registrarService.registrarId(),
|
||||||
|
BULK_ACTION_NAME.DELETE
|
||||||
|
)
|
||||||
|
.pipe(take(1))
|
||||||
|
.subscribe({
|
||||||
|
next: (result: DomainData) => {
|
||||||
|
this.isLoading = false;
|
||||||
|
const successCount = Object.keys(result).filter((domainName) =>
|
||||||
|
result[domainName].responseCode.toString().startsWith('1')
|
||||||
|
).length;
|
||||||
|
const failureCount = Object.keys(result).length - successCount;
|
||||||
|
this.dialog.open(ResponseDialogComponent, {
|
||||||
|
data: {
|
||||||
|
title: 'Domain Deletion Results',
|
||||||
|
content: `Successfully deleted - ${successCount} domain(s)<br/>Failed to delete - ${failureCount} domain(s)<br/>${
|
||||||
|
failureCount
|
||||||
|
? 'Some domains could not be deleted due to ongoing processes or server errors. '
|
||||||
|
: ''
|
||||||
|
}Please check the table for more information.`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.selection.clear();
|
||||||
|
this.operationResult = result;
|
||||||
|
this.reloadData();
|
||||||
|
},
|
||||||
|
error: (err: HttpErrorResponse) => {
|
||||||
|
this.isLoading = false;
|
||||||
|
this._snackBar.open(err.error || err.message);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteSelectedDomains() {
|
||||||
|
const dialogRef = this.dialog.open(ReasonDialogComponent, {
|
||||||
|
data: {
|
||||||
|
operation: Operation.deleting,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef
|
||||||
|
.afterClosed()
|
||||||
|
.pipe(
|
||||||
|
take(1),
|
||||||
|
filter((reason) => !!reason)
|
||||||
|
)
|
||||||
|
.subscribe(this.sendDeleteRequest.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
sendSuspendUnsuspendRequest(
|
||||||
|
domainName: string,
|
||||||
|
reason: string,
|
||||||
|
actionName: BULK_ACTION_NAME
|
||||||
|
) {
|
||||||
|
this.isLoading = true;
|
||||||
|
this.domainListService
|
||||||
|
.bulkDomainAction(
|
||||||
|
[domainName],
|
||||||
|
reason,
|
||||||
|
this.registrarService.registrarId(),
|
||||||
|
actionName
|
||||||
|
)
|
||||||
|
.pipe(take(1))
|
||||||
|
.subscribe({
|
||||||
|
next: (result: DomainData) => {
|
||||||
|
this.isLoading = false;
|
||||||
|
if (result[domainName].responseCode.toString().startsWith('2')) {
|
||||||
|
this._snackBar.open(result[domainName].message);
|
||||||
|
} else {
|
||||||
|
this.reloadData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: (err: HttpErrorResponse) => {
|
||||||
|
this.isLoading = false;
|
||||||
|
this._snackBar.open(err.error || err.message);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
onSuspendClick(domainName: string) {
|
||||||
|
const dialogRef = this.dialog.open(ReasonDialogComponent, {
|
||||||
|
data: {
|
||||||
|
operation: Operation.suspending,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef
|
||||||
|
.afterClosed()
|
||||||
|
.pipe(
|
||||||
|
take(1),
|
||||||
|
filter((reason) => !!reason)
|
||||||
|
)
|
||||||
|
.subscribe((reason) => {
|
||||||
|
this.sendSuspendUnsuspendRequest(
|
||||||
|
domainName,
|
||||||
|
reason,
|
||||||
|
BULK_ACTION_NAME.SUSPEND
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onUnsuspendClick(domainName: string) {
|
||||||
|
const dialogRef = this.dialog.open(ReasonDialogComponent, {
|
||||||
|
data: {
|
||||||
|
operation: Operation.unsuspending,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef
|
||||||
|
.afterClosed()
|
||||||
|
.pipe(
|
||||||
|
take(1),
|
||||||
|
filter((reason) => !!reason)
|
||||||
|
)
|
||||||
|
.subscribe((reason) => {
|
||||||
|
this.sendSuspendUnsuspendRequest(
|
||||||
|
domainName,
|
||||||
|
reason,
|
||||||
|
BULK_ACTION_NAME.UNSUSPEND
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
// Copyright 2024 The Nomulus Authors. 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.
|
||||||
|
|
||||||
|
import { Injectable, Type } from '@angular/core';
|
||||||
|
import { tap } from 'rxjs';
|
||||||
|
import { RegistrarService } from '../registrar/registrar.service';
|
||||||
|
import { BackendService } from '../shared/services/backend.service';
|
||||||
|
|
||||||
|
export interface CreateAutoTimestamp {
|
||||||
|
creationTime: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Domain {
|
||||||
|
creationTime: CreateAutoTimestamp;
|
||||||
|
currentSponsorRegistrarId: string;
|
||||||
|
domainName: string;
|
||||||
|
registrationExpirationTime: string;
|
||||||
|
statuses: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DomainListResult {
|
||||||
|
checkpointTime: string;
|
||||||
|
domains: Domain[];
|
||||||
|
totalResults: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum BULK_ACTION_NAME {
|
||||||
|
DELETE = 'DELETE',
|
||||||
|
SUSPEND = 'SUSPEND',
|
||||||
|
UNSUSPEND = 'UNSUSPEND',
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class DomainListService {
|
||||||
|
checkpointTime?: string;
|
||||||
|
selectedDomain?: string;
|
||||||
|
public activeActionComponent: Type<any> | null = null;
|
||||||
|
public domainsList: Domain[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private backendService: BackendService,
|
||||||
|
private registrarService: RegistrarService
|
||||||
|
) {}
|
||||||
|
retrieveDomains(
|
||||||
|
pageNumber?: number,
|
||||||
|
resultsPerPage?: number,
|
||||||
|
totalResults?: number,
|
||||||
|
searchTerm?: string
|
||||||
|
) {
|
||||||
|
return this.backendService
|
||||||
|
.getDomains(
|
||||||
|
this.registrarService.registrarId(),
|
||||||
|
this.checkpointTime,
|
||||||
|
pageNumber,
|
||||||
|
resultsPerPage,
|
||||||
|
totalResults,
|
||||||
|
searchTerm
|
||||||
|
)
|
||||||
|
.pipe(
|
||||||
|
tap((domainListResult: DomainListResult) => {
|
||||||
|
this.checkpointTime = domainListResult?.checkpointTime;
|
||||||
|
this.domainsList = domainListResult?.domains;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bulkDomainAction(
|
||||||
|
domains: string[],
|
||||||
|
reason: string,
|
||||||
|
registrarId: string,
|
||||||
|
actionName: BULK_ACTION_NAME
|
||||||
|
) {
|
||||||
|
return this.backendService.bulkDomainAction(
|
||||||
|
domains,
|
||||||
|
reason,
|
||||||
|
actionName,
|
||||||
|
registrarId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
<div class="console-app__registry-lock">
|
||||||
|
<p>
|
||||||
|
<button
|
||||||
|
mat-icon-button
|
||||||
|
aria-label="Back to domains list"
|
||||||
|
(click)="goBack()"
|
||||||
|
>
|
||||||
|
<mat-icon>arrow_back</mat-icon>
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
@if(!registrarService.registrar()?.registryLockAllowed) {
|
||||||
|
<h1>
|
||||||
|
Sorry, your registrar hasn't enrolled in registry lock yet. To do so, please
|
||||||
|
contact {{ userDataService.userData()?.supportEmail }}.
|
||||||
|
</h1>
|
||||||
|
} @else if (isLocked()) {
|
||||||
|
<h1>Unlock the domain {{ domainListService.selectedDomain }}</h1>
|
||||||
|
<form (ngSubmit)="save(false)" [formGroup]="unlockDomain">
|
||||||
|
<p>
|
||||||
|
<mat-label for="password">Password: </mat-label>
|
||||||
|
<mat-form-field name="password" appearance="outline">
|
||||||
|
<input matInput type="text" formControlName="password" required />
|
||||||
|
</mat-form-field>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<mat-label for="relockTime"
|
||||||
|
>Automatically re-lock the domain after:</mat-label
|
||||||
|
>
|
||||||
|
<mat-radio-group
|
||||||
|
name="relockTime"
|
||||||
|
formControlName="relockTime"
|
||||||
|
aria-label="Automatically relock option"
|
||||||
|
>
|
||||||
|
@for (option of relockOptions; track option.name) {
|
||||||
|
<mat-radio-button [value]="option.duration">{{
|
||||||
|
option.name
|
||||||
|
}}</mat-radio-button>
|
||||||
|
}
|
||||||
|
</mat-radio-group>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="console-app__registry-lock-notification">
|
||||||
|
<mat-icon>priority_high</mat-icon>Confirmation email will be sent to your
|
||||||
|
email address to confirm the unlock
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
mat-flat-button
|
||||||
|
color="primary"
|
||||||
|
type="submit"
|
||||||
|
[disabled]="!unlockDomain.valid"
|
||||||
|
aria-label="Submit domain unlock request"
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
} @else {
|
||||||
|
<h1>Lock the domain {{ domainListService.selectedDomain }}</h1>
|
||||||
|
<form (ngSubmit)="save(true)" [formGroup]="lockDomain">
|
||||||
|
<p>
|
||||||
|
<mat-label for="password">Password: </mat-label>
|
||||||
|
<mat-form-field name="password" appearance="outline">
|
||||||
|
<input matInput type="text" formControlName="password" required />
|
||||||
|
</mat-form-field>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="console-app__registry-lock-notification">
|
||||||
|
<mat-icon>priority_high</mat-icon>The lock will not take effect until you
|
||||||
|
click the confirmation link that will be emailed to you. When it takes
|
||||||
|
effect, you will be billed the standard server status change billing cost.
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
mat-flat-button
|
||||||
|
color="primary"
|
||||||
|
type="submit"
|
||||||
|
[disabled]="!lockDomain.valid"
|
||||||
|
aria-label="Submit domain lock request"
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
.console-app {
|
||||||
|
&__registry-lock {
|
||||||
|
mat-label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&__registry-lock-notification {
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: var(--light-highlight);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
width: max-content;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
// Copyright 2024 The Nomulus Authors. 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.
|
||||||
|
|
||||||
|
import { HttpErrorResponse } from '@angular/common/http';
|
||||||
|
import { Component, computed } from '@angular/core';
|
||||||
|
import { FormControl, FormGroup } from '@angular/forms';
|
||||||
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
|
import { RegistrarService } from '../registrar/registrar.service';
|
||||||
|
import { UserDataService } from '../shared/services/userData.service';
|
||||||
|
import { DomainListService } from './domainList.service';
|
||||||
|
import { RegistryLockService } from './registryLock.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-registry-lock',
|
||||||
|
templateUrl: './registryLock.component.html',
|
||||||
|
styleUrls: ['./registryLock.component.scss'],
|
||||||
|
standalone: false,
|
||||||
|
})
|
||||||
|
export class RegistryLockComponent {
|
||||||
|
readonly isLocked = computed(() =>
|
||||||
|
this.registryLockService.domainsLocks.some(
|
||||||
|
(dl) => dl.domainName === this.domainListService.selectedDomain
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
relockOptions = [
|
||||||
|
{ name: '1 hour', duration: 3600000 },
|
||||||
|
{ name: '6 hours', duration: 21600000 },
|
||||||
|
{ name: '24 hours', duration: 86400000 },
|
||||||
|
{ name: 'Never', duration: undefined },
|
||||||
|
];
|
||||||
|
|
||||||
|
lockDomain = new FormGroup({
|
||||||
|
password: new FormControl(''),
|
||||||
|
});
|
||||||
|
|
||||||
|
unlockDomain = new FormGroup({
|
||||||
|
password: new FormControl(''),
|
||||||
|
relockTime: new FormControl(undefined),
|
||||||
|
});
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected registrarService: RegistrarService,
|
||||||
|
protected domainListService: DomainListService,
|
||||||
|
protected registryLockService: RegistryLockService,
|
||||||
|
protected userDataService: UserDataService,
|
||||||
|
private _snackBar: MatSnackBar
|
||||||
|
) {}
|
||||||
|
|
||||||
|
goBack() {
|
||||||
|
this.domainListService.selectedDomain = undefined;
|
||||||
|
this.domainListService.activeActionComponent = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
save(isLock: boolean) {
|
||||||
|
let request;
|
||||||
|
if (!isLock) {
|
||||||
|
request = this.registryLockService.registryLockDomain(
|
||||||
|
this.domainListService.selectedDomain || '',
|
||||||
|
this.unlockDomain.value.password || '',
|
||||||
|
this.unlockDomain.value.relockTime || undefined,
|
||||||
|
isLock
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
request = this.registryLockService.registryLockDomain(
|
||||||
|
this.domainListService.selectedDomain || '',
|
||||||
|
this.lockDomain.value.password || '',
|
||||||
|
undefined,
|
||||||
|
isLock
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
request.subscribe({
|
||||||
|
complete: () => {
|
||||||
|
this.goBack();
|
||||||
|
},
|
||||||
|
error: (err: HttpErrorResponse) => {
|
||||||
|
this._snackBar.open(err.error);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
// Copyright 2024 The Nomulus Authors. 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.
|
||||||
|
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { tap } from 'rxjs';
|
||||||
|
import { RegistrarService } from '../registrar/registrar.service';
|
||||||
|
import { BackendService } from '../shared/services/backend.service';
|
||||||
|
|
||||||
|
export interface DomainLocksResult {
|
||||||
|
domainName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class RegistryLockService {
|
||||||
|
public domainsLocks: DomainLocksResult[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private backendService: BackendService,
|
||||||
|
private registrarService: RegistrarService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
retrieveLocks() {
|
||||||
|
return this.backendService
|
||||||
|
.getLocks(this.registrarService.registrarId())
|
||||||
|
.pipe(
|
||||||
|
tap((domainLocksResult) => {
|
||||||
|
this.domainsLocks = domainLocksResult;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
registryLockDomain(
|
||||||
|
domainName: string,
|
||||||
|
password: string,
|
||||||
|
relockDurationMillis: number | undefined,
|
||||||
|
isLock: boolean
|
||||||
|
) {
|
||||||
|
return this.backendService.registryLockDomain(
|
||||||
|
domainName,
|
||||||
|
password,
|
||||||
|
relockDurationMillis,
|
||||||
|
this.registrarService.registrarId(),
|
||||||
|
isLock
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 The Nomulus Authors. All Rights Reserved.
|
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -16,8 +16,24 @@
|
|||||||
&__logo {
|
&__logo {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
margin-left: -15px;
|
||||||
|
}
|
||||||
|
&__menu-btn {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
&__header {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
|
||||||
|
.mat-toolbar {
|
||||||
|
background: transparent;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-user-icon {
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.spacer {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 The Nomulus Authors. All Rights Reserved.
|
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -17,6 +17,12 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|||||||
import { HeaderComponent } from './header.component';
|
import { HeaderComponent } from './header.component';
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { MaterialModule } from '../material.module';
|
import { MaterialModule } from '../material.module';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { AppModule, SelectedRegistrarModule } from '../app.module';
|
||||||
|
import { AppRoutingModule } from '../app-routing.module';
|
||||||
|
import { BackendService } from '../shared/services/backend.service';
|
||||||
|
import { provideHttpClient } from '@angular/common/http';
|
||||||
|
import { provideHttpClientTesting } from '@angular/common/http/testing';
|
||||||
|
|
||||||
describe('HeaderComponent', () => {
|
describe('HeaderComponent', () => {
|
||||||
let component: HeaderComponent;
|
let component: HeaderComponent;
|
||||||
@@ -24,7 +30,19 @@ describe('HeaderComponent', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [MaterialModule, BrowserAnimationsModule],
|
imports: [
|
||||||
|
SelectedRegistrarModule,
|
||||||
|
MaterialModule,
|
||||||
|
BrowserAnimationsModule,
|
||||||
|
AppRoutingModule,
|
||||||
|
AppModule,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
BackendService,
|
||||||
|
{ provide: ActivatedRoute, useValue: {} as ActivatedRoute },
|
||||||
|
provideHttpClient(),
|
||||||
|
provideHttpClientTesting(),
|
||||||
|
],
|
||||||
declarations: [HeaderComponent],
|
declarations: [HeaderComponent],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 The Nomulus Authors. All Rights Reserved.
|
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -13,19 +13,27 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, EventEmitter, Output } from '@angular/core';
|
import { Component, EventEmitter, Output } from '@angular/core';
|
||||||
|
import { BreakPointObserverService } from '../shared/services/breakPoint.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-header',
|
selector: 'app-header',
|
||||||
templateUrl: './header.component.html',
|
templateUrl: './header.component.html',
|
||||||
styleUrls: ['./header.component.scss'],
|
styleUrls: ['./header.component.scss'],
|
||||||
|
standalone: false,
|
||||||
})
|
})
|
||||||
export class HeaderComponent {
|
export class HeaderComponent {
|
||||||
private isNavOpen = false;
|
private isNavOpen = false;
|
||||||
|
|
||||||
|
constructor(protected breakpointObserver: BreakPointObserverService) {}
|
||||||
|
|
||||||
@Output() toggleNavOpen = new EventEmitter<boolean>();
|
@Output() toggleNavOpen = new EventEmitter<boolean>();
|
||||||
|
|
||||||
toggleNavPane() {
|
toggleNavPane() {
|
||||||
this.isNavOpen = !this.isNavOpen;
|
this.isNavOpen = !this.isNavOpen;
|
||||||
this.toggleNavOpen.emit(this.isNavOpen);
|
this.toggleNavOpen.emit(this.isNavOpen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logOut() {
|
||||||
|
window.open('/console?gcp-iap-mode=CLEAR_LOGIN_COOKIE', '_self');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,55 @@
|
|||||||
<div class="console-app__home">
|
<div
|
||||||
<h1>Welcome to the Google Registry Console</h1>
|
class="console-app__home"
|
||||||
|
[class.console-app__home_tablet]="breakPointObserverService.isTabletView()"
|
||||||
|
>
|
||||||
|
<h1 class="mat-headline-4">Dashboard</h1>
|
||||||
<div class="console-app__home-widgets">
|
<div class="console-app__home-widgets">
|
||||||
<div app-domains-widget class="console-app__widget-wrapper__wide"></div>
|
<mat-card appearance="outlined">
|
||||||
<div app-contact-widget class="console-app__widget-wrapper__wide"></div>
|
<mat-card-content>
|
||||||
<div app-tlds-widget></div>
|
<h3>
|
||||||
<div app-promotions-widget class="console-app__widget-wrapper__wide"></div>
|
<mat-icon class="secondary-text">view_list</mat-icon>
|
||||||
<div app-settings-widget class="console-app__widget-wrapper__wide"></div>
|
DUMs
|
||||||
<div app-resources-widget></div>
|
</h3>
|
||||||
<div app-billing-widget></div>
|
<p class="secondary-text">View Domains Under Management</p>
|
||||||
<div app-epp-widget></div>
|
</mat-card-content>
|
||||||
|
<mat-card-actions>
|
||||||
|
<button mat-button color="primary" (click)="viewDums()">
|
||||||
|
View DUMs
|
||||||
|
</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card>
|
||||||
|
|
||||||
|
<mat-card appearance="outlined">
|
||||||
|
<mat-card-content>
|
||||||
|
<h3>
|
||||||
|
<mat-icon class="secondary-text">settings</mat-icon>
|
||||||
|
EPP Passwords
|
||||||
|
</h3>
|
||||||
|
<p class="secondary-text">View / Update EPP Password</p>
|
||||||
|
</mat-card-content>
|
||||||
|
<mat-card-actions>
|
||||||
|
<button mat-button color="primary" (click)="updateEppPassword()">
|
||||||
|
Update EPP Password
|
||||||
|
</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card>
|
||||||
|
|
||||||
|
<mat-card
|
||||||
|
appearance="outlined"
|
||||||
|
[elementId]="getElementIdForRegistrarsBlock()"
|
||||||
|
>
|
||||||
|
<mat-card-content>
|
||||||
|
<h3>
|
||||||
|
<mat-icon class="secondary-text">account_circle</mat-icon>
|
||||||
|
Registrars
|
||||||
|
</h3>
|
||||||
|
<p class="secondary-text">View all registrars</p>
|
||||||
|
</mat-card-content>
|
||||||
|
<mat-card-actions>
|
||||||
|
<button mat-button color="primary" (click)="viewRegistrars()">
|
||||||
|
Manage Registrars
|
||||||
|
</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
|
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -13,20 +13,23 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
.console-app {
|
.console-app {
|
||||||
&__home {
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
&__home-widgets {
|
&__home-widgets {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
gap: 1rem;
|
||||||
grid-gap: 20px;
|
h3 {
|
||||||
grid-auto-flow: dense;
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
mat-card {
|
mat-card {
|
||||||
height: 100%;
|
flex: 1;
|
||||||
h1 {
|
}
|
||||||
margin-top: 1rem;
|
}
|
||||||
}
|
&__home_tablet {
|
||||||
|
.console-app__home-widgets {
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
|
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -16,6 +16,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|||||||
|
|
||||||
import { HomeComponent } from './home.component';
|
import { HomeComponent } from './home.component';
|
||||||
import { MaterialModule } from '../material.module';
|
import { MaterialModule } from '../material.module';
|
||||||
|
import { AppModule } from '../app.module';
|
||||||
|
|
||||||
describe('HomeComponent', () => {
|
describe('HomeComponent', () => {
|
||||||
let component: HomeComponent;
|
let component: HomeComponent;
|
||||||
@@ -23,7 +24,7 @@ describe('HomeComponent', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [MaterialModule],
|
imports: [MaterialModule, AppModule],
|
||||||
declarations: [HomeComponent],
|
declarations: [HomeComponent],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
|
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -12,12 +12,43 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, ViewEncapsulation } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { DomainListComponent } from '../domains/domainList.component';
|
||||||
|
import { RegistrarComponent } from '../registrar/registrarsTable.component';
|
||||||
|
import SecurityComponent from '../settings/security/security.component';
|
||||||
|
import { SettingsComponent } from '../settings/settings.component';
|
||||||
|
import { RESTRICTED_ELEMENTS } from '../shared/directives/userLevelVisiblity.directive';
|
||||||
|
import { BreakPointObserverService } from '../shared/services/breakPoint.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-home',
|
selector: 'app-home',
|
||||||
templateUrl: './home.component.html',
|
templateUrl: './home.component.html',
|
||||||
styleUrls: ['./home.component.scss'],
|
styleUrls: ['./home.component.scss'],
|
||||||
encapsulation: ViewEncapsulation.None,
|
standalone: false,
|
||||||
})
|
})
|
||||||
export class HomeComponent {}
|
export class HomeComponent {
|
||||||
|
constructor(
|
||||||
|
protected breakPointObserverService: BreakPointObserverService,
|
||||||
|
private router: Router
|
||||||
|
) {}
|
||||||
|
getElementIdForRegistrarsBlock() {
|
||||||
|
return RESTRICTED_ELEMENTS.REGISTRAR_ELEMENT;
|
||||||
|
}
|
||||||
|
viewRegistrars() {
|
||||||
|
this.router.navigate([RegistrarComponent.PATH], {
|
||||||
|
queryParamsHandling: 'merge',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
updateEppPassword() {
|
||||||
|
this.router.navigate(
|
||||||
|
[SettingsComponent.PATH + '/' + SecurityComponent.PATH],
|
||||||
|
{ queryParamsHandling: 'merge' }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
viewDums() {
|
||||||
|
this.router.navigate([DomainListComponent.PATH], {
|
||||||
|
queryParamsHandling: 'merge',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
<mat-card>
|
|
||||||
<mat-card-content>
|
|
||||||
<div class="console-app__widget">
|
|
||||||
<a
|
|
||||||
class="console-app__widget_left"
|
|
||||||
href="{{ driveFolderUrl }}"
|
|
||||||
(click)="openBillingDetails($event)"
|
|
||||||
>
|
|
||||||
<mat-icon class="console-app__widget-icon">account_balance</mat-icon>
|
|
||||||
<h1 class="console-app__widget-title">Billing Info</h1>
|
|
||||||
<h4 class="secondary-text text-center">
|
|
||||||
<span *ngIf="driveFolderUrl; else noDriveFolderUrl">
|
|
||||||
View important billing and payments information.
|
|
||||||
</span>
|
|
||||||
<ng-template #noDriveFolderUrl>
|
|
||||||
<span> Your billing folder is pending allocation. </span>
|
|
||||||
</ng-template>
|
|
||||||
</h4>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<mat-card>
|
|
||||||
<mat-card-content>
|
|
||||||
<div class="console-app__widget">
|
|
||||||
<div class="console-app__widget_left">
|
|
||||||
<mat-icon class="console-app__widget-icon">call</mat-icon>
|
|
||||||
<h1 class="console-app__widget-title">Contact Support</h1>
|
|
||||||
<h4 class="secondary-text text-center">
|
|
||||||
View Google Registry support email and phone information
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div class="console-app__widget_right">
|
|
||||||
<button mat-button color="primary" class="console-app__widget-link">
|
|
||||||
Give us a Call
|
|
||||||
</button>
|
|
||||||
<p class="secondary-text">
|
|
||||||
Call Google Registry support at <b>+1 (404) 978 8419</b>
|
|
||||||
</p>
|
|
||||||
<button mat-button color="primary" class="console-app__widget-link">
|
|
||||||
Send us an Email
|
|
||||||
</button>
|
|
||||||
<p class="secondary-text">
|
|
||||||
Email Google Registry at <b>support@google.com</b>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
<mat-card>
|
|
||||||
<mat-card-content>
|
|
||||||
<div class="console-app__widget">
|
|
||||||
<div class="console-app__widget_left">
|
|
||||||
<mat-icon class="console-app__widget-icon">view_list</mat-icon>
|
|
||||||
<h1 class="console-app__widget-title">Domains</h1>
|
|
||||||
<h4 class="secondary-text text-center">
|
|
||||||
Manage domain names and registry lock settings.
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div class="console-app__widget_right">
|
|
||||||
<button mat-button color="primary" class="console-app__widget-link">
|
|
||||||
Create a Domain
|
|
||||||
</button>
|
|
||||||
<p class="secondary-text">Register a new domain name</p>
|
|
||||||
<button mat-button color="primary" class="console-app__widget-link">
|
|
||||||
View DUMs
|
|
||||||
</button>
|
|
||||||
<p class="secondary-text">
|
|
||||||
Download a csv of all domains under management
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<mat-card>
|
|
||||||
<mat-card-content>
|
|
||||||
<div class="console-app__widget">
|
|
||||||
<div class="console-app__widget_left">
|
|
||||||
<mat-icon class="console-app__widget-icon">computer</mat-icon>
|
|
||||||
<h1 class="console-app__widget-title">EPP Console</h1>
|
|
||||||
<h4 class="secondary-text text-center">
|
|
||||||
Test run and execute EPP commands using XML files.
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<mat-card class="console-app__widget-wrapper__wide">
|
|
||||||
<mat-card-content>
|
|
||||||
<div class="console-app__widget">
|
|
||||||
<div class="console-app__widget_left">
|
|
||||||
<mat-icon class="console-app__widget-icon">subject</mat-icon>
|
|
||||||
<h1 class="console-app__widget-title">Promotions</h1>
|
|
||||||
<h4 class="secondary-text text-center">
|
|
||||||
Manage Google Registry promotions and view onboarding process.
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div class="console-app__widget_right">
|
|
||||||
<button mat-button color="primary" class="console-app__widget-link">
|
|
||||||
Manage Preferred Partner Program
|
|
||||||
</button>
|
|
||||||
<p class="secondary-text">
|
|
||||||
Onboard or view current preferred partner status
|
|
||||||
</p>
|
|
||||||
<button mat-button color="primary" class="console-app__widget-link">
|
|
||||||
Manage Registry Lock Program
|
|
||||||
</button>
|
|
||||||
<p class="secondary-text">
|
|
||||||
Onboard or view current registry lock status
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<mat-card>
|
|
||||||
<mat-card-content>
|
|
||||||
<div class="console-app__widget">
|
|
||||||
<a
|
|
||||||
class="console-app__widget_left"
|
|
||||||
href="{{ userDataService.userData?.technicalDocsUrl }}"
|
|
||||||
>
|
|
||||||
<mat-icon class="console-app__widget-icon">menu_book</mat-icon>
|
|
||||||
<h1 class="console-app__widget-title">Resources</h1>
|
|
||||||
<h4 class="secondary-text text-center">
|
|
||||||
Use Google Drive to view onboarding FAQs, and technical documentation.
|
|
||||||
</h4>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
// Copyright 2023 The Nomulus Authors. 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.
|
|
||||||
|
|
||||||
import { Component } from '@angular/core';
|
|
||||||
import { UserDataService } from 'src/app/shared/services/userData.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: '[app-resources-widget]',
|
|
||||||
templateUrl: './resources-widget.component.html',
|
|
||||||
})
|
|
||||||
export class ResourcesWidgetComponent {
|
|
||||||
constructor(public userDataService: UserDataService) {}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
<mat-card class="console-app__widget-wrapper__wide">
|
|
||||||
<mat-card-content>
|
|
||||||
<div class="console-app__widget">
|
|
||||||
<div class="console-app__widget_left">
|
|
||||||
<mat-icon class="console-app__widget-icon">settings</mat-icon>
|
|
||||||
<h1 class="console-app__widget-title">Settings</h1>
|
|
||||||
<h4 class="secondary-text text-center">
|
|
||||||
Configure registrar settings, manage console users, and view activity
|
|
||||||
log.
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div class="console-app__widget_right">
|
|
||||||
<button
|
|
||||||
mat-button
|
|
||||||
color="primary"
|
|
||||||
class="console-app__widget-link"
|
|
||||||
(click)="openContactsPage()"
|
|
||||||
>
|
|
||||||
Contact Information
|
|
||||||
</button>
|
|
||||||
<p class="secondary-text">Manage Primary, Technical, etc contacts.</p>
|
|
||||||
<button
|
|
||||||
mat-button
|
|
||||||
color="primary"
|
|
||||||
class="console-app__widget-link"
|
|
||||||
(click)="openSecurityPage()"
|
|
||||||
>
|
|
||||||
Security
|
|
||||||
</button>
|
|
||||||
<p class="secondary-text">
|
|
||||||
Manage IP allow lists and SSL certificates.
|
|
||||||
</p>
|
|
||||||
<button mat-button color="primary" class="console-app__widget-link">
|
|
||||||
Nomulus Password
|
|
||||||
</button>
|
|
||||||
<p class="secondary-text">Reset your Nomulus password.</p>
|
|
||||||
<button mat-button color="primary" class="console-app__widget-link">
|
|
||||||
User Management
|
|
||||||
</button>
|
|
||||||
<p class="secondary-text">Create and manage console user accounts</p>
|
|
||||||
<button
|
|
||||||
mat-button
|
|
||||||
color="primary"
|
|
||||||
class="console-app__widget-link"
|
|
||||||
(click)="openRegistrarsPage()"
|
|
||||||
>
|
|
||||||
Registrar Management
|
|
||||||
</button>
|
|
||||||
<p class="secondary-text">Create and manage registrar accounts</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
// Copyright 2023 The Nomulus Authors. 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.
|
|
||||||
|
|
||||||
import { Component } from '@angular/core';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
import { RegistrarComponent } from 'src/app/registrar/registrarsTable.component';
|
|
||||||
import ContactComponent from 'src/app/settings/contact/contact.component';
|
|
||||||
import SecurityComponent from 'src/app/settings/security/security.component';
|
|
||||||
import { SettingsComponent } from 'src/app/settings/settings.component';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: '[app-settings-widget]',
|
|
||||||
templateUrl: './settings-widget.component.html',
|
|
||||||
})
|
|
||||||
export class SettingsWidgetComponent {
|
|
||||||
constructor(private router: Router) {}
|
|
||||||
|
|
||||||
openRegistrarsPage() {
|
|
||||||
this.navigate(RegistrarComponent.PATH);
|
|
||||||
}
|
|
||||||
|
|
||||||
openSecurityPage() {
|
|
||||||
this.navigate(SecurityComponent.PATH);
|
|
||||||
}
|
|
||||||
|
|
||||||
openContactsPage() {
|
|
||||||
this.navigate(ContactComponent.PATH);
|
|
||||||
}
|
|
||||||
|
|
||||||
private navigate(route: string) {
|
|
||||||
this.router.navigate([SettingsComponent.PATH, route]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<mat-card>
|
|
||||||
<mat-card-content>
|
|
||||||
<div class="console-app__widget">
|
|
||||||
<div class="console-app__widget_left">
|
|
||||||
<mat-icon class="console-app__widget-icon">list_alt</mat-icon>
|
|
||||||
<h1 class="console-app__widget-title">TLDs</h1>
|
|
||||||
<h4 class="secondary-text text-center">
|
|
||||||
View launch phase information for all onboarded and available TLDs.
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
// Copyright 2023 The Nomulus Authors. 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.
|
|
||||||
|
|
||||||
import { Component } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: '[app-tlds-widget]',
|
|
||||||
templateUrl: './tlds-widget.component.html',
|
|
||||||
})
|
|
||||||
export class TldsWidgetComponent {
|
|
||||||
constructor() {}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
@if (isLoading) {
|
||||||
|
<div class="console-app__registry-lock-verify-spinner">
|
||||||
|
<mat-spinner />
|
||||||
|
</div>
|
||||||
|
} @else if (domainName) {
|
||||||
|
<h1 class="mat-headline-4">Success!</h1>
|
||||||
|
<div class="console-app__registry-lock-content">
|
||||||
|
<div class="console-app__registry-lock-subhead">
|
||||||
|
The domain {{ domainName }} has been successfully {{ action }}ed.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a
|
||||||
|
class="text-l"
|
||||||
|
routerLink="{{ DOMAIN_LIST_COMPONENT_PATH }}"
|
||||||
|
[queryParams]="{ registrarId: this.registrarService.registrarId() }"
|
||||||
|
>Return to the list of domains</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
} @else {
|
||||||
|
<h1 class="mat-headline-4">Failure</h1>
|
||||||
|
<div class="console-app__registry-lock-content">
|
||||||
|
<div class="console-app__registry-lock-subhead">
|
||||||
|
An error occurred: {{ errorMessage }}.<br /><br />Please double-check the
|
||||||
|
verification code and try again.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
.console-app__registry-lock {
|
||||||
|
&-content {
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
&-subhead {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
// Copyright 2024 The Nomulus Authors. 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.
|
||||||
|
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { RegistrarService } from '../registrar/registrar.service';
|
||||||
|
import { ActivatedRoute, ParamMap } from '@angular/router';
|
||||||
|
import { RegistryLockVerifyService } from './registryLockVerify.service';
|
||||||
|
import { HttpErrorResponse } from '@angular/common/http';
|
||||||
|
import { take } from 'rxjs';
|
||||||
|
import { DomainListComponent } from '../domains/domainList.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-registry-lock-verify',
|
||||||
|
templateUrl: './registryLockVerify.component.html',
|
||||||
|
styleUrls: ['./registryLockVerify.component.scss'],
|
||||||
|
providers: [RegistryLockVerifyService],
|
||||||
|
standalone: false,
|
||||||
|
})
|
||||||
|
export class RegistryLockVerifyComponent {
|
||||||
|
public static PATH = 'registry-lock-verify';
|
||||||
|
|
||||||
|
readonly DOMAIN_LIST_COMPONENT_PATH = `/${DomainListComponent.PATH}`;
|
||||||
|
|
||||||
|
isLoading = true;
|
||||||
|
domainName?: string;
|
||||||
|
action?: string;
|
||||||
|
errorMessage?: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected registrarService: RegistrarService,
|
||||||
|
protected registryLockVerifyService: RegistryLockVerifyService,
|
||||||
|
private route: ActivatedRoute
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.route.queryParamMap.pipe(take(1)).subscribe((params: ParamMap) => {
|
||||||
|
this.registryLockVerifyService
|
||||||
|
.verifyRequest(params.get('lockVerificationCode') || '')
|
||||||
|
.subscribe({
|
||||||
|
error: (err: HttpErrorResponse) => {
|
||||||
|
this.isLoading = false;
|
||||||
|
this.errorMessage = err.error;
|
||||||
|
},
|
||||||
|
next: (verificationResponse) => {
|
||||||
|
this.domainName = verificationResponse.domainName;
|
||||||
|
this.action = verificationResponse.action;
|
||||||
|
this.registrarService.registrarId.set(
|
||||||
|
verificationResponse.registrarId
|
||||||
|
);
|
||||||
|
this.isLoading = false;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+13
-15
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2020 The Nomulus Authors. All Rights Reserved.
|
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -12,22 +12,20 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package google.registry.persistence.converter;
|
import { Injectable } from '@angular/core';
|
||||||
|
import { BackendService } from '../shared/services/backend.service';
|
||||||
|
|
||||||
import com.google.common.net.InetAddresses;
|
export interface RegistryLockVerificationResponse {
|
||||||
import java.net.InetAddress;
|
action: string;
|
||||||
import javax.persistence.Converter;
|
domainName: string;
|
||||||
|
registrarId: string;
|
||||||
|
}
|
||||||
|
|
||||||
@Converter(autoApply = true)
|
@Injectable()
|
||||||
public class InetAddressSetConverter extends StringSetConverterBase<InetAddress> {
|
export class RegistryLockVerifyService {
|
||||||
|
constructor(private backendService: BackendService) {}
|
||||||
|
|
||||||
@Override
|
verifyRequest(lockVerificationCode: string) {
|
||||||
String toString(InetAddress element) {
|
return this.backendService.verifyRegistryLockRequest(lockVerificationCode);
|
||||||
return InetAddresses.toAddrString(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
InetAddress fromString(String value) {
|
|
||||||
return InetAddresses.forString(value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2023 The Nomulus Authors. All Rights Reserved.
|
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -12,16 +12,23 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { NgModule } from '@angular/core';
|
|
||||||
import { A11yModule } from '@angular/cdk/a11y';
|
import { A11yModule } from '@angular/cdk/a11y';
|
||||||
|
import { DialogModule } from '@angular/cdk/dialog';
|
||||||
|
import { CdkMenuModule } from '@angular/cdk/menu';
|
||||||
|
import { OverlayModule } from '@angular/cdk/overlay';
|
||||||
import { CdkTableModule } from '@angular/cdk/table';
|
import { CdkTableModule } from '@angular/cdk/table';
|
||||||
import { CdkTreeModule } from '@angular/cdk/tree';
|
import { CdkTreeModule } from '@angular/cdk/tree';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||||
import { MatBadgeModule } from '@angular/material/badge';
|
import { MatBadgeModule } from '@angular/material/badge';
|
||||||
|
import { MatBottomSheetModule } from '@angular/material/bottom-sheet';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatButtonToggleModule } from '@angular/material/button-toggle';
|
import { MatButtonToggleModule } from '@angular/material/button-toggle';
|
||||||
import { MatBottomSheetModule } from '@angular/material/bottom-sheet';
|
|
||||||
import { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||||
|
import { MatChipsModule } from '@angular/material/chips';
|
||||||
|
import { MatNativeDateModule, MatRippleModule } from '@angular/material/core';
|
||||||
import { MatDialogModule } from '@angular/material/dialog';
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
import { MatDividerModule } from '@angular/material/divider';
|
import { MatDividerModule } from '@angular/material/divider';
|
||||||
import { MatGridListModule } from '@angular/material/grid-list';
|
import { MatGridListModule } from '@angular/material/grid-list';
|
||||||
@@ -29,22 +36,18 @@ import { MatIconModule } from '@angular/material/icon';
|
|||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { MatListModule } from '@angular/material/list';
|
import { MatListModule } from '@angular/material/list';
|
||||||
import { MatMenuModule } from '@angular/material/menu';
|
import { MatMenuModule } from '@angular/material/menu';
|
||||||
import { MatNativeDateModule, MatRippleModule } from '@angular/material/core';
|
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
import { MatRadioModule } from '@angular/material/radio';
|
import { MatRadioModule } from '@angular/material/radio';
|
||||||
import { MatSelectModule } from '@angular/material/select';
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
|
import { MatSidenavModule } from '@angular/material/sidenav';
|
||||||
|
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||||
import { MatTableModule } from '@angular/material/table';
|
import { MatTableModule } from '@angular/material/table';
|
||||||
import { MatTabsModule } from '@angular/material/tabs';
|
import { MatTabsModule } from '@angular/material/tabs';
|
||||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
import { MatTreeModule } from '@angular/material/tree';
|
import { MatTreeModule } from '@angular/material/tree';
|
||||||
import { OverlayModule } from '@angular/cdk/overlay';
|
|
||||||
import { CdkMenuModule } from '@angular/cdk/menu';
|
|
||||||
import { DialogModule } from '@angular/cdk/dialog';
|
|
||||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
|
||||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
|
||||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
exports: [
|
exports: [
|
||||||
@@ -81,6 +84,9 @@ import { MatPaginatorModule } from '@angular/material/paginator';
|
|||||||
DialogModule,
|
DialogModule,
|
||||||
MatSnackBarModule,
|
MatSnackBarModule,
|
||||||
MatPaginatorModule,
|
MatPaginatorModule,
|
||||||
|
MatChipsModule,
|
||||||
|
MatAutocompleteModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class MaterialModule {}
|
export class MaterialModule {}
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<mat-tree
|
||||||
|
[dataSource]="dataSource"
|
||||||
|
[treeControl]="treeControl"
|
||||||
|
class="console-app__nav-tree"
|
||||||
|
>
|
||||||
|
<mat-tree-node
|
||||||
|
*matTreeNodeDef="let node"
|
||||||
|
matTreeNodeToggle
|
||||||
|
tabindex="0"
|
||||||
|
(click)="onClick(node)"
|
||||||
|
(keyup.enter)="onClick(node)"
|
||||||
|
[class.active]="router.url.includes(node.path)"
|
||||||
|
[elementId]="getElementId(node)"
|
||||||
|
>
|
||||||
|
<mat-icon class="console-app__nav-icon" *ngIf="node.iconName">
|
||||||
|
{{ node.iconName }}
|
||||||
|
</mat-icon>
|
||||||
|
{{ node.title }}
|
||||||
|
</mat-tree-node>
|
||||||
|
<mat-nested-tree-node
|
||||||
|
*matTreeNodeDef="let node; when: hasChild"
|
||||||
|
(click)="onClick(node)"
|
||||||
|
tabindex="0"
|
||||||
|
(keyup.enter)="onClick(node)"
|
||||||
|
>
|
||||||
|
<div class="mat-tree-node" [class.active]="router.url.includes(node.path)">
|
||||||
|
<button
|
||||||
|
class="console-app__nav-icon_expand"
|
||||||
|
mat-icon-button
|
||||||
|
matTreeNodeToggle
|
||||||
|
[attr.aria-label]="'Toggle ' + node.title"
|
||||||
|
>
|
||||||
|
<mat-icon>
|
||||||
|
{{ treeControl.isExpanded(node) ? "expand_more" : "chevron_right" }}
|
||||||
|
</mat-icon>
|
||||||
|
</button>
|
||||||
|
<mat-icon class="console-app__nav-icon" *ngIf="node.iconName">
|
||||||
|
{{ node.iconName }}
|
||||||
|
</mat-icon>
|
||||||
|
{{ node.title }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
[class.console-app__nav-tree_invisible]="!treeControl.isExpanded(node)"
|
||||||
|
role="group"
|
||||||
|
>
|
||||||
|
<ng-container matTreeNodeOutlet></ng-container>
|
||||||
|
</div>
|
||||||
|
</mat-nested-tree-node>
|
||||||
|
</mat-tree>
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
// Copyright 2024 The Nomulus Authors. 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.
|
||||||
|
|
||||||
|
$expand-icon-size: 26px;
|
||||||
|
|
||||||
|
.console-app {
|
||||||
|
&__sidebar {
|
||||||
|
min-width: 300px;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__nav-icon {
|
||||||
|
width: $expand-icon-size;
|
||||||
|
height: $expand-icon-size;
|
||||||
|
color: var(--secondary) !important;
|
||||||
|
margin-right: $expand-icon-size;
|
||||||
|
|
||||||
|
&_expand {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
margin: auto;
|
||||||
|
padding: 0px;
|
||||||
|
width: $expand-icon-size;
|
||||||
|
height: $expand-icon-size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__nav-tree {
|
||||||
|
.mat-tree-node {
|
||||||
|
cursor: pointer;
|
||||||
|
padding-left: $expand-icon-size;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--light-highlight);
|
||||||
|
border-radius: 0 15px 15px 0;
|
||||||
|
}
|
||||||
|
&.active {
|
||||||
|
border-radius: 0 15px 15px 0;
|
||||||
|
background-color: var(--lightest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div[role="group"] > .mat-tree-node {
|
||||||
|
// expand icon + regular icon + spacing = 3 * $expand-icon-size
|
||||||
|
padding-left: calc($expand-icon-size * 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
ul,
|
||||||
|
li {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_invisible {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
// Copyright 2024 The Nomulus Authors. 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.
|
||||||
|
|
||||||
|
import { NestedTreeControl } from '@angular/cdk/tree';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { MatTreeNestedDataSource } from '@angular/material/tree';
|
||||||
|
import { NavigationEnd, Router } from '@angular/router';
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
import { RouteWithIcon, routes, PATHS } from '../app-routing.module';
|
||||||
|
import { RESTRICTED_ELEMENTS } from '../shared/directives/userLevelVisiblity.directive';
|
||||||
|
import { RegistrarComponent } from '../registrar/registrarsTable.component';
|
||||||
|
|
||||||
|
interface NavMenuNode extends RouteWithIcon {
|
||||||
|
parentRoute?: RouteWithIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component is responsible for rendering navigation menu based on the allowed routes
|
||||||
|
* and keeping UI in sync when route changes (eg highlights selected route in the menu).
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'app-navigation',
|
||||||
|
templateUrl: './navigation.component.html',
|
||||||
|
styleUrls: ['./navigation.component.scss'],
|
||||||
|
standalone: false,
|
||||||
|
})
|
||||||
|
export class NavigationComponent {
|
||||||
|
renderRouter: boolean = true;
|
||||||
|
treeControl = new NestedTreeControl<RouteWithIcon>((node) => node.children);
|
||||||
|
dataSource = new MatTreeNestedDataSource<RouteWithIcon>();
|
||||||
|
private subscription!: Subscription;
|
||||||
|
|
||||||
|
hasChild = (_: number, node: RouteWithIcon) =>
|
||||||
|
!!node.children && node.children.length > 0;
|
||||||
|
|
||||||
|
constructor(protected router: Router) {
|
||||||
|
this.dataSource.data = this.ngRoutesToNavMenuNodes(routes);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.subscription = this.router.events.subscribe((navigationParams) => {
|
||||||
|
if (navigationParams instanceof NavigationEnd) {
|
||||||
|
this.syncExpandedNavigationWithRoute(navigationParams.url);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.subscription.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
getElementId(node: RouteWithIcon) {
|
||||||
|
if (node.path === RegistrarComponent.PATH) {
|
||||||
|
return RESTRICTED_ELEMENTS.REGISTRAR_ELEMENT;
|
||||||
|
} else if (node.path === PATHS.UsersComponent) {
|
||||||
|
return RESTRICTED_ELEMENTS.USERS;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
syncExpandedNavigationWithRoute(url: string) {
|
||||||
|
const maybeComponentWithChildren = this.dataSource.data.find((menuNode) => {
|
||||||
|
return (
|
||||||
|
// @ts-ignore - optional function added to components with children,
|
||||||
|
// there's no availble tools to get current active router component
|
||||||
|
typeof menuNode.component?.matchesUrl === 'function' &&
|
||||||
|
// @ts-ignore
|
||||||
|
menuNode.component?.matchesUrl(url)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
if (maybeComponentWithChildren) {
|
||||||
|
this.treeControl.expand(maybeComponentWithChildren);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick(node: NavMenuNode) {
|
||||||
|
if (node.parentRoute) {
|
||||||
|
this.router.navigate([node.parentRoute.path + '/' + node.path], {
|
||||||
|
queryParamsHandling: 'merge',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.router.navigate([node.path], { queryParamsHandling: 'merge' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We only want to use routes with titles and we want to provide easy reference to parent node
|
||||||
|
*/
|
||||||
|
ngRoutesToNavMenuNodes(routes: RouteWithIcon[]): NavMenuNode[] {
|
||||||
|
return routes
|
||||||
|
.filter((r) => r.title)
|
||||||
|
.map((r) => {
|
||||||
|
if (r.children) {
|
||||||
|
return {
|
||||||
|
...r,
|
||||||
|
children: r.children
|
||||||
|
.filter((r) => r.title)
|
||||||
|
.map((childRoute) => {
|
||||||
|
return {
|
||||||
|
...childRoute,
|
||||||
|
parentRoute: r,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<h1 class="mat-headline-4">Generate OT&E Accounts</h1>
|
||||||
|
<div class="console-app__new-ote">
|
||||||
|
@if (oteCreateResponseFormatted()) {
|
||||||
|
<h1>Generated Successfully</h1>
|
||||||
|
<mat-card appearance="outlined">
|
||||||
|
<mat-card-header>
|
||||||
|
<mat-card-title>Epp Credentials</mat-card-title>
|
||||||
|
<mat-card-subtitle
|
||||||
|
>Copy and paste this into an email to the registrars</mat-card-subtitle
|
||||||
|
>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<p>{{ oteCreateResponseFormatted() }}</p>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
} @else {
|
||||||
|
<form (ngSubmit)="onSubmit()" [formGroup]="createOte">
|
||||||
|
<p>
|
||||||
|
<mat-form-field name="registrarId" appearance="outline">
|
||||||
|
<mat-label>Base Registrar Id: </mat-label>
|
||||||
|
<input matInput type="text" formControlName="registrarId" required />
|
||||||
|
</mat-form-field>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<mat-form-field name="registrarEmail" appearance="outline">
|
||||||
|
<mat-label>Contact Email: </mat-label>
|
||||||
|
<input matInput type="text" formControlName="registrarEmail" required />
|
||||||
|
<mat-hint
|
||||||
|
>Will be granted web-console access to the OTE registrars.</mat-hint
|
||||||
|
>
|
||||||
|
</mat-form-field>
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
mat-flat-button
|
||||||
|
color="primary"
|
||||||
|
type="submit"
|
||||||
|
aria-label="Submit new OT&E account"
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
.console-app__new-ote {
|
||||||
|
max-width: 720px;
|
||||||
|
mat-card-content {
|
||||||
|
white-space: break-spaces;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
mat-form-field {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 350px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
// Copyright 2024 The Nomulus Authors. 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.
|
||||||
|
|
||||||
|
import { HttpErrorResponse } from '@angular/common/http';
|
||||||
|
import { Component, computed, signal } from '@angular/core';
|
||||||
|
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
||||||
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
|
import { RegistrarService } from '../registrar/registrar.service';
|
||||||
|
import { MaterialModule } from '../material.module';
|
||||||
|
import { SnackBarModule } from '../snackbar.module';
|
||||||
|
|
||||||
|
export interface OteCreateResponse extends Map<string, string> {
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-ote',
|
||||||
|
imports: [MaterialModule, SnackBarModule],
|
||||||
|
templateUrl: './newOte.component.html',
|
||||||
|
styleUrls: ['./newOte.component.scss'],
|
||||||
|
})
|
||||||
|
export class NewOteComponent {
|
||||||
|
oteCreateResponse = signal<OteCreateResponse | undefined>(undefined);
|
||||||
|
|
||||||
|
readonly oteCreateResponseFormatted = computed(() => {
|
||||||
|
const oteCreateResponse = this.oteCreateResponse();
|
||||||
|
if (oteCreateResponse) {
|
||||||
|
const { password } = oteCreateResponse;
|
||||||
|
return Object.entries(oteCreateResponse)
|
||||||
|
.filter((entry) => entry[0] !== 'password')
|
||||||
|
.map(
|
||||||
|
([login, tld]) =>
|
||||||
|
`Login: ${login}\t\tPassword: ${password}\t\tTLD: ${tld}`
|
||||||
|
)
|
||||||
|
.join('\n');
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
createOte = new FormGroup({
|
||||||
|
registrarId: new FormControl('', [Validators.required]),
|
||||||
|
registrarEmail: new FormControl('', [Validators.required]),
|
||||||
|
});
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected registrarService: RegistrarService,
|
||||||
|
private _snackBar: MatSnackBar
|
||||||
|
) {}
|
||||||
|
|
||||||
|
onSubmit() {
|
||||||
|
if (this.createOte.valid) {
|
||||||
|
const { registrarId, registrarEmail } = this.createOte.value;
|
||||||
|
this.registrarService
|
||||||
|
.generateOte(
|
||||||
|
{
|
||||||
|
registrarId,
|
||||||
|
registrarEmail,
|
||||||
|
},
|
||||||
|
registrarId || ''
|
||||||
|
)
|
||||||
|
.subscribe({
|
||||||
|
next: (oteCreateResponse: OteCreateResponse) => {
|
||||||
|
this.oteCreateResponse.set(oteCreateResponse);
|
||||||
|
},
|
||||||
|
error: (err: HttpErrorResponse) => {
|
||||||
|
this._snackBar.open(err.error || err.message);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<h1 class="mat-headline-4">OT&E Status Check</h1>
|
||||||
|
@if(registrarId() === null) {
|
||||||
|
<h1>Missing registrarId param</h1>
|
||||||
|
} @else if(isOte()) {
|
||||||
|
<h1 *ngIf="oteStatusResponse().length">
|
||||||
|
Status:
|
||||||
|
<span>{{ oteStatusUnfinished().length ? "Unfinished" : "Completed" }}</span>
|
||||||
|
</h1>
|
||||||
|
<div class="console-app__ote-status">
|
||||||
|
@if(oteStatusCompleted().length) {
|
||||||
|
<div class="console-app__ote-status_completed">
|
||||||
|
<h1>Completed</h1>
|
||||||
|
<div *ngFor="let entry of oteStatusCompleted()">
|
||||||
|
<mat-icon>check_box</mat-icon>{{ entry.description }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
} @if(oteStatusUnfinished().length) {
|
||||||
|
<div class="console-app__ote-status_unfinished">
|
||||||
|
<h1>Unfinished</h1>
|
||||||
|
<div *ngFor="let entry of oteStatusUnfinished()">
|
||||||
|
<mat-icon>check_box_outline_blank</mat-icon>{{ entry.description }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
} @else {
|
||||||
|
<h1>Registrar {{ registrarId() }} is not an OT&E registrar</h1>
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
.console-app__ote-status {
|
||||||
|
max-width: 730px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
&_completed,
|
||||||
|
&_unfinished {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin: 0 20px 30px 0;
|
||||||
|
div {
|
||||||
|
display: flex;
|
||||||
|
min-width: 300px;
|
||||||
|
align-items: flex-start;
|
||||||
|
max-width: 300px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
&:last-child {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-icon {
|
||||||
|
min-width: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
// Copyright 2024 The Nomulus Authors. 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.
|
||||||
|
|
||||||
|
import { HttpErrorResponse } from '@angular/common/http';
|
||||||
|
import { Component, computed, OnInit, signal } from '@angular/core';
|
||||||
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
|
import { RegistrarService } from '../registrar/registrar.service';
|
||||||
|
import { MaterialModule } from '../material.module';
|
||||||
|
import { SnackBarModule } from '../snackbar.module';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { ActivatedRoute, ParamMap } from '@angular/router';
|
||||||
|
import { take } from 'rxjs';
|
||||||
|
|
||||||
|
export interface OteStatusResponse {
|
||||||
|
description: string;
|
||||||
|
requirement: number;
|
||||||
|
timesPerformed: number;
|
||||||
|
completed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-ote-status',
|
||||||
|
imports: [MaterialModule, SnackBarModule, CommonModule],
|
||||||
|
templateUrl: './oteStatus.component.html',
|
||||||
|
styleUrls: ['./oteStatus.component.scss'],
|
||||||
|
})
|
||||||
|
export class OteStatusComponent implements OnInit {
|
||||||
|
registrarId = signal<string | null>(null);
|
||||||
|
oteStatusResponse = signal<OteStatusResponse[]>([]);
|
||||||
|
|
||||||
|
oteStatusCompleted = computed(() =>
|
||||||
|
this.oteStatusResponse().filter((v) => v.completed)
|
||||||
|
);
|
||||||
|
oteStatusUnfinished = computed(() =>
|
||||||
|
this.oteStatusResponse().filter((v) => !v.completed)
|
||||||
|
);
|
||||||
|
isOte = computed(
|
||||||
|
() =>
|
||||||
|
this.registrarService
|
||||||
|
.registrars()
|
||||||
|
.find((r) => r.registrarId === this.registrarId())
|
||||||
|
?.type?.toLowerCase() === 'ote'
|
||||||
|
);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
protected registrarService: RegistrarService,
|
||||||
|
private _snackBar: MatSnackBar
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.route.paramMap.pipe(take(1)).subscribe((params: ParamMap) => {
|
||||||
|
this.registrarId.set(params.get('registrarId'));
|
||||||
|
const registrarId = this.registrarId();
|
||||||
|
if (!registrarId) throw 'Missing registrarId param';
|
||||||
|
|
||||||
|
this.registrarService.oteStatus(registrarId).subscribe({
|
||||||
|
next: (oteStatusResponse: OteStatusResponse[]) => {
|
||||||
|
this.oteStatusResponse.set(oteStatusResponse);
|
||||||
|
},
|
||||||
|
error: (err: HttpErrorResponse) => {
|
||||||
|
this._snackBar.open(err.error || err.message);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<div class="console-app__emty-registrar">
|
|
||||||
<h1>
|
|
||||||
<mat-icon class="console-app__emty-registrar-icon">block</mat-icon>
|
|
||||||
</h1>
|
|
||||||
<h1>No registrar selected</h1>
|
|
||||||
<h4 class="mat-body-2">Please select a registrar</h4>
|
|
||||||
</div>
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
// Copyright 2023 The Nomulus Authors. 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.
|
|
||||||
|
|
||||||
import { Component } from '@angular/core';
|
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
|
||||||
import { Subscription } from 'rxjs';
|
|
||||||
|
|
||||||
import { RegistrarService } from './registrar.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-empty-registrar',
|
|
||||||
templateUrl: './emptyRegistrar.component.html',
|
|
||||||
styleUrls: ['./emptyRegistrar.component.scss'],
|
|
||||||
})
|
|
||||||
export class EmptyRegistrar {
|
|
||||||
private registrarIdChangeSubscription?: Subscription;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private route: ActivatedRoute,
|
|
||||||
protected registrarService: RegistrarService,
|
|
||||||
private router: Router
|
|
||||||
) {
|
|
||||||
this.registrarIdChangeSubscription =
|
|
||||||
registrarService.activeRegistrarIdChange.subscribe((newRegistrarId) => {
|
|
||||||
if (newRegistrarId) {
|
|
||||||
this.router.navigate([this.route.snapshot.paramMap.get('nextUrl')]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy() {
|
|
||||||
this.registrarIdChangeSubscription?.unsubscribe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
<div class="console-new-registrar">
|
||||||
|
<button
|
||||||
|
mat-icon-button
|
||||||
|
aria-label="Back to registrars list"
|
||||||
|
(click)="goBack()"
|
||||||
|
>
|
||||||
|
<mat-icon>arrow_back</mat-icon>
|
||||||
|
</button>
|
||||||
|
<div class="spacer"></div>
|
||||||
|
<h1>Create a registrar</h1>
|
||||||
|
<form (ngSubmit)="save($event)" #form>
|
||||||
|
<h2>General</h2>
|
||||||
|
<section>
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Registrar Name: </mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
[required]="true"
|
||||||
|
[(ngModel)]="newRegistrar.registrarName"
|
||||||
|
[ngModelOptions]="{ standalone: true }"
|
||||||
|
/>
|
||||||
|
</mat-form-field>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Registrar ID: </mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
[required]="true"
|
||||||
|
[(ngModel)]="newRegistrar.registrarId"
|
||||||
|
[ngModelOptions]="{ standalone: true }"
|
||||||
|
/>
|
||||||
|
</mat-form-field>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Registrar email address: </mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
type="email"
|
||||||
|
[required]="true"
|
||||||
|
[(ngModel)]="newRegistrar.emailAddress"
|
||||||
|
[ngModelOptions]="{ standalone: true }"
|
||||||
|
/>
|
||||||
|
</mat-form-field>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Billing Accounts: </mat-label>
|
||||||
|
<textarea
|
||||||
|
matInput
|
||||||
|
required="true"
|
||||||
|
placeholder="USD=billing-id-for-usd
|
||||||
|
JPY=billing-id-for-yen"
|
||||||
|
[ngModel]="billingAccountMap"
|
||||||
|
(ngModelChange)="onBillingAccountMapChange($event)"
|
||||||
|
[ngModelOptions]="{ standalone: true }"
|
||||||
|
></textarea>
|
||||||
|
</mat-form-field>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>IANA ID: </mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
[required]="true"
|
||||||
|
[(ngModel)]="newRegistrar.ianaIdentifier"
|
||||||
|
[ngModelOptions]="{ standalone: true }"
|
||||||
|
/>
|
||||||
|
</mat-form-field>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>ICANN referral email: </mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
[required]="true"
|
||||||
|
type="email"
|
||||||
|
[(ngModel)]="newRegistrar.icannReferralEmail"
|
||||||
|
[ngModelOptions]="{ standalone: true }"
|
||||||
|
/>
|
||||||
|
</mat-form-field>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Drive ID: </mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
[required]="true"
|
||||||
|
[(ngModel)]="newRegistrar.driveFolderId"
|
||||||
|
[ngModelOptions]="{ standalone: true }"
|
||||||
|
/>
|
||||||
|
</mat-form-field>
|
||||||
|
</section>
|
||||||
|
<h2>Contact Info</h2>
|
||||||
|
<section>
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Street address (Line 1): </mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
[required]="true"
|
||||||
|
[(ngModel)]="localizedAddressStreet.line1"
|
||||||
|
[ngModelOptions]="{ standalone: true }"
|
||||||
|
/>
|
||||||
|
</mat-form-field>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Street address (Line 2)</mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
[required]="false"
|
||||||
|
[(ngModel)]="localizedAddressStreet.line2"
|
||||||
|
[ngModelOptions]="{ standalone: true }"
|
||||||
|
/>
|
||||||
|
</mat-form-field>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Street address (Line 3)</mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
[required]="false"
|
||||||
|
[(ngModel)]="localizedAddressStreet.line3"
|
||||||
|
[ngModelOptions]="{ standalone: true }"
|
||||||
|
/>
|
||||||
|
</mat-form-field>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>City: </mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
[required]="true"
|
||||||
|
[(ngModel)]="newRegistrar.localizedAddress.city"
|
||||||
|
[ngModelOptions]="{ standalone: true }"
|
||||||
|
/>
|
||||||
|
</mat-form-field>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>State/Region: </mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
[required]="true"
|
||||||
|
[(ngModel)]="newRegistrar.localizedAddress.state"
|
||||||
|
[ngModelOptions]="{ standalone: true }"
|
||||||
|
/>
|
||||||
|
</mat-form-field>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>ZIP/Postal Code: </mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
[required]="true"
|
||||||
|
[(ngModel)]="newRegistrar.localizedAddress.zip"
|
||||||
|
[ngModelOptions]="{ standalone: true }"
|
||||||
|
/>
|
||||||
|
</mat-form-field>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Country Code (e.g. US): </mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
[required]="true"
|
||||||
|
[(ngModel)]="newRegistrar.localizedAddress.countryCode"
|
||||||
|
[ngModelOptions]="{ standalone: true }"
|
||||||
|
/>
|
||||||
|
</mat-form-field>
|
||||||
|
</section>
|
||||||
|
<button
|
||||||
|
class="console-new-registrar__submit"
|
||||||
|
mat-flat-button
|
||||||
|
color="primary"
|
||||||
|
type="submit"
|
||||||
|
aria-label="Submit new registrar request"
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user