mirror of
https://github.com/fosrl/pangolin.git
synced 2026-05-07 00:39:53 +00:00
Compare commits
1689 Commits
1.15.3-s.1
...
redis
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
52a90fbd2b | ||
|
|
2ecf076c0f | ||
|
|
e06dda27cb | ||
|
|
18f6e0f75d | ||
|
|
3b232bcc58 | ||
|
|
c575bb76e7 | ||
|
|
c8e7e0ee1e | ||
|
|
0e7aafd364 | ||
|
|
91f1bae3e9 | ||
|
|
53c138ce3e | ||
|
|
969db14a3c | ||
|
|
c1c387bdd8 | ||
|
|
6e83d77a87 | ||
|
|
ba9a1efa4c | ||
|
|
9e046b9608 | ||
|
|
37794eb299 | ||
|
|
4e66b0e74b | ||
|
|
44fa873977 | ||
|
|
505461a533 | ||
|
|
a88c5b1428 | ||
|
|
97ef1d605c | ||
|
|
3fc1c9d948 | ||
|
|
68bd37ab6c | ||
|
|
5c317c535b | ||
|
|
37c6b11899 | ||
|
|
45c567ffa0 | ||
|
|
775ea64b55 | ||
|
|
64ad7641af | ||
|
|
d724f5bb5d | ||
|
|
d4f7c4a9c4 | ||
|
|
1cc0e9b689 | ||
|
|
584be4dbd2 | ||
|
|
c33e295ce7 | ||
|
|
1a926a7127 | ||
|
|
eb515a8f7f | ||
|
|
81b8a8a9e3 | ||
|
|
bcd164219f | ||
|
|
c90e405105 | ||
|
|
b2c8311b26 | ||
|
|
2154811ffb | ||
|
|
1772ac220f | ||
|
|
9bd33072f4 | ||
|
|
70f619b726 | ||
|
|
7743e3890b | ||
|
|
d8df250555 | ||
|
|
45c9f217c6 | ||
|
|
8371692cc5 | ||
|
|
5377dc7a1c | ||
|
|
02649468e0 | ||
|
|
c5ef00fb0e | ||
|
|
6f4325e9a0 | ||
|
|
a2a031dfe7 | ||
|
|
e34a4c82eb | ||
|
|
52fd7df727 | ||
|
|
d5f08437d7 | ||
|
|
9ee07ba343 | ||
|
|
4baaa5fc14 | ||
|
|
61de100630 | ||
|
|
3694f43ae8 | ||
|
|
279211142d | ||
|
|
b8822b4d25 | ||
|
|
0655ba9423 | ||
|
|
e1afbc226c | ||
|
|
96c450fd08 | ||
|
|
587e4d104b | ||
|
|
368c5c374f | ||
|
|
7675b6409c | ||
|
|
d31da1a41e | ||
|
|
49e259e259 | ||
|
|
f4684c1858 | ||
|
|
6e223bb363 | ||
|
|
22e7038b2c | ||
|
|
76ba4c1fdf | ||
|
|
7f25d94a83 | ||
|
|
769ba27e3a | ||
|
|
a188552ba0 | ||
|
|
208132082e | ||
|
|
fcd5789221 | ||
|
|
2c85bcd06b | ||
|
|
c6a8b09cff | ||
|
|
380ff381fc | ||
|
|
5eb3951f00 | ||
|
|
c30e94da98 | ||
|
|
6ca24d51a1 | ||
|
|
13f512aed6 | ||
|
|
2bdbc9d688 | ||
|
|
8e2f30d8de | ||
|
|
a84e1cc9e0 | ||
|
|
6b28f0c81e | ||
|
|
d28d3ba6ea | ||
|
|
6efaf9f40d | ||
|
|
5379b32959 | ||
|
|
9bb936a40d | ||
|
|
960fe760f1 | ||
|
|
2f2105a085 | ||
|
|
de92a28435 | ||
|
|
d8c3484ed5 | ||
|
|
726e000154 | ||
|
|
d6abe83fdc | ||
|
|
9df46f7014 | ||
|
|
908f0d54e2 | ||
|
|
f0010ea12a | ||
|
|
cab8be1a9a | ||
|
|
0a9dab7cca | ||
|
|
889ab1f8a8 | ||
|
|
a9019cfb23 | ||
|
|
441d4bce6e | ||
|
|
dd1e681a9c | ||
|
|
a882619eaf | ||
|
|
f43baaaf1f | ||
|
|
c3dc0bd015 | ||
|
|
1fd2a0fae2 | ||
|
|
8ba5b43569 | ||
|
|
6deefcd003 | ||
|
|
4d6cea5fcd | ||
|
|
f175ac774f | ||
|
|
0fe2b24f6b | ||
|
|
6ad06e6faf | ||
|
|
d47faeced1 | ||
|
|
498f586eeb | ||
|
|
e94fc6bc65 | ||
|
|
0a1fe1b725 | ||
|
|
eb40b04b43 | ||
|
|
6685afdcf9 | ||
|
|
49232e32bf | ||
|
|
aec0aed211 | ||
|
|
d43b3176f5 | ||
|
|
190074ea0c | ||
|
|
c5a7719239 | ||
|
|
5eac131d2e | ||
|
|
0bc3276ee2 | ||
|
|
5073507b90 | ||
|
|
805e6f856a | ||
|
|
412a9b5294 | ||
|
|
fbf95c5363 | ||
|
|
b907850344 | ||
|
|
22116373e3 | ||
|
|
9757c3d8b6 | ||
|
|
f8b85d4b4e | ||
|
|
4651f19c53 | ||
|
|
4524bdc094 | ||
|
|
741850880e | ||
|
|
53e096f7cb | ||
|
|
3dfd7e8a43 | ||
|
|
db6e60d0a3 | ||
|
|
54d2d689c1 | ||
|
|
bb5853827b | ||
|
|
68f5512732 | ||
|
|
416e124c02 | ||
|
|
d3e4d8cda8 | ||
|
|
81972dbb73 | ||
|
|
b715786a1e | ||
|
|
ae24eb2d2c | ||
|
|
20fc59dcda | ||
|
|
93b09de425 | ||
|
|
bacc130453 | ||
|
|
79541ec7b8 | ||
|
|
81197f8a86 | ||
|
|
dcfc7822f4 | ||
|
|
269bd9aa0f | ||
|
|
0a0817b860 | ||
|
|
b7a903ab32 | ||
|
|
ab60438aa7 | ||
|
|
b9f3f90de6 | ||
|
|
b53cc397be | ||
|
|
994fb456c2 | ||
|
|
b36927c7a0 | ||
|
|
1c57473b6d | ||
|
|
c02c3eaa4a | ||
|
|
3c265ee577 | ||
|
|
98dfd05f06 | ||
|
|
faa2e97530 | ||
|
|
175f10a51d | ||
|
|
6284930fce | ||
|
|
6c93aca444 | ||
|
|
d83318cbfc | ||
|
|
143f362a48 | ||
|
|
698cd868a8 | ||
|
|
a55842ffff | ||
|
|
2ffe254879 | ||
|
|
e173f59d89 | ||
|
|
d3870f4920 | ||
|
|
227501d8f8 | ||
|
|
a16f805709 | ||
|
|
a029b107ae | ||
|
|
f03389a9a0 | ||
|
|
78fff6bfde | ||
|
|
bc585c24fc | ||
|
|
0f6c66dc67 | ||
|
|
6be150bafe | ||
|
|
1eac7741a5 | ||
|
|
b8ca0499af | ||
|
|
b39a2bcfb1 | ||
|
|
d45b727dca | ||
|
|
5c31d35e28 | ||
|
|
8c645315f3 | ||
|
|
ab6377e086 | ||
|
|
8685cf4208 | ||
|
|
26fe1259da | ||
|
|
3bcbeb24f3 | ||
|
|
1d0a92c83e | ||
|
|
a44100c2bd | ||
|
|
2203ebf723 | ||
|
|
70958185bd | ||
|
|
7e374baee9 | ||
|
|
4cf6ca1d55 | ||
|
|
2957d6592d | ||
|
|
de2a22aad8 | ||
|
|
b96db4f133 | ||
|
|
2a29062659 | ||
|
|
8ed9adbfae | ||
|
|
98406f63af | ||
|
|
85415176ab | ||
|
|
b81ae3d998 | ||
|
|
208289f498 | ||
|
|
8783c47a3c | ||
|
|
592ca64253 | ||
|
|
1de6e58eef | ||
|
|
92822a20e8 | ||
|
|
4a3035d597 | ||
|
|
bd866a5fd2 | ||
|
|
1c6cd57c31 | ||
|
|
a0619868be | ||
|
|
6c2dd4331a | ||
|
|
5e6171263b | ||
|
|
d33c704f76 | ||
|
|
3cb1cd9f2f | ||
|
|
926fe5e474 | ||
|
|
243da6379b | ||
|
|
68ea7d1d98 | ||
|
|
c0a4541455 | ||
|
|
e4bf2da2e5 | ||
|
|
85334f082c | ||
|
|
c771722127 | ||
|
|
f89b0a17ac | ||
|
|
81a6fb8d00 | ||
|
|
dbee049ac8 | ||
|
|
c03519b7f5 | ||
|
|
7affaf63d0 | ||
|
|
08e9cb862d | ||
|
|
cbb2388a46 | ||
|
|
24f437e260 | ||
|
|
3439a3690f | ||
|
|
b88469f901 | ||
|
|
e573125934 | ||
|
|
c5072bed80 | ||
|
|
28dd06c41f | ||
|
|
61aaa5a832 | ||
|
|
512ba2150b | ||
|
|
d1f7a9c6df | ||
|
|
1cdb261f7e | ||
|
|
17631599a2 | ||
|
|
7563b37cd0 | ||
|
|
7318c86cca | ||
|
|
467cd70b72 | ||
|
|
8ca72a39da | ||
|
|
4ff811c5bd | ||
|
|
ca2370e31d | ||
|
|
06af53c4d6 | ||
|
|
6befdfe01e | ||
|
|
5695137280 | ||
|
|
e2e0936f43 | ||
|
|
32d8bde96d | ||
|
|
f24f867684 | ||
|
|
491636851f | ||
|
|
bf1870608b | ||
|
|
6f6c24b6df | ||
|
|
7c7d1f641e | ||
|
|
82212af643 | ||
|
|
8e16ff07a9 | ||
|
|
56816c7584 | ||
|
|
477712b73c | ||
|
|
ecacb26445 | ||
|
|
cca7cea2f1 | ||
|
|
07154d2a16 | ||
|
|
b509c8aeec | ||
|
|
a2c76cbb24 | ||
|
|
960ada4d66 | ||
|
|
34296e5f40 | ||
|
|
33f1662c91 | ||
|
|
29f26021df | ||
|
|
15f02cf79a | ||
|
|
2a5d836747 | ||
|
|
593a7fdd69 | ||
|
|
99f9b68efe | ||
|
|
9655f119a5 | ||
|
|
48ddc700a0 | ||
|
|
0473d5f639 | ||
|
|
537f9ae66b | ||
|
|
d08f276794 | ||
|
|
6a96f743aa | ||
|
|
b4f0b4e285 | ||
|
|
07c7501669 | ||
|
|
009bac64bf | ||
|
|
5e293e8364 | ||
|
|
1ba7fca798 | ||
|
|
e7a9a19816 | ||
|
|
fa117198a0 | ||
|
|
f03d0cd47f | ||
|
|
925a59c080 | ||
|
|
a7c7319407 | ||
|
|
230f77118a | ||
|
|
bcb5b7b4a7 | ||
|
|
90a2ed2f10 | ||
|
|
fc69364feb | ||
|
|
245755a140 | ||
|
|
dcbd22b4ad | ||
|
|
8481b0a073 | ||
|
|
f651ca84fa | ||
|
|
6b83d3c3f1 | ||
|
|
d463a578c2 | ||
|
|
9d0a8ecb09 | ||
|
|
af5394d464 | ||
|
|
c956e0d401 | ||
|
|
2a281ec002 | ||
|
|
4c000c1d49 | ||
|
|
ea4ff75552 | ||
|
|
c78b866087 | ||
|
|
48b6e98bbc | ||
|
|
3d5260b13e | ||
|
|
d0b0d95b9a | ||
|
|
c2c8b7a631 | ||
|
|
9bc11b8717 | ||
|
|
1d53211fe0 | ||
|
|
81922f54d5 | ||
|
|
9474792e14 | ||
|
|
0c6acfe282 | ||
|
|
0ae20c0b25 | ||
|
|
bcd3bee148 | ||
|
|
e2814517d6 | ||
|
|
c24db3df0e | ||
|
|
7ecfc9cbd3 | ||
|
|
0b18194397 | ||
|
|
18dfc21197 | ||
|
|
e178ed12ab | ||
|
|
7a0b7dc17b | ||
|
|
c40dd7bb43 | ||
|
|
059ea57b88 | ||
|
|
1ce11d0f5f | ||
|
|
cba1a67b8f | ||
|
|
a218f5dc82 | ||
|
|
a83126a67e | ||
|
|
0620fed9c1 | ||
|
|
87e09dd407 | ||
|
|
77b38c757a | ||
|
|
5e29572f49 | ||
|
|
520cc0d0bf | ||
|
|
ebb4630472 | ||
|
|
c7b8e9c5b9 | ||
|
|
0a65e200b6 | ||
|
|
70b87c04aa | ||
|
|
1a8e9072b4 | ||
|
|
55261c43f8 | ||
|
|
e02545ada7 | ||
|
|
4edeb26e32 | ||
|
|
6f007da609 | ||
|
|
c3e59b73b9 | ||
|
|
cc44b46d91 | ||
|
|
dfe4888123 | ||
|
|
45fb24d0c8 | ||
|
|
3f1c5d305b | ||
|
|
c9caa44c06 | ||
|
|
19e0452d84 | ||
|
|
7f5c164e16 | ||
|
|
4df3613df7 | ||
|
|
4f9f235398 | ||
|
|
a7c212ffa4 | ||
|
|
320543f7f8 | ||
|
|
88eb1649e4 | ||
|
|
6f07156075 | ||
|
|
b3aafa5fe6 | ||
|
|
f71355fe7a | ||
|
|
6ea3f69fea | ||
|
|
95fc30f21d | ||
|
|
a2d8386b4a | ||
|
|
73a59bc1de | ||
|
|
0434b1a656 | ||
|
|
13afa90d28 | ||
|
|
90eb6d66c0 | ||
|
|
22a6dabeb2 | ||
|
|
84346fc23e | ||
|
|
09744cf2f0 | ||
|
|
38f1387db1 | ||
|
|
db2942447a | ||
|
|
b22ac17178 | ||
|
|
709f2c187d | ||
|
|
ccfa165632 | ||
|
|
a68ba9e04d | ||
|
|
23293da845 | ||
|
|
b5dd20e499 | ||
|
|
38243ad887 | ||
|
|
5b18612426 | ||
|
|
8dbe0a4bfe | ||
|
|
7d9a0cd0cc | ||
|
|
e1efae7426 | ||
|
|
f9a4e25dc9 | ||
|
|
85029ff518 | ||
|
|
adf15bdc87 | ||
|
|
a20111043f | ||
|
|
177ce20dda | ||
|
|
1a0bde2ee9 | ||
|
|
ff1ca7eafb | ||
|
|
dc299a740b | ||
|
|
7b3c10c7b0 | ||
|
|
b1293e6f56 | ||
|
|
6969671fc4 | ||
|
|
e765f661a7 | ||
|
|
7da3719a00 | ||
|
|
206b3a7d22 | ||
|
|
4ce4e63a0a | ||
|
|
ed327626bb | ||
|
|
30c4010c8b | ||
|
|
85f7c1e87b | ||
|
|
6f06f98cc1 | ||
|
|
e3aabc6b2d | ||
|
|
b59262b7af | ||
|
|
8093904d47 | ||
|
|
66c0ed5bf0 | ||
|
|
725603101b | ||
|
|
7f0264dec3 | ||
|
|
5e88862e29 | ||
|
|
b3bc70875b | ||
|
|
34dc4c2d07 | ||
|
|
2ef7a709d3 | ||
|
|
d7a9e1a517 | ||
|
|
f938e9c3c0 | ||
|
|
c8d560d78f | ||
|
|
3641969dd4 | ||
|
|
49b3163bbe | ||
|
|
1a36475afa | ||
|
|
335de04a4e | ||
|
|
f38069623b | ||
|
|
0a70896080 | ||
|
|
5a09062070 | ||
|
|
47be3dbdf9 | ||
|
|
9f5f89c9eb | ||
|
|
2e8d170114 | ||
|
|
bf1787acd5 | ||
|
|
78ff835ac9 | ||
|
|
3c005c9ab1 | ||
|
|
54adcd2c56 | ||
|
|
9a6408d28c | ||
|
|
2dad97cb6b | ||
|
|
473bce856d | ||
|
|
86ec8eedac | ||
|
|
0ac97ecd5e | ||
|
|
387049beac | ||
|
|
c9240ecb84 | ||
|
|
b87e71c557 | ||
|
|
866293aa5a | ||
|
|
e142dd32b4 | ||
|
|
949786dab5 | ||
|
|
2dd142b0e9 | ||
|
|
dfd16a6752 | ||
|
|
f4454d4d48 | ||
|
|
e7efc917f0 | ||
|
|
5ffe1ba07d | ||
|
|
b56e2972c4 | ||
|
|
ca1a084397 | ||
|
|
a7a1f81e9d | ||
|
|
9c09f17dc5 | ||
|
|
21e2c022c7 | ||
|
|
222cbc886d | ||
|
|
db2e76bd31 | ||
|
|
bf32cc150d | ||
|
|
967de0b79f | ||
|
|
22231e6c45 | ||
|
|
2c8b7b5ca5 | ||
|
|
55989c2019 | ||
|
|
20ed9966b9 | ||
|
|
b2d5a1ffdf | ||
|
|
a5b8a44e78 | ||
|
|
dddf060e1a | ||
|
|
df8104fe56 | ||
|
|
a569054e94 | ||
|
|
8214700eaa | ||
|
|
5885e8eb39 | ||
|
|
74165aa1cc | ||
|
|
0872fd5818 | ||
|
|
008ad0a1de | ||
|
|
f74791111e | ||
|
|
22964cff0f | ||
|
|
e952c2d34a | ||
|
|
408eaf55f6 | ||
|
|
0a043af482 | ||
|
|
8324445895 | ||
|
|
796d14a9e4 | ||
|
|
bd89867ecb | ||
|
|
3645cc5759 | ||
|
|
707cc4b275 | ||
|
|
93400ace27 | ||
|
|
f932cc7aca | ||
|
|
c1782a2650 | ||
|
|
d6c15c8b81 | ||
|
|
b958537f3e | ||
|
|
597cae2b78 | ||
|
|
c4308aaa69 | ||
|
|
6fb8dae966 | ||
|
|
a9d68bd0cf | ||
|
|
5fcb80a193 | ||
|
|
a27a169160 | ||
|
|
f0a1de3474 | ||
|
|
79c6fcac95 | ||
|
|
50697e32c2 | ||
|
|
6fe74a9f8d | ||
|
|
a246de2b1f | ||
|
|
5ac8e4e098 | ||
|
|
57579e635c | ||
|
|
1a1d1cfb83 | ||
|
|
1397e61643 | ||
|
|
a04e2a5e00 | ||
|
|
b169a872a7 | ||
|
|
1d4b2b1da1 | ||
|
|
ad15b7c3c6 | ||
|
|
b070570cb6 | ||
|
|
55595ec042 | ||
|
|
5e505224d0 | ||
|
|
3c6775992d | ||
|
|
bf64e226d3 | ||
|
|
f379986a59 | ||
|
|
22ead84aa7 | ||
|
|
570ff75164 | ||
|
|
aa95e5bb86 | ||
|
|
87a554b6ef | ||
|
|
cf741a6f87 | ||
|
|
06e7c1d6cb | ||
|
|
7d50703c26 | ||
|
|
33182bcf85 | ||
|
|
fb29efeff3 | ||
|
|
7d13ed79b2 | ||
|
|
c1f65c802c | ||
|
|
bcc429221e | ||
|
|
bd73609b9e | ||
|
|
2dbb21a7f2 | ||
|
|
fe68533ff2 | ||
|
|
01a40daf38 | ||
|
|
097744275f | ||
|
|
e481a4d847 | ||
|
|
95c6bb4de6 | ||
|
|
18e194e152 | ||
|
|
b2f391307b | ||
|
|
a4da3c7ba2 | ||
|
|
af3abef3bf | ||
|
|
f7633a43ce | ||
|
|
ffd345f044 | ||
|
|
ae36d3228f | ||
|
|
1c78a6b483 | ||
|
|
b6c6590aad | ||
|
|
5a792e9913 | ||
|
|
a2f822889d | ||
|
|
83ba463a34 | ||
|
|
a909c5cbe0 | ||
|
|
d615f34f94 | ||
|
|
37378895cf | ||
|
|
19ef055296 | ||
|
|
599fa5eb30 | ||
|
|
4d82b37cab | ||
|
|
77d01d50db | ||
|
|
013c1ab92c | ||
|
|
d4fc60f2f4 | ||
|
|
cd25cde47f | ||
|
|
af709331fb | ||
|
|
e20a21bacd | ||
|
|
49ae5eecb6 | ||
|
|
74b3b283f7 | ||
|
|
9fe4f78269 | ||
|
|
646e440dec | ||
|
|
03d95874e6 | ||
|
|
bd3d6994c1 | ||
|
|
5fd78817a8 | ||
|
|
1b9a395432 | ||
|
|
3996e14e70 | ||
|
|
7a40084bf4 | ||
|
|
30fd48a14a | ||
|
|
1c95d46eaa | ||
|
|
72bc125f84 | ||
|
|
5d51af4330 | ||
|
|
173a81ead8 | ||
|
|
676eacc9cf | ||
|
|
b18ea66def | ||
|
|
93998f9fd5 | ||
|
|
c554e69514 | ||
|
|
a6e10e55cc | ||
|
|
41f541a531 | ||
|
|
9cb1043545 | ||
|
|
96e33d33b0 | ||
|
|
ccc7003ac1 | ||
|
|
93cbd47b5d | ||
|
|
8b808e44b6 | ||
|
|
0644e26297 | ||
|
|
682653b977 | ||
|
|
0053cfc8fc | ||
|
|
5cb62a30cc | ||
|
|
e596a63058 | ||
|
|
3ec32afb37 | ||
|
|
0189a86757 | ||
|
|
ee32307654 | ||
|
|
2f08e6b838 | ||
|
|
c8a3fc350d | ||
|
|
dc63ef1284 | ||
|
|
92332fb02f | ||
|
|
acc6a26654 | ||
|
|
2bd4d2faaf | ||
|
|
1e77ead488 | ||
|
|
561a9ab379 | ||
|
|
c008ef7c1b | ||
|
|
02dfeed3ce | ||
|
|
34cc2e0ed1 | ||
|
|
7a483ab1e2 | ||
|
|
71497a7887 | ||
|
|
aa41a63430 | ||
|
|
0db55daff6 | ||
|
|
9b271950d2 | ||
|
|
89b6b1fb56 | ||
|
|
f5d0694574 | ||
|
|
f91da2ec46 | ||
|
|
89471a0174 | ||
|
|
789b991c56 | ||
|
|
0cbcc0c29c | ||
|
|
b5e239d1ad | ||
|
|
5f79e8ebbd | ||
|
|
1564c4bee7 | ||
|
|
0cf385b718 | ||
|
|
8e1905a695 | ||
|
|
0cb04d0290 | ||
|
|
63a38de059 | ||
|
|
83ecf53776 | ||
|
|
5803da4893 | ||
|
|
e118e5b047 | ||
|
|
7e4e8ea266 | ||
|
|
fc4633db91 | ||
|
|
2f386f8e47 | ||
|
|
f4ea572f6b | ||
|
|
825df7da63 | ||
|
|
cd34f0a7b0 | ||
|
|
b1b22c439a | ||
|
|
eac747849b | ||
|
|
9e50569c31 | ||
|
|
a19f0acfb9 | ||
|
|
1aedf9da0a | ||
|
|
4430042419 | ||
|
|
8a47d69d0d | ||
|
|
73482c2a05 | ||
|
|
79751c208d | ||
|
|
510931e7d6 | ||
|
|
584a8e7d1d | ||
|
|
a74378e1d3 | ||
|
|
840684aeba | ||
|
|
c027c8958b | ||
|
|
a730f4da1d | ||
|
|
d73796b92e | ||
|
|
96b9123306 | ||
|
|
e4cbf088b4 | ||
|
|
333ccb8438 | ||
|
|
f57012eb90 | ||
|
|
34387d9859 | ||
|
|
eb771ceda4 | ||
|
|
80f5914fdd | ||
|
|
eaa70da4dd | ||
|
|
1efd2af44b | ||
|
|
466f137590 | ||
|
|
efc1f67017 | ||
|
|
3dc819eb31 | ||
|
|
028df8bf27 | ||
|
|
28ef5238c9 | ||
|
|
7d3d5b2b22 | ||
|
|
81eba50c9a | ||
|
|
e8d1b779cc | ||
|
|
d9000b55e3 | ||
|
|
3436105bec | ||
|
|
d948d2ec33 | ||
|
|
4b3375ab8e | ||
|
|
6b8a3c8d77 | ||
|
|
ba9794c067 | ||
|
|
6ce165bfd5 | ||
|
|
eb4b2daaab | ||
|
|
8cbc8dec89 | ||
|
|
e89e60d50b | ||
|
|
c45308f234 | ||
|
|
40205c40c5 | ||
|
|
f3fe2dd33b | ||
|
|
8edcc45033 | ||
|
|
91471a4aca | ||
|
|
ae2c37a2f6 | ||
|
|
c8208f0a88 | ||
|
|
e11dfbd29c | ||
|
|
b375d20598 | ||
|
|
c4b82c69f8 | ||
|
|
c9a00420a0 | ||
|
|
36ef9cd442 | ||
|
|
5e08779ab0 | ||
|
|
16a0e1ce7b | ||
|
|
8b03484ade | ||
|
|
9da9974adf | ||
|
|
6f80cf3db2 | ||
|
|
76d8f44779 | ||
|
|
700c92efcb | ||
|
|
d17e0c9f50 | ||
|
|
f00b9794f5 | ||
|
|
daff59c93f | ||
|
|
aa8954366c | ||
|
|
87464d53bd | ||
|
|
e04f17c9aa | ||
|
|
b25e3499d8 | ||
|
|
2e6f74a6f8 | ||
|
|
8eee0ca5a5 | ||
|
|
c2ebc0a0ff | ||
|
|
03c905a7af | ||
|
|
035644eaf7 | ||
|
|
8ce45a1acd | ||
|
|
16e7233a3e | ||
|
|
a331dd3fb4 | ||
|
|
e3e2938b28 | ||
|
|
73e96b1b28 | ||
|
|
b8194295ec | ||
|
|
382a46dfff | ||
|
|
1f74e1b320 | ||
|
|
fee780cb81 | ||
|
|
5056cba85d | ||
|
|
dab38ff82c | ||
|
|
d83fa63af5 | ||
|
|
d5837ab718 | ||
|
|
f85cfc4c68 | ||
|
|
0b2aceafe0 | ||
|
|
059db34a53 | ||
|
|
bc1ea86b4e | ||
|
|
9f2ced1933 | ||
|
|
013cff9b6e | ||
|
|
aa19437031 | ||
|
|
e848ef848b | ||
|
|
bb6605337f | ||
|
|
8df8383468 | ||
|
|
a7e9de3ac4 | ||
|
|
8df41f514e | ||
|
|
c2bf50b121 | ||
|
|
4e7dcbd7b5 | ||
|
|
b7ccb92236 | ||
|
|
23a151dd45 | ||
|
|
122079ddb2 | ||
|
|
1d0b0ae6ec | ||
|
|
a55dd769cf | ||
|
|
f1a0bc97e3 | ||
|
|
a57dfd1d12 | ||
|
|
c0a8304b91 | ||
|
|
ab7b968e28 | ||
|
|
f10b40c3b0 | ||
|
|
7878ac9c76 | ||
|
|
0752951842 | ||
|
|
06bb6636a1 | ||
|
|
2fdd332a31 | ||
|
|
98b1e9546a | ||
|
|
184aa65c6d | ||
|
|
70b3a432a4 | ||
|
|
fb4fc75bd8 | ||
|
|
0479ed9e7f | ||
|
|
1dc3409135 | ||
|
|
1bb89fce26 | ||
|
|
8f3fbb94d2 | ||
|
|
e8c35bec1c | ||
|
|
728e7252eb | ||
|
|
1218507f7d | ||
|
|
a2dff0a35d | ||
|
|
f411180908 | ||
|
|
231a19b679 | ||
|
|
58a87a986a | ||
|
|
61a78ef352 | ||
|
|
e28e5ebb4e | ||
|
|
19cef8c453 | ||
|
|
1290d6cd5c | ||
|
|
ad301074db | ||
|
|
30a756d254 | ||
|
|
363c13c387 | ||
|
|
08e4afaef0 | ||
|
|
69aa6e2d1d | ||
|
|
547865e0da | ||
|
|
3a9e79e6d5 | ||
|
|
0fc1aa9191 | ||
|
|
f34a3559be | ||
|
|
ddf417f4ca | ||
|
|
f2abbf01e5 | ||
|
|
d08be59055 | ||
|
|
44bb87e4ac | ||
|
|
1d2f1405aa | ||
|
|
ff64a79014 | ||
|
|
f6cdadbc2d | ||
|
|
546769ca66 | ||
|
|
d07996d435 | ||
|
|
467808f174 | ||
|
|
64d3c6b2d9 | ||
|
|
75193bb0a2 | ||
|
|
82ba2bd809 | ||
|
|
8559942c5c | ||
|
|
a7fefc84a8 | ||
|
|
c8e83fedeb | ||
|
|
4bf148a4bf | ||
|
|
44664faf3c | ||
|
|
322c136d1f | ||
|
|
3b8dd45a73 | ||
|
|
c1bd36231d | ||
|
|
2cee723f0e | ||
|
|
edfeec900d | ||
|
|
958bde2090 | ||
|
|
29b272f5d5 | ||
|
|
9162ac6d91 | ||
|
|
fe30bb280e | ||
|
|
a1e9396999 | ||
|
|
543542713b | ||
|
|
2a1c290dff | ||
|
|
d155d7e31b | ||
|
|
3dc258da16 | ||
|
|
a4d8789c20 | ||
|
|
0db1397f2f | ||
|
|
0254fb1695 | ||
|
|
954b492aa9 | ||
|
|
8aadc10530 | ||
|
|
6ea719c50f | ||
|
|
b50886179a | ||
|
|
152b452bee | ||
|
|
e06f2f47b1 | ||
|
|
ed8c8bedcd | ||
|
|
1711e39219 | ||
|
|
a73879ec7a | ||
|
|
45c613dec4 | ||
|
|
5150a2c386 | ||
|
|
ca0dd09964 | ||
|
|
5e0e4f1452 | ||
|
|
3ed72dd96b | ||
|
|
b8d7d5c910 | ||
|
|
673b8b7af5 | ||
|
|
a651e50759 | ||
|
|
6484e8e302 | ||
|
|
b01d266629 | ||
|
|
4465b05404 | ||
|
|
d1182c3a59 | ||
|
|
cb6c47678b | ||
|
|
8106620a19 | ||
|
|
be3e066843 | ||
|
|
e345c6ee6e | ||
|
|
073b89b355 | ||
|
|
5cad07f8ad | ||
|
|
f9d872558e | ||
|
|
c5015d02ae | ||
|
|
48013228c1 | ||
|
|
dbafffe73d | ||
|
|
61cbcb2a06 | ||
|
|
89c1ad5d98 | ||
|
|
b343ca6290 | ||
|
|
b913466671 | ||
|
|
9054f4f9c3 | ||
|
|
3915024d9a | ||
|
|
7d1085b43f | ||
|
|
7c2477cccc | ||
|
|
5aecb5fb90 | ||
|
|
f86d040ee4 | ||
|
|
ed32717b3f | ||
|
|
aab8462134 | ||
|
|
04943fb4a6 | ||
|
|
e0c96e7224 | ||
|
|
caacd1e677 | ||
|
|
c995c5a674 | ||
|
|
1e9544af07 | ||
|
|
4cce6e0820 | ||
|
|
c20dfdabfb | ||
|
|
11a6f1f47f | ||
|
|
fcf92d4e2c | ||
|
|
77cef554be | ||
|
|
2841c5ed4e | ||
|
|
9dc9b6a2c3 | ||
|
|
9808a48da0 | ||
|
|
8a6960d9c3 | ||
|
|
d966ef66e1 | ||
|
|
ed97cf5d97 | ||
|
|
a3b088f8d2 | ||
|
|
2828dee94c | ||
|
|
bdc45887f9 | ||
|
|
ee6fb34906 | ||
|
|
bff2ba7cc2 | ||
|
|
8e821b397f | ||
|
|
6f71af278e | ||
|
|
757bb39622 | ||
|
|
00ef6d617f | ||
|
|
d1b2105c80 | ||
|
|
50ee28b1f7 | ||
|
|
ba529ad14e | ||
|
|
6ab0555148 | ||
|
|
c6f269b3fa | ||
|
|
8e160902af | ||
|
|
7bcb852dba | ||
|
|
ed604c8810 | ||
|
|
bea20674a8 | ||
|
|
177926932b | ||
|
|
04dfbd0a14 | ||
|
|
06f840a680 | ||
|
|
5ddcfeb506 | ||
|
|
a143b7de7c | ||
|
|
63372b174f | ||
|
|
ad7d68d2b4 | ||
|
|
e05af54f76 | ||
|
|
914e95e47f | ||
|
|
13eadeaa8f | ||
|
|
19a686b3e4 | ||
|
|
d046084e84 | ||
|
|
e13a076939 | ||
|
|
b4ca6432db | ||
|
|
5b9efc3c5f | ||
|
|
6d7a19b0a0 | ||
|
|
6b3a6fa380 | ||
|
|
e2a65b4b74 | ||
|
|
1f01108b62 | ||
|
|
c80c7df1d0 | ||
|
|
99a064b77a | ||
|
|
9b84623d0c | ||
|
|
6bb6cf8a48 | ||
|
|
348fcbcabf | ||
|
|
1f4cde5f7f | ||
|
|
3e3b02021c | ||
|
|
17eb93d045 | ||
|
|
660420ddef | ||
|
|
395cab795c | ||
|
|
0fecbe704b | ||
|
|
ce59a8a52b | ||
|
|
2091b5f359 | ||
|
|
62c63ddcaa | ||
|
|
dfd604c781 | ||
|
|
3525b367b3 | ||
|
|
0b5b6ed5a3 | ||
|
|
6fe9494df4 | ||
|
|
b2eab95a3b | ||
|
|
38d30b0214 | ||
|
|
c96c5e8ae8 | ||
|
|
6f71e9f0f2 | ||
|
|
d17ec6dc1f | ||
|
|
212b7a104f | ||
|
|
d21dfb750e | ||
|
|
c36a019f5d | ||
|
|
cf2dfdea5b | ||
|
|
985e1bb9ab | ||
|
|
fff38aac85 | ||
|
|
7db58f920c | ||
|
|
e9b16b8801 | ||
|
|
5a2a97b23a | ||
|
|
5b894e8682 | ||
|
|
84925f724d | ||
|
|
7b78b91449 | ||
|
|
f9bff5954f | ||
|
|
2c6e9507b5 | ||
|
|
6471571bc6 | ||
|
|
fe40ea58c1 | ||
|
|
0d4edcd1c7 | ||
|
|
7d8797840a | ||
|
|
19f8c1772f | ||
|
|
37d331e813 | ||
|
|
c660df55cd | ||
|
|
60982bf19f | ||
|
|
efb2e78d9d | ||
|
|
294532ecbb | ||
|
|
062bec23b6 | ||
|
|
0461b5a764 | ||
|
|
6d0e10a4aa | ||
|
|
7c8b865379 | ||
|
|
02033f611f | ||
|
|
b648aa605c | ||
|
|
3cca0c09c0 | ||
|
|
1366901e24 | ||
|
|
c4f48f5748 | ||
|
|
c48bc71443 | ||
|
|
85335bfecc | ||
|
|
7c2b4f422a | ||
|
|
ad2a0ae127 | ||
|
|
d85496453f | ||
|
|
871f14ef3a | ||
|
|
6c2c620c99 | ||
|
|
21b91374a3 | ||
|
|
f643abf19a | ||
|
|
a1729033cf | ||
|
|
7311766512 | ||
|
|
17105f3a51 | ||
|
|
edcfbd26e4 | ||
|
|
0c4d9ea164 | ||
|
|
a5a5224f5c | ||
|
|
8773f7c0a7 | ||
|
|
f385bc2d22 | ||
|
|
a8c9d2e7e6 | ||
|
|
db3f90318b | ||
|
|
2d4d0df5ca | ||
|
|
569ebc671d | ||
|
|
8c8e4e6233 | ||
|
|
c7901ef74b | ||
|
|
be3bd72c1b | ||
|
|
73d1f9288d | ||
|
|
fb7e9f6898 | ||
|
|
38e4b3077f | ||
|
|
312cdc563b | ||
|
|
48ff6dd705 | ||
|
|
695e831090 | ||
|
|
046b431bb8 | ||
|
|
ce2704fc1a | ||
|
|
7e89b36188 | ||
|
|
222dd6bba3 | ||
|
|
a1ce7f54a0 | ||
|
|
87524fe8ae | ||
|
|
2093bb5357 | ||
|
|
ca9ab65228 | ||
|
|
ee4e8f7029 | ||
|
|
f86a1eb32b | ||
|
|
ffd648ed74 | ||
|
|
b2b72169fd | ||
|
|
76746fb6e1 | ||
|
|
6258787c73 | ||
|
|
720080e487 | ||
|
|
46ad1317e4 | ||
|
|
cd28720e46 | ||
|
|
38af02ad3c | ||
|
|
5eed547f91 | ||
|
|
d363ee02ed | ||
|
|
594ee31f43 | ||
|
|
6f2e37948c | ||
|
|
b7421e47cc | ||
|
|
56e25d01ae | ||
|
|
e0fa5607e5 | ||
|
|
572c9bf319 | ||
|
|
52cac4aa21 | ||
|
|
e358d12765 | ||
|
|
02697e27a4 | ||
|
|
ce58e71c44 | ||
|
|
d9766b0f99 | ||
|
|
eeaa1d56ad | ||
|
|
e7f5bc585c | ||
|
|
4f26fb7750 | ||
|
|
cdbc190bfc | ||
|
|
1b1f9ab4cf | ||
|
|
2efe6cfdb3 | ||
|
|
517c607ecf | ||
|
|
802e8f7a22 | ||
|
|
c7cfe2efcb | ||
|
|
ae1f36f39a | ||
|
|
a479ef28ac | ||
|
|
ce2cf50b5a | ||
|
|
f48d01acde | ||
|
|
991fed93ee | ||
|
|
26ab63d0e4 | ||
|
|
7cbe3d42a1 | ||
|
|
d8b511b198 | ||
|
|
102a235407 | ||
|
|
e15703164d | ||
|
|
8f33e25782 | ||
|
|
722595c131 | ||
|
|
4843268537 | ||
|
|
c9be84a8a8 | ||
|
|
03288d2a60 | ||
|
|
f60ae13e4e | ||
|
|
e72697f8b8 | ||
|
|
0c3dc1ad14 | ||
|
|
840fe86f78 | ||
|
|
e079927a5b | ||
|
|
63379964fa | ||
|
|
0cfaf6ed7f | ||
|
|
043ee9e9d2 | ||
|
|
1d5dfd6db2 | ||
|
|
b63e3e5888 | ||
|
|
4f82470506 | ||
|
|
40e21b6f28 | ||
|
|
67fab1928d | ||
|
|
eb98374566 | ||
|
|
1169b68619 | ||
|
|
6c83e78256 | ||
|
|
d3bfd67738 | ||
|
|
0908f0f057 | ||
|
|
2785449c7a | ||
|
|
d2419ba572 | ||
|
|
d44292cf33 | ||
|
|
435cae06a2 | ||
|
|
18ed38889f | ||
|
|
aed86ce4ba | ||
|
|
2c2be50b19 | ||
|
|
e2db4c6246 | ||
|
|
c4839fee08 | ||
|
|
965b7026f0 | ||
|
|
e14e15fcbb | ||
|
|
4ca5acf158 | ||
|
|
ea41fcc566 | ||
|
|
5736c1d8ce | ||
|
|
d142366dd9 | ||
|
|
bab09dff95 | ||
|
|
23d3345ab9 | ||
|
|
09a64815d4 | ||
|
|
6d5f969798 | ||
|
|
10349932f4 | ||
|
|
9c430b37aa | ||
|
|
ad3fe2fa76 | ||
|
|
863eb8efe9 | ||
|
|
86bba494fe | ||
|
|
1a43f1ef4b | ||
|
|
75ab074805 | ||
|
|
dc4e0253de | ||
|
|
47a99e35ee | ||
|
|
cccf236042 | ||
|
|
63fd63c65c | ||
|
|
beee1d692d | ||
|
|
fde786ca84 | ||
|
|
3086fdd064 | ||
|
|
6c30f6db31 | ||
|
|
84b082e194 | ||
|
|
f021b73458 | ||
|
|
74f4751bcc | ||
|
|
e5bce4e180 | ||
|
|
9b0e7b381c | ||
|
|
90afe5a7ac | ||
|
|
b24de85157 | ||
|
|
eda43dffe1 | ||
|
|
82c9a1eb70 | ||
|
|
a3d4553d14 | ||
|
|
1cc5f59f66 | ||
|
|
4e2d88efdd | ||
|
|
4975cabb2c | ||
|
|
225591094f | ||
|
|
82f88f2cd3 | ||
|
|
99e6bd31b6 | ||
|
|
5c50590d7b | ||
|
|
072c89e704 | ||
|
|
dbdff6812d | ||
|
|
42b9d5158d | ||
|
|
2ba225299e | ||
|
|
cc841d5640 | ||
|
|
fa0818d3fa | ||
|
|
dec358c4cd | ||
|
|
5455d1c118 | ||
|
|
ae39084a75 | ||
|
|
e98f873f81 | ||
|
|
e9a2a7e752 | ||
|
|
06015d5191 | ||
|
|
af688d2a23 | ||
|
|
7d0b3ec6b5 | ||
|
|
cf5fb8dc33 | ||
|
|
27d20eb1bc | ||
|
|
2e2684c695 | ||
|
|
7e2fd8f49d | ||
|
|
9a0a255445 | ||
|
|
91b7ceb2cf | ||
|
|
d5a37436c0 | ||
|
|
be609b5000 | ||
|
|
0503c6e66e | ||
|
|
d4b830b9bb | ||
|
|
14d6ff25a7 | ||
|
|
1f62f305ce | ||
|
|
9405b0b70a | ||
|
|
a26ee4ac1a | ||
|
|
cebcf3e337 | ||
|
|
4cfcc64481 | ||
|
|
1a2069a6d9 | ||
|
|
2a5c9465e9 | ||
|
|
f36b66e397 | ||
|
|
8c6d44677d | ||
|
|
1bfff630bf | ||
|
|
ebcef28b05 | ||
|
|
e87e12898c | ||
|
|
d60ab281cf | ||
|
|
483d54a9f0 | ||
|
|
0ab6ff9148 | ||
|
|
c73a39f797 | ||
|
|
c87b6872e5 | ||
|
|
f315c8bc43 | ||
|
|
20fa1519fd | ||
|
|
54430afc40 | ||
|
|
7990d08fee | ||
|
|
e9042d9e2e | ||
|
|
24a15841e4 | ||
|
|
bb8f6e09fd | ||
|
|
04bc8ab694 | ||
|
|
6ac8335cf2 | ||
|
|
4c6144f8fb | ||
|
|
255003794e | ||
|
|
119d5c79a0 | ||
|
|
8e2d7c25df | ||
|
|
753dee3023 | ||
|
|
cac0272952 | ||
|
|
ee5b74f9fc | ||
|
|
1362b72cd3 | ||
|
|
35b1566962 | ||
|
|
a4bcce5a0c | ||
|
|
c03f1946e8 | ||
|
|
c11e107758 | ||
|
|
3b4e49f63a | ||
|
|
ea7253f7e8 | ||
|
|
8a529f7946 | ||
|
|
e76612e018 | ||
|
|
e1f99985d8 | ||
|
|
e0c2735635 | ||
|
|
8e6b4e243d | ||
|
|
2623fa8f02 | ||
|
|
7ff92d32cd | ||
|
|
c7f691b20a | ||
|
|
db042e520e | ||
|
|
4cab693cfc | ||
|
|
c9515ae77c | ||
|
|
d14de86f65 | ||
|
|
f6ee9db730 | ||
|
|
94353aea44 | ||
|
|
b01fcc70fe | ||
|
|
ed95f10fcc | ||
|
|
35fed74e49 | ||
|
|
64bae5b142 | ||
|
|
6cf1b9b010 | ||
|
|
dae169540b | ||
|
|
19f9dda490 | ||
|
|
a060c8029f | ||
|
|
aca9d1e070 | ||
|
|
cdf79edb00 | ||
|
|
df53dfc936 | ||
|
|
8e2e09ab81 | ||
|
|
1eac7cbccd | ||
|
|
ddaaed65e4 | ||
|
|
8e633c21c7 | ||
|
|
e7c4ef44d8 | ||
|
|
3d71470bd2 | ||
|
|
dd627a222e | ||
|
|
62cc20fa1c | ||
|
|
0450fc9f57 | ||
|
|
c58aaf5ba6 | ||
|
|
655522d4e2 | ||
|
|
225475dcae | ||
|
|
ccb977fdfb | ||
|
|
280cbb6e22 | ||
|
|
c20babcb53 | ||
|
|
768eebe2cd | ||
|
|
44e3eedffa | ||
|
|
bb189874cb | ||
|
|
34dadd0e16 | ||
|
|
87b5cd9988 | ||
|
|
6a537a23e8 | ||
|
|
e63a6e9b77 | ||
|
|
7ce589c4f2 | ||
|
|
75a909784a | ||
|
|
244f497a9c | ||
|
|
e58f0c9f07 | ||
|
|
5f18c06e03 | ||
|
|
f36cf06e26 | ||
|
|
27d52646a0 | ||
|
|
4dd8080c55 | ||
|
|
0b35d4f2e3 | ||
|
|
54a9fb9e54 | ||
|
|
60a9e68f02 | ||
|
|
ad374298e3 | ||
|
|
c5dc4e6127 | ||
|
|
291ad831c5 | ||
|
|
0a018f0ca8 | ||
|
|
6673eeb1bb | ||
|
|
4641f0b9ef | ||
|
|
a4487964e5 | ||
|
|
fe42fdd1ec | ||
|
|
375211f184 | ||
|
|
66c377a5c9 | ||
|
|
50c2aa0111 | ||
|
|
fdeb891137 | ||
|
|
6a6e3a43b1 | ||
|
|
b0a34fa21b | ||
|
|
5c4de03588 | ||
|
|
72bf6f3c41 | ||
|
|
ad9289e0c1 | ||
|
|
b0cb0e5a99 | ||
|
|
8347203bbe | ||
|
|
4aa1186aed | ||
|
|
eed87af61d | ||
|
|
daeea8e7ea | ||
|
|
0d63a15715 | ||
|
|
fa2e229ada | ||
|
|
81c1a1da9c | ||
|
|
5d9700d84c | ||
|
|
f8a8cdaa5f | ||
|
|
e23e446476 | ||
|
|
fa097df50b | ||
|
|
75f34ff127 | ||
|
|
c9586b4d93 | ||
|
|
52937a6d90 | ||
|
|
186c131cce | ||
|
|
8de3f9a440 | ||
|
|
ea49e179f9 | ||
|
|
485f4f1c8e | ||
|
|
5fb35d12d7 | ||
|
|
ec8a9fe3d2 | ||
|
|
411a34e15e | ||
|
|
3df71fd2bc | ||
|
|
5e1f6085e3 | ||
|
|
53fc7ab6e3 | ||
|
|
7779ed24fe | ||
|
|
6e4193dae3 | ||
|
|
f138609f48 | ||
|
|
98154b5de3 | ||
|
|
6322fd9eef | ||
|
|
1c0949e957 | ||
|
|
52f26396ac | ||
|
|
c3847e6001 | ||
|
|
5cf13a963d | ||
|
|
b017877826 | ||
|
|
959f68b520 | ||
|
|
14cab3fdb8 | ||
|
|
b8d468f6de | ||
|
|
fc66394243 | ||
|
|
8fca243c9a | ||
|
|
388f710379 | ||
|
|
ba3ab4362b | ||
|
|
e18c9afc2d | ||
|
|
a9b4a86c4a | ||
|
|
200ea502dd | ||
|
|
de36db97eb | ||
|
|
30283b044f | ||
|
|
055bed8a07 | ||
|
|
12b5c2ab34 | ||
|
|
dd78674888 | ||
|
|
0d0df63847 | ||
|
|
3ab00d9da8 | ||
|
|
3e6e72c5c7 | ||
|
|
5d8a55f08c | ||
|
|
81c569aae4 | ||
|
|
88fd3fc4da | ||
|
|
2282d3ae39 | ||
|
|
c4dcec463a | ||
|
|
5b7f893ad7 | ||
|
|
2ede0d498a | ||
|
|
f518e8a0ff | ||
|
|
767284408a | ||
|
|
eef51f3b84 | ||
|
|
69b7114a49 | ||
|
|
0ea38ea568 | ||
|
|
c600da71e3 | ||
|
|
c64dd14b1a | ||
|
|
8ea6d9fa67 | ||
|
|
978ac8f53c | ||
|
|
49a326cde7 | ||
|
|
63e208f4ec | ||
|
|
f50d1549b0 | ||
|
|
55e24df671 | ||
|
|
b37e1d0cc0 | ||
|
|
afa26c0dd4 | ||
|
|
c71f46ede5 | ||
|
|
20e547a0f6 | ||
|
|
2edebaddc2 | ||
|
|
119e1d4867 | ||
|
|
63e30d3378 | ||
|
|
d6fe04ec4e | ||
|
|
b8a364af6a | ||
|
|
5ef808d4a2 | ||
|
|
848d4d91e6 | ||
|
|
a502780c9b | ||
|
|
418e099804 | ||
|
|
06258aa386 | ||
|
|
d7608b1cc8 | ||
|
|
cb86ad4104 | ||
|
|
8cd51df1e1 | ||
|
|
8ef7220766 | ||
|
|
b5333a3686 | ||
|
|
e6e92dbc0f | ||
|
|
01fdd41a10 | ||
|
|
6af06a38ae | ||
|
|
5d9c66d22d | ||
|
|
81f5a4b127 | ||
|
|
da3e68a20b | ||
|
|
8712c1719e | ||
|
|
593c5db0e8 | ||
|
|
b28391feae | ||
|
|
5f8df6d4cd | ||
|
|
c36efe7f14 | ||
|
|
cf97b6df9c | ||
|
|
720d3a8135 | ||
|
|
9c42458fa5 | ||
|
|
6d9b129ac9 | ||
|
|
e17ec798d4 | ||
|
|
58ac499f30 | ||
|
|
f07f0092ad | ||
|
|
bcd3475d17 | ||
|
|
7c04526088 | ||
|
|
2d7ab68576 | ||
|
|
218a4893b6 | ||
|
|
266bf261aa | ||
|
|
63694032e8 | ||
|
|
b77aaedb58 | ||
|
|
a316d0301f | ||
|
|
dcd499720e | ||
|
|
e18fe21eca | ||
|
|
2970b51fb8 | ||
|
|
b9236ff52e | ||
|
|
38eb0ec7ed | ||
|
|
ecba4a0b80 | ||
|
|
e6da18c952 | ||
|
|
12941ac5ae | ||
|
|
11085bda63 | ||
|
|
c03211cc53 | ||
|
|
2867459600 | ||
|
|
32b24db9bf | ||
|
|
660bf9ff87 | ||
|
|
78c4ddebba | ||
|
|
f2dfadb37b | ||
|
|
3f2bdf081f | ||
|
|
d6ba34aeea | ||
|
|
b622aca221 | ||
|
|
6442eb12fb | ||
|
|
01c15afa74 | ||
|
|
4e88f1f38a | ||
|
|
13ab505f4d | ||
|
|
7d112aab27 | ||
|
|
b786497299 | ||
|
|
eedf57af89 | ||
|
|
7a01a4e090 | ||
|
|
874794c996 | ||
|
|
5e37c4e85f | ||
|
|
4e7eac368f | ||
|
|
e8398cb221 | ||
|
|
9460e28c7b | ||
|
|
756f3f32ca | ||
|
|
362981ad19 | ||
|
|
fa4f7e4ac2 | ||
|
|
c6bca4e2ab | ||
|
|
e28b361e05 | ||
|
|
a18691011b | ||
|
|
c4a6403cba | ||
|
|
1851bf941a | ||
|
|
b7ab3c2e92 | ||
|
|
ce1ad032ba | ||
|
|
8446c68e1b | ||
|
|
40ed388b0f | ||
|
|
ce1693aa2f | ||
|
|
11d16a1552 | ||
|
|
0ac54a2c88 | ||
|
|
b7d8b32123 | ||
|
|
5987f6b2cd | ||
|
|
7ad76f5683 | ||
|
|
09a9457021 | ||
|
|
d8b45396e3 | ||
|
|
ca4643ec36 | ||
|
|
e2f78ba476 | ||
|
|
5d92190d50 | ||
|
|
2b0d6de986 | ||
|
|
057f82a561 | ||
|
|
719d2a5ffe | ||
|
|
d4bff9d5cb | ||
|
|
19fcc1f93b | ||
|
|
d45ea127c2 | ||
|
|
f591cf8601 | ||
|
|
6661a76aa8 | ||
|
|
a2ed22bfcc | ||
|
|
e370f8891a | ||
|
|
8a83e32c42 | ||
|
|
831eb6325c | ||
|
|
4d6240c987 | ||
|
|
79cf7c84dc | ||
|
|
b71f582329 | ||
|
|
8315d4b6ae | ||
|
|
b8c3cc751a | ||
|
|
d00262dc31 | ||
|
|
952d0c74d0 | ||
|
|
ffbea7af59 | ||
|
|
3debc6c8d3 | ||
|
|
5092eb58fb | ||
|
|
f0b9240575 | ||
|
|
9cf59c409e | ||
|
|
971c375398 | ||
|
|
ac4439c5ae | ||
|
|
bfd5aa30a7 | ||
|
|
9737170665 | ||
|
|
922a040466 | ||
|
|
9eacefb155 | ||
|
|
33f0782f3a | ||
|
|
e6a5cef945 | ||
|
|
4c8edb80b3 | ||
|
|
d4668fae99 | ||
|
|
ddfe55e3ae | ||
|
|
761a5f1d4c | ||
|
|
1fbcad8787 | ||
|
|
aba586e605 | ||
|
|
27b21b5ad4 | ||
|
|
b6e54dab17 | ||
|
|
1f8e89772d | ||
|
|
843b13ed57 | ||
|
|
be89e5ca55 | ||
|
|
5f3657fd56 | ||
|
|
494162400e | ||
|
|
ab65bb6a8a | ||
|
|
333625f199 | ||
|
|
dbfd715381 | ||
|
|
f1d989964e | ||
|
|
b701629498 | ||
|
|
8250946325 | ||
|
|
71f63d8e6f | ||
|
|
dd5e834db0 | ||
|
|
970ecb52f0 | ||
|
|
62ea1b40e1 | ||
|
|
3b0fd5c592 | ||
|
|
b7616026dd | ||
|
|
16ad60b89a | ||
|
|
db7971d2f7 | ||
|
|
f3f8bd3125 | ||
|
|
516fd0ee8f | ||
|
|
8d6700d493 | ||
|
|
9d4ace9b3e | ||
|
|
2800655e33 | ||
|
|
91eecee11d | ||
|
|
899e5aa395 | ||
|
|
d5820c4902 | ||
|
|
a91c002274 | ||
|
|
4d142b93dd | ||
|
|
04dcf57ff3 | ||
|
|
975550c755 | ||
|
|
a964a80d85 | ||
|
|
22c3b8f116 | ||
|
|
c4b1831cfe | ||
|
|
cdb6813384 | ||
|
|
b14b68d83c | ||
|
|
3c2f930e6b | ||
|
|
ca9c7ce555 | ||
|
|
c2e95a0607 | ||
|
|
2767ee9e80 | ||
|
|
d998a8087f | ||
|
|
fdce016921 | ||
|
|
c73d70933b | ||
|
|
e9d0ad6e37 | ||
|
|
a35586f762 | ||
|
|
f527c30923 | ||
|
|
94e70219cf | ||
|
|
6496763aae | ||
|
|
a409ec269b | ||
|
|
bc7bc8da66 | ||
|
|
52484c774e | ||
|
|
4e1e0cade1 | ||
|
|
fda5904dac | ||
|
|
69ecc22318 | ||
|
|
bff9d33ee6 | ||
|
|
edf506953b | ||
|
|
5e11746549 | ||
|
|
1ae315e303 | ||
|
|
758b03ab25 | ||
|
|
e756fad573 | ||
|
|
3547450b03 | ||
|
|
733f6692c6 | ||
|
|
2d83160b16 | ||
|
|
256fa880dd | ||
|
|
b08c5f5c67 | ||
|
|
d0862a2d26 | ||
|
|
e97340ed52 | ||
|
|
e27c81eea6 | ||
|
|
7f7f3d43b2 | ||
|
|
4b1b772098 | ||
|
|
f66b88490f | ||
|
|
18f9157169 | ||
|
|
6eb82a807b | ||
|
|
bf57a97833 | ||
|
|
e9e2093220 | ||
|
|
c3540da2e3 | ||
|
|
d228cf56dd | ||
|
|
8f4cecd963 | ||
|
|
66adff44bb | ||
|
|
be41c094dc | ||
|
|
273848ca18 | ||
|
|
1e9dbead3b | ||
|
|
aeaa8ba133 | ||
|
|
24654af635 | ||
|
|
e88a21d6db | ||
|
|
bcd01badaf | ||
|
|
8e063506e0 | ||
|
|
84f5d6137a | ||
|
|
0a8565f5e8 | ||
|
|
bd8da25a46 | ||
|
|
a841f588dd | ||
|
|
75a4362ce3 | ||
|
|
e763e001e5 | ||
|
|
69475a0ae7 | ||
|
|
53e14c2ad7 | ||
|
|
1edc33148a | ||
|
|
a4cbfc74e4 | ||
|
|
c0d25aeb02 | ||
|
|
40f49bf6da | ||
|
|
0bfce87dc6 | ||
|
|
2a0655e9de | ||
|
|
a86cfa5934 | ||
|
|
54b77523c5 | ||
|
|
ba06c8928d | ||
|
|
c8a4ac1ed4 | ||
|
|
143acbae48 | ||
|
|
937f6fdae8 | ||
|
|
ba7239ac08 | ||
|
|
2e748274c0 | ||
|
|
eab2750953 | ||
|
|
17b6cb0c73 | ||
|
|
ce74489df5 | ||
|
|
342b188fae | ||
|
|
fa6fee7b55 | ||
|
|
c53d5a4d7d | ||
|
|
521e905724 | ||
|
|
4623090050 | ||
|
|
dd9e5cc541 | ||
|
|
626be6a347 | ||
|
|
56327ed503 | ||
|
|
6d1665004b | ||
|
|
59b8119fbd | ||
|
|
9ff863db5e | ||
|
|
e2ac6e6d4d | ||
|
|
df4101875a | ||
|
|
3f5c788d48 | ||
|
|
45cd4df6e5 | ||
|
|
94ac3ec76e | ||
|
|
af7263a0b1 | ||
|
|
035396f95c | ||
|
|
f318f6304b | ||
|
|
9d0ff472e5 | ||
|
|
d27482e812 | ||
|
|
d5b6de70da | ||
|
|
69c2212ea0 | ||
|
|
10be9bcd56 | ||
|
|
f531def0d2 | ||
|
|
ed40eae655 | ||
|
|
ba5ae6ed04 | ||
|
|
d6ade102dc | ||
|
|
0a6301697e | ||
|
|
13b4fc6725 | ||
|
|
c94d246c24 | ||
|
|
5b779ba9fe | ||
|
|
3ba2cb19a9 | ||
|
|
a095dddd01 | ||
|
|
1b5cfaa49b | ||
|
|
66f3fabbae | ||
|
|
0be8fb7931 | ||
|
|
431e6ffaae | ||
|
|
7d8185e0ee | ||
|
|
dff45748bd | ||
|
|
da514ef314 | ||
|
|
7f73cde794 | ||
|
|
b0af0d9cd5 | ||
|
|
e6464929ff | ||
|
|
122053939d | ||
|
|
8429197b07 | ||
|
|
44f2081882 | ||
|
|
300b4a3706 | ||
|
|
81ef2db7f8 | ||
|
|
c41e8be3e8 | ||
|
|
41bab0ce0b | ||
|
|
5f26b9eeea | ||
|
|
1cca69ad23 | ||
|
|
410ed3949b | ||
|
|
efc6ef3075 | ||
|
|
63f7dd1d20 | ||
|
|
57b8c69983 | ||
|
|
aad060810a | ||
|
|
9222b00a6f | ||
|
|
ff61b22e7e | ||
|
|
577cb91343 | ||
|
|
1889386f64 | ||
|
|
5d7f082ebf | ||
|
|
db6327c4ff | ||
|
|
fd7f6b2b99 | ||
|
|
49435398a8 | ||
|
|
e101ac341b | ||
|
|
6cfc7b7c69 | ||
|
|
313acabc86 | ||
|
|
34cced872f | ||
|
|
ac09e3aaf9 | ||
|
|
9f2fd34e99 | ||
|
|
67b63d3084 | ||
|
|
4a31a7b84b | ||
|
|
538b601b1e | ||
|
|
588f064c25 | ||
|
|
d521e79662 | ||
|
|
ccddb9244d | ||
|
|
0547396213 | ||
|
|
6c85171091 | ||
|
|
a8f6b6c1da | ||
|
|
f899326189 | ||
|
|
0f4d1d2a74 | ||
|
|
941d5c08e3 | ||
|
|
db9f74158b | ||
|
|
609ffccd67 | ||
|
|
748af1d8cb | ||
|
|
d309ec249e | ||
|
|
67949b4968 | ||
|
|
1fc40b3017 | ||
|
|
bb1a375484 | ||
|
|
13c011895d | ||
|
|
bd8d0e3392 | ||
|
|
cda6b67bef | ||
|
|
066305b095 | ||
|
|
f2ba4b270f | ||
|
|
89695df012 | ||
|
|
b04385a340 | ||
|
|
d374ea6ea6 | ||
|
|
01a2820390 | ||
|
|
c89c1a03da | ||
|
|
38ac4c5980 | ||
|
|
ed3ee64e4b | ||
|
|
8c15855fc3 | ||
|
|
3d4df906cf | ||
|
|
e051142334 |
5
.cursor/rules/Localization.mdc
Normal file
5
.cursor/rules/Localization.mdc
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
Always localize strings and use the `t` function to convert keys to strings. Add the keys to the en-us.json file. Never edit the other language files, as en-us.json is the single source of truth.
|
||||||
7
.cursor/rules/Nomenclature.mdc
Normal file
7
.cursor/rules/Nomenclature.mdc
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
description:
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
Proxy resources = public resources
|
||||||
|
Private resources = client resources = site resources
|
||||||
@@ -28,7 +28,9 @@ LICENSE
|
|||||||
CONTRIBUTING.md
|
CONTRIBUTING.md
|
||||||
dist
|
dist
|
||||||
.git
|
.git
|
||||||
migrations/
|
server/migrations/
|
||||||
config/
|
config/
|
||||||
build.ts
|
build.ts
|
||||||
tsconfig.json
|
tsconfig.json
|
||||||
|
Dockerfile*
|
||||||
|
drizzle.config.ts
|
||||||
|
|||||||
1
.github/CODEOWNERS
vendored
Normal file
1
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
* @oschwartz10612 @miloschwartz
|
||||||
180
.github/workflows/cicd.yml
vendored
180
.github/workflows/cicd.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: CI/CD Pipeline
|
name: Public CICD Pipeline
|
||||||
|
|
||||||
# CI/CD workflow for building, publishing, mirroring, signing container images and building release binaries.
|
# CI/CD workflow for building, publishing, mirroring, signing container images and building release binaries.
|
||||||
# Actions are pinned to specific SHAs to reduce supply-chain risk. This workflow triggers on tag push events.
|
# Actions are pinned to specific SHAs to reduce supply-chain risk. This workflow triggers on tag push events.
|
||||||
@@ -29,7 +29,7 @@ jobs:
|
|||||||
permissions: write-all
|
permissions: write-all
|
||||||
steps:
|
steps:
|
||||||
- name: Configure AWS credentials
|
- name: Configure AWS credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v5
|
uses: aws-actions/configure-aws-credentials@v6
|
||||||
with:
|
with:
|
||||||
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}
|
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}
|
||||||
role-duration-seconds: 3600
|
role-duration-seconds: 3600
|
||||||
@@ -62,7 +62,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Monitor storage space
|
- name: Monitor storage space
|
||||||
run: |
|
run: |
|
||||||
@@ -77,7 +77,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
- name: Log in to Docker Hub
|
||||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
registry: docker.io
|
registry: docker.io
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||||
@@ -134,7 +134,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Monitor storage space
|
- name: Monitor storage space
|
||||||
run: |
|
run: |
|
||||||
@@ -149,7 +149,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
- name: Log in to Docker Hub
|
||||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
registry: docker.io
|
registry: docker.io
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||||
@@ -201,10 +201,10 @@ jobs:
|
|||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
- name: Log in to Docker Hub
|
||||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
registry: docker.io
|
registry: docker.io
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||||
@@ -256,7 +256,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Extract tag name
|
- name: Extract tag name
|
||||||
id: get-tag
|
id: get-tag
|
||||||
@@ -264,9 +264,9 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
with:
|
with:
|
||||||
go-version: 1.24
|
go-version: 1.25
|
||||||
|
|
||||||
- name: Update version in package.json
|
- name: Update version in package.json
|
||||||
run: |
|
run: |
|
||||||
@@ -289,25 +289,17 @@ jobs:
|
|||||||
echo "LATEST_BADGER_TAG=$LATEST_TAG" >> $GITHUB_ENV
|
echo "LATEST_BADGER_TAG=$LATEST_TAG" >> $GITHUB_ENV
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Update install/main.go
|
|
||||||
run: |
|
|
||||||
PANGOLIN_VERSION=${{ env.TAG }}
|
|
||||||
GERBIL_VERSION=${{ env.LATEST_GERBIL_TAG }}
|
|
||||||
BADGER_VERSION=${{ env.LATEST_BADGER_TAG }}
|
|
||||||
sed -i "s/config.PangolinVersion = \".*\"/config.PangolinVersion = \"$PANGOLIN_VERSION\"/" install/main.go
|
|
||||||
sed -i "s/config.GerbilVersion = \".*\"/config.GerbilVersion = \"$GERBIL_VERSION\"/" install/main.go
|
|
||||||
sed -i "s/config.BadgerVersion = \".*\"/config.BadgerVersion = \"$BADGER_VERSION\"/" install/main.go
|
|
||||||
echo "Updated install/main.go with Pangolin version $PANGOLIN_VERSION, Gerbil version $GERBIL_VERSION, and Badger version $BADGER_VERSION"
|
|
||||||
cat install/main.go
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Build installer
|
- name: Build installer
|
||||||
working-directory: install
|
working-directory: install
|
||||||
run: |
|
run: |
|
||||||
make go-build-release
|
make go-build-release \
|
||||||
|
PANGOLIN_VERSION=${{ env.TAG }} \
|
||||||
|
GERBIL_VERSION=${{ env.LATEST_GERBIL_TAG }} \
|
||||||
|
BADGER_VERSION=${{ env.LATEST_BADGER_TAG }}
|
||||||
|
shell: bash
|
||||||
|
|
||||||
- name: Upload artifacts from /install/bin
|
- name: Upload artifacts from /install/bin
|
||||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
with:
|
with:
|
||||||
name: install-bin
|
name: install-bin
|
||||||
path: install/bin/
|
path: install/bin/
|
||||||
@@ -415,31 +407,25 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry (for cosign)
|
- name: Login to GitHub Container Registry (for cosign)
|
||||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Install cosign
|
- name: Install cosign
|
||||||
# cosign is used to sign and verify container images (key and keyless)
|
# cosign is used to sign container images using keyless (OIDC) signing
|
||||||
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
|
uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
|
||||||
|
|
||||||
- name: Dual-sign and verify (GHCR & Docker Hub)
|
- name: Sign (GHCR, keyless)
|
||||||
# Sign each image by digest using keyless (OIDC) and key-based signing,
|
# Sign each GHCR image by digest using keyless (OIDC) signing via Sigstore/Rekor.
|
||||||
# then verify both the public key signature and the keyless OIDC signature.
|
# Signatures are stored in the registry alongside the image.
|
||||||
env:
|
env:
|
||||||
TAG: ${{ env.TAG }}
|
TAG: ${{ env.TAG }}
|
||||||
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
|
|
||||||
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
|
|
||||||
COSIGN_PUBLIC_KEY: ${{ secrets.COSIGN_PUBLIC_KEY }}
|
|
||||||
COSIGN_YES: "true"
|
COSIGN_YES: "true"
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
issuer="https://token.actions.githubusercontent.com"
|
|
||||||
id_regex="^https://github.com/${{ github.repository }}/.+" # accept this repo (all workflows/refs)
|
|
||||||
|
|
||||||
# Determine if this is an RC release
|
# Determine if this is an RC release
|
||||||
IS_RC="false"
|
IS_RC="false"
|
||||||
if [[ "$TAG" == *"-rc."* ]]; then
|
if [[ "$TAG" == *"-rc."* ]]; then
|
||||||
@@ -467,98 +453,48 @@ jobs:
|
|||||||
)
|
)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Sign each image variant for both registries
|
FAILED_TAGS=()
|
||||||
for BASE_IMAGE in "${GHCR_IMAGE}" "${DOCKERHUB_IMAGE}"; do
|
SUCCESSFUL_TAGS=()
|
||||||
for IMAGE_TAG in "${IMAGE_TAGS[@]}"; do
|
|
||||||
echo "Processing ${BASE_IMAGE}:${IMAGE_TAG}"
|
|
||||||
|
|
||||||
DIGEST="$(skopeo inspect --retry-times 3 docker://${BASE_IMAGE}:${IMAGE_TAG} | jq -r '.Digest')"
|
for IMAGE_TAG in "${IMAGE_TAGS[@]}"; do
|
||||||
REF="${BASE_IMAGE}@${DIGEST}"
|
echo "Processing ${GHCR_IMAGE}:${IMAGE_TAG}"
|
||||||
|
TAG_FAILED=false
|
||||||
|
|
||||||
|
(
|
||||||
|
set -e
|
||||||
|
DIGEST="$(skopeo inspect --retry-times 3 docker://${GHCR_IMAGE}:${IMAGE_TAG} | jq -r '.Digest')"
|
||||||
|
REF="${GHCR_IMAGE}@${DIGEST}"
|
||||||
echo "Resolved digest: ${REF}"
|
echo "Resolved digest: ${REF}"
|
||||||
|
|
||||||
echo "==> cosign sign (keyless) --recursive ${REF}"
|
echo "==> cosign sign (keyless) --recursive ${REF}"
|
||||||
cosign sign --recursive "${REF}"
|
cosign sign --recursive "${REF}"
|
||||||
|
) || TAG_FAILED=true
|
||||||
|
|
||||||
echo "==> cosign sign (key) --recursive ${REF}"
|
if [ "$TAG_FAILED" = "true" ]; then
|
||||||
cosign sign --key env://COSIGN_PRIVATE_KEY --recursive "${REF}"
|
echo "⚠️ WARNING: Failed to sign ${GHCR_IMAGE}:${IMAGE_TAG}"
|
||||||
|
FAILED_TAGS+=("${GHCR_IMAGE}:${IMAGE_TAG}")
|
||||||
# Retry wrapper for verification to handle registry propagation delays
|
else
|
||||||
retry_verify() {
|
echo "✓ Successfully signed ${GHCR_IMAGE}:${IMAGE_TAG}"
|
||||||
local cmd="$1"
|
SUCCESSFUL_TAGS+=("${GHCR_IMAGE}:${IMAGE_TAG}")
|
||||||
local attempts=6
|
fi
|
||||||
local delay=5
|
|
||||||
local i=1
|
|
||||||
until eval "$cmd"; do
|
|
||||||
if [ $i -ge $attempts ]; then
|
|
||||||
echo "Verification failed after $attempts attempts"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
echo "Verification not yet available. Retry $i/$attempts after ${delay}s..."
|
|
||||||
sleep $delay
|
|
||||||
i=$((i+1))
|
|
||||||
delay=$((delay*2))
|
|
||||||
# Cap the delay to avoid very long waits
|
|
||||||
if [ $delay -gt 60 ]; then delay=60; fi
|
|
||||||
done
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "==> cosign verify (public key) ${REF}"
|
|
||||||
if retry_verify "cosign verify --key env://COSIGN_PUBLIC_KEY '${REF}' -o text"; then
|
|
||||||
VERIFIED_INDEX=true
|
|
||||||
else
|
|
||||||
VERIFIED_INDEX=false
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "==> cosign verify (keyless policy) ${REF}"
|
|
||||||
if retry_verify "cosign verify --certificate-oidc-issuer '${issuer}' --certificate-identity-regexp '${id_regex}' '${REF}' -o text"; then
|
|
||||||
VERIFIED_INDEX_KEYLESS=true
|
|
||||||
else
|
|
||||||
VERIFIED_INDEX_KEYLESS=false
|
|
||||||
fi
|
|
||||||
|
|
||||||
# If index verification fails, attempt to verify child platform manifests
|
|
||||||
if [ "${VERIFIED_INDEX}" != "true" ] || [ "${VERIFIED_INDEX_KEYLESS}" != "true" ]; then
|
|
||||||
echo "Index verification not available; attempting child manifest verification for ${BASE_IMAGE}:${IMAGE_TAG}"
|
|
||||||
CHILD_VERIFIED=false
|
|
||||||
|
|
||||||
for ARCH in arm64 amd64; do
|
|
||||||
CHILD_TAG="${IMAGE_TAG}-${ARCH}"
|
|
||||||
echo "Resolving child digest for ${BASE_IMAGE}:${CHILD_TAG}"
|
|
||||||
CHILD_DIGEST="$(skopeo inspect --retry-times 3 docker://${BASE_IMAGE}:${CHILD_TAG} | jq -r '.Digest' || true)"
|
|
||||||
if [ -n "${CHILD_DIGEST}" ] && [ "${CHILD_DIGEST}" != "null" ]; then
|
|
||||||
CHILD_REF="${BASE_IMAGE}@${CHILD_DIGEST}"
|
|
||||||
echo "==> cosign verify (public key) child ${CHILD_REF}"
|
|
||||||
if retry_verify "cosign verify --key env://COSIGN_PUBLIC_KEY '${CHILD_REF}' -o text"; then
|
|
||||||
CHILD_VERIFIED=true
|
|
||||||
echo "Public key verification succeeded for child ${CHILD_REF}"
|
|
||||||
else
|
|
||||||
echo "Public key verification failed for child ${CHILD_REF}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "==> cosign verify (keyless policy) child ${CHILD_REF}"
|
|
||||||
if retry_verify "cosign verify --certificate-oidc-issuer '${issuer}' --certificate-identity-regexp '${id_regex}' '${CHILD_REF}' -o text"; then
|
|
||||||
CHILD_VERIFIED=true
|
|
||||||
echo "Keyless verification succeeded for child ${CHILD_REF}"
|
|
||||||
else
|
|
||||||
echo "Keyless verification failed for child ${CHILD_REF}"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "No child digest found for ${BASE_IMAGE}:${CHILD_TAG}; skipping"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "${CHILD_VERIFIED}" != "true" ]; then
|
|
||||||
echo "Failed to verify index and no child manifests verified for ${BASE_IMAGE}:${IMAGE_TAG}"
|
|
||||||
exit 10
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "✓ Successfully signed and verified ${BASE_IMAGE}:${IMAGE_TAG}"
|
|
||||||
done
|
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "All images signed and verified successfully!"
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Sign Summary"
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Successful: ${#SUCCESSFUL_TAGS[@]}"
|
||||||
|
echo "Failed: ${#FAILED_TAGS[@]}"
|
||||||
|
|
||||||
|
if [ ${#FAILED_TAGS[@]} -gt 0 ]; then
|
||||||
|
echo "Failed tags:"
|
||||||
|
for tag in "${FAILED_TAGS[@]}"; do
|
||||||
|
echo " - $tag"
|
||||||
|
done
|
||||||
|
echo "⚠️ WARNING: Some tags failed to sign, but continuing anyway"
|
||||||
|
else
|
||||||
|
echo "✓ All images signed successfully!"
|
||||||
|
fi
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
post-run:
|
post-run:
|
||||||
@@ -576,7 +512,7 @@ jobs:
|
|||||||
permissions: write-all
|
permissions: write-all
|
||||||
steps:
|
steps:
|
||||||
- name: Configure AWS credentials
|
- name: Configure AWS credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v5
|
uses: aws-actions/configure-aws-credentials@v6
|
||||||
with:
|
with:
|
||||||
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}
|
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}
|
||||||
role-duration-seconds: 3600
|
role-duration-seconds: 3600
|
||||||
|
|||||||
426
.github/workflows/cicd.yml.backup
vendored
426
.github/workflows/cicd.yml.backup
vendored
@@ -1,426 +0,0 @@
|
|||||||
name: CI/CD Pipeline
|
|
||||||
|
|
||||||
# CI/CD workflow for building, publishing, mirroring, signing container images and building release binaries.
|
|
||||||
# Actions are pinned to specific SHAs to reduce supply-chain risk. This workflow triggers on tag push events.
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write # for GHCR push
|
|
||||||
id-token: write # for Cosign Keyless (OIDC) Signing
|
|
||||||
|
|
||||||
# Required secrets:
|
|
||||||
# - DOCKER_HUB_USERNAME / DOCKER_HUB_ACCESS_TOKEN: push to Docker Hub
|
|
||||||
# - GITHUB_TOKEN: used for GHCR login and OIDC keyless signing
|
|
||||||
# - COSIGN_PRIVATE_KEY / COSIGN_PASSWORD / COSIGN_PUBLIC_KEY: for key-based signing
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "[0-9]+.[0-9]+.[0-9]+"
|
|
||||||
- "[0-9]+.[0-9]+.[0-9]+-rc.[0-9]+"
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
pre-run:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions: write-all
|
|
||||||
steps:
|
|
||||||
- name: Configure AWS credentials
|
|
||||||
uses: aws-actions/configure-aws-credentials@v2
|
|
||||||
with:
|
|
||||||
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}
|
|
||||||
role-duration-seconds: 3600
|
|
||||||
aws-region: ${{ secrets.AWS_REGION }}
|
|
||||||
|
|
||||||
- name: Verify AWS identity
|
|
||||||
run: aws sts get-caller-identity
|
|
||||||
|
|
||||||
- name: Start EC2 instances
|
|
||||||
run: |
|
|
||||||
aws ec2 start-instances --instance-ids ${{ secrets.EC2_INSTANCE_ID_ARM_RUNNER }}
|
|
||||||
aws ec2 start-instances --instance-ids ${{ secrets.EC2_INSTANCE_ID_AMD_RUNNER }}
|
|
||||||
echo "EC2 instances started"
|
|
||||||
|
|
||||||
|
|
||||||
release-arm:
|
|
||||||
name: Build and Release (ARM64)
|
|
||||||
runs-on: [self-hosted, linux, arm64, us-east-1]
|
|
||||||
needs: [pre-run]
|
|
||||||
if: >-
|
|
||||||
${{
|
|
||||||
needs.pre-run.result == 'success'
|
|
||||||
}}
|
|
||||||
# Job-level timeout to avoid runaway or stuck runs
|
|
||||||
timeout-minutes: 120
|
|
||||||
env:
|
|
||||||
# Target images
|
|
||||||
DOCKERHUB_IMAGE: docker.io/fosrl/${{ github.event.repository.name }}
|
|
||||||
GHCR_IMAGE: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
||||||
|
|
||||||
- name: Monitor storage space
|
|
||||||
run: |
|
|
||||||
THRESHOLD=75
|
|
||||||
USED_SPACE=$(df / | grep / | awk '{ print $5 }' | sed 's/%//g')
|
|
||||||
echo "Used space: $USED_SPACE%"
|
|
||||||
if [ "$USED_SPACE" -ge "$THRESHOLD" ]; then
|
|
||||||
echo "Used space is below the threshold of 75% free. Running Docker system prune."
|
|
||||||
echo y | docker system prune -a
|
|
||||||
else
|
|
||||||
echo "Storage space is above the threshold. No action needed."
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
|
||||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
|
||||||
with:
|
|
||||||
registry: docker.io
|
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
|
||||||
|
|
||||||
- name: Extract tag name
|
|
||||||
id: get-tag
|
|
||||||
run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Update version in package.json
|
|
||||||
run: |
|
|
||||||
TAG=${{ env.TAG }}
|
|
||||||
sed -i "s/export const APP_VERSION = \".*\";/export const APP_VERSION = \"$TAG\";/" server/lib/consts.ts
|
|
||||||
cat server/lib/consts.ts
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Check if release candidate
|
|
||||||
id: check-rc
|
|
||||||
run: |
|
|
||||||
TAG=${{ env.TAG }}
|
|
||||||
if [[ "$TAG" == *"-rc."* ]]; then
|
|
||||||
echo "IS_RC=true" >> $GITHUB_ENV
|
|
||||||
else
|
|
||||||
echo "IS_RC=false" >> $GITHUB_ENV
|
|
||||||
fi
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Build and push Docker images (Docker Hub - ARM64)
|
|
||||||
run: |
|
|
||||||
TAG=${{ env.TAG }}
|
|
||||||
if [ "$IS_RC" = "true" ]; then
|
|
||||||
make build-rc-arm tag=$TAG
|
|
||||||
else
|
|
||||||
make build-release-arm tag=$TAG
|
|
||||||
fi
|
|
||||||
echo "Built & pushed ARM64 images to: ${{ env.DOCKERHUB_IMAGE }}:${TAG}"
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
release-amd:
|
|
||||||
name: Build and Release (AMD64)
|
|
||||||
runs-on: [self-hosted, linux, x64, us-east-1]
|
|
||||||
needs: [pre-run]
|
|
||||||
if: >-
|
|
||||||
${{
|
|
||||||
needs.pre-run.result == 'success'
|
|
||||||
}}
|
|
||||||
# Job-level timeout to avoid runaway or stuck runs
|
|
||||||
timeout-minutes: 120
|
|
||||||
env:
|
|
||||||
# Target images
|
|
||||||
DOCKERHUB_IMAGE: docker.io/fosrl/${{ github.event.repository.name }}
|
|
||||||
GHCR_IMAGE: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
||||||
|
|
||||||
- name: Monitor storage space
|
|
||||||
run: |
|
|
||||||
THRESHOLD=75
|
|
||||||
USED_SPACE=$(df / | grep / | awk '{ print $5 }' | sed 's/%//g')
|
|
||||||
echo "Used space: $USED_SPACE%"
|
|
||||||
if [ "$USED_SPACE" -ge "$THRESHOLD" ]; then
|
|
||||||
echo "Used space is below the threshold of 75% free. Running Docker system prune."
|
|
||||||
echo y | docker system prune -a
|
|
||||||
else
|
|
||||||
echo "Storage space is above the threshold. No action needed."
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
|
||||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
|
||||||
with:
|
|
||||||
registry: docker.io
|
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
|
||||||
|
|
||||||
- name: Extract tag name
|
|
||||||
id: get-tag
|
|
||||||
run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Update version in package.json
|
|
||||||
run: |
|
|
||||||
TAG=${{ env.TAG }}
|
|
||||||
sed -i "s/export const APP_VERSION = \".*\";/export const APP_VERSION = \"$TAG\";/" server/lib/consts.ts
|
|
||||||
cat server/lib/consts.ts
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Check if release candidate
|
|
||||||
id: check-rc
|
|
||||||
run: |
|
|
||||||
TAG=${{ env.TAG }}
|
|
||||||
if [[ "$TAG" == *"-rc."* ]]; then
|
|
||||||
echo "IS_RC=true" >> $GITHUB_ENV
|
|
||||||
else
|
|
||||||
echo "IS_RC=false" >> $GITHUB_ENV
|
|
||||||
fi
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Build and push Docker images (Docker Hub - AMD64)
|
|
||||||
run: |
|
|
||||||
TAG=${{ env.TAG }}
|
|
||||||
if [ "$IS_RC" = "true" ]; then
|
|
||||||
make build-rc-amd tag=$TAG
|
|
||||||
else
|
|
||||||
make build-release-amd tag=$TAG
|
|
||||||
fi
|
|
||||||
echo "Built & pushed AMD64 images to: ${{ env.DOCKERHUB_IMAGE }}:${TAG}"
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
create-manifest:
|
|
||||||
name: Create Multi-Arch Manifests
|
|
||||||
runs-on: [self-hosted, linux, x64, us-east-1]
|
|
||||||
needs: [release-arm, release-amd]
|
|
||||||
if: >-
|
|
||||||
${{
|
|
||||||
needs.release-arm.result == 'success' &&
|
|
||||||
needs.release-amd.result == 'success'
|
|
||||||
}}
|
|
||||||
timeout-minutes: 30
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
|
||||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
|
||||||
with:
|
|
||||||
registry: docker.io
|
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
|
||||||
|
|
||||||
- name: Extract tag name
|
|
||||||
id: get-tag
|
|
||||||
run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Check if release candidate
|
|
||||||
id: check-rc
|
|
||||||
run: |
|
|
||||||
TAG=${{ env.TAG }}
|
|
||||||
if [[ "$TAG" == *"-rc."* ]]; then
|
|
||||||
echo "IS_RC=true" >> $GITHUB_ENV
|
|
||||||
else
|
|
||||||
echo "IS_RC=false" >> $GITHUB_ENV
|
|
||||||
fi
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Create multi-arch manifests
|
|
||||||
run: |
|
|
||||||
TAG=${{ env.TAG }}
|
|
||||||
if [ "$IS_RC" = "true" ]; then
|
|
||||||
make create-manifests-rc tag=$TAG
|
|
||||||
else
|
|
||||||
make create-manifests tag=$TAG
|
|
||||||
fi
|
|
||||||
echo "Created multi-arch manifests for tag: ${TAG}"
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
sign-and-package:
|
|
||||||
name: Sign and Package
|
|
||||||
runs-on: [self-hosted, linux, x64, us-east-1]
|
|
||||||
needs: [release-arm, release-amd, create-manifest]
|
|
||||||
if: >-
|
|
||||||
${{
|
|
||||||
needs.release-arm.result == 'success' &&
|
|
||||||
needs.release-amd.result == 'success' &&
|
|
||||||
needs.create-manifest.result == 'success'
|
|
||||||
}}
|
|
||||||
# Job-level timeout to avoid runaway or stuck runs
|
|
||||||
timeout-minutes: 120
|
|
||||||
env:
|
|
||||||
# Target images
|
|
||||||
DOCKERHUB_IMAGE: docker.io/fosrl/${{ github.event.repository.name }}
|
|
||||||
GHCR_IMAGE: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
||||||
|
|
||||||
- name: Extract tag name
|
|
||||||
id: get-tag
|
|
||||||
run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
|
|
||||||
with:
|
|
||||||
go-version: 1.24
|
|
||||||
|
|
||||||
- name: Update version in package.json
|
|
||||||
run: |
|
|
||||||
TAG=${{ env.TAG }}
|
|
||||||
sed -i "s/export const APP_VERSION = \".*\";/export const APP_VERSION = \"$TAG\";/" server/lib/consts.ts
|
|
||||||
cat server/lib/consts.ts
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Pull latest Gerbil version
|
|
||||||
id: get-gerbil-tag
|
|
||||||
run: |
|
|
||||||
LATEST_TAG=$(curl -s https://api.github.com/repos/fosrl/gerbil/tags | jq -r '.[0].name')
|
|
||||||
echo "LATEST_GERBIL_TAG=$LATEST_TAG" >> $GITHUB_ENV
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Pull latest Badger version
|
|
||||||
id: get-badger-tag
|
|
||||||
run: |
|
|
||||||
LATEST_TAG=$(curl -s https://api.github.com/repos/fosrl/badger/tags | jq -r '.[0].name')
|
|
||||||
echo "LATEST_BADGER_TAG=$LATEST_TAG" >> $GITHUB_ENV
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Update install/main.go
|
|
||||||
run: |
|
|
||||||
PANGOLIN_VERSION=${{ env.TAG }}
|
|
||||||
GERBIL_VERSION=${{ env.LATEST_GERBIL_TAG }}
|
|
||||||
BADGER_VERSION=${{ env.LATEST_BADGER_TAG }}
|
|
||||||
sed -i "s/config.PangolinVersion = \".*\"/config.PangolinVersion = \"$PANGOLIN_VERSION\"/" install/main.go
|
|
||||||
sed -i "s/config.GerbilVersion = \".*\"/config.GerbilVersion = \"$GERBIL_VERSION\"/" install/main.go
|
|
||||||
sed -i "s/config.BadgerVersion = \".*\"/config.BadgerVersion = \"$BADGER_VERSION\"/" install/main.go
|
|
||||||
echo "Updated install/main.go with Pangolin version $PANGOLIN_VERSION, Gerbil version $GERBIL_VERSION, and Badger version $BADGER_VERSION"
|
|
||||||
cat install/main.go
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Build installer
|
|
||||||
working-directory: install
|
|
||||||
run: |
|
|
||||||
make go-build-release
|
|
||||||
|
|
||||||
- name: Upload artifacts from /install/bin
|
|
||||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
|
||||||
with:
|
|
||||||
name: install-bin
|
|
||||||
path: install/bin/
|
|
||||||
|
|
||||||
- name: Install skopeo + jq
|
|
||||||
# skopeo: copy/inspect images between registries
|
|
||||||
# jq: JSON parsing tool used to extract digest values
|
|
||||||
run: |
|
|
||||||
sudo apt-get update -y
|
|
||||||
sudo apt-get install -y skopeo jq
|
|
||||||
skopeo --version
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Login to GHCR
|
|
||||||
env:
|
|
||||||
REGISTRY_AUTH_FILE: ${{ runner.temp }}/containers/auth.json
|
|
||||||
run: |
|
|
||||||
mkdir -p "$(dirname "$REGISTRY_AUTH_FILE")"
|
|
||||||
skopeo login ghcr.io -u "${{ github.actor }}" -p "${{ secrets.GITHUB_TOKEN }}"
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Copy tag from Docker Hub to GHCR
|
|
||||||
# Mirror the already-built image (all architectures) to GHCR so we can sign it
|
|
||||||
# Wait a bit for both architectures to be available in Docker Hub manifest
|
|
||||||
env:
|
|
||||||
REGISTRY_AUTH_FILE: ${{ runner.temp }}/containers/auth.json
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
TAG=${{ env.TAG }}
|
|
||||||
echo "Waiting for multi-arch manifest to be ready..."
|
|
||||||
sleep 30
|
|
||||||
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:${TAG} -> ${{ env.GHCR_IMAGE }}:${TAG}"
|
|
||||||
skopeo copy --all --retry-times 3 \
|
|
||||||
docker://$DOCKERHUB_IMAGE:$TAG \
|
|
||||||
docker://$GHCR_IMAGE:$TAG
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry (for cosign)
|
|
||||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Install cosign
|
|
||||||
# cosign is used to sign and verify container images (key and keyless)
|
|
||||||
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
|
|
||||||
|
|
||||||
- name: Dual-sign and verify (GHCR & Docker Hub)
|
|
||||||
# Sign each image by digest using keyless (OIDC) and key-based signing,
|
|
||||||
# then verify both the public key signature and the keyless OIDC signature.
|
|
||||||
env:
|
|
||||||
TAG: ${{ env.TAG }}
|
|
||||||
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
|
|
||||||
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
|
|
||||||
COSIGN_PUBLIC_KEY: ${{ secrets.COSIGN_PUBLIC_KEY }}
|
|
||||||
COSIGN_YES: "true"
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
issuer="https://token.actions.githubusercontent.com"
|
|
||||||
id_regex="^https://github.com/${{ github.repository }}/.+" # accept this repo (all workflows/refs)
|
|
||||||
|
|
||||||
for IMAGE in "${GHCR_IMAGE}" "${DOCKERHUB_IMAGE}"; do
|
|
||||||
echo "Processing ${IMAGE}:${TAG}"
|
|
||||||
|
|
||||||
DIGEST="$(skopeo inspect --retry-times 3 docker://${IMAGE}:${TAG} | jq -r '.Digest')"
|
|
||||||
REF="${IMAGE}@${DIGEST}"
|
|
||||||
echo "Resolved digest: ${REF}"
|
|
||||||
|
|
||||||
echo "==> cosign sign (keyless) --recursive ${REF}"
|
|
||||||
cosign sign --recursive "${REF}"
|
|
||||||
|
|
||||||
echo "==> cosign sign (key) --recursive ${REF}"
|
|
||||||
cosign sign --key env://COSIGN_PRIVATE_KEY --recursive "${REF}"
|
|
||||||
|
|
||||||
echo "==> cosign verify (public key) ${REF}"
|
|
||||||
cosign verify --key env://COSIGN_PUBLIC_KEY "${REF}" -o text
|
|
||||||
|
|
||||||
echo "==> cosign verify (keyless policy) ${REF}"
|
|
||||||
cosign verify \
|
|
||||||
--certificate-oidc-issuer "${issuer}" \
|
|
||||||
--certificate-identity-regexp "${id_regex}" \
|
|
||||||
"${REF}" -o text
|
|
||||||
done
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
post-run:
|
|
||||||
needs: [pre-run, release-arm, release-amd, create-manifest, sign-and-package]
|
|
||||||
if: >-
|
|
||||||
${{
|
|
||||||
always() &&
|
|
||||||
needs.pre-run.result == 'success' &&
|
|
||||||
(needs.release-arm.result == 'success' || needs.release-arm.result == 'skipped' || needs.release-arm.result == 'failure') &&
|
|
||||||
(needs.release-amd.result == 'success' || needs.release-amd.result == 'skipped' || needs.release-amd.result == 'failure') &&
|
|
||||||
(needs.create-manifest.result == 'success' || needs.create-manifest.result == 'skipped' || needs.create-manifest.result == 'failure') &&
|
|
||||||
(needs.sign-and-package.result == 'success' || needs.sign-and-package.result == 'skipped' || needs.sign-and-package.result == 'failure')
|
|
||||||
}}
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions: write-all
|
|
||||||
steps:
|
|
||||||
- name: Configure AWS credentials
|
|
||||||
uses: aws-actions/configure-aws-credentials@v2
|
|
||||||
with:
|
|
||||||
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}
|
|
||||||
role-duration-seconds: 3600
|
|
||||||
aws-region: ${{ secrets.AWS_REGION }}
|
|
||||||
|
|
||||||
- name: Verify AWS identity
|
|
||||||
run: aws sts get-caller-identity
|
|
||||||
|
|
||||||
- name: Stop EC2 instances
|
|
||||||
run: |
|
|
||||||
aws ec2 stop-instances --instance-ids ${{ secrets.EC2_INSTANCE_ID_ARM_RUNNER }}
|
|
||||||
aws ec2 stop-instances --instance-ids ${{ secrets.EC2_INSTANCE_ID_AMD_RUNNER }}
|
|
||||||
echo "EC2 instances stopped"
|
|
||||||
4
.github/workflows/linting.yml
vendored
4
.github/workflows/linting.yml
vendored
@@ -21,10 +21,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
with:
|
with:
|
||||||
node-version: '24'
|
node-version: '24'
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/mirror.yaml
vendored
2
.github/workflows/mirror.yaml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
skopeo --version
|
skopeo --version
|
||||||
|
|
||||||
- name: Install cosign
|
- name: Install cosign
|
||||||
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
|
uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
|
||||||
|
|
||||||
- name: Input check
|
- name: Input check
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
2
.github/workflows/restart-runners.yml
vendored
2
.github/workflows/restart-runners.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
permissions: write-all
|
permissions: write-all
|
||||||
steps:
|
steps:
|
||||||
- name: Configure AWS credentials
|
- name: Configure AWS credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v5
|
uses: aws-actions/configure-aws-credentials@v6
|
||||||
with:
|
with:
|
||||||
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}
|
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}
|
||||||
role-duration-seconds: 3600
|
role-duration-seconds: 3600
|
||||||
|
|||||||
45
.github/workflows/saas.yml
vendored
45
.github/workflows/saas.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: CI/CD Pipeline
|
name: SAAS Pipeline
|
||||||
|
|
||||||
# CI/CD workflow for building, publishing, mirroring, signing container images and building release binaries.
|
# CI/CD workflow for building, publishing, mirroring, signing container images and building release binaries.
|
||||||
# Actions are pinned to specific SHAs to reduce supply-chain risk. This workflow triggers on tag push events.
|
# Actions are pinned to specific SHAs to reduce supply-chain risk. This workflow triggers on tag push events.
|
||||||
@@ -23,7 +23,7 @@ jobs:
|
|||||||
permissions: write-all
|
permissions: write-all
|
||||||
steps:
|
steps:
|
||||||
- name: Configure AWS credentials
|
- name: Configure AWS credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v5
|
uses: aws-actions/configure-aws-credentials@v6
|
||||||
with:
|
with:
|
||||||
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}
|
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}
|
||||||
role-duration-seconds: 3600
|
role-duration-seconds: 3600
|
||||||
@@ -54,7 +54,42 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
|
- name: Download MaxMind GeoLite2 databases
|
||||||
|
env:
|
||||||
|
MAXMIND_LICENSE_KEY: ${{ secrets.MAXMIND_LICENSE_KEY }}
|
||||||
|
run: |
|
||||||
|
echo "Downloading MaxMind GeoLite2 databases..."
|
||||||
|
|
||||||
|
# Download GeoLite2-Country
|
||||||
|
curl -L "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=${MAXMIND_LICENSE_KEY}&suffix=tar.gz" \
|
||||||
|
-o GeoLite2-Country.tar.gz
|
||||||
|
|
||||||
|
# Download GeoLite2-ASN
|
||||||
|
curl -L "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-ASN&license_key=${MAXMIND_LICENSE_KEY}&suffix=tar.gz" \
|
||||||
|
-o GeoLite2-ASN.tar.gz
|
||||||
|
|
||||||
|
# Extract the .mmdb files
|
||||||
|
tar -xzf GeoLite2-Country.tar.gz --strip-components=1 --wildcards '*.mmdb'
|
||||||
|
tar -xzf GeoLite2-ASN.tar.gz --strip-components=1 --wildcards '*.mmdb'
|
||||||
|
|
||||||
|
# Verify files exist
|
||||||
|
if [ ! -f "GeoLite2-Country.mmdb" ]; then
|
||||||
|
echo "ERROR: Failed to download GeoLite2-Country.mmdb"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "GeoLite2-ASN.mmdb" ]; then
|
||||||
|
echo "ERROR: Failed to download GeoLite2-ASN.mmdb"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean up tar files
|
||||||
|
rm -f GeoLite2-Country.tar.gz GeoLite2-ASN.tar.gz
|
||||||
|
|
||||||
|
echo "MaxMind databases downloaded successfully"
|
||||||
|
ls -lh GeoLite2-*.mmdb
|
||||||
|
|
||||||
- name: Monitor storage space
|
- name: Monitor storage space
|
||||||
run: |
|
run: |
|
||||||
@@ -69,7 +104,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Configure AWS credentials
|
- name: Configure AWS credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v5
|
uses: aws-actions/configure-aws-credentials@v6
|
||||||
with:
|
with:
|
||||||
role-to-assume: arn:aws:iam::${{ secrets.aws_account_id }}:role/${{ secrets.AWS_ROLE_NAME }}
|
role-to-assume: arn:aws:iam::${{ secrets.aws_account_id }}:role/${{ secrets.AWS_ROLE_NAME }}
|
||||||
role-duration-seconds: 3600
|
role-duration-seconds: 3600
|
||||||
@@ -110,7 +145,7 @@ jobs:
|
|||||||
permissions: write-all
|
permissions: write-all
|
||||||
steps:
|
steps:
|
||||||
- name: Configure AWS credentials
|
- name: Configure AWS credentials
|
||||||
uses: aws-actions/configure-aws-credentials@v5
|
uses: aws-actions/configure-aws-credentials@v6
|
||||||
with:
|
with:
|
||||||
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}
|
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}
|
||||||
role-duration-seconds: 3600
|
role-duration-seconds: 3600
|
||||||
|
|||||||
2
.github/workflows/stale-bot.yml
vendored
2
.github/workflows/stale-bot.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
stale:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1
|
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
|
||||||
with:
|
with:
|
||||||
days-before-stale: 14
|
days-before-stale: 14
|
||||||
days-before-close: 14
|
days-before-close: 14
|
||||||
|
|||||||
10
.github/workflows/test.yml
vendored
10
.github/workflows/test.yml
vendored
@@ -14,10 +14,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
with:
|
with:
|
||||||
node-version: '24'
|
node-version: '24'
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ jobs:
|
|||||||
run: npm run db:generate
|
run: npm run db:generate
|
||||||
|
|
||||||
- name: Apply database migrations
|
- name: Apply database migrations
|
||||||
run: npm run db:sqlite:push
|
run: npm run db:push
|
||||||
|
|
||||||
- name: Test with tsc
|
- name: Test with tsc
|
||||||
run: npx tsc --noEmit
|
run: npx tsc --noEmit
|
||||||
@@ -62,7 +62,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Build Docker image sqlite
|
- name: Build Docker image sqlite
|
||||||
run: make dev-build-sqlite
|
run: make dev-build-sqlite
|
||||||
@@ -71,7 +71,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Build Docker image pg
|
- name: Build Docker image pg
|
||||||
run: make dev-build-pg
|
run: make dev-build-pg
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -51,4 +51,6 @@ dynamic/
|
|||||||
scratch/
|
scratch/
|
||||||
tsconfig.json
|
tsconfig.json
|
||||||
hydrateSaas.ts
|
hydrateSaas.ts
|
||||||
CLAUDE.md
|
CLAUDE.md
|
||||||
|
drizzle.config.ts
|
||||||
|
server/setup/migrations.ts
|
||||||
|
|||||||
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -10,7 +10,7 @@
|
|||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"[typescript]": {
|
"[typescript]": {
|
||||||
"editor.defaultFormatter": "vscode.typescript-language-features"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"[typescriptreact]": {
|
"[typescriptreact]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
|||||||
87
Dockerfile
87
Dockerfile
@@ -1,32 +1,71 @@
|
|||||||
FROM node:24-alpine AS builder
|
# FROM node:24-slim AS base
|
||||||
|
FROM public.ecr.aws/docker/library/node:24-slim AS base
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
ARG BUILD=oss
|
RUN apt-get update && apt-get install -y python3 make g++ && rm -rf /var/lib/apt/lists/*
|
||||||
ARG DATABASE=sqlite
|
|
||||||
|
|
||||||
RUN apk add --no-cache python3 make g++
|
|
||||||
|
|
||||||
# COPY package.json package-lock.json ./
|
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
|
|
||||||
|
FROM base AS builder-dev
|
||||||
|
|
||||||
RUN npm ci
|
RUN npm ci
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
ARG BUILD=oss
|
||||||
|
ARG DATABASE=sqlite
|
||||||
|
|
||||||
RUN if [ "$BUILD" = "oss" ]; then rm -rf server/private; fi && \
|
RUN if [ "$BUILD" = "oss" ]; then rm -rf server/private; fi && \
|
||||||
npm run set:$DATABASE && \
|
npm run set:$DATABASE && \
|
||||||
npm run set:$BUILD && \
|
npm run set:$BUILD && \
|
||||||
npm run db:$DATABASE:generate && \
|
npm run db:generate && \
|
||||||
npm run build && \
|
npm run build && \
|
||||||
npm run build:cli
|
npm run build:cli && \
|
||||||
|
test -f dist/server.mjs
|
||||||
|
|
||||||
# test to make sure the build output is there and error if not
|
# Create placeholder files for MaxMind databases to avoid COPY errors
|
||||||
RUN test -f dist/server.mjs
|
# Real files should be present for saas builds, placeholders for oss builds
|
||||||
|
RUN touch /app/GeoLite2-Country.mmdb /app/GeoLite2-ASN.mmdb
|
||||||
|
|
||||||
# Prune dev dependencies and clean up to prepare for copy to runner
|
FROM base AS builder
|
||||||
RUN npm prune --omit=dev && npm cache clean --force
|
|
||||||
|
|
||||||
FROM node:24-alpine AS runner
|
RUN npm ci --omit=dev
|
||||||
|
|
||||||
|
# FROM node:24-slim AS runner
|
||||||
|
FROM public.ecr.aws/docker/library/node:24-slim AS runner
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y curl tzdata && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY --from=builder /app/node_modules ./node_modules
|
||||||
|
COPY --from=builder /app/package.json ./package.json
|
||||||
|
|
||||||
|
COPY --from=builder-dev /app/.next/standalone ./
|
||||||
|
COPY --from=builder-dev /app/.next/static ./.next/static
|
||||||
|
COPY --from=builder-dev /app/dist ./dist
|
||||||
|
COPY --from=builder-dev /app/server/migrations ./dist/init
|
||||||
|
|
||||||
|
COPY ./cli/wrapper.sh /usr/local/bin/pangctl
|
||||||
|
RUN chmod +x /usr/local/bin/pangctl ./dist/cli.mjs
|
||||||
|
|
||||||
|
COPY server/db/names.json ./dist/names.json
|
||||||
|
COPY server/db/ios_models.json ./dist/ios_models.json
|
||||||
|
COPY server/db/mac_models.json ./dist/mac_models.json
|
||||||
|
COPY public ./public
|
||||||
|
|
||||||
|
# Copy MaxMind databases for SaaS builds
|
||||||
|
ARG BUILD=oss
|
||||||
|
|
||||||
|
RUN mkdir -p ./maxmind
|
||||||
|
|
||||||
|
# Copy MaxMind databases (placeholders exist for oss builds, real files for saas)
|
||||||
|
COPY --from=builder-dev /app/GeoLite2-Country.mmdb ./maxmind/GeoLite2-Country.mmdb
|
||||||
|
COPY --from=builder-dev /app/GeoLite2-ASN.mmdb ./maxmind/GeoLite2-ASN.mmdb
|
||||||
|
|
||||||
|
# Remove MaxMind databases for non-saas builds (keep only for saas)
|
||||||
|
RUN if [ "$BUILD" != "saas" ]; then rm -rf ./maxmind; fi
|
||||||
|
|
||||||
# OCI Image Labels - Build Args for dynamic values
|
# OCI Image Labels - Build Args for dynamic values
|
||||||
ARG VERSION="dev"
|
ARG VERSION="dev"
|
||||||
@@ -38,28 +77,6 @@ ARG LICENSE="AGPL-3.0"
|
|||||||
ARG IMAGE_TITLE="Pangolin"
|
ARG IMAGE_TITLE="Pangolin"
|
||||||
ARG IMAGE_DESCRIPTION="Identity-aware VPN and proxy for remote access to anything, anywhere"
|
ARG IMAGE_DESCRIPTION="Identity-aware VPN and proxy for remote access to anything, anywhere"
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Only curl and tzdata needed at runtime - no build tools!
|
|
||||||
RUN apk add --no-cache curl tzdata
|
|
||||||
|
|
||||||
# Copy pre-built node_modules from builder (already pruned to production only)
|
|
||||||
# This includes the compiled native modules like better-sqlite3
|
|
||||||
COPY --from=builder /app/node_modules ./node_modules
|
|
||||||
COPY --from=builder /app/.next/standalone ./
|
|
||||||
COPY --from=builder /app/.next/static ./.next/static
|
|
||||||
COPY --from=builder /app/dist ./dist
|
|
||||||
COPY --from=builder /app/server/migrations ./dist/init
|
|
||||||
COPY --from=builder /app/package.json ./package.json
|
|
||||||
|
|
||||||
COPY ./cli/wrapper.sh /usr/local/bin/pangctl
|
|
||||||
RUN chmod +x /usr/local/bin/pangctl ./dist/cli.mjs
|
|
||||||
|
|
||||||
COPY server/db/names.json ./dist/names.json
|
|
||||||
COPY server/db/ios_models.json ./dist/ios_models.json
|
|
||||||
COPY server/db/mac_models.json ./dist/mac_models.json
|
|
||||||
COPY public ./public
|
|
||||||
|
|
||||||
# OCI Image Labels
|
# OCI Image Labels
|
||||||
# https://github.com/opencontainers/image-spec/blob/main/annotations.md
|
# https://github.com/opencontainers/image-spec/blob/main/annotations.md
|
||||||
LABEL org.opencontainers.image.source="https://github.com/fosrl/pangolin" \
|
LABEL org.opencontainers.image.source="https://github.com/fosrl/pangolin" \
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
FROM node:22-alpine
|
FROM node:24-alpine
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN apk add --no-cache python3 make g++
|
||||||
|
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
|
|||||||
67
README.md
67
README.md
@@ -35,43 +35,53 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<a href="https://docs.pangolin.net/careers/join-us">
|
|
||||||
<img src="https://img.shields.io/badge/🚀_We're_Hiring!-Join_Our_Team-brightgreen?style=for-the-badge" alt="We're Hiring!" />
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<strong>
|
<strong>
|
||||||
Start testing Pangolin at <a href="https://app.pangolin.net/auth/signup">app.pangolin.net</a>
|
Get started with Pangolin at <a href="https://app.pangolin.net/auth/signup">app.pangolin.net</a>
|
||||||
</strong>
|
</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
Pangolin is an open-source, identity-based remote access platform built on WireGuard that enables secure, seamless connectivity to private and public resources. Pangolin combines reverse proxy and VPN capabilities into one platform, providing browser-based access to web applications and client-based access to any private resources, all with zero-trust security and granular access control.
|
Pangolin is an open-source, identity-based remote access platform built on WireGuard® that enables secure, seamless connectivity to private and public resources. Pangolin combines reverse proxy and VPN capabilities into one platform, providing browser-based access to web applications and client-based access to any private resources with NAT traversal, all with granular access controls.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
- Check out the [quick install guide](https://docs.pangolin.net/self-host/quick-install) for how to install and set up Pangolin.
|
- Get started for free with [Pangolin Cloud](https://app.pangolin.net/).
|
||||||
- Install from the [DigitalOcean marketplace](https://marketplace.digitalocean.com/apps/pangolin-ce-1?refcode=edf0480eeb81) for a one-click pre-configured installer.
|
- Or, check out the [quick install guide](https://docs.pangolin.net/self-host/quick-install) for how to self-host Pangolin.
|
||||||
|
- Install from the [DigitalOcean marketplace](https://marketplace.digitalocean.com/apps/pangolin-ce-1?refcode=edf0480eeb81) for a one-click pre-configured installer.
|
||||||
|
|
||||||
<img src="public/screenshots/hero.png" />
|
<img src="public/screenshots/hero.png" alt="Pangolin" width="100%" />
|
||||||
|
|
||||||
## Deployment Options
|
## Deployment Options
|
||||||
|
|
||||||
| <img width=500 /> | Description |
|
- **Pangolin Cloud** - Fully managed service - no infrastructure required.
|
||||||
|-----------------|--------------|
|
- **Self-Host: Community Edition** - Free, open source, and licensed under AGPL-3.
|
||||||
| **Self-Host: Community Edition** | Free, open source, and licensed under AGPL-3. |
|
- **Self-Host: Enterprise Edition** - Licensed under Fossorial Commercial License. Free for personal and hobbyist use, and for businesses making less than \$100K USD gross annual revenue.
|
||||||
| **Self-Host: Enterprise Edition** | Licensed under Fossorial Commercial License. Free for personal and hobbyist use, and for businesses earning under \$100K USD annually. |
|
|
||||||
| **Pangolin Cloud** | Fully managed service with instant setup and pay-as-you-go pricing — no infrastructure required. Or, self-host your own [remote node](https://docs.pangolin.net/manage/remote-node/nodes) and connect to our control plane. |
|
|
||||||
|
|
||||||
## Key Features
|
## Key Features
|
||||||
|
|
||||||
| <img width=500 /> | <img width=500 /> |
|
### Connect remote networks with sites and NAT traversal
|
||||||
|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------|
|
|
||||||
| **Connect remote networks with sites**<br /><br />Pangolin's lightweight site connectors create secure tunnels from remote networks without requiring public IP addresses or open ports. Sites make any network anywhere available for authorized access. | <img src="public/screenshots/sites.png" width=500 /><tr></tr> |
|
Pangolin's site connectors provide gateways into networks so you can access any networked resources. Sites use outbound tunnels and intelligent NAT traversal to make networks behind restrictive firewalls available for authorized access without public IPs or open ports. Easily deploy a site as a binary or container on any platform.
|
||||||
| **Browser-based reverse proxy access**<br /><br />Expose web applications through identity and context-aware tunneled reverse proxies. Pangolin handles routing, load balancing, health checking, and automatic SSL certificates without exposing your network directly to the internet. Users access applications through any web browser with authentication and granular access control. | <img src="public/clip.gif" width=500 /><tr></tr> |
|
|
||||||
| **Client-based private resource access**<br /><br />Access private resources like SSH servers, databases, RDP, and entire network ranges through Pangolin clients. Intelligent NAT traversal enables connections even through restrictive firewalls, while DNS aliases provide friendly names and fast connections to resources across all your sites. | <img src="public/screenshots/private-resources.png" width=500 /><tr></tr> |
|
<img src="public/screenshots/sites.png" alt="Sites" width="100%" />
|
||||||
| **Zero-trust granular access**<br /><br />Grant users access to specific resources, not entire networks. Unlike traditional VPNs that expose full network access, Pangolin's zero-trust model ensures users can only reach the applications and services you explicitly define, reducing security risk and attack surface. | <img src="public/screenshots/user-devices.png" width=500 /><tr></tr> |
|
|
||||||
|
### Browser-based reverse proxy access
|
||||||
|
|
||||||
|
Expose web applications through identity and context-aware tunneled reverse proxies. Users access applications through any web browser with authentication and granular access control without installing a client. Pangolin handles routing, load balancing, health checking, and automatic SSL certificates without exposing your network directly to the internet.
|
||||||
|
|
||||||
|
<img src="public/clip.gif" alt="Reverse proxy access" width="100%" />
|
||||||
|
|
||||||
|
### Client-based private resource access
|
||||||
|
|
||||||
|
Access private resources like SSH servers, databases, RDP, and entire network ranges through Pangolin clients. Intelligent NAT traversal enables connections even through restrictive firewalls, while DNS aliases provide friendly names and fast connections to resources across all your sites. Add redundancy by routing traffic through multiple connectors in your network.
|
||||||
|
|
||||||
|
<img src="public/screenshots/private-resources.png" alt="Private resources" width="100%" />
|
||||||
|
|
||||||
|
### Give users and roles access to resources
|
||||||
|
|
||||||
|
Use Pangolin's built in users or bring your own identity provider and set up role based access control (RBAC). Grant users access to specific resources, not entire networks. Unlike traditional VPNs that expose full network access, Pangolin's zero-trust model ensures users can only reach the applications, services, and routes you explicitly define.
|
||||||
|
|
||||||
|
<img src="public/screenshots/users.png" alt="Users from identity provider with roles" width="100%" />
|
||||||
|
|
||||||
## Download Clients
|
## Download Clients
|
||||||
|
|
||||||
@@ -85,17 +95,16 @@ Download the Pangolin client for your platform:
|
|||||||
|
|
||||||
## Get Started
|
## Get Started
|
||||||
|
|
||||||
|
### Sign up now
|
||||||
|
|
||||||
|
Create a free account at [app.pangolin.net](https://app.pangolin.net) to get started with Pangolin Cloud.
|
||||||
|
|
||||||
### Check out the docs
|
### Check out the docs
|
||||||
|
|
||||||
We encourage everyone to read the full documentation first, which is
|
We encourage everyone to read the full documentation first, which is
|
||||||
available at [docs.pangolin.net](https://docs.pangolin.net). This README provides only a very brief subset of
|
available at [docs.pangolin.net](https://docs.pangolin.net). This README provides only a very brief subset of
|
||||||
the docs to illustrate some basic ideas.
|
the docs to illustrate some basic ideas.
|
||||||
|
|
||||||
### Sign up and try now
|
|
||||||
|
|
||||||
For Pangolin's managed service, you will first need to create an account at
|
|
||||||
[app.pangolin.net](https://app.pangolin.net). We have a generous free tier to get started.
|
|
||||||
|
|
||||||
## Licensing
|
## Licensing
|
||||||
|
|
||||||
Pangolin is dual licensed under the AGPL-3 and the [Fossorial Commercial License](https://pangolin.net/fcl.html). For inquiries about commercial licensing, please contact us at [contact@pangolin.net](mailto:contact@pangolin.net).
|
Pangolin is dual licensed under the AGPL-3 and the [Fossorial Commercial License](https://pangolin.net/fcl.html). For inquiries about commercial licensing, please contact us at [contact@pangolin.net](mailto:contact@pangolin.net).
|
||||||
@@ -103,7 +112,3 @@ Pangolin is dual licensed under the AGPL-3 and the [Fossorial Commercial License
|
|||||||
## Contributions
|
## Contributions
|
||||||
|
|
||||||
Please see [CONTRIBUTING](./CONTRIBUTING.md) in the repository for guidelines and best practices.
|
Please see [CONTRIBUTING](./CONTRIBUTING.md) in the repository for guidelines and best practices.
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
WireGuard® is a registered trademark of Jason A. Donenfeld.
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
If you discover a security vulnerability, please follow the steps below to responsibly disclose it to us:
|
If you discover a security vulnerability, please follow the steps below to responsibly disclose it to us:
|
||||||
|
|
||||||
1. **Do not create a public GitHub issue or discussion post.** This could put the security of other users at risk.
|
1. **Do not create a public GitHub issue or discussion post.** This could put the security of other users at risk.
|
||||||
2. Send a detailed report to [security@pangolin.net](mailto:security@pangolin.net) or send a **private** message to a maintainer on [Discord](https://discord.gg/HCJR8Xhme4). Include:
|
2. Send a detailed report to [security@pangolin.net](mailto:security@pangolin.net) with the following information:
|
||||||
|
|
||||||
- Description and location of the vulnerability.
|
- Description and location of the vulnerability.
|
||||||
- Potential impact of the vulnerability.
|
- Potential impact of the vulnerability.
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Create API Key
|
|
||||||
type: http
|
|
||||||
seq: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
put {
|
|
||||||
url: http://localhost:3000/api/v1/api-key
|
|
||||||
body: json
|
|
||||||
auth: inherit
|
|
||||||
}
|
|
||||||
|
|
||||||
body:json {
|
|
||||||
{
|
|
||||||
"isRoot": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Delete API Key
|
|
||||||
type: http
|
|
||||||
seq: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
delete {
|
|
||||||
url: http://localhost:3000/api/v1/api-key/dm47aacqxxn3ubj
|
|
||||||
body: none
|
|
||||||
auth: inherit
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: List API Key Actions
|
|
||||||
type: http
|
|
||||||
seq: 6
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: http://localhost:3000/api/v1/api-key/ex0izu2c37fjz9x/actions
|
|
||||||
body: none
|
|
||||||
auth: inherit
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: List Org API Keys
|
|
||||||
type: http
|
|
||||||
seq: 4
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: http://localhost:3000/api/v1/org/home-lab/api-keys
|
|
||||||
body: none
|
|
||||||
auth: inherit
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: List Root API Keys
|
|
||||||
type: http
|
|
||||||
seq: 3
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: http://localhost:3000/api/v1/root/api-keys
|
|
||||||
body: none
|
|
||||||
auth: inherit
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Set API Key Actions
|
|
||||||
type: http
|
|
||||||
seq: 5
|
|
||||||
}
|
|
||||||
|
|
||||||
post {
|
|
||||||
url: http://localhost:3000/api/v1/api-key/ex0izu2c37fjz9x/actions
|
|
||||||
body: json
|
|
||||||
auth: inherit
|
|
||||||
}
|
|
||||||
|
|
||||||
body:json {
|
|
||||||
{
|
|
||||||
"actionIds": ["listSites"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Set API Key Orgs
|
|
||||||
type: http
|
|
||||||
seq: 7
|
|
||||||
}
|
|
||||||
|
|
||||||
post {
|
|
||||||
url: http://localhost:3000/api/v1/api-key/ex0izu2c37fjz9x/orgs
|
|
||||||
body: json
|
|
||||||
auth: inherit
|
|
||||||
}
|
|
||||||
|
|
||||||
body:json {
|
|
||||||
{
|
|
||||||
"orgIds": ["home-lab"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: API Keys
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: 2fa-disable
|
|
||||||
type: http
|
|
||||||
seq: 6
|
|
||||||
}
|
|
||||||
|
|
||||||
post {
|
|
||||||
url: http://localhost:3000/api/v1/auth/2fa/disable
|
|
||||||
body: json
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
body:json {
|
|
||||||
{
|
|
||||||
"password": "aaaaa-1A",
|
|
||||||
"code": "377289"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: 2fa-enable
|
|
||||||
type: http
|
|
||||||
seq: 4
|
|
||||||
}
|
|
||||||
|
|
||||||
post {
|
|
||||||
url: http://localhost:3000/api/v1/auth/2fa/enable
|
|
||||||
body: json
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
body:json {
|
|
||||||
{
|
|
||||||
"code": "374138"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: 2fa-request
|
|
||||||
type: http
|
|
||||||
seq: 5
|
|
||||||
}
|
|
||||||
|
|
||||||
post {
|
|
||||||
url: http://localhost:3000/api/v1/auth/2fa/request
|
|
||||||
body: json
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
body:json {
|
|
||||||
{
|
|
||||||
"password": "aaaaa-1A"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: change-password
|
|
||||||
type: http
|
|
||||||
seq: 9
|
|
||||||
}
|
|
||||||
|
|
||||||
post {
|
|
||||||
url: http://localhost:3000/api/v1/auth/change-password
|
|
||||||
body: json
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
body:json {
|
|
||||||
{
|
|
||||||
"oldPassword": "",
|
|
||||||
"newPassword": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: login
|
|
||||||
type: http
|
|
||||||
seq: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
post {
|
|
||||||
url: http://localhost:3000/api/v1/auth/login
|
|
||||||
body: json
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
body:json {
|
|
||||||
{
|
|
||||||
"email": "admin@fosrl.io",
|
|
||||||
"password": "Password123!"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: logout
|
|
||||||
type: http
|
|
||||||
seq: 3
|
|
||||||
}
|
|
||||||
|
|
||||||
post {
|
|
||||||
url: http://localhost:4000/api/v1/auth/logout
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: reset-password-request
|
|
||||||
type: http
|
|
||||||
seq: 10
|
|
||||||
}
|
|
||||||
|
|
||||||
post {
|
|
||||||
url: http://localhost:3000/api/v1/auth/reset-password/request
|
|
||||||
body: json
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
body:json {
|
|
||||||
{
|
|
||||||
"email": "milo@pangolin.net"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: reset-password
|
|
||||||
type: http
|
|
||||||
seq: 11
|
|
||||||
}
|
|
||||||
|
|
||||||
post {
|
|
||||||
url: http://localhost:3000/api/v1/auth/reset-password
|
|
||||||
body: json
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
body:json {
|
|
||||||
{
|
|
||||||
"token": "3uhsbom72dwdhboctwrtntyd6jrlg4jtf5oaxy4k",
|
|
||||||
"newPassword": "aaaaa-1A",
|
|
||||||
"code": "6irqCGR3"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: signup
|
|
||||||
type: http
|
|
||||||
seq: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
put {
|
|
||||||
url: http://localhost:3000/api/v1/auth/signup
|
|
||||||
body: json
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
body:json {
|
|
||||||
{
|
|
||||||
"email": "numbat@pangolin.net",
|
|
||||||
"password": "Password123!"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: verify-email-request
|
|
||||||
type: http
|
|
||||||
seq: 8
|
|
||||||
}
|
|
||||||
|
|
||||||
post {
|
|
||||||
url: http://localhost:3000/api/v1/auth/verify-email/request
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: verify-email
|
|
||||||
type: http
|
|
||||||
seq: 7
|
|
||||||
}
|
|
||||||
|
|
||||||
post {
|
|
||||||
url: http://localhost:3000/api/v1/auth/verify-email
|
|
||||||
body: json
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
body:json {
|
|
||||||
{
|
|
||||||
"code": "50317187"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: verify-user
|
|
||||||
type: http
|
|
||||||
seq: 4
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: http://localhost:3001/api/v1/badger/verify-user?sessionId=mb52273jkb6t3oys2bx6ur5x7rcrkl26c7warg3e
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
params:query {
|
|
||||||
sessionId: mb52273jkb6t3oys2bx6ur5x7rcrkl26c7warg3e
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: createClient
|
|
||||||
type: http
|
|
||||||
seq: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
put {
|
|
||||||
url: http://localhost:3000/api/v1/site/1/client
|
|
||||||
body: json
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
body:json {
|
|
||||||
{
|
|
||||||
"siteId": 1,
|
|
||||||
"name": "test",
|
|
||||||
"type": "olm",
|
|
||||||
"subnet": "100.90.129.4/30",
|
|
||||||
"olmId": "029yzunhx6nh3y5",
|
|
||||||
"secret": "l0ymp075y3d4rccb25l6sqpgar52k09etunui970qq5gj7x6"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: pickClientDefaults
|
|
||||||
type: http
|
|
||||||
seq: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: http://localhost:3000/api/v1/site/1/pick-client-defaults
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Create OIDC Provider
|
|
||||||
type: http
|
|
||||||
seq: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
put {
|
|
||||||
url: http://localhost:3000/api/v1/org/home-lab/idp/oidc
|
|
||||||
body: json
|
|
||||||
auth: inherit
|
|
||||||
}
|
|
||||||
|
|
||||||
body:json {
|
|
||||||
{
|
|
||||||
"clientId": "JJoSvHCZcxnXT2sn6CObj6a21MuKNRXs3kN5wbys",
|
|
||||||
"clientSecret": "2SlGL2wOGgMEWLI9yUuMAeFxre7qSNJVnXMzyepdNzH1qlxYnC4lKhhQ6a157YQEkYH3vm40KK4RCqbYiF8QIweuPGagPX3oGxEj2exwutoXFfOhtq4hHybQKoFq01Z3",
|
|
||||||
"authUrl": "http://localhost:9000/application/o/authorize/",
|
|
||||||
"tokenUrl": "http://localhost:9000/application/o/token/",
|
|
||||||
"scopes": ["email", "openid", "profile"],
|
|
||||||
"userIdentifier": "email"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Generate OIDC URL
|
|
||||||
type: http
|
|
||||||
seq: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: http://localhost:3000/api/v1
|
|
||||||
body: none
|
|
||||||
auth: inherit
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: IDP
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Traefik Config
|
|
||||||
type: http
|
|
||||||
seq: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: http://localhost:3001/api/v1/traefik-config
|
|
||||||
body: none
|
|
||||||
auth: inherit
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Internal
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Create Newt
|
|
||||||
type: http
|
|
||||||
seq: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: http://localhost:3000/api/v1/newt
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Get Token
|
|
||||||
type: http
|
|
||||||
seq: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: http://localhost:3000/api/v1/auth/newt/get-token
|
|
||||||
body: json
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
body:json {
|
|
||||||
{
|
|
||||||
"newtId": "o0d4rdxq3stnz7b",
|
|
||||||
"secret": "sy7l09fnaesd03iwrfp9m3qf0ryn19g0zf3dqieaazb4k7vk"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: createOlm
|
|
||||||
type: http
|
|
||||||
seq: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
put {
|
|
||||||
url: http://localhost:3000/api/v1/olm
|
|
||||||
body: none
|
|
||||||
auth: inherit
|
|
||||||
}
|
|
||||||
|
|
||||||
settings {
|
|
||||||
encodeUrl: true
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Olm
|
|
||||||
seq: 15
|
|
||||||
}
|
|
||||||
|
|
||||||
auth {
|
|
||||||
mode: inherit
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Check Id
|
|
||||||
type: http
|
|
||||||
seq: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: http://localhost:3000/api/v1/org/checkId
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: listOrgs
|
|
||||||
type: http
|
|
||||||
seq: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url:
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: createRemoteExitNode
|
|
||||||
type: http
|
|
||||||
seq: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
put {
|
|
||||||
url: http://localhost:4000/api/v1/org/org_i21aifypnlyxur2/remote-exit-node
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: listResourcesByOrg
|
|
||||||
type: http
|
|
||||||
seq: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url:
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: listResourcesBySite
|
|
||||||
type: http
|
|
||||||
seq: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: http://localhost:3000/api/v1/site/1/resources?limit=10&offset=0
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
params:query {
|
|
||||||
limit: 10
|
|
||||||
offset: 0
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Get Site
|
|
||||||
type: http
|
|
||||||
seq: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: http://localhost:3000/api/v1/org/test/sites/mexican-mole-lizard-windy
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: listSites
|
|
||||||
type: http
|
|
||||||
seq: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url:
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: listTargets
|
|
||||||
type: http
|
|
||||||
seq: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: http://localhost:3000/api/v1/resource/web.main.localhost/targets?limit=10&offset=0
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
|
|
||||||
params:query {
|
|
||||||
limit: 10
|
|
||||||
offset: 0
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: Test
|
|
||||||
type: http
|
|
||||||
seq: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: http://localhost:3000/api/v1
|
|
||||||
body: none
|
|
||||||
auth: inherit
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: traefik-config
|
|
||||||
type: http
|
|
||||||
seq: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: http://localhost:3001/api/v1/traefik-config
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: adminListUsers
|
|
||||||
type: http
|
|
||||||
seq: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url: http://localhost:3000/api/v1/users
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: adminRemoveUser
|
|
||||||
type: http
|
|
||||||
seq: 3
|
|
||||||
}
|
|
||||||
|
|
||||||
delete {
|
|
||||||
url: http://localhost:3000/api/v1/user/ky5r7ivqs8wc7u4
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
meta {
|
|
||||||
name: getUser
|
|
||||||
type: http
|
|
||||||
seq: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
get {
|
|
||||||
url:
|
|
||||||
body: none
|
|
||||||
auth: none
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "1",
|
|
||||||
"name": "Pangolin",
|
|
||||||
"type": "collection",
|
|
||||||
"ignore": [
|
|
||||||
"node_modules",
|
|
||||||
".git"
|
|
||||||
],
|
|
||||||
"presets": {
|
|
||||||
"requestType": "http",
|
|
||||||
"requestUrl": "http://localhost:3000/api/v1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
121
cli/commands/generateOrgCaKeys.ts
Normal file
121
cli/commands/generateOrgCaKeys.ts
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import { CommandModule } from "yargs";
|
||||||
|
import { db, orgs } from "@server/db";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { encrypt } from "@server/lib/crypto";
|
||||||
|
import { configFilePath1, configFilePath2 } from "@server/lib/consts";
|
||||||
|
import { generateCA } from "@server/lib/sshCA";
|
||||||
|
import fs from "fs";
|
||||||
|
import yaml from "js-yaml";
|
||||||
|
|
||||||
|
type GenerateOrgCaKeysArgs = {
|
||||||
|
orgId: string;
|
||||||
|
secret?: string;
|
||||||
|
force?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateOrgCaKeys: CommandModule<{}, GenerateOrgCaKeysArgs> = {
|
||||||
|
command: "generate-org-ca-keys",
|
||||||
|
describe:
|
||||||
|
"Generate SSH CA public/private key pair for an organization and store them in the database (private key encrypted with server secret)",
|
||||||
|
builder: (yargs) => {
|
||||||
|
return yargs
|
||||||
|
.option("orgId", {
|
||||||
|
type: "string",
|
||||||
|
demandOption: true,
|
||||||
|
describe: "The organization ID"
|
||||||
|
})
|
||||||
|
.option("secret", {
|
||||||
|
type: "string",
|
||||||
|
describe:
|
||||||
|
"Server secret used to encrypt the CA private key. If omitted, read from config file (config.yml or config.yaml)."
|
||||||
|
})
|
||||||
|
.option("force", {
|
||||||
|
type: "boolean",
|
||||||
|
default: false,
|
||||||
|
describe:
|
||||||
|
"Overwrite existing CA keys for the org if they already exist"
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handler: async (argv: {
|
||||||
|
orgId: string;
|
||||||
|
secret?: string;
|
||||||
|
force?: boolean;
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
const { orgId, force } = argv;
|
||||||
|
let secret = argv.secret;
|
||||||
|
|
||||||
|
if (!secret) {
|
||||||
|
const configPath = fs.existsSync(configFilePath1)
|
||||||
|
? configFilePath1
|
||||||
|
: fs.existsSync(configFilePath2)
|
||||||
|
? configFilePath2
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!configPath) {
|
||||||
|
console.error(
|
||||||
|
"Error: No server secret provided and config file not found. " +
|
||||||
|
"Expected config.yml or config.yaml in the config directory, or pass --secret."
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const configContent = fs.readFileSync(configPath, "utf8");
|
||||||
|
const config = yaml.load(configContent) as {
|
||||||
|
server?: { secret?: string };
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!config?.server?.secret) {
|
||||||
|
console.error(
|
||||||
|
"Error: No server.secret in config file. Pass --secret or set server.secret in config."
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
secret = config.server.secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [org] = await db
|
||||||
|
.select({
|
||||||
|
orgId: orgs.orgId,
|
||||||
|
sshCaPrivateKey: orgs.sshCaPrivateKey,
|
||||||
|
sshCaPublicKey: orgs.sshCaPublicKey
|
||||||
|
})
|
||||||
|
.from(orgs)
|
||||||
|
.where(eq(orgs.orgId, orgId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!org) {
|
||||||
|
console.error(`Error: Organization with orgId "${orgId}" not found.`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (org.sshCaPrivateKey != null || org.sshCaPublicKey != null) {
|
||||||
|
if (!force) {
|
||||||
|
console.error(
|
||||||
|
"Error: This organization already has CA keys. Use --force to overwrite."
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ca = generateCA(`pangolin-ssh-ca-${orgId}`);
|
||||||
|
const encryptedPrivateKey = encrypt(ca.privateKeyPem, secret);
|
||||||
|
|
||||||
|
await db
|
||||||
|
.update(orgs)
|
||||||
|
.set({
|
||||||
|
sshCaPrivateKey: encryptedPrivateKey,
|
||||||
|
sshCaPublicKey: ca.publicKeyOpenSSH
|
||||||
|
})
|
||||||
|
.where(eq(orgs.orgId, orgId));
|
||||||
|
|
||||||
|
console.log("SSH CA keys generated and stored for org:", orgId);
|
||||||
|
console.log("\nPublic key (OpenSSH format):");
|
||||||
|
console.log(ca.publicKeyOpenSSH);
|
||||||
|
process.exit(0);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error generating org CA keys:", error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { CommandModule } from "yargs";
|
import { CommandModule } from "yargs";
|
||||||
import { db, idpOidcConfig, licenseKey } from "@server/db";
|
import { db, idpOidcConfig, licenseKey, certificates, eventStreamingDestinations, alertWebhookActions } from "@server/db";
|
||||||
import { encrypt, decrypt } from "@server/lib/crypto";
|
import { encrypt, decrypt } from "@server/lib/crypto";
|
||||||
import { configFilePath1, configFilePath2 } from "@server/lib/consts";
|
import { configFilePath1, configFilePath2 } from "@server/lib/consts";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
@@ -129,9 +129,15 @@ export const rotateServerSecret: CommandModule<
|
|||||||
console.log("\nReading encrypted data from database...");
|
console.log("\nReading encrypted data from database...");
|
||||||
const idpConfigs = await db.select().from(idpOidcConfig);
|
const idpConfigs = await db.select().from(idpOidcConfig);
|
||||||
const licenseKeys = await db.select().from(licenseKey);
|
const licenseKeys = await db.select().from(licenseKey);
|
||||||
|
const certs = await db.select().from(certificates);
|
||||||
|
const streamingDestinations = await db.select().from(eventStreamingDestinations);
|
||||||
|
const webhookActions = await db.select().from(alertWebhookActions);
|
||||||
|
|
||||||
console.log(`Found ${idpConfigs.length} OIDC IdP configuration(s)`);
|
console.log(`Found ${idpConfigs.length} OIDC IdP configuration(s)`);
|
||||||
console.log(`Found ${licenseKeys.length} license key(s)`);
|
console.log(`Found ${licenseKeys.length} license key(s)`);
|
||||||
|
console.log(`Found ${certs.length} certificate(s)`);
|
||||||
|
console.log(`Found ${streamingDestinations.length} event streaming destination(s)`);
|
||||||
|
console.log(`Found ${webhookActions.length} alert webhook action(s)`);
|
||||||
|
|
||||||
// Prepare all decrypted and re-encrypted values
|
// Prepare all decrypted and re-encrypted values
|
||||||
console.log("\nDecrypting and re-encrypting values...");
|
console.log("\nDecrypting and re-encrypting values...");
|
||||||
@@ -149,8 +155,27 @@ export const rotateServerSecret: CommandModule<
|
|||||||
encryptedInstanceId: string;
|
encryptedInstanceId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type CertUpdate = {
|
||||||
|
certId: number;
|
||||||
|
encryptedCertFile: string | null;
|
||||||
|
encryptedKeyFile: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StreamingDestinationUpdate = {
|
||||||
|
destinationId: number;
|
||||||
|
encryptedConfig: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type WebhookActionUpdate = {
|
||||||
|
webhookActionId: number;
|
||||||
|
encryptedConfig: string;
|
||||||
|
};
|
||||||
|
|
||||||
const idpUpdates: IdpUpdate[] = [];
|
const idpUpdates: IdpUpdate[] = [];
|
||||||
const licenseKeyUpdates: LicenseKeyUpdate[] = [];
|
const licenseKeyUpdates: LicenseKeyUpdate[] = [];
|
||||||
|
const certUpdates: CertUpdate[] = [];
|
||||||
|
const streamingDestinationUpdates: StreamingDestinationUpdate[] = [];
|
||||||
|
const webhookActionUpdates: WebhookActionUpdate[] = [];
|
||||||
|
|
||||||
// Process idpOidcConfig entries
|
// Process idpOidcConfig entries
|
||||||
for (const idpConfig of idpConfigs) {
|
for (const idpConfig of idpConfigs) {
|
||||||
@@ -217,6 +242,70 @@ export const rotateServerSecret: CommandModule<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process certificate entries
|
||||||
|
for (const cert of certs) {
|
||||||
|
try {
|
||||||
|
const encryptedCertFile = cert.certFile
|
||||||
|
? encrypt(decrypt(cert.certFile, oldSecret), newSecret)
|
||||||
|
: null;
|
||||||
|
const encryptedKeyFile = cert.keyFile
|
||||||
|
? encrypt(decrypt(cert.keyFile, oldSecret), newSecret)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
certUpdates.push({
|
||||||
|
certId: cert.certId,
|
||||||
|
encryptedCertFile,
|
||||||
|
encryptedKeyFile
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
`Error processing certificate ${cert.certId} (${cert.domain}):`,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process eventStreamingDestinations entries
|
||||||
|
for (const dest of streamingDestinations) {
|
||||||
|
try {
|
||||||
|
const decryptedConfig = decrypt(dest.config, oldSecret);
|
||||||
|
const encryptedConfig = encrypt(decryptedConfig, newSecret);
|
||||||
|
|
||||||
|
streamingDestinationUpdates.push({
|
||||||
|
destinationId: dest.destinationId,
|
||||||
|
encryptedConfig
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
`Error processing event streaming destination ${dest.destinationId}:`,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process alertWebhookActions entries
|
||||||
|
for (const webhook of webhookActions) {
|
||||||
|
try {
|
||||||
|
if (webhook.config == null) continue;
|
||||||
|
|
||||||
|
const decryptedConfig = decrypt(webhook.config, oldSecret);
|
||||||
|
const encryptedConfig = encrypt(decryptedConfig, newSecret);
|
||||||
|
|
||||||
|
webhookActionUpdates.push({
|
||||||
|
webhookActionId: webhook.webhookActionId,
|
||||||
|
encryptedConfig
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
`Error processing alert webhook action ${webhook.webhookActionId}:`,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Perform all database updates in a single transaction
|
// Perform all database updates in a single transaction
|
||||||
console.log("\nUpdating database in transaction...");
|
console.log("\nUpdating database in transaction...");
|
||||||
await db.transaction(async (trx) => {
|
await db.transaction(async (trx) => {
|
||||||
@@ -250,10 +339,50 @@ export const rotateServerSecret: CommandModule<
|
|||||||
instanceId: update.encryptedInstanceId
|
instanceId: update.encryptedInstanceId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update certificate entries
|
||||||
|
for (const update of certUpdates) {
|
||||||
|
await trx
|
||||||
|
.update(certificates)
|
||||||
|
.set({
|
||||||
|
certFile: update.encryptedCertFile,
|
||||||
|
keyFile: update.encryptedKeyFile
|
||||||
|
})
|
||||||
|
.where(eq(certificates.certId, update.certId));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update event streaming destination entries
|
||||||
|
for (const update of streamingDestinationUpdates) {
|
||||||
|
await trx
|
||||||
|
.update(eventStreamingDestinations)
|
||||||
|
.set({ config: update.encryptedConfig })
|
||||||
|
.where(
|
||||||
|
eq(
|
||||||
|
eventStreamingDestinations.destinationId,
|
||||||
|
update.destinationId
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update alert webhook action entries
|
||||||
|
for (const update of webhookActionUpdates) {
|
||||||
|
await trx
|
||||||
|
.update(alertWebhookActions)
|
||||||
|
.set({ config: update.encryptedConfig })
|
||||||
|
.where(
|
||||||
|
eq(
|
||||||
|
alertWebhookActions.webhookActionId,
|
||||||
|
update.webhookActionId
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`Rotated ${idpUpdates.length} OIDC IdP configuration(s)`);
|
console.log(`Rotated ${idpUpdates.length} OIDC IdP configuration(s)`);
|
||||||
console.log(`Rotated ${licenseKeyUpdates.length} license key(s)`);
|
console.log(`Rotated ${licenseKeyUpdates.length} license key(s)`);
|
||||||
|
console.log(`Rotated ${certUpdates.length} certificate(s)`);
|
||||||
|
console.log(`Rotated ${streamingDestinationUpdates.length} event streaming destination(s)`);
|
||||||
|
console.log(`Rotated ${webhookActionUpdates.length} alert webhook action(s)`);
|
||||||
|
|
||||||
// Update config file with new secret
|
// Update config file with new secret
|
||||||
console.log("\nUpdating config file...");
|
console.log("\nUpdating config file...");
|
||||||
@@ -270,6 +399,9 @@ export const rotateServerSecret: CommandModule<
|
|||||||
console.log(`\nSummary:`);
|
console.log(`\nSummary:`);
|
||||||
console.log(` - OIDC IdP configurations: ${idpUpdates.length}`);
|
console.log(` - OIDC IdP configurations: ${idpUpdates.length}`);
|
||||||
console.log(` - License keys: ${licenseKeyUpdates.length}`);
|
console.log(` - License keys: ${licenseKeyUpdates.length}`);
|
||||||
|
console.log(` - Certificates: ${certUpdates.length}`);
|
||||||
|
console.log(` - Event streaming destinations: ${streamingDestinationUpdates.length}`);
|
||||||
|
console.log(` - Alert webhook actions: ${webhookActionUpdates.length}`);
|
||||||
console.log(
|
console.log(
|
||||||
`\n IMPORTANT: Restart the server for the new secret to take effect.`
|
`\n IMPORTANT: Restart the server for the new secret to take effect.`
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { clearExitNodes } from "./commands/clearExitNodes";
|
|||||||
import { rotateServerSecret } from "./commands/rotateServerSecret";
|
import { rotateServerSecret } from "./commands/rotateServerSecret";
|
||||||
import { clearLicenseKeys } from "./commands/clearLicenseKeys";
|
import { clearLicenseKeys } from "./commands/clearLicenseKeys";
|
||||||
import { deleteClient } from "./commands/deleteClient";
|
import { deleteClient } from "./commands/deleteClient";
|
||||||
|
import { generateOrgCaKeys } from "./commands/generateOrgCaKeys";
|
||||||
|
|
||||||
yargs(hideBin(process.argv))
|
yargs(hideBin(process.argv))
|
||||||
.scriptName("pangctl")
|
.scriptName("pangctl")
|
||||||
@@ -17,5 +18,6 @@ yargs(hideBin(process.argv))
|
|||||||
.command(rotateServerSecret)
|
.command(rotateServerSecret)
|
||||||
.command(clearLicenseKeys)
|
.command(clearLicenseKeys)
|
||||||
.command(deleteClient)
|
.command(deleteClient)
|
||||||
|
.command(generateOrgCaKeys)
|
||||||
.demandCommand()
|
.demandCommand()
|
||||||
.help().argv;
|
.help().argv;
|
||||||
|
|||||||
1
config/db/.gitignore
vendored
Normal file
1
config/db/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*-journal
|
||||||
@@ -4,6 +4,12 @@ services:
|
|||||||
image: fosrl/pangolin:latest
|
image: fosrl/pangolin:latest
|
||||||
container_name: pangolin
|
container_name: pangolin
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 1g
|
||||||
|
reservations:
|
||||||
|
memory: 256m
|
||||||
volumes:
|
volumes:
|
||||||
- ./config:/app/config
|
- ./config:/app/config
|
||||||
healthcheck:
|
healthcheck:
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ services:
|
|||||||
POSTGRES_DB: postgres # Default database name
|
POSTGRES_DB: postgres # Default database name
|
||||||
POSTGRES_USER: postgres # Default user
|
POSTGRES_USER: postgres # Default user
|
||||||
POSTGRES_PASSWORD: password # Default password (change for production!)
|
POSTGRES_PASSWORD: password # Default password (change for production!)
|
||||||
# volumes:
|
volumes:
|
||||||
# - ./config/postgres:/var/lib/postgresql/data
|
- ./config/postgres:/var/lib/postgresql/data
|
||||||
ports:
|
ports:
|
||||||
- "5432:5432" # Map host port 5432 to container port 5432
|
- "5432:5432" # Map host port 5432 to container port 5432
|
||||||
restart: no
|
restart: no
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
import { defineConfig } from "drizzle-kit";
|
|
||||||
import path from "path";
|
|
||||||
|
|
||||||
const schema = [path.join("server", "db", "pg", "schema")];
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
dialect: "postgresql",
|
|
||||||
schema: schema,
|
|
||||||
out: path.join("server", "migrations"),
|
|
||||||
verbose: true,
|
|
||||||
dbCredentials: {
|
|
||||||
url: process.env.DATABASE_URL as string
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -281,7 +281,7 @@ esbuild
|
|||||||
})
|
})
|
||||||
],
|
],
|
||||||
sourcemap: "inline",
|
sourcemap: "inline",
|
||||||
target: "node22"
|
target: "node24"
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
// Check if there were any errors in the build result
|
// Check if there were any errors in the build result
|
||||||
|
|||||||
@@ -1,41 +1,24 @@
|
|||||||
all: update-versions go-build-release put-back
|
all: go-build-release
|
||||||
dev-all: dev-update-versions dev-build dev-clean
|
|
||||||
|
# Build with version injection via ldflags
|
||||||
|
# Versions can be passed via: make go-build-release PANGOLIN_VERSION=x.x.x GERBIL_VERSION=x.x.x BADGER_VERSION=x.x.x
|
||||||
|
# Or fetched automatically if not provided (requires curl and jq)
|
||||||
|
|
||||||
|
PANGOLIN_VERSION ?= $(shell curl -s https://api.github.com/repos/fosrl/pangolin/tags | jq -r '.[0].name')
|
||||||
|
GERBIL_VERSION ?= $(shell curl -s https://api.github.com/repos/fosrl/gerbil/tags | jq -r '.[0].name')
|
||||||
|
BADGER_VERSION ?= $(shell curl -s https://api.github.com/repos/fosrl/badger/tags | jq -r '.[0].name')
|
||||||
|
|
||||||
|
LDFLAGS = -X main.pangolinVersion=$(PANGOLIN_VERSION) \
|
||||||
|
-X main.gerbilVersion=$(GERBIL_VERSION) \
|
||||||
|
-X main.badgerVersion=$(BADGER_VERSION)
|
||||||
|
|
||||||
go-build-release:
|
go-build-release:
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/installer_linux_amd64
|
@echo "Building with versions - Pangolin: $(PANGOLIN_VERSION), Gerbil: $(GERBIL_VERSION), Badger: $(BADGER_VERSION)"
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o bin/installer_linux_arm64
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o bin/installer_linux_amd64
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o bin/installer_linux_arm64
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f bin/installer_linux_amd64
|
rm -f bin/installer_linux_amd64
|
||||||
rm -f bin/installer_linux_arm64
|
rm -f bin/installer_linux_arm64
|
||||||
|
|
||||||
update-versions:
|
.PHONY: all go-build-release clean
|
||||||
@echo "Fetching latest versions..."
|
|
||||||
cp main.go main.go.bak && \
|
|
||||||
$(MAKE) dev-update-versions
|
|
||||||
|
|
||||||
put-back:
|
|
||||||
mv main.go.bak main.go
|
|
||||||
|
|
||||||
dev-update-versions:
|
|
||||||
if [ -z "$(tag)" ]; then \
|
|
||||||
PANGOLIN_VERSION=$$(curl -s https://api.github.com/repos/fosrl/pangolin/tags | jq -r '.[0].name'); \
|
|
||||||
else \
|
|
||||||
PANGOLIN_VERSION=$(tag); \
|
|
||||||
fi && \
|
|
||||||
GERBIL_VERSION=$$(curl -s https://api.github.com/repos/fosrl/gerbil/tags | jq -r '.[0].name') && \
|
|
||||||
BADGER_VERSION=$$(curl -s https://api.github.com/repos/fosrl/badger/tags | jq -r '.[0].name') && \
|
|
||||||
echo "Latest versions - Pangolin: $$PANGOLIN_VERSION, Gerbil: $$GERBIL_VERSION, Badger: $$BADGER_VERSION" && \
|
|
||||||
sed -i "s/config.PangolinVersion = \".*\"/config.PangolinVersion = \"$$PANGOLIN_VERSION\"/" main.go && \
|
|
||||||
sed -i "s/config.GerbilVersion = \".*\"/config.GerbilVersion = \"$$GERBIL_VERSION\"/" main.go && \
|
|
||||||
sed -i "s/config.BadgerVersion = \".*\"/config.BadgerVersion = \"$$BADGER_VERSION\"/" main.go && \
|
|
||||||
echo "Updated main.go with latest versions"
|
|
||||||
|
|
||||||
dev-build: go-build-release
|
|
||||||
|
|
||||||
dev-clean:
|
|
||||||
@echo "Restoring version values ..."
|
|
||||||
sed -i "s/config.PangolinVersion = \".*\"/config.PangolinVersion = \"replaceme\"/" main.go && \
|
|
||||||
sed -i "s/config.GerbilVersion = \".*\"/config.GerbilVersion = \"replaceme\"/" main.go && \
|
|
||||||
sed -i "s/config.BadgerVersion = \".*\"/config.BadgerVersion = \"replaceme\"/" main.go
|
|
||||||
@echo "Restored version strings in main.go"
|
|
||||||
|
|||||||
@@ -99,11 +99,6 @@ func ReadAppConfig(configPath string) (*AppConfigValues, error) {
|
|||||||
return values, nil
|
return values, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// findPattern finds the start of a pattern in a string
|
|
||||||
func findPattern(s, pattern string) int {
|
|
||||||
return bytes.Index([]byte(s), []byte(pattern))
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyDockerService(sourceFile, destFile, serviceName string) error {
|
func copyDockerService(sourceFile, destFile, serviceName string) error {
|
||||||
// Read source file
|
// Read source file
|
||||||
sourceData, err := os.ReadFile(sourceFile)
|
sourceData, err := os.ReadFile(sourceFile)
|
||||||
@@ -118,19 +113,19 @@ func copyDockerService(sourceFile, destFile, serviceName string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse source Docker Compose YAML
|
// Parse source Docker Compose YAML
|
||||||
var sourceCompose map[string]interface{}
|
var sourceCompose map[string]any
|
||||||
if err := yaml.Unmarshal(sourceData, &sourceCompose); err != nil {
|
if err := yaml.Unmarshal(sourceData, &sourceCompose); err != nil {
|
||||||
return fmt.Errorf("error parsing source Docker Compose file: %w", err)
|
return fmt.Errorf("error parsing source Docker Compose file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse destination Docker Compose YAML
|
// Parse destination Docker Compose YAML
|
||||||
var destCompose map[string]interface{}
|
var destCompose map[string]any
|
||||||
if err := yaml.Unmarshal(destData, &destCompose); err != nil {
|
if err := yaml.Unmarshal(destData, &destCompose); err != nil {
|
||||||
return fmt.Errorf("error parsing destination Docker Compose file: %w", err)
|
return fmt.Errorf("error parsing destination Docker Compose file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get services section from source
|
// Get services section from source
|
||||||
sourceServices, ok := sourceCompose["services"].(map[string]interface{})
|
sourceServices, ok := sourceCompose["services"].(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("services section not found in source file or has invalid format")
|
return fmt.Errorf("services section not found in source file or has invalid format")
|
||||||
}
|
}
|
||||||
@@ -142,10 +137,10 @@ func copyDockerService(sourceFile, destFile, serviceName string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get or create services section in destination
|
// Get or create services section in destination
|
||||||
destServices, ok := destCompose["services"].(map[string]interface{})
|
destServices, ok := destCompose["services"].(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
// If services section doesn't exist, create it
|
// If services section doesn't exist, create it
|
||||||
destServices = make(map[string]interface{})
|
destServices = make(map[string]any)
|
||||||
destCompose["services"] = destServices
|
destCompose["services"] = destServices
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,17 +182,21 @@ func backupConfig() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func MarshalYAMLWithIndent(data interface{}, indent int) ([]byte, error) {
|
func MarshalYAMLWithIndent(data any, indent int) (resp []byte, err error) {
|
||||||
buffer := new(bytes.Buffer)
|
buffer := new(bytes.Buffer)
|
||||||
encoder := yaml.NewEncoder(buffer)
|
encoder := yaml.NewEncoder(buffer)
|
||||||
encoder.SetIndent(indent)
|
encoder.SetIndent(indent)
|
||||||
|
|
||||||
err := encoder.Encode(data)
|
if err := encoder.Encode(data); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer encoder.Close()
|
defer func() {
|
||||||
|
if cerr := encoder.Close(); cerr != nil && err == nil {
|
||||||
|
err = cerr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
return buffer.Bytes(), nil
|
return buffer.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,7 +208,7 @@ func replaceInFile(filepath, oldStr, newStr string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Replace the string
|
// Replace the string
|
||||||
newContent := strings.Replace(string(content), oldStr, newStr, -1)
|
newContent := strings.ReplaceAll(string(content), oldStr, newStr)
|
||||||
|
|
||||||
// Write the modified content back to the file
|
// Write the modified content back to the file
|
||||||
err = os.WriteFile(filepath, []byte(newContent), 0644)
|
err = os.WriteFile(filepath, []byte(newContent), 0644)
|
||||||
@@ -228,28 +227,28 @@ func CheckAndAddTraefikLogVolume(composePath string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse YAML into a generic map
|
// Parse YAML into a generic map
|
||||||
var compose map[string]interface{}
|
var compose map[string]any
|
||||||
if err := yaml.Unmarshal(data, &compose); err != nil {
|
if err := yaml.Unmarshal(data, &compose); err != nil {
|
||||||
return fmt.Errorf("error parsing compose file: %w", err)
|
return fmt.Errorf("error parsing compose file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get services section
|
// Get services section
|
||||||
services, ok := compose["services"].(map[string]interface{})
|
services, ok := compose["services"].(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("services section not found or invalid")
|
return fmt.Errorf("services section not found or invalid")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get traefik service
|
// Get traefik service
|
||||||
traefik, ok := services["traefik"].(map[string]interface{})
|
traefik, ok := services["traefik"].(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("traefik service not found or invalid")
|
return fmt.Errorf("traefik service not found or invalid")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check volumes
|
// Check volumes
|
||||||
logVolume := "./config/traefik/logs:/var/log/traefik"
|
logVolume := "./config/traefik/logs:/var/log/traefik"
|
||||||
var volumes []interface{}
|
var volumes []any
|
||||||
|
|
||||||
if existingVolumes, ok := traefik["volumes"].([]interface{}); ok {
|
if existingVolumes, ok := traefik["volumes"].([]any); ok {
|
||||||
// Check if volume already exists
|
// Check if volume already exists
|
||||||
for _, v := range existingVolumes {
|
for _, v := range existingVolumes {
|
||||||
if v.(string) == logVolume {
|
if v.(string) == logVolume {
|
||||||
@@ -295,13 +294,13 @@ func MergeYAML(baseFile, overlayFile string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse base YAML into a map
|
// Parse base YAML into a map
|
||||||
var baseMap map[string]interface{}
|
var baseMap map[string]any
|
||||||
if err := yaml.Unmarshal(baseContent, &baseMap); err != nil {
|
if err := yaml.Unmarshal(baseContent, &baseMap); err != nil {
|
||||||
return fmt.Errorf("error parsing base YAML: %v", err)
|
return fmt.Errorf("error parsing base YAML: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse overlay YAML into a map
|
// Parse overlay YAML into a map
|
||||||
var overlayMap map[string]interface{}
|
var overlayMap map[string]any
|
||||||
if err := yaml.Unmarshal(overlayContent, &overlayMap); err != nil {
|
if err := yaml.Unmarshal(overlayContent, &overlayMap); err != nil {
|
||||||
return fmt.Errorf("error parsing overlay YAML: %v", err)
|
return fmt.Errorf("error parsing overlay YAML: %v", err)
|
||||||
}
|
}
|
||||||
@@ -324,8 +323,8 @@ func MergeYAML(baseFile, overlayFile string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// mergeMap recursively merges two maps
|
// mergeMap recursively merges two maps
|
||||||
func mergeMap(base, overlay map[string]interface{}) map[string]interface{} {
|
func mergeMap(base, overlay map[string]any) map[string]any {
|
||||||
result := make(map[string]interface{})
|
result := make(map[string]any)
|
||||||
|
|
||||||
// Copy all key-values from base map
|
// Copy all key-values from base map
|
||||||
for k, v := range base {
|
for k, v := range base {
|
||||||
@@ -336,8 +335,8 @@ func mergeMap(base, overlay map[string]interface{}) map[string]interface{} {
|
|||||||
for k, v := range overlay {
|
for k, v := range overlay {
|
||||||
// If both maps have the same key and both values are maps, merge recursively
|
// If both maps have the same key and both values are maps, merge recursively
|
||||||
if baseVal, ok := base[k]; ok {
|
if baseVal, ok := base[k]; ok {
|
||||||
if baseMap, isBaseMap := baseVal.(map[string]interface{}); isBaseMap {
|
if baseMap, isBaseMap := baseVal.(map[string]any); isBaseMap {
|
||||||
if overlayMap, isOverlayMap := v.(map[string]interface{}); isOverlayMap {
|
if overlayMap, isOverlayMap := v.(map[string]any); isOverlayMap {
|
||||||
result[k] = mergeMap(baseMap, overlayMap)
|
result[k] = mergeMap(baseMap, overlayMap)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,11 +81,19 @@ entryPoints:
|
|||||||
transport:
|
transport:
|
||||||
respondingTimeouts:
|
respondingTimeouts:
|
||||||
readTimeout: "30m"
|
readTimeout: "30m"
|
||||||
|
http3:
|
||||||
|
advertisedPort: 443
|
||||||
http:
|
http:
|
||||||
tls:
|
tls:
|
||||||
certResolver: "letsencrypt"
|
certResolver: "letsencrypt"
|
||||||
middlewares:
|
middlewares:
|
||||||
- crowdsec@file
|
- crowdsec@file
|
||||||
|
encodedCharacters:
|
||||||
|
allowEncodedSlash: true
|
||||||
|
allowEncodedQuestionMark: true
|
||||||
|
|
||||||
serversTransport:
|
serversTransport:
|
||||||
insecureSkipVerify: true
|
insecureSkipVerify: true
|
||||||
|
|
||||||
|
ping:
|
||||||
|
entryPoint: "web"
|
||||||
|
|||||||
@@ -4,6 +4,12 @@ services:
|
|||||||
image: docker.io/fosrl/pangolin:{{if .IsEnterprise}}ee-{{end}}{{.PangolinVersion}}
|
image: docker.io/fosrl/pangolin:{{if .IsEnterprise}}ee-{{end}}{{.PangolinVersion}}
|
||||||
container_name: pangolin
|
container_name: pangolin
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 1g
|
||||||
|
reservations:
|
||||||
|
memory: 256m
|
||||||
volumes:
|
volumes:
|
||||||
- ./config:/app/config
|
- ./config:/app/config
|
||||||
healthcheck:
|
healthcheck:
|
||||||
@@ -32,15 +38,14 @@ services:
|
|||||||
- 51820:51820/udp
|
- 51820:51820/udp
|
||||||
- 21820:21820/udp
|
- 21820:21820/udp
|
||||||
- 443:443
|
- 443:443
|
||||||
|
- 443:443/udp # For http3 QUIC if desired
|
||||||
- 80:80
|
- 80:80
|
||||||
{{end}}
|
{{end}}
|
||||||
traefik:
|
traefik:
|
||||||
image: docker.io/traefik:v3.6
|
image: docker.io/traefik:v3.6
|
||||||
container_name: traefik
|
container_name: traefik
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
{{if .InstallGerbil}}
|
{{if .InstallGerbil}} network_mode: service:gerbil # Ports appear on the gerbil service{{end}}{{if not .InstallGerbil}}
|
||||||
network_mode: service:gerbil # Ports appear on the gerbil service
|
|
||||||
{{end}}{{if not .InstallGerbil}}
|
|
||||||
ports:
|
ports:
|
||||||
- 443:443
|
- 443:443
|
||||||
- 80:80
|
- 80:80
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ entryPoints:
|
|||||||
transport:
|
transport:
|
||||||
respondingTimeouts:
|
respondingTimeouts:
|
||||||
readTimeout: "30m"
|
readTimeout: "30m"
|
||||||
|
http3:
|
||||||
|
advertisedPort: 443
|
||||||
http:
|
http:
|
||||||
tls:
|
tls:
|
||||||
certResolver: "letsencrypt"
|
certResolver: "letsencrypt"
|
||||||
|
|||||||
@@ -144,12 +144,13 @@ func installDocker() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func startDockerService() error {
|
func startDockerService() error {
|
||||||
if runtime.GOOS == "linux" {
|
switch runtime.GOOS {
|
||||||
|
case "linux":
|
||||||
cmd := exec.Command("systemctl", "enable", "--now", "docker")
|
cmd := exec.Command("systemctl", "enable", "--now", "docker")
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
} else if runtime.GOOS == "darwin" {
|
case "darwin":
|
||||||
// On macOS, Docker is usually started via the Docker Desktop application
|
// On macOS, Docker is usually started via the Docker Desktop application
|
||||||
fmt.Println("Please start Docker Desktop manually on macOS.")
|
fmt.Println("Please start Docker Desktop manually on macOS.")
|
||||||
return nil
|
return nil
|
||||||
@@ -302,7 +303,7 @@ func pullContainers(containerType SupportedContainer) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("Unsupported container type: %s", containerType)
|
return fmt.Errorf("unsupported container type: %s", containerType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// startContainers starts the containers using the appropriate command.
|
// startContainers starts the containers using the appropriate command.
|
||||||
@@ -325,7 +326,7 @@ func startContainers(containerType SupportedContainer) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("Unsupported container type: %s", containerType)
|
return fmt.Errorf("unsupported container type: %s", containerType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// stopContainers stops the containers using the appropriate command.
|
// stopContainers stops the containers using the appropriate command.
|
||||||
@@ -347,7 +348,7 @@ func stopContainers(containerType SupportedContainer) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("Unsupported container type: %s", containerType)
|
return fmt.Errorf("unsupported container type: %s", containerType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// restartContainer restarts a specific container using the appropriate command.
|
// restartContainer restarts a specific container using the appropriate command.
|
||||||
@@ -369,5 +370,5 @@ func restartContainer(container string, containerType SupportedContainer) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("Unsupported container type: %s", containerType)
|
return fmt.Errorf("unsupported container type: %s", containerType)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func installCrowdsec(config Config) error {
|
func installCrowdsec(config Config, installDir string) error {
|
||||||
|
|
||||||
if err := stopContainers(config.InstallationContainerType); err != nil {
|
if err := stopContainers(config.InstallationContainerType); err != nil {
|
||||||
return fmt.Errorf("failed to stop containers: %v", err)
|
return fmt.Errorf("failed to stop containers: %v", err)
|
||||||
@@ -27,9 +28,20 @@ func installCrowdsec(config Config) error {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
os.MkdirAll("config/crowdsec/db", 0755)
|
if err := os.MkdirAll("config/crowdsec/db", 0755); err != nil {
|
||||||
os.MkdirAll("config/crowdsec/acquis.d", 0755)
|
fmt.Printf("Error creating config files: %v\n", err)
|
||||||
os.MkdirAll("config/traefik/logs", 0755)
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll("config/crowdsec/acquis.d", 0755); err != nil {
|
||||||
|
fmt.Printf("Error creating config files: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll("config/traefik/logs", 0755); err != nil {
|
||||||
|
fmt.Printf("Error creating config files: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
setupTraefikLogRotate(installDir)
|
||||||
|
|
||||||
if err := copyDockerService("config/crowdsec/docker-compose.yml", "docker-compose.yml", "crowdsec"); err != nil {
|
if err := copyDockerService("config/crowdsec/docker-compose.yml", "docker-compose.yml", "crowdsec"); err != nil {
|
||||||
fmt.Printf("Error copying docker service: %v\n", err)
|
fmt.Printf("Error copying docker service: %v\n", err)
|
||||||
@@ -153,34 +165,34 @@ func CheckAndAddCrowdsecDependency(composePath string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse YAML into a generic map
|
// Parse YAML into a generic map
|
||||||
var compose map[string]interface{}
|
var compose map[string]any
|
||||||
if err := yaml.Unmarshal(data, &compose); err != nil {
|
if err := yaml.Unmarshal(data, &compose); err != nil {
|
||||||
return fmt.Errorf("error parsing compose file: %w", err)
|
return fmt.Errorf("error parsing compose file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get services section
|
// Get services section
|
||||||
services, ok := compose["services"].(map[string]interface{})
|
services, ok := compose["services"].(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("services section not found or invalid")
|
return fmt.Errorf("services section not found or invalid")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get traefik service
|
// Get traefik service
|
||||||
traefik, ok := services["traefik"].(map[string]interface{})
|
traefik, ok := services["traefik"].(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("traefik service not found or invalid")
|
return fmt.Errorf("traefik service not found or invalid")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get dependencies
|
// Get dependencies
|
||||||
dependsOn, ok := traefik["depends_on"].(map[string]interface{})
|
dependsOn, ok := traefik["depends_on"].(map[string]any)
|
||||||
if ok {
|
if ok {
|
||||||
// Append the new block for crowdsec
|
// Append the new block for crowdsec
|
||||||
dependsOn["crowdsec"] = map[string]interface{}{
|
dependsOn["crowdsec"] = map[string]any{
|
||||||
"condition": "service_healthy",
|
"condition": "service_healthy",
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No dependencies exist, create it
|
// No dependencies exist, create it
|
||||||
traefik["depends_on"] = map[string]interface{}{
|
traefik["depends_on"] = map[string]any{
|
||||||
"crowdsec": map[string]interface{}{
|
"crowdsec": map[string]any{
|
||||||
"condition": "service_healthy",
|
"condition": "service_healthy",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -199,3 +211,69 @@ func CheckAndAddCrowdsecDependency(composePath string) error {
|
|||||||
fmt.Println("Added dependency of crowdsec to traefik")
|
fmt.Println("Added dependency of crowdsec to traefik")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setupTraefikLogRotate writes a logrotate config for the Traefik access log
|
||||||
|
// that CrowdSec depends on. This is only needed when CrowdSec is installed
|
||||||
|
// because the default Pangolin install does not enable Traefik access logs.
|
||||||
|
//
|
||||||
|
// copytruncate is used so Traefik does not need to be restarted or sent a
|
||||||
|
// signal after rotation — it keeps writing to the same file descriptor while
|
||||||
|
// the rotated copy is made and the original is truncated in place.
|
||||||
|
func setupTraefikLogRotate(installDir string) {
|
||||||
|
const logrotateDir = "/etc/logrotate.d"
|
||||||
|
const logrotateFile = "/etc/logrotate.d/pangolin-traefik"
|
||||||
|
|
||||||
|
logPath := filepath.Join(installDir, "config/traefik/logs/access.log")
|
||||||
|
|
||||||
|
if os.Geteuid() != 0 {
|
||||||
|
fmt.Println("\n[logrotate] Skipping automatic logrotate setup: not running as root.")
|
||||||
|
fmt.Println("[logrotate] To prevent unbounded growth of the Traefik access log used by CrowdSec,")
|
||||||
|
fmt.Println("[logrotate] create the file /etc/logrotate.d/pangolin-traefik manually with:")
|
||||||
|
printLogrotateConfig(logPath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
config := fmt.Sprintf(`# Logrotate config for Traefik access logs used by CrowdSec.
|
||||||
|
# Generated by the Pangolin installer. Safe to edit.
|
||||||
|
%s {
|
||||||
|
daily
|
||||||
|
rotate 7
|
||||||
|
compress
|
||||||
|
delaycompress
|
||||||
|
missingok
|
||||||
|
notifempty
|
||||||
|
copytruncate
|
||||||
|
}
|
||||||
|
`, logPath)
|
||||||
|
|
||||||
|
if err := os.MkdirAll(logrotateDir, 0755); err != nil {
|
||||||
|
fmt.Printf("[logrotate] Warning: could not create %s: %v\n", logrotateDir, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile(logrotateFile, []byte(config), 0644); err != nil {
|
||||||
|
fmt.Printf("[logrotate] Warning: could not write %s: %v\n", logrotateFile, err)
|
||||||
|
fmt.Println("[logrotate] Set it up manually:")
|
||||||
|
printLogrotateConfig(logPath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("[logrotate] Wrote logrotate config to %s\n", logrotateFile)
|
||||||
|
fmt.Println("[logrotate] Traefik access logs will be rotated daily, keeping 7 compressed copies.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// printLogrotateConfig prints a logrotate config block to stdout so users can
|
||||||
|
// set it up manually when the installer cannot write to /etc.
|
||||||
|
func printLogrotateConfig(logPath string) {
|
||||||
|
fmt.Printf(`
|
||||||
|
%s {
|
||||||
|
daily
|
||||||
|
rotate 7
|
||||||
|
compress
|
||||||
|
delaycompress
|
||||||
|
missingok
|
||||||
|
notifempty
|
||||||
|
copytruncate
|
||||||
|
}
|
||||||
|
`, logPath)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,38 @@
|
|||||||
module installer
|
module installer
|
||||||
|
|
||||||
go 1.24.0
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
golang.org/x/term v0.39.0
|
github.com/charmbracelet/huh v1.0.0
|
||||||
|
github.com/charmbracelet/lipgloss v1.1.0
|
||||||
|
golang.org/x/term v0.42.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require golang.org/x/sys v0.40.0 // indirect
|
require (
|
||||||
|
github.com/atotto/clipboard v0.1.4 // indirect
|
||||||
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
|
github.com/catppuccin/go v0.3.0 // indirect
|
||||||
|
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 // indirect
|
||||||
|
github.com/charmbracelet/bubbletea v1.3.6 // indirect
|
||||||
|
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
||||||
|
github.com/charmbracelet/x/ansi v0.9.3 // indirect
|
||||||
|
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
|
||||||
|
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
|
||||||
|
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||||
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
|
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
|
||||||
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||||
|
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||||
|
github.com/muesli/termenv v0.16.0 // indirect
|
||||||
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
|
golang.org/x/sync v0.15.0 // indirect
|
||||||
|
golang.org/x/sys v0.43.0 // indirect
|
||||||
|
golang.org/x/text v0.23.0 // indirect
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,7 +1,80 @@
|
|||||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
|
||||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
|
||||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||||
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||||
|
github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
|
||||||
|
github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
|
||||||
|
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
|
||||||
|
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
|
||||||
|
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 h1:JFgG/xnwFfbezlUnFMJy0nusZvytYysV4SCS2cYbvws=
|
||||||
|
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7/go.mod h1:ISC1gtLcVilLOf23wvTfoQuYbW2q0JevFxPfUzZ9Ybw=
|
||||||
|
github.com/charmbracelet/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGsK6et5taU=
|
||||||
|
github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc=
|
||||||
|
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
|
||||||
|
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
|
||||||
|
github.com/charmbracelet/huh v1.0.0 h1:wOnedH8G4qzJbmhftTqrpppyqHakl/zbbNdXIWJyIxw=
|
||||||
|
github.com/charmbracelet/huh v1.0.0/go.mod h1:5YVc+SlZ1IhQALxRPpkGwwEKftN/+OlJlnJYlDRFqN4=
|
||||||
|
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||||
|
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||||
|
github.com/charmbracelet/x/ansi v0.9.3 h1:BXt5DHS/MKF+LjuK4huWrC6NCvHtexww7dMayh6GXd0=
|
||||||
|
github.com/charmbracelet/x/ansi v0.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
|
||||||
|
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
|
||||||
|
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
||||||
|
github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U=
|
||||||
|
github.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ=
|
||||||
|
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA=
|
||||||
|
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
|
||||||
|
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
|
||||||
|
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
|
||||||
|
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4=
|
||||||
|
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ=
|
||||||
|
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||||
|
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||||
|
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
|
||||||
|
github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
|
||||||
|
github.com/charmbracelet/x/xpty v0.1.2 h1:Pqmu4TEJ8KeA9uSkISKMU3f+C1F6OGBn8ABuGlqCbtI=
|
||||||
|
github.com/charmbracelet/x/xpty v0.1.2/go.mod h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJnzHI0Lq13Xzq4=
|
||||||
|
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||||
|
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||||
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||||
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||||
|
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||||
|
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
||||||
|
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||||
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||||
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||||
|
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||||
|
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||||
|
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||||
|
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||||
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||||
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||||
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||||
|
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||||
|
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
|
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||||
|
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
|
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
|
||||||
|
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
|
||||||
|
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||||
|
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|||||||
260
install/input.go
260
install/input.go
@@ -1,92 +1,208 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"os"
|
||||||
"syscall"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/huh"
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
func readString(reader *bufio.Reader, prompt string, defaultValue string) string {
|
// pangolinTheme is the custom theme using brand colors
|
||||||
|
var pangolinTheme = ThemePangolin()
|
||||||
|
|
||||||
|
// isAccessibleMode checks if we should use accessible mode (simple prompts)
|
||||||
|
// This is true for: non-TTY, TERM=dumb, or ACCESSIBLE env var set
|
||||||
|
func isAccessibleMode() bool {
|
||||||
|
// Check if stdin is not a terminal (piped input, CI, etc.)
|
||||||
|
if !term.IsTerminal(int(os.Stdin.Fd())) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Check for dumb terminal
|
||||||
|
if os.Getenv("TERM") == "dumb" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Check for explicit accessible mode request
|
||||||
|
if os.Getenv("ACCESSIBLE") != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleAbort checks if the error is a user abort (Ctrl+C) and exits if so
|
||||||
|
func handleAbort(err error) {
|
||||||
|
if err != nil && errors.Is(err, huh.ErrUserAborted) {
|
||||||
|
fmt.Println("\nInstallation cancelled.")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// runField runs a single field with the Pangolin theme, handling accessible mode
|
||||||
|
func runField(field huh.Field) error {
|
||||||
|
if isAccessibleMode() {
|
||||||
|
return field.RunAccessible(os.Stdout, os.Stdin)
|
||||||
|
}
|
||||||
|
form := huh.NewForm(huh.NewGroup(field)).WithTheme(pangolinTheme)
|
||||||
|
return form.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func readString(prompt string, defaultValue string) string {
|
||||||
|
var value string
|
||||||
|
|
||||||
|
title := prompt
|
||||||
if defaultValue != "" {
|
if defaultValue != "" {
|
||||||
fmt.Printf("%s (default: %s): ", prompt, defaultValue)
|
title = fmt.Sprintf("%s (default: %s)", prompt, defaultValue)
|
||||||
} else {
|
|
||||||
fmt.Print(prompt + ": ")
|
|
||||||
}
|
}
|
||||||
input, _ := reader.ReadString('\n')
|
|
||||||
input = strings.TrimSpace(input)
|
|
||||||
if input == "" {
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
return input
|
|
||||||
}
|
|
||||||
|
|
||||||
func readStringNoDefault(reader *bufio.Reader, prompt string) string {
|
input := huh.NewInput().
|
||||||
fmt.Print(prompt + ": ")
|
Title(title).
|
||||||
input, _ := reader.ReadString('\n')
|
Value(&value)
|
||||||
return strings.TrimSpace(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
func readPassword(prompt string, reader *bufio.Reader) string {
|
// If no default value, this field is required
|
||||||
if term.IsTerminal(int(syscall.Stdin)) {
|
if defaultValue == "" {
|
||||||
fmt.Print(prompt + ": ")
|
input = input.Validate(func(s string) error {
|
||||||
// Read password without echo if we're in a terminal
|
if s == "" {
|
||||||
password, err := term.ReadPassword(int(syscall.Stdin))
|
return fmt.Errorf("this field is required")
|
||||||
fmt.Println() // Add a newline since ReadPassword doesn't add one
|
}
|
||||||
if err != nil {
|
return nil
|
||||||
return ""
|
})
|
||||||
}
|
|
||||||
input := strings.TrimSpace(string(password))
|
|
||||||
if input == "" {
|
|
||||||
return readPassword(prompt, reader)
|
|
||||||
}
|
|
||||||
return input
|
|
||||||
} else {
|
|
||||||
// Fallback to reading from stdin if not in a terminal
|
|
||||||
return readString(reader, prompt, "")
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func readBool(reader *bufio.Reader, prompt string, defaultValue bool) bool {
|
err := runField(input)
|
||||||
defaultStr := "no"
|
handleAbort(err)
|
||||||
if defaultValue {
|
|
||||||
defaultStr = "yes"
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
input := readString(reader, prompt+" (yes/no)", defaultStr)
|
|
||||||
lower := strings.ToLower(input)
|
|
||||||
if lower == "yes" {
|
|
||||||
return true
|
|
||||||
} else if lower == "no" {
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
fmt.Println("Please enter 'yes' or 'no'.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func readBoolNoDefault(reader *bufio.Reader, prompt string) bool {
|
if value == "" {
|
||||||
for {
|
value = defaultValue
|
||||||
input := readStringNoDefault(reader, prompt+" (yes/no)")
|
|
||||||
lower := strings.ToLower(input)
|
|
||||||
if lower == "yes" {
|
|
||||||
return true
|
|
||||||
} else if lower == "no" {
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
fmt.Println("Please enter 'yes' or 'no'.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func readInt(reader *bufio.Reader, prompt string, defaultValue int) int {
|
// Print the answer so it remains visible in terminal history (skip in accessible mode as it already shows)
|
||||||
input := readString(reader, prompt, fmt.Sprintf("%d", defaultValue))
|
if !isAccessibleMode() {
|
||||||
if input == "" {
|
fmt.Printf("%s: %s\n", prompt, value)
|
||||||
return defaultValue
|
|
||||||
}
|
}
|
||||||
value := defaultValue
|
|
||||||
fmt.Sscanf(input, "%d", &value)
|
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func readPassword(prompt string) string {
|
||||||
|
var value string
|
||||||
|
|
||||||
|
for {
|
||||||
|
input := huh.NewInput().
|
||||||
|
Title(prompt).
|
||||||
|
Value(&value).
|
||||||
|
EchoMode(huh.EchoModePassword).
|
||||||
|
Validate(func(s string) error {
|
||||||
|
if s == "" {
|
||||||
|
return fmt.Errorf("password is required")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
err := runField(input)
|
||||||
|
handleAbort(err)
|
||||||
|
|
||||||
|
if value != "" {
|
||||||
|
// Print confirmation without revealing the password
|
||||||
|
if !isAccessibleMode() {
|
||||||
|
fmt.Printf("%s: %s\n", prompt, "********")
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readBool(prompt string, defaultValue bool) bool {
|
||||||
|
var value = defaultValue
|
||||||
|
|
||||||
|
confirm := huh.NewConfirm().
|
||||||
|
Title(prompt).
|
||||||
|
Value(&value).
|
||||||
|
Affirmative("Yes").
|
||||||
|
Negative("No")
|
||||||
|
|
||||||
|
err := runField(confirm)
|
||||||
|
handleAbort(err)
|
||||||
|
|
||||||
|
// Print the answer so it remains visible in terminal history
|
||||||
|
if !isAccessibleMode() {
|
||||||
|
answer := "No"
|
||||||
|
if value {
|
||||||
|
answer = "Yes"
|
||||||
|
}
|
||||||
|
fmt.Printf("%s: %s\n", prompt, answer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func readBoolNoDefault(prompt string) bool {
|
||||||
|
var value bool
|
||||||
|
|
||||||
|
confirm := huh.NewConfirm().
|
||||||
|
Title(prompt).
|
||||||
|
Value(&value).
|
||||||
|
Affirmative("Yes").
|
||||||
|
Negative("No")
|
||||||
|
|
||||||
|
err := runField(confirm)
|
||||||
|
handleAbort(err)
|
||||||
|
|
||||||
|
// Print the answer so it remains visible in terminal history
|
||||||
|
if !isAccessibleMode() {
|
||||||
|
answer := "No"
|
||||||
|
if value {
|
||||||
|
answer = "Yes"
|
||||||
|
}
|
||||||
|
fmt.Printf("%s: %s\n", prompt, answer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func readInt(prompt string, defaultValue int) int {
|
||||||
|
var value string
|
||||||
|
|
||||||
|
title := fmt.Sprintf("%s (default: %d)", prompt, defaultValue)
|
||||||
|
|
||||||
|
input := huh.NewInput().
|
||||||
|
Title(title).
|
||||||
|
Value(&value).
|
||||||
|
Validate(func(s string) error {
|
||||||
|
if s == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
_, err := strconv.Atoi(s)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("please enter a valid number")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
err := runField(input)
|
||||||
|
handleAbort(err)
|
||||||
|
|
||||||
|
if value == "" {
|
||||||
|
// Print the answer so it remains visible in terminal history
|
||||||
|
if !isAccessibleMode() {
|
||||||
|
fmt.Printf("%s: %d\n", prompt, defaultValue)
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
if !isAccessibleMode() {
|
||||||
|
fmt.Printf("%s: %d\n", prompt, defaultValue)
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print the answer so it remains visible in terminal history
|
||||||
|
if !isAccessibleMode() {
|
||||||
|
fmt.Printf("%s: %d\n", prompt, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|||||||
308
install/main.go
308
install/main.go
@@ -1,30 +1,35 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"crypto/rand"
|
||||||
"embed"
|
"embed"
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"crypto/rand"
|
|
||||||
"encoding/base64"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DO NOT EDIT THIS FUNCTION; IT MATCHED BY REGEX IN CICD
|
// Version variables injected at build time via -ldflags
|
||||||
|
var (
|
||||||
|
pangolinVersion string
|
||||||
|
gerbilVersion string
|
||||||
|
badgerVersion string
|
||||||
|
)
|
||||||
|
|
||||||
func loadVersions(config *Config) {
|
func loadVersions(config *Config) {
|
||||||
config.PangolinVersion = "replaceme"
|
config.PangolinVersion = pangolinVersion
|
||||||
config.GerbilVersion = "replaceme"
|
config.GerbilVersion = gerbilVersion
|
||||||
config.BadgerVersion = "replaceme"
|
config.BadgerVersion = badgerVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed config/*
|
//go:embed config/*
|
||||||
@@ -82,14 +87,19 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
|
||||||
|
|
||||||
var config Config
|
var config Config
|
||||||
var alreadyInstalled = false
|
var alreadyInstalled = false
|
||||||
|
|
||||||
|
// Determine installation directory
|
||||||
|
installDir := findOrSelectInstallDirectory()
|
||||||
|
if err := os.Chdir(installDir); err != nil {
|
||||||
|
fmt.Printf("Error changing to installation directory: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
// check if there is already a config file
|
// check if there is already a config file
|
||||||
if _, err := os.Stat("config/config.yml"); err != nil {
|
if _, err := os.Stat("config/config.yml"); err != nil {
|
||||||
config = collectUserInput(reader)
|
config = collectUserInput()
|
||||||
|
|
||||||
loadVersions(&config)
|
loadVersions(&config)
|
||||||
config.DoCrowdsecInstall = false
|
config.DoCrowdsecInstall = false
|
||||||
@@ -102,7 +112,10 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
moveFile("config/docker-compose.yml", "docker-compose.yml")
|
if err := moveFile("config/docker-compose.yml", "docker-compose.yml"); err != nil {
|
||||||
|
fmt.Printf("Error moving docker-compose.yml: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Println("\nConfiguration files created successfully!")
|
fmt.Println("\nConfiguration files created successfully!")
|
||||||
|
|
||||||
@@ -117,13 +130,17 @@ func main() {
|
|||||||
|
|
||||||
fmt.Println("\n=== Starting installation ===")
|
fmt.Println("\n=== Starting installation ===")
|
||||||
|
|
||||||
if readBool(reader, "Would you like to install and start the containers?", true) {
|
if readBool("Would you like to install and start the containers?", true) {
|
||||||
|
|
||||||
config.InstallationContainerType = podmanOrDocker(reader)
|
config.InstallationContainerType = podmanOrDocker()
|
||||||
|
|
||||||
if !isDockerInstalled() && runtime.GOOS == "linux" && config.InstallationContainerType == Docker {
|
if !isDockerInstalled() && runtime.GOOS == "linux" && config.InstallationContainerType == Docker {
|
||||||
if readBool(reader, "Docker is not installed. Would you like to install it?", true) {
|
if readBool("Docker is not installed. Would you like to install it?", true) {
|
||||||
installDocker()
|
if err := installDocker(); err != nil {
|
||||||
|
fmt.Printf("Error installing Docker: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// try to start docker service but ignore errors
|
// try to start docker service but ignore errors
|
||||||
if err := startDockerService(); err != nil {
|
if err := startDockerService(); err != nil {
|
||||||
fmt.Println("Error starting Docker service:", err)
|
fmt.Println("Error starting Docker service:", err)
|
||||||
@@ -132,7 +149,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
// wait 10 seconds for docker to start checking if docker is running every 2 seconds
|
// wait 10 seconds for docker to start checking if docker is running every 2 seconds
|
||||||
fmt.Println("Waiting for Docker to start...")
|
fmt.Println("Waiting for Docker to start...")
|
||||||
for i := 0; i < 5; i++ {
|
for range 5 {
|
||||||
if isDockerRunning() {
|
if isDockerRunning() {
|
||||||
fmt.Println("Docker is running!")
|
fmt.Println("Docker is running!")
|
||||||
break
|
break
|
||||||
@@ -167,7 +184,7 @@ func main() {
|
|||||||
fmt.Println("\n=== MaxMind Database Update ===")
|
fmt.Println("\n=== MaxMind Database Update ===")
|
||||||
if _, err := os.Stat("config/GeoLite2-Country.mmdb"); err == nil {
|
if _, err := os.Stat("config/GeoLite2-Country.mmdb"); err == nil {
|
||||||
fmt.Println("MaxMind GeoLite2 Country database found.")
|
fmt.Println("MaxMind GeoLite2 Country database found.")
|
||||||
if readBool(reader, "Would you like to update the MaxMind database to the latest version?", false) {
|
if readBool("Would you like to update the MaxMind database to the latest version?", false) {
|
||||||
if err := downloadMaxMindDatabase(); err != nil {
|
if err := downloadMaxMindDatabase(); err != nil {
|
||||||
fmt.Printf("Error updating MaxMind database: %v\n", err)
|
fmt.Printf("Error updating MaxMind database: %v\n", err)
|
||||||
fmt.Println("You can try updating it manually later if needed.")
|
fmt.Println("You can try updating it manually later if needed.")
|
||||||
@@ -175,7 +192,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("MaxMind GeoLite2 Country database not found.")
|
fmt.Println("MaxMind GeoLite2 Country database not found.")
|
||||||
if readBool(reader, "Would you like to download the MaxMind GeoLite2 database for geoblocking functionality?", false) {
|
if readBool("Would you like to download the MaxMind GeoLite2 database for geoblocking functionality?", false) {
|
||||||
if err := downloadMaxMindDatabase(); err != nil {
|
if err := downloadMaxMindDatabase(); err != nil {
|
||||||
fmt.Printf("Error downloading MaxMind database: %v\n", err)
|
fmt.Printf("Error downloading MaxMind database: %v\n", err)
|
||||||
fmt.Println("You can try downloading it manually later if needed.")
|
fmt.Println("You can try downloading it manually later if needed.")
|
||||||
@@ -192,11 +209,11 @@ func main() {
|
|||||||
if !checkIsCrowdsecInstalledInCompose() {
|
if !checkIsCrowdsecInstalledInCompose() {
|
||||||
fmt.Println("\n=== CrowdSec Install ===")
|
fmt.Println("\n=== CrowdSec Install ===")
|
||||||
// check if crowdsec is installed
|
// check if crowdsec is installed
|
||||||
if readBool(reader, "Would you like to install CrowdSec?", false) {
|
if readBool("Would you like to install CrowdSec?", false) {
|
||||||
fmt.Println("This installer constitutes a minimal viable CrowdSec deployment. CrowdSec will add extra complexity to your Pangolin installation and may not work to the best of its abilities out of the box. Users are expected to implement configuration adjustments on their own to achieve the best security posture. Consult the CrowdSec documentation for detailed configuration instructions.")
|
fmt.Println("This installer constitutes a minimal viable CrowdSec deployment. CrowdSec will add extra complexity to your Pangolin installation and may not work to the best of its abilities out of the box. Users are expected to implement configuration adjustments on their own to achieve the best security posture. Consult the CrowdSec documentation for detailed configuration instructions.")
|
||||||
|
|
||||||
// BUG: crowdsec installation will be skipped if the user chooses to install on the first installation.
|
// BUG: crowdsec installation will be skipped if the user chooses to install on the first installation.
|
||||||
if readBool(reader, "Are you willing to manage CrowdSec?", false) {
|
if readBool("Are you willing to manage CrowdSec?", false) {
|
||||||
if config.DashboardDomain == "" {
|
if config.DashboardDomain == "" {
|
||||||
traefikConfig, err := ReadTraefikConfig("config/traefik/traefik_config.yml")
|
traefikConfig, err := ReadTraefikConfig("config/traefik/traefik_config.yml")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -225,8 +242,8 @@ func main() {
|
|||||||
fmt.Printf("Let's Encrypt Email: %s\n", config.LetsEncryptEmail)
|
fmt.Printf("Let's Encrypt Email: %s\n", config.LetsEncryptEmail)
|
||||||
fmt.Printf("Badger Version: %s\n", config.BadgerVersion)
|
fmt.Printf("Badger Version: %s\n", config.BadgerVersion)
|
||||||
|
|
||||||
if !readBool(reader, "Are these values correct?", true) {
|
if !readBool("Are these values correct?", true) {
|
||||||
config = collectUserInput(reader)
|
config = collectUserInput()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,14 +252,14 @@ func main() {
|
|||||||
if detectedType == Undefined {
|
if detectedType == Undefined {
|
||||||
// If detection fails, prompt the user
|
// If detection fails, prompt the user
|
||||||
fmt.Println("Unable to detect container type from existing installation.")
|
fmt.Println("Unable to detect container type from existing installation.")
|
||||||
config.InstallationContainerType = podmanOrDocker(reader)
|
config.InstallationContainerType = podmanOrDocker()
|
||||||
} else {
|
} else {
|
||||||
config.InstallationContainerType = detectedType
|
config.InstallationContainerType = detectedType
|
||||||
fmt.Printf("Detected container type: %s\n", config.InstallationContainerType)
|
fmt.Printf("Detected container type: %s\n", config.InstallationContainerType)
|
||||||
}
|
}
|
||||||
|
|
||||||
config.DoCrowdsecInstall = true
|
config.DoCrowdsecInstall = true
|
||||||
err := installCrowdsec(config)
|
err := installCrowdsec(config, installDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error installing CrowdSec: %v\n", err)
|
fmt.Printf("Error installing CrowdSec: %v\n", err)
|
||||||
return
|
return
|
||||||
@@ -277,8 +294,119 @@ func main() {
|
|||||||
fmt.Printf("\nTo complete the initial setup, please visit:\nhttps://%s/auth/initial-setup\n", config.DashboardDomain)
|
fmt.Printf("\nTo complete the initial setup, please visit:\nhttps://%s/auth/initial-setup\n", config.DashboardDomain)
|
||||||
}
|
}
|
||||||
|
|
||||||
func podmanOrDocker(reader *bufio.Reader) SupportedContainer {
|
func hasExistingInstall(dir string) bool {
|
||||||
inputContainer := readString(reader, "Would you like to run Pangolin as Docker or Podman containers?", "docker")
|
configPath := filepath.Join(dir, "config", "config.yml")
|
||||||
|
_, err := os.Stat(configPath)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findOrSelectInstallDirectory() string {
|
||||||
|
const defaultInstallDir = "/opt/pangolin"
|
||||||
|
|
||||||
|
// Get current working directory
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error getting current directory: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Check current directory for existing install
|
||||||
|
if hasExistingInstall(cwd) {
|
||||||
|
fmt.Printf("Found existing Pangolin installation in current directory: %s\n", cwd)
|
||||||
|
return cwd
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Check default location (/opt/pangolin) for existing install
|
||||||
|
if cwd != defaultInstallDir && hasExistingInstall(defaultInstallDir) {
|
||||||
|
fmt.Printf("\nFound existing Pangolin installation at: %s\n", defaultInstallDir)
|
||||||
|
if readBool(fmt.Sprintf("Would you like to use the existing installation at %s?", defaultInstallDir), true) {
|
||||||
|
return defaultInstallDir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. No existing install found, prompt for installation directory
|
||||||
|
fmt.Println("\n=== Installation Directory ===")
|
||||||
|
fmt.Println("No existing Pangolin installation detected.")
|
||||||
|
|
||||||
|
installDir := readString("Enter the installation directory", defaultInstallDir)
|
||||||
|
|
||||||
|
// Expand ~ to home directory if present
|
||||||
|
if strings.HasPrefix(installDir, "~") {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error getting home directory: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
installDir = filepath.Join(home, installDir[1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to absolute path
|
||||||
|
absPath, err := filepath.Abs(installDir)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error resolving path: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
installDir = absPath
|
||||||
|
|
||||||
|
// Check if directory exists
|
||||||
|
if _, err := os.Stat(installDir); os.IsNotExist(err) {
|
||||||
|
// Directory doesn't exist, create it
|
||||||
|
if readBool(fmt.Sprintf("Directory %s does not exist. Create it?", installDir), true) {
|
||||||
|
if err := os.MkdirAll(installDir, 0755); err != nil {
|
||||||
|
fmt.Printf("Error creating directory: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Printf("Created directory: %s\n", installDir)
|
||||||
|
|
||||||
|
// Offer to change ownership if running via sudo
|
||||||
|
changeDirectoryOwnership(installDir)
|
||||||
|
} else {
|
||||||
|
fmt.Println("Installation cancelled.")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Installation directory: %s\n", installDir)
|
||||||
|
return installDir
|
||||||
|
}
|
||||||
|
|
||||||
|
func changeDirectoryOwnership(dir string) {
|
||||||
|
// Check if we're running via sudo by looking for SUDO_USER
|
||||||
|
sudoUser := os.Getenv("SUDO_USER")
|
||||||
|
if sudoUser == "" || os.Geteuid() != 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sudoUID := os.Getenv("SUDO_UID")
|
||||||
|
sudoGID := os.Getenv("SUDO_GID")
|
||||||
|
|
||||||
|
if sudoUID == "" || sudoGID == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\nRunning as root via sudo (original user: %s)\n", sudoUser)
|
||||||
|
if readBool(fmt.Sprintf("Would you like to change ownership of %s to user '%s'? This makes it easier to manage config files without sudo.", dir, sudoUser), true) {
|
||||||
|
uid, err := strconv.Atoi(sudoUID)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Warning: Could not parse SUDO_UID: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gid, err := strconv.Atoi(sudoGID)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Warning: Could not parse SUDO_GID: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Chown(dir, uid, gid); err != nil {
|
||||||
|
fmt.Printf("Warning: Could not change ownership: %v\n", err)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Changed ownership of %s to %s\n", dir, sudoUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func podmanOrDocker() SupportedContainer {
|
||||||
|
inputContainer := readString("Would you like to run Pangolin as Docker or Podman containers?", "docker")
|
||||||
|
|
||||||
chosenContainer := Docker
|
chosenContainer := Docker
|
||||||
if strings.EqualFold(inputContainer, "docker") {
|
if strings.EqualFold(inputContainer, "docker") {
|
||||||
@@ -290,7 +418,8 @@ func podmanOrDocker(reader *bufio.Reader) SupportedContainer {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if chosenContainer == Podman {
|
switch chosenContainer {
|
||||||
|
case Podman:
|
||||||
if !isPodmanInstalled() {
|
if !isPodmanInstalled() {
|
||||||
fmt.Println("Podman or podman-compose is not installed. Please install both manually. Automated installation will be available in a later release.")
|
fmt.Println("Podman or podman-compose is not installed. Please install both manually. Automated installation will be available in a later release.")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -299,7 +428,7 @@ func podmanOrDocker(reader *bufio.Reader) SupportedContainer {
|
|||||||
if err := exec.Command("bash", "-c", "cat /etc/sysctl.d/99-podman.conf 2>/dev/null | grep 'net.ipv4.ip_unprivileged_port_start=' || cat /etc/sysctl.conf 2>/dev/null | grep 'net.ipv4.ip_unprivileged_port_start='").Run(); err != nil {
|
if err := exec.Command("bash", "-c", "cat /etc/sysctl.d/99-podman.conf 2>/dev/null | grep 'net.ipv4.ip_unprivileged_port_start=' || cat /etc/sysctl.conf 2>/dev/null | grep 'net.ipv4.ip_unprivileged_port_start='").Run(); err != nil {
|
||||||
fmt.Println("Would you like to configure ports >= 80 as unprivileged ports? This enables podman containers to listen on low-range ports.")
|
fmt.Println("Would you like to configure ports >= 80 as unprivileged ports? This enables podman containers to listen on low-range ports.")
|
||||||
fmt.Println("Pangolin will experience startup issues if this is not configured, because it needs to listen on port 80/443 by default.")
|
fmt.Println("Pangolin will experience startup issues if this is not configured, because it needs to listen on port 80/443 by default.")
|
||||||
approved := readBool(reader, "The installer is about to execute \"echo 'net.ipv4.ip_unprivileged_port_start=80' > /etc/sysctl.d/99-podman.conf && sysctl --system\". Approve?", true)
|
approved := readBool("The installer is about to execute \"echo 'net.ipv4.ip_unprivileged_port_start=80' > /etc/sysctl.d/99-podman.conf && sysctl --system\". Approve?", true)
|
||||||
if approved {
|
if approved {
|
||||||
if os.Geteuid() != 0 {
|
if os.Geteuid() != 0 {
|
||||||
fmt.Println("You need to run the installer as root for such a configuration.")
|
fmt.Println("You need to run the installer as root for such a configuration.")
|
||||||
@@ -311,7 +440,7 @@ func podmanOrDocker(reader *bufio.Reader) SupportedContainer {
|
|||||||
// Linux only.
|
// Linux only.
|
||||||
|
|
||||||
if err := run("bash", "-c", "echo 'net.ipv4.ip_unprivileged_port_start=80' > /etc/sysctl.d/99-podman.conf && sysctl --system"); err != nil {
|
if err := run("bash", "-c", "echo 'net.ipv4.ip_unprivileged_port_start=80' > /etc/sysctl.d/99-podman.conf && sysctl --system"); err != nil {
|
||||||
fmt.Printf("Error configuring unprivileged ports: %v\n", err)
|
fmt.Printf("Error configuring unprivileged ports: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -321,7 +450,7 @@ func podmanOrDocker(reader *bufio.Reader) SupportedContainer {
|
|||||||
fmt.Println("Unprivileged ports have been configured.")
|
fmt.Println("Unprivileged ports have been configured.")
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if chosenContainer == Docker {
|
case Docker:
|
||||||
// check if docker is not installed and the user is root
|
// check if docker is not installed and the user is root
|
||||||
if !isDockerInstalled() {
|
if !isDockerInstalled() {
|
||||||
if os.Geteuid() != 0 {
|
if os.Geteuid() != 0 {
|
||||||
@@ -336,7 +465,7 @@ func podmanOrDocker(reader *bufio.Reader) SupportedContainer {
|
|||||||
fmt.Println("The installer will not be able to run docker commands without running it as root.")
|
fmt.Println("The installer will not be able to run docker commands without running it as root.")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
} else {
|
default:
|
||||||
// This shouldn't happen unless there's a third container runtime.
|
// This shouldn't happen unless there's a third container runtime.
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
@@ -344,35 +473,35 @@ func podmanOrDocker(reader *bufio.Reader) SupportedContainer {
|
|||||||
return chosenContainer
|
return chosenContainer
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectUserInput(reader *bufio.Reader) Config {
|
func collectUserInput() Config {
|
||||||
config := Config{}
|
config := Config{}
|
||||||
|
|
||||||
// Basic configuration
|
// Basic configuration
|
||||||
fmt.Println("\n=== Basic Configuration ===")
|
fmt.Println("\n=== Basic Configuration ===")
|
||||||
|
|
||||||
config.IsEnterprise = readBoolNoDefault(reader, "Do you want to install the Enterprise version of Pangolin? The EE is free for personal use or for businesses making less than 100k USD annually.")
|
config.IsEnterprise = readBoolNoDefault("Do you want to install the Enterprise version of Pangolin? The EE is free for personal use or for businesses making less than 100k USD annually.")
|
||||||
|
|
||||||
config.BaseDomain = readString(reader, "Enter your base domain (no subdomain e.g. example.com)", "")
|
config.BaseDomain = readString("Enter your base domain (no subdomain e.g. example.com)", "")
|
||||||
|
|
||||||
// Set default dashboard domain after base domain is collected
|
// Set default dashboard domain after base domain is collected
|
||||||
defaultDashboardDomain := ""
|
defaultDashboardDomain := ""
|
||||||
if config.BaseDomain != "" {
|
if config.BaseDomain != "" {
|
||||||
defaultDashboardDomain = "pangolin." + config.BaseDomain
|
defaultDashboardDomain = "pangolin." + config.BaseDomain
|
||||||
}
|
}
|
||||||
config.DashboardDomain = readString(reader, "Enter the domain for the Pangolin dashboard", defaultDashboardDomain)
|
config.DashboardDomain = readString("Enter the domain for the Pangolin dashboard", defaultDashboardDomain)
|
||||||
config.LetsEncryptEmail = readString(reader, "Enter email for Let's Encrypt certificates", "")
|
config.LetsEncryptEmail = readString("Enter email for Let's Encrypt certificates", "")
|
||||||
config.InstallGerbil = readBool(reader, "Do you want to use Gerbil to allow tunneled connections", true)
|
config.InstallGerbil = readBool("Do you want to use Gerbil to allow tunneled connections", true)
|
||||||
|
|
||||||
// Email configuration
|
// Email configuration
|
||||||
fmt.Println("\n=== Email Configuration ===")
|
fmt.Println("\n=== Email Configuration ===")
|
||||||
config.EnableEmail = readBool(reader, "Enable email functionality (SMTP)", false)
|
config.EnableEmail = readBool("Enable email functionality (SMTP)", false)
|
||||||
|
|
||||||
if config.EnableEmail {
|
if config.EnableEmail {
|
||||||
config.EmailSMTPHost = readString(reader, "Enter SMTP host", "")
|
config.EmailSMTPHost = readString("Enter SMTP host", "")
|
||||||
config.EmailSMTPPort = readInt(reader, "Enter SMTP port (default 587)", 587)
|
config.EmailSMTPPort = readInt("Enter SMTP port (default 587)", 587)
|
||||||
config.EmailSMTPUser = readString(reader, "Enter SMTP username", "")
|
config.EmailSMTPUser = readString("Enter SMTP username", "")
|
||||||
config.EmailSMTPPass = readString(reader, "Enter SMTP password", "") // Should this be readPassword?
|
config.EmailSMTPPass = readPassword("Enter SMTP password")
|
||||||
config.EmailNoReply = readString(reader, "Enter no-reply email address (often the same as SMTP username)", "")
|
config.EmailNoReply = readString("Enter no-reply email address (often the same as SMTP username)", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate required fields
|
// Validate required fields
|
||||||
@@ -393,8 +522,8 @@ func collectUserInput(reader *bufio.Reader) Config {
|
|||||||
|
|
||||||
fmt.Println("\n=== Advanced Configuration ===")
|
fmt.Println("\n=== Advanced Configuration ===")
|
||||||
|
|
||||||
config.EnableIPv6 = readBool(reader, "Is your server IPv6 capable?", true)
|
config.EnableIPv6 = readBool("Is your server IPv6 capable?", true)
|
||||||
config.EnableGeoblocking = readBool(reader, "Do you want to download the MaxMind GeoLite2 database for geoblocking functionality?", true)
|
config.EnableGeoblocking = readBool("Do you want to download the MaxMind GeoLite2 database for geoblocking functionality?", true)
|
||||||
|
|
||||||
if config.DashboardDomain == "" {
|
if config.DashboardDomain == "" {
|
||||||
fmt.Println("Error: Dashboard Domain name is required")
|
fmt.Println("Error: Dashboard Domain name is required")
|
||||||
@@ -405,15 +534,23 @@ func collectUserInput(reader *bufio.Reader) Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createConfigFiles(config Config) error {
|
func createConfigFiles(config Config) error {
|
||||||
os.MkdirAll("config", 0755)
|
if err := os.MkdirAll("config", 0755); err != nil {
|
||||||
os.MkdirAll("config/letsencrypt", 0755)
|
return fmt.Errorf("failed to create config directory: %v", err)
|
||||||
os.MkdirAll("config/db", 0755)
|
}
|
||||||
os.MkdirAll("config/logs", 0755)
|
if err := os.MkdirAll("config/letsencrypt", 0755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create letsencrypt directory: %v", err)
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll("config/db", 0755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create db directory: %v", err)
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll("config/logs", 0755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create logs directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Walk through all embedded files
|
// Walk through all embedded files
|
||||||
err := fs.WalkDir(configFiles, "config", func(path string, d fs.DirEntry, err error) error {
|
err := fs.WalkDir(configFiles, "config", func(path string, d fs.DirEntry, walkErr error) (err error) {
|
||||||
if err != nil {
|
if walkErr != nil {
|
||||||
return err
|
return walkErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip the root fs directory itself
|
// Skip the root fs directory itself
|
||||||
@@ -464,7 +601,11 @@ func createConfigFiles(config Config) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create %s: %v", path, err)
|
return fmt.Errorf("failed to create %s: %v", path, err)
|
||||||
}
|
}
|
||||||
defer outFile.Close()
|
defer func() {
|
||||||
|
if cerr := outFile.Close(); cerr != nil && err == nil {
|
||||||
|
err = cerr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// Execute template
|
// Execute template
|
||||||
if err := tmpl.Execute(outFile, config); err != nil {
|
if err := tmpl.Execute(outFile, config); err != nil {
|
||||||
@@ -480,18 +621,26 @@ func createConfigFiles(config Config) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyFile(src, dst string) error {
|
func copyFile(src, dst string) (err error) {
|
||||||
source, err := os.Open(src)
|
source, err := os.Open(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer source.Close()
|
defer func() {
|
||||||
|
if cerr := source.Close(); cerr != nil && err == nil {
|
||||||
|
err = cerr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
destination, err := os.Create(dst)
|
destination, err := os.Create(dst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer destination.Close()
|
defer func() {
|
||||||
|
if cerr := destination.Close(); cerr != nil && err == nil {
|
||||||
|
err = cerr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
_, err = io.Copy(destination, source)
|
_, err = io.Copy(destination, source)
|
||||||
return err
|
return err
|
||||||
@@ -562,22 +711,24 @@ func showSetupTokenInstructions(containerType SupportedContainer, dashboardDomai
|
|||||||
fmt.Println("To get your setup token, you need to:")
|
fmt.Println("To get your setup token, you need to:")
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
fmt.Println("1. Start the containers")
|
fmt.Println("1. Start the containers")
|
||||||
if containerType == Docker {
|
switch containerType {
|
||||||
|
case Docker:
|
||||||
fmt.Println(" docker compose up -d")
|
fmt.Println(" docker compose up -d")
|
||||||
} else if containerType == Podman {
|
case Podman:
|
||||||
fmt.Println(" podman-compose up -d")
|
fmt.Println(" podman-compose up -d")
|
||||||
} else {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
fmt.Println("2. Wait for the Pangolin container to start and generate the token")
|
fmt.Println("2. Wait for the Pangolin container to start and generate the token")
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
fmt.Println("3. Check the container logs for the setup token")
|
fmt.Println("3. Check the container logs for the setup token")
|
||||||
if containerType == Docker {
|
switch containerType {
|
||||||
|
case Docker:
|
||||||
fmt.Println(" docker logs pangolin | grep -A 2 -B 2 'SETUP TOKEN'")
|
fmt.Println(" docker logs pangolin | grep -A 2 -B 2 'SETUP TOKEN'")
|
||||||
} else if containerType == Podman {
|
case Podman:
|
||||||
fmt.Println(" podman logs pangolin | grep -A 2 -B 2 'SETUP TOKEN'")
|
fmt.Println(" podman logs pangolin | grep -A 2 -B 2 'SETUP TOKEN'")
|
||||||
} else {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
fmt.Println("4. Look for output like")
|
fmt.Println("4. Look for output like")
|
||||||
fmt.Println(" === SETUP TOKEN GENERATED ===")
|
fmt.Println(" === SETUP TOKEN GENERATED ===")
|
||||||
@@ -601,32 +752,6 @@ func generateRandomSecretKey() string {
|
|||||||
return base64.StdEncoding.EncodeToString(secret)
|
return base64.StdEncoding.EncodeToString(secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPublicIP() string {
|
|
||||||
client := &http.Client{
|
|
||||||
Timeout: 10 * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := client.Get("https://ifconfig.io/ip")
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
ip := strings.TrimSpace(string(body))
|
|
||||||
|
|
||||||
// Validate that it's a valid IP address
|
|
||||||
if net.ParseIP(ip) != nil {
|
|
||||||
return ip
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run external commands with stdio/stderr attached.
|
// Run external commands with stdio/stderr attached.
|
||||||
func run(name string, args ...string) error {
|
func run(name string, args ...string) error {
|
||||||
cmd := exec.Command(name, args...)
|
cmd := exec.Command(name, args...)
|
||||||
@@ -639,10 +764,7 @@ func checkPortsAvailable(port int) error {
|
|||||||
addr := fmt.Sprintf(":%d", port)
|
addr := fmt.Sprintf(":%d", port)
|
||||||
ln, err := net.Listen("tcp", addr)
|
ln, err := net.Listen("tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf("ERROR: port %d is occupied or cannot be bound: %w", port, err)
|
||||||
"ERROR: port %d is occupied or cannot be bound: %w\n\n",
|
|
||||||
port, err,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
if closeErr := ln.Close(); closeErr != nil {
|
if closeErr := ln.Close(); closeErr != nil {
|
||||||
fmt.Fprintf(os.Stderr,
|
fmt.Fprintf(os.Stderr,
|
||||||
|
|||||||
51
install/theme.go
Normal file
51
install/theme.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/charmbracelet/huh"
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pangolin brand colors (converted from oklch to hex)
|
||||||
|
var (
|
||||||
|
// Primary orange/amber - oklch(0.6717 0.1946 41.93)
|
||||||
|
primaryColor = lipgloss.AdaptiveColor{Light: "#D97706", Dark: "#F59E0B"}
|
||||||
|
// Muted foreground
|
||||||
|
mutedColor = lipgloss.AdaptiveColor{Light: "#737373", Dark: "#A3A3A3"}
|
||||||
|
// Success green
|
||||||
|
successColor = lipgloss.AdaptiveColor{Light: "#16A34A", Dark: "#22C55E"}
|
||||||
|
// Error red - oklch(0.577 0.245 27.325)
|
||||||
|
errorColor = lipgloss.AdaptiveColor{Light: "#DC2626", Dark: "#EF4444"}
|
||||||
|
// Normal text
|
||||||
|
normalFg = lipgloss.AdaptiveColor{Light: "#171717", Dark: "#FAFAFA"}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ThemePangolin returns a huh theme using Pangolin brand colors
|
||||||
|
func ThemePangolin() *huh.Theme {
|
||||||
|
t := huh.ThemeBase()
|
||||||
|
|
||||||
|
// Focused state styles
|
||||||
|
t.Focused.Base = t.Focused.Base.BorderForeground(primaryColor)
|
||||||
|
t.Focused.Title = t.Focused.Title.Foreground(primaryColor).Bold(true)
|
||||||
|
t.Focused.Description = t.Focused.Description.Foreground(mutedColor)
|
||||||
|
t.Focused.ErrorIndicator = t.Focused.ErrorIndicator.Foreground(errorColor)
|
||||||
|
t.Focused.ErrorMessage = t.Focused.ErrorMessage.Foreground(errorColor)
|
||||||
|
t.Focused.SelectSelector = t.Focused.SelectSelector.Foreground(primaryColor)
|
||||||
|
t.Focused.NextIndicator = t.Focused.NextIndicator.Foreground(primaryColor)
|
||||||
|
t.Focused.PrevIndicator = t.Focused.PrevIndicator.Foreground(primaryColor)
|
||||||
|
t.Focused.Option = t.Focused.Option.Foreground(normalFg)
|
||||||
|
t.Focused.SelectedOption = t.Focused.SelectedOption.Foreground(primaryColor)
|
||||||
|
t.Focused.SelectedPrefix = lipgloss.NewStyle().Foreground(successColor).SetString("✓ ")
|
||||||
|
t.Focused.UnselectedPrefix = lipgloss.NewStyle().Foreground(mutedColor).SetString(" ")
|
||||||
|
t.Focused.FocusedButton = t.Focused.FocusedButton.Foreground(lipgloss.Color("#FFFFFF")).Background(primaryColor)
|
||||||
|
t.Focused.BlurredButton = t.Focused.BlurredButton.Foreground(normalFg).Background(lipgloss.AdaptiveColor{Light: "#E5E5E5", Dark: "#404040"})
|
||||||
|
t.Focused.TextInput.Cursor = t.Focused.TextInput.Cursor.Foreground(primaryColor)
|
||||||
|
t.Focused.TextInput.Prompt = t.Focused.TextInput.Prompt.Foreground(primaryColor)
|
||||||
|
|
||||||
|
// Blurred state inherits from focused but with hidden border
|
||||||
|
t.Blurred = t.Focused
|
||||||
|
t.Blurred.Base = t.Focused.Base.BorderStyle(lipgloss.HiddenBorder())
|
||||||
|
t.Blurred.Title = t.Blurred.Title.Foreground(mutedColor).Bold(false)
|
||||||
|
t.Blurred.TextInput.Prompt = t.Blurred.TextInput.Prompt.Foreground(mutedColor)
|
||||||
|
|
||||||
|
return t
|
||||||
|
}
|
||||||
137
license_header_checker.py
Normal file
137
license_header_checker.py
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# --- Configuration ---
|
||||||
|
# The header text to be added to the files.
|
||||||
|
HEADER_TEXT = """/*
|
||||||
|
* This file is part of a proprietary work.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025-2026 Fossorial, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This file is licensed under the Fossorial Commercial License.
|
||||||
|
* You may not use this file except in compliance with the License.
|
||||||
|
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
||||||
|
*
|
||||||
|
* This file is not licensed under the AGPLv3.
|
||||||
|
*/
|
||||||
|
"""
|
||||||
|
|
||||||
|
HEADER_NORMALIZED = HEADER_TEXT.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def extract_leading_block_comment(content):
|
||||||
|
"""
|
||||||
|
If the file content begins with a /* ... */ block comment, return the
|
||||||
|
full text of that comment (including the delimiters) and the index at
|
||||||
|
which the rest of the file starts (after any trailing newlines).
|
||||||
|
Returns (None, 0) when no such comment is found.
|
||||||
|
"""
|
||||||
|
stripped = content.lstrip()
|
||||||
|
if not stripped.startswith('/*'):
|
||||||
|
return None, 0
|
||||||
|
|
||||||
|
# Account for any leading whitespace before the comment
|
||||||
|
comment_start = content.index('/*')
|
||||||
|
end_marker = content.find('*/', comment_start + 2)
|
||||||
|
if end_marker == -1:
|
||||||
|
return None, 0
|
||||||
|
|
||||||
|
comment_end = end_marker + 2 # position just after '*/'
|
||||||
|
comment_text = content[comment_start:comment_end].strip()
|
||||||
|
|
||||||
|
# Advance past any whitespace / newlines that follow the closing */
|
||||||
|
rest_start = comment_end
|
||||||
|
while rest_start < len(content) and content[rest_start] in '\n\r':
|
||||||
|
rest_start += 1
|
||||||
|
|
||||||
|
return comment_text, rest_start
|
||||||
|
|
||||||
|
|
||||||
|
def should_add_header(file_path):
|
||||||
|
"""
|
||||||
|
Checks if a file should receive the commercial license header.
|
||||||
|
Returns True if 'server/private' is in the path.
|
||||||
|
"""
|
||||||
|
if 'server/private' in file_path.lower():
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def process_directory(root_dir):
|
||||||
|
"""
|
||||||
|
Recursively scans a directory and adds/replaces/removes headers in
|
||||||
|
qualifying .ts or .tsx files, skipping any 'node_modules' directories.
|
||||||
|
"""
|
||||||
|
print(f"Scanning directory: {root_dir}")
|
||||||
|
files_processed = 0
|
||||||
|
files_modified = 0
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(root_dir):
|
||||||
|
# Exclude 'node_modules' directories from the scan.
|
||||||
|
if 'node_modules' in dirs:
|
||||||
|
dirs.remove('node_modules')
|
||||||
|
|
||||||
|
for file in files:
|
||||||
|
if not (file.endswith('.ts') or file.endswith('.tsx')):
|
||||||
|
continue
|
||||||
|
|
||||||
|
file_path = os.path.join(root, file)
|
||||||
|
files_processed += 1
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
original_content = f.read()
|
||||||
|
|
||||||
|
existing_comment, body_start = extract_leading_block_comment(
|
||||||
|
original_content
|
||||||
|
)
|
||||||
|
has_any_header = existing_comment is not None
|
||||||
|
has_correct_header = existing_comment == HEADER_NORMALIZED
|
||||||
|
|
||||||
|
body = original_content[body_start:] if has_any_header else original_content
|
||||||
|
|
||||||
|
if should_add_header(file_path):
|
||||||
|
if has_correct_header:
|
||||||
|
print(f"Header up-to-date: {file_path}")
|
||||||
|
else:
|
||||||
|
# Either no header exists or the header is outdated - write
|
||||||
|
# the correct one.
|
||||||
|
action = "Replaced header in" if has_any_header else "Added header to"
|
||||||
|
new_content = HEADER_NORMALIZED + '\n\n' + body
|
||||||
|
with open(file_path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(new_content)
|
||||||
|
print(f"{action}: {file_path}")
|
||||||
|
files_modified += 1
|
||||||
|
else:
|
||||||
|
if has_any_header:
|
||||||
|
# Remove the header - it shouldn't be here.
|
||||||
|
with open(file_path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(body)
|
||||||
|
print(f"Removed header from: {file_path}")
|
||||||
|
files_modified += 1
|
||||||
|
else:
|
||||||
|
print(f"No header needed: {file_path}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error processing file {file_path}: {e}")
|
||||||
|
|
||||||
|
print("\n--- Scan Complete ---")
|
||||||
|
print(f"Total .ts or .tsx files found: {files_processed}")
|
||||||
|
print(f"Files modified (added/replaced/removed): {files_modified}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Get the target directory from the command line arguments.
|
||||||
|
# If no directory is provided, it uses the current directory ('.').
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
target_directory = sys.argv[1]
|
||||||
|
else:
|
||||||
|
target_directory = '.' # Default to current directory
|
||||||
|
|
||||||
|
if not os.path.isdir(target_directory):
|
||||||
|
print(f"Error: Directory '{target_directory}' not found.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
process_directory(os.path.abspath(target_directory))
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,8 @@
|
|||||||
{
|
{
|
||||||
|
"contactSalesEnable": "Kontakt salgsavdelingen for å aktivere denne funksjonen.",
|
||||||
|
"contactSalesBookDemo": "Bestill en demo",
|
||||||
|
"contactSalesOr": "eller",
|
||||||
|
"contactSalesContactUs": "kontakt oss",
|
||||||
"setupCreate": "Opprett organisasjonen, nettstedet og ressursene",
|
"setupCreate": "Opprett organisasjonen, nettstedet og ressursene",
|
||||||
"headerAuthCompatibilityInfo": "Aktiver dette for å tvinge frem en 401 Uautorisert-respons når en autentiseringstoken mangler. Dette kreves for nettlesere eller spesifikke HTTP-biblioteker som ikke sender legitimasjon uten en serverutfordring.",
|
"headerAuthCompatibilityInfo": "Aktiver dette for å tvinge frem en 401 Uautorisert-respons når en autentiseringstoken mangler. Dette kreves for nettlesere eller spesifikke HTTP-biblioteker som ikke sender legitimasjon uten en serverutfordring.",
|
||||||
"headerAuthCompatibility": "Utvidet kompatibilitet",
|
"headerAuthCompatibility": "Utvidet kompatibilitet",
|
||||||
@@ -19,6 +23,18 @@
|
|||||||
"componentsInvalidKey": "Ugyldig eller utgått lisensnøkkel oppdaget. Følg lisensvilkårene for å fortsette å kunne bruke alle funksjonene.",
|
"componentsInvalidKey": "Ugyldig eller utgått lisensnøkkel oppdaget. Følg lisensvilkårene for å fortsette å kunne bruke alle funksjonene.",
|
||||||
"dismiss": "Avvis",
|
"dismiss": "Avvis",
|
||||||
"subscriptionViolationMessage": "Du er utenfor grensen for gjeldende plan. Rett problemet ved å fjerne nettsteder, brukere eller andre ressurser for å bli innenfor planen din.",
|
"subscriptionViolationMessage": "Du er utenfor grensen for gjeldende plan. Rett problemet ved å fjerne nettsteder, brukere eller andre ressurser for å bli innenfor planen din.",
|
||||||
|
"trialBannerMessage": "Din prøveperiode utløper om {countdown}. Oppgrader for å beholde tilgangen.",
|
||||||
|
"trialBannerExpired": "Prøveperioden din har utløpt. Oppgrader nå for å gjenopprette tilgangen.",
|
||||||
|
"billingTrialBannerTitle": "Prøveversjon Aktiv",
|
||||||
|
"billingTrialBannerDescription": "Du har for øyeblikket en gratis prøveversjon på forretningsnivået. Når prøven avsluttes, vil kontoen din automatisk gå tilbake til funksjoner og begrensninger på Basis-nivået. Oppgrader når som helst for å beholde tilgang til de nåværende planens funksjoner.",
|
||||||
|
"billingTrialBannerUpgrade": "Oppgrader nå",
|
||||||
|
"billingTrialBadge": "Prøveversjon",
|
||||||
|
"trialActive": "Gratis prøveversjon aktiv",
|
||||||
|
"trialExpired": "Prøveperioden er utløpt",
|
||||||
|
"trialHasEnded": "Din prøveperiode har avsluttet.",
|
||||||
|
"trialDaysRemaining": "{count, plural, one {# dag igjen} other {# dager igjen}}",
|
||||||
|
"trialDaysLeftShort": "{days}d igjen av prøveperioden",
|
||||||
|
"trialGoToBilling": "Gå til faktureringssiden",
|
||||||
"subscriptionViolationViewBilling": "Vis fakturering",
|
"subscriptionViolationViewBilling": "Vis fakturering",
|
||||||
"componentsLicenseViolation": "Lisens Brudd: Denne serveren bruker {usedSites} områder som overskrider den lisensierte grenser av {maxSites} områder. Følg lisensvilkårene for å fortsette å kunne bruke alle funksjonene.",
|
"componentsLicenseViolation": "Lisens Brudd: Denne serveren bruker {usedSites} områder som overskrider den lisensierte grenser av {maxSites} områder. Følg lisensvilkårene for å fortsette å kunne bruke alle funksjonene.",
|
||||||
"componentsSupporterMessage": "Takk for at du støtter Pangolin som en {tier}!",
|
"componentsSupporterMessage": "Takk for at du støtter Pangolin som en {tier}!",
|
||||||
@@ -81,6 +97,8 @@
|
|||||||
"siteConfirmCopy": "Jeg har kopiert konfigurasjonen",
|
"siteConfirmCopy": "Jeg har kopiert konfigurasjonen",
|
||||||
"searchSitesProgress": "Søker i områder...",
|
"searchSitesProgress": "Søker i områder...",
|
||||||
"siteAdd": "Legg til område",
|
"siteAdd": "Legg til område",
|
||||||
|
"sitesTableViewPublicResources": "Vis offentlige ressurser",
|
||||||
|
"sitesTableViewPrivateResources": "Vis private ressurser",
|
||||||
"siteInstallNewt": "Installer Newt",
|
"siteInstallNewt": "Installer Newt",
|
||||||
"siteInstallNewtDescription": "Få Newt til å kjøre på systemet ditt",
|
"siteInstallNewtDescription": "Få Newt til å kjøre på systemet ditt",
|
||||||
"WgConfiguration": "WireGuard Konfigurasjon",
|
"WgConfiguration": "WireGuard Konfigurasjon",
|
||||||
@@ -98,6 +116,21 @@
|
|||||||
"siteUpdatedDescription": "Området har blitt oppdatert.",
|
"siteUpdatedDescription": "Området har blitt oppdatert.",
|
||||||
"siteGeneralDescription": "Konfigurer de generelle innstillingene for dette området",
|
"siteGeneralDescription": "Konfigurer de generelle innstillingene for dette området",
|
||||||
"siteSettingDescription": "Konfigurere innstillingene på nettstedet",
|
"siteSettingDescription": "Konfigurere innstillingene på nettstedet",
|
||||||
|
"siteResourcesTab": "Ressurser",
|
||||||
|
"siteResourcesNoneOnSite": "Dette nettstedet har ingen offentlige eller private ressurser enda.",
|
||||||
|
"siteResourcesSectionPublic": "Offentlige ressurser",
|
||||||
|
"siteResourcesSectionPrivate": "Private ressurser",
|
||||||
|
"siteResourcesSectionPublicDescription": "Ressurser eksponert eksternt gjennom domener eller porter.",
|
||||||
|
"siteResourcesSectionPrivateDescription": "Ressurser tilgjengelig på ditt private nettverk gjennom nettstedet.",
|
||||||
|
"siteResourcesViewAllPublic": "Vis alle ressurser",
|
||||||
|
"siteResourcesViewAllPrivate": "Vis alle ressurser",
|
||||||
|
"siteResourcesDialogDescription": "Oversikt over offentlige og private ressurser assosiert med dette nettstedet.",
|
||||||
|
"siteResourcesShowMore": "Vis mer",
|
||||||
|
"siteResourcesPermissionDenied": "Du har ikke tillatelse til å liste opp disse ressursene.",
|
||||||
|
"siteResourcesEmptyPublic": "Ingen offentlige ressurser retter seg mot dette nettstedet enda.",
|
||||||
|
"siteResourcesEmptyPrivate": "Ingen private ressurser er assosiert med dette nettstedet enda.",
|
||||||
|
"siteResourcesHowToAccess": "Hvordan få tilgang",
|
||||||
|
"siteResourcesTargetsOnSite": "Mål på dette nettstedet",
|
||||||
"siteSetting": "{siteName} Innstillinger",
|
"siteSetting": "{siteName} Innstillinger",
|
||||||
"siteNewtTunnel": "Nyhetsnettsted (anbefalt)",
|
"siteNewtTunnel": "Nyhetsnettsted (anbefalt)",
|
||||||
"siteNewtTunnelDescription": "Lekkeste måte å lage et inngangspunkt til ethvert nettverk. Ingen ekstra oppsett på.",
|
"siteNewtTunnelDescription": "Lekkeste måte å lage et inngangspunkt til ethvert nettverk. Ingen ekstra oppsett på.",
|
||||||
@@ -148,6 +181,11 @@
|
|||||||
"createLink": "Opprett lenke",
|
"createLink": "Opprett lenke",
|
||||||
"resourcesNotFound": "Ingen ressurser funnet",
|
"resourcesNotFound": "Ingen ressurser funnet",
|
||||||
"resourceSearch": "Søk i ressurser",
|
"resourceSearch": "Søk i ressurser",
|
||||||
|
"machineSearch": "Søk etter maskiner",
|
||||||
|
"machinesSearch": "Søk etter maskinklienter...",
|
||||||
|
"machineNotFound": "Ingen maskiner funnet",
|
||||||
|
"userDeviceSearch": "Søk etter brukerenheter",
|
||||||
|
"userDevicesSearch": "Søk etter brukerenheter...",
|
||||||
"openMenu": "Åpne meny",
|
"openMenu": "Åpne meny",
|
||||||
"resource": "Ressurs",
|
"resource": "Ressurs",
|
||||||
"title": "Tittel",
|
"title": "Tittel",
|
||||||
@@ -175,6 +213,7 @@
|
|||||||
"resourceHTTPDescription": "Proxy forespørsler over HTTPS ved å bruke et fullstendig kvalifisert domenenavn.",
|
"resourceHTTPDescription": "Proxy forespørsler over HTTPS ved å bruke et fullstendig kvalifisert domenenavn.",
|
||||||
"resourceRaw": "Rå TCP/UDP-ressurs",
|
"resourceRaw": "Rå TCP/UDP-ressurs",
|
||||||
"resourceRawDescription": "Proxy forespørsler over rå TCP/UDP ved å bruke et portnummer.",
|
"resourceRawDescription": "Proxy forespørsler over rå TCP/UDP ved å bruke et portnummer.",
|
||||||
|
"resourceRawDescriptionCloud": "Proxy forespørsler om rå TCP/UDP ved hjelp av et portnummer. Krever sider for å koble til en ekstern node.",
|
||||||
"resourceCreate": "Opprett ressurs",
|
"resourceCreate": "Opprett ressurs",
|
||||||
"resourceCreateDescription": "Følg trinnene nedenfor for å opprette en ny ressurs",
|
"resourceCreateDescription": "Følg trinnene nedenfor for å opprette en ny ressurs",
|
||||||
"resourceSeeAll": "Se alle ressurser",
|
"resourceSeeAll": "Se alle ressurser",
|
||||||
@@ -201,6 +240,7 @@
|
|||||||
"protocolSelect": "Velg en protokoll",
|
"protocolSelect": "Velg en protokoll",
|
||||||
"resourcePortNumber": "Portnummer",
|
"resourcePortNumber": "Portnummer",
|
||||||
"resourcePortNumberDescription": "Det eksterne portnummeret for proxy forespørsler.",
|
"resourcePortNumberDescription": "Det eksterne portnummeret for proxy forespørsler.",
|
||||||
|
"back": "Tilbake",
|
||||||
"cancel": "Avbryt",
|
"cancel": "Avbryt",
|
||||||
"resourceConfig": "Konfigurasjonsutdrag",
|
"resourceConfig": "Konfigurasjonsutdrag",
|
||||||
"resourceConfigDescription": "Kopier og lim inn disse konfigurasjons-øyeblikkene for å sette opp TCP/UDP ressursen",
|
"resourceConfigDescription": "Kopier og lim inn disse konfigurasjons-øyeblikkene for å sette opp TCP/UDP ressursen",
|
||||||
@@ -246,11 +286,25 @@
|
|||||||
"orgErrorDeleteMessage": "Det oppsto en feil under sletting av organisasjonen.",
|
"orgErrorDeleteMessage": "Det oppsto en feil under sletting av organisasjonen.",
|
||||||
"orgDeleted": "Organisasjon slettet",
|
"orgDeleted": "Organisasjon slettet",
|
||||||
"orgDeletedMessage": "Organisasjonen og tilhørende data er slettet.",
|
"orgDeletedMessage": "Organisasjonen og tilhørende data er slettet.",
|
||||||
|
"deleteAccount": "Slett konto",
|
||||||
|
"deleteAccountDescription": "Slett kontoen din permanent, alle organisasjoner du eier, og alle data i disse organisasjonene. Dette kan ikke angres.",
|
||||||
|
"deleteAccountButton": "Slett konto",
|
||||||
|
"deleteAccountConfirmTitle": "Slett konto",
|
||||||
|
"deleteAccountConfirmMessage": "Dette vil slette kontoen din, alle organisasjoner du eier og alle data i disse organisasjonene. Dette kan ikke gjøres om.",
|
||||||
|
"deleteAccountConfirmString": "Slett konto",
|
||||||
|
"deleteAccountSuccess": "Kontoen er slettet",
|
||||||
|
"deleteAccountSuccessMessage": "Kontoen din er slettet.",
|
||||||
|
"deleteAccountError": "Kunne ikke slette konto",
|
||||||
|
"deleteAccountPreviewAccount": "Din konto",
|
||||||
|
"deleteAccountPreviewOrgs": "Organisasjoner du eier (og alle deres data)",
|
||||||
"orgMissing": "Organisasjons-ID Mangler",
|
"orgMissing": "Organisasjons-ID Mangler",
|
||||||
"orgMissingMessage": "Kan ikke regenerere invitasjon uten en organisasjons-ID.",
|
"orgMissingMessage": "Kan ikke regenerere invitasjon uten en organisasjons-ID.",
|
||||||
"accessUsersManage": "Administrer brukere",
|
"accessUsersManage": "Administrer brukere",
|
||||||
|
"accessUserManage": "Administrer brukere",
|
||||||
"accessUsersDescription": "Inviter og behandle brukere med tilgang til denne organisasjonen",
|
"accessUsersDescription": "Inviter og behandle brukere med tilgang til denne organisasjonen",
|
||||||
"accessUsersSearch": "Søk etter brukere...",
|
"accessUsersSearch": "Søk etter brukere...",
|
||||||
|
"accessUsersRoleFilterCount": "{count, plural, one {# rolle} other {# roller}}",
|
||||||
|
"accessUsersRoleFilterClear": "Fjern rollesøkefiltre",
|
||||||
"accessUserCreate": "Opprett bruker",
|
"accessUserCreate": "Opprett bruker",
|
||||||
"accessUserRemove": "Fjern bruker",
|
"accessUserRemove": "Fjern bruker",
|
||||||
"username": "Brukernavn",
|
"username": "Brukernavn",
|
||||||
@@ -310,6 +364,54 @@
|
|||||||
"apiKeysDelete": "Slett API-nøkkel",
|
"apiKeysDelete": "Slett API-nøkkel",
|
||||||
"apiKeysManage": "Administrer API-nøkler",
|
"apiKeysManage": "Administrer API-nøkler",
|
||||||
"apiKeysDescription": "API-nøkler brukes for å autentisere med integrasjons-API",
|
"apiKeysDescription": "API-nøkler brukes for å autentisere med integrasjons-API",
|
||||||
|
"provisioningKeysTitle": "Foreløpig nøkkel",
|
||||||
|
"provisioningKeysManage": "Behandle bestemmende nøkler",
|
||||||
|
"provisioningKeysDescription": "Bestemmelsesnøkler brukes til å godkjenne automatisert nettstedsløsning for din organisasjon.",
|
||||||
|
"provisioningManage": "Levering",
|
||||||
|
"provisioningDescription": "Administrer foreløpig nøkler og gjennomgå ventende nettsteder som venter på godkjenning.",
|
||||||
|
"pendingSites": "Ventende nettsteder",
|
||||||
|
"siteApproveSuccess": "Vellykket godkjenning av nettsted",
|
||||||
|
"siteApproveError": "Feil ved godkjenning av side",
|
||||||
|
"provisioningKeys": "Foreløpig nøkler",
|
||||||
|
"searchProvisioningKeys": "Søk varer i lagrings nøkler...",
|
||||||
|
"provisioningKeysAdd": "Generer fremvisende nøkkel",
|
||||||
|
"provisioningKeysErrorDelete": "Feil under sletting av foreløpig nøkkel",
|
||||||
|
"provisioningKeysErrorDeleteMessage": "Feil under sletting av foreløpig nøkkel",
|
||||||
|
"provisioningKeysQuestionRemove": "Er du sikker på at du vil fjerne denne midlertidig nøkkelen fra organisasjonen?",
|
||||||
|
"provisioningKeysMessageRemove": "Når nøkkelen er fjernet, kan den ikke lenger brukes til anleggsavsetning.",
|
||||||
|
"provisioningKeysDeleteConfirm": "Bekreft sletting av bestemmelsesnøkkel",
|
||||||
|
"provisioningKeysDelete": "Slett bestemmelsesnøkkel",
|
||||||
|
"provisioningKeysCreate": "Generer fremvisende nøkkel",
|
||||||
|
"provisioningKeysCreateDescription": "Generer en ny foreløpig nøkkel til organisasjonen",
|
||||||
|
"provisioningKeysSeeAll": "Se alle foreløpig nøkler",
|
||||||
|
"provisioningKeysSave": "Lagre den midlertidig nøkkelen",
|
||||||
|
"provisioningKeysSaveDescription": "Du kan bare se denne én gang. Kopier det til et sikkert sted.",
|
||||||
|
"provisioningKeysErrorCreate": "Feil under oppretting av foreløpig nøkkel",
|
||||||
|
"provisioningKeysList": "Ny provisorisk nøkkel",
|
||||||
|
"provisioningKeysMaxBatchSize": "Maks størrelse på bunt",
|
||||||
|
"provisioningKeysUnlimitedBatchSize": "Ubegrenset mengde bunt (ingen begrensning)",
|
||||||
|
"provisioningKeysMaxBatchUnlimited": "Ubegrenset",
|
||||||
|
"provisioningKeysMaxBatchSizeInvalid": "Angi en gyldig sjakkstørrelse (1–1 000.000).",
|
||||||
|
"provisioningKeysValidUntil": "Gyldig til",
|
||||||
|
"provisioningKeysValidUntilHint": "La stå tomt for ingen utløp.",
|
||||||
|
"provisioningKeysValidUntilInvalid": "Angi en gyldig dato og klokkeslett.",
|
||||||
|
"provisioningKeysNumUsed": "Antall ganger brukt",
|
||||||
|
"provisioningKeysLastUsed": "Sist brukt",
|
||||||
|
"provisioningKeysNoExpiry": "Ingen utløpsdato",
|
||||||
|
"provisioningKeysNeverUsed": "Aldri",
|
||||||
|
"provisioningKeysEdit": "Rediger bestemmelsesnøkkel",
|
||||||
|
"provisioningKeysEditDescription": "Oppdater maksimal størrelse for bunt og utløpstid for denne nøkkelen.",
|
||||||
|
"provisioningKeysApproveNewSites": "Godkjenn nye nettsteder",
|
||||||
|
"provisioningKeysApproveNewSitesDescription": "Godkjenn automatisk nettsteder som registrerer deg med denne nøkkelen.",
|
||||||
|
"provisioningKeysUpdateError": "Feil under oppdatering av foreløpig nøkkel",
|
||||||
|
"provisioningKeysUpdated": "Foreslå nøkkel oppdatert",
|
||||||
|
"provisioningKeysUpdatedDescription": "Dine endringer er lagret.",
|
||||||
|
"provisioningKeysBannerTitle": "Sidens bestemmende nøkler",
|
||||||
|
"provisioningKeysBannerDescription": "Generer en provisjonsnøkkel og bruk den med Newt-kontakten for automatisk opprettelse av nettsteder ved første oppstart - ingen behov for å sette opp separate legitimasjoner for hvert nettsted.",
|
||||||
|
"provisioningKeysBannerButtonText": "Lær mer",
|
||||||
|
"pendingSitesBannerTitle": "Ventende nettsteder",
|
||||||
|
"pendingSitesBannerDescription": "Nettsteder som kobler seg til ved bruk av en provisjonsnøkkel vises her for vurdering.",
|
||||||
|
"pendingSitesBannerButtonText": "Lær mer",
|
||||||
"apiKeysSettings": "{apiKeyName} Innstillinger",
|
"apiKeysSettings": "{apiKeyName} Innstillinger",
|
||||||
"userTitle": "Administrer alle brukere",
|
"userTitle": "Administrer alle brukere",
|
||||||
"userDescription": "Vis og administrer alle brukere i systemet",
|
"userDescription": "Vis og administrer alle brukere i systemet",
|
||||||
@@ -339,6 +441,10 @@
|
|||||||
"licenseErrorKeyActivate": "Aktivering av lisensnøkkel feilet",
|
"licenseErrorKeyActivate": "Aktivering av lisensnøkkel feilet",
|
||||||
"licenseErrorKeyActivateDescription": "Det oppstod en feil under aktivering av lisensnøkkelen.",
|
"licenseErrorKeyActivateDescription": "Det oppstod en feil under aktivering av lisensnøkkelen.",
|
||||||
"licenseAbout": "Om Lisensiering",
|
"licenseAbout": "Om Lisensiering",
|
||||||
|
"licenseBannerTitle": "Aktiver din bedriftslisens",
|
||||||
|
"licenseBannerDescription": "Lås opp bedriftsfunksjoner for din egenvertede Pangolin-instans. Kjøp en lisensnøkkel for å aktivere premium-funksjoner og legg den inn nedenfor.",
|
||||||
|
"licenseBannerGetLicense": "Få en lisens",
|
||||||
|
"licenseBannerViewDocs": "Vis dokumentasjon",
|
||||||
"communityEdition": "Fellesskapsutgave",
|
"communityEdition": "Fellesskapsutgave",
|
||||||
"licenseAboutDescription": "Dette er for bedrifts- og foretaksbrukere som bruker Pangolin i et kommersielt miljø. Hvis du bruker Pangolin til personlig bruk, kan du ignorere denne seksjonen.",
|
"licenseAboutDescription": "Dette er for bedrifts- og foretaksbrukere som bruker Pangolin i et kommersielt miljø. Hvis du bruker Pangolin til personlig bruk, kan du ignorere denne seksjonen.",
|
||||||
"licenseKeyActivated": "Lisensnøkkel aktivert",
|
"licenseKeyActivated": "Lisensnøkkel aktivert",
|
||||||
@@ -461,6 +567,8 @@
|
|||||||
"filterByApprovalState": "Filtrer etter godkjenningsstatus",
|
"filterByApprovalState": "Filtrer etter godkjenningsstatus",
|
||||||
"approvalListEmpty": "Ingen godkjenninger",
|
"approvalListEmpty": "Ingen godkjenninger",
|
||||||
"approvalState": "Godkjennings tilstand",
|
"approvalState": "Godkjennings tilstand",
|
||||||
|
"approvalLoadMore": "Last mer",
|
||||||
|
"loadingApprovals": "Laster inn godkjenninger",
|
||||||
"approve": "Godkjenn",
|
"approve": "Godkjenn",
|
||||||
"approved": "Godkjent",
|
"approved": "Godkjent",
|
||||||
"denied": "Avvist",
|
"denied": "Avvist",
|
||||||
@@ -494,9 +602,12 @@
|
|||||||
"userSaved": "Bruker lagret",
|
"userSaved": "Bruker lagret",
|
||||||
"userSavedDescription": "Brukeren har blitt oppdatert.",
|
"userSavedDescription": "Brukeren har blitt oppdatert.",
|
||||||
"autoProvisioned": "Auto avlyst",
|
"autoProvisioned": "Auto avlyst",
|
||||||
|
"autoProvisionSettings": "Auto leveringsinnstillinger",
|
||||||
"autoProvisionedDescription": "Tillat denne brukeren å bli automatisk administrert av en identitetsleverandør",
|
"autoProvisionedDescription": "Tillat denne brukeren å bli automatisk administrert av en identitetsleverandør",
|
||||||
"accessControlsDescription": "Administrer hva denne brukeren kan få tilgang til og gjøre i organisasjonen",
|
"accessControlsDescription": "Administrer hva denne brukeren kan få tilgang til og gjøre i organisasjonen",
|
||||||
"accessControlsSubmit": "Lagre tilgangskontroller",
|
"accessControlsSubmit": "Lagre tilgangskontroller",
|
||||||
|
"singleRolePerUserPlanNotice": "Din plan støtter bare én rolle per bruker.",
|
||||||
|
"singleRolePerUserEditionNotice": "Denne utgaven støtter bare én rolle per bruker.",
|
||||||
"roles": "Roller",
|
"roles": "Roller",
|
||||||
"accessUsersRoles": "Administrer brukere og roller",
|
"accessUsersRoles": "Administrer brukere og roller",
|
||||||
"accessUsersRolesDescription": "Inviter brukere og legg dem til roller for å administrere tilgang til organisasjonen",
|
"accessUsersRolesDescription": "Inviter brukere og legg dem til roller for å administrere tilgang til organisasjonen",
|
||||||
@@ -553,6 +664,8 @@
|
|||||||
"targetErrorInvalidPortDescription": "Vennligst skriv inn et gyldig portnummer",
|
"targetErrorInvalidPortDescription": "Vennligst skriv inn et gyldig portnummer",
|
||||||
"targetErrorNoSite": "Ingen nettsted valgt",
|
"targetErrorNoSite": "Ingen nettsted valgt",
|
||||||
"targetErrorNoSiteDescription": "Velg et nettsted for målet",
|
"targetErrorNoSiteDescription": "Velg et nettsted for målet",
|
||||||
|
"targetTargetsCleared": "Mål ryddet",
|
||||||
|
"targetTargetsClearedDescription": "Alle mål har blitt fjernet fra denne ressursen",
|
||||||
"targetCreated": "Mål opprettet",
|
"targetCreated": "Mål opprettet",
|
||||||
"targetCreatedDescription": "Målet har blitt opprettet",
|
"targetCreatedDescription": "Målet har blitt opprettet",
|
||||||
"targetErrorCreate": "Kunne ikke opprette målet",
|
"targetErrorCreate": "Kunne ikke opprette målet",
|
||||||
@@ -636,6 +749,7 @@
|
|||||||
"resourcesErrorUpdate": "Feilet å slå av/på ressurs",
|
"resourcesErrorUpdate": "Feilet å slå av/på ressurs",
|
||||||
"resourcesErrorUpdateDescription": "En feil oppstod under oppdatering av ressursen",
|
"resourcesErrorUpdateDescription": "En feil oppstod under oppdatering av ressursen",
|
||||||
"access": "Tilgang",
|
"access": "Tilgang",
|
||||||
|
"accessControl": "Tilgangskontroll",
|
||||||
"shareLink": "{resource} Del Lenke",
|
"shareLink": "{resource} Del Lenke",
|
||||||
"resourceSelect": "Velg ressurs",
|
"resourceSelect": "Velg ressurs",
|
||||||
"shareLinks": "Del lenker",
|
"shareLinks": "Del lenker",
|
||||||
@@ -653,6 +767,7 @@
|
|||||||
"newtEndpoint": "Endpoint",
|
"newtEndpoint": "Endpoint",
|
||||||
"newtId": "ID",
|
"newtId": "ID",
|
||||||
"newtSecretKey": "Sikkerhetsnøkkel",
|
"newtSecretKey": "Sikkerhetsnøkkel",
|
||||||
|
"newtVersion": "Versjon",
|
||||||
"architecture": "Arkitektur",
|
"architecture": "Arkitektur",
|
||||||
"sites": "Områder",
|
"sites": "Områder",
|
||||||
"siteWgAnyClients": "Bruk hvilken som helst WireGuard klient til å koble til. Du må adressere interne ressurser ved hjelp av peer IP.",
|
"siteWgAnyClients": "Bruk hvilken som helst WireGuard klient til å koble til. Du må adressere interne ressurser ved hjelp av peer IP.",
|
||||||
@@ -776,6 +891,7 @@
|
|||||||
"accessRoleRemoved": "Rolle fjernet",
|
"accessRoleRemoved": "Rolle fjernet",
|
||||||
"accessRoleRemovedDescription": "Rollen er vellykket fjernet.",
|
"accessRoleRemovedDescription": "Rollen er vellykket fjernet.",
|
||||||
"accessRoleRequiredRemove": "Før du sletter denne rollen, vennligst velg en ny rolle å overføre eksisterende medlemmer til.",
|
"accessRoleRequiredRemove": "Før du sletter denne rollen, vennligst velg en ny rolle å overføre eksisterende medlemmer til.",
|
||||||
|
"network": "Nettverk",
|
||||||
"manage": "Administrer",
|
"manage": "Administrer",
|
||||||
"sitesNotFound": "Ingen områder funnet.",
|
"sitesNotFound": "Ingen områder funnet.",
|
||||||
"pangolinServerAdmin": "Server Admin - Pangolin",
|
"pangolinServerAdmin": "Server Admin - Pangolin",
|
||||||
@@ -791,6 +907,9 @@
|
|||||||
"sitestCountIncrease": "Øk antall områder",
|
"sitestCountIncrease": "Øk antall områder",
|
||||||
"idpManage": "Administrer Identitetsleverandører",
|
"idpManage": "Administrer Identitetsleverandører",
|
||||||
"idpManageDescription": "Vis og administrer identitetsleverandører i systemet",
|
"idpManageDescription": "Vis og administrer identitetsleverandører i systemet",
|
||||||
|
"idpGlobalModeBanner": "Identitetsleverandører (IdPs) per organisasjon er deaktivert på denne serveren. Den bruker globale IdP (delt over alle organisasjoner). Administrer globale IdP'er i <adminPanelLink>admin-panelet</adminPanelLink>. For å aktivere IdP per organisasjon, rediger serverkonfigurasjonen og sett IdP-modus til org. <configDocsLink>Se dokumentasjonen</configDocsLink>. Hvis du vil fortsette å bruke globale IdPs og få denne til å forsvinne fra organisasjonens innstillinger, satt eksplisitt modusen til global i konfigurasjonen.",
|
||||||
|
"idpGlobalModeBannerUpgradeRequired": "Identitetsleverandører (IdPs) per organisasjon er deaktivert på denne serveren. Den bruker globale IdPs (delt på tvers av alle organisasjoner). Administrer globale IdPs i <adminPanelLink>administrasjons-panelet</adminPanelLink>. For å bruke identitetsleverandører per organisasjon, må du oppgradere til Enterprise-utgaven.",
|
||||||
|
"idpGlobalModeBannerLicenseRequired": "Identitetsleverandører (IdPs) per organisasjon er deaktivert på denne serveren. Den bruker globale IdPs (delt på tvers av alle organisasjoner). Administrer globale IdPs i <adminPanelLink>administrasjons-panelet</adminPanelLink>. For å bruke identitetsleverandører per organisasjon, kreves en Enterprise-lisens.",
|
||||||
"idpDeletedDescription": "Identitetsleverandør slettet vellykket",
|
"idpDeletedDescription": "Identitetsleverandør slettet vellykket",
|
||||||
"idpOidc": "OAuth2/OIDC",
|
"idpOidc": "OAuth2/OIDC",
|
||||||
"idpQuestionRemove": "Er du sikker på at du vil slette identitetsleverandøren permanent?",
|
"idpQuestionRemove": "Er du sikker på at du vil slette identitetsleverandøren permanent?",
|
||||||
@@ -816,6 +935,7 @@
|
|||||||
"idpDisplayName": "Et visningsnavn for denne identitetsleverandøren",
|
"idpDisplayName": "Et visningsnavn for denne identitetsleverandøren",
|
||||||
"idpAutoProvisionUsers": "Automatisk brukerklargjøring",
|
"idpAutoProvisionUsers": "Automatisk brukerklargjøring",
|
||||||
"idpAutoProvisionUsersDescription": "Når aktivert, opprettes brukere automatisk i systemet ved første innlogging, med mulighet til å tilordne brukere til roller og organisasjoner.",
|
"idpAutoProvisionUsersDescription": "Når aktivert, opprettes brukere automatisk i systemet ved første innlogging, med mulighet til å tilordne brukere til roller og organisasjoner.",
|
||||||
|
"idpAutoProvisionConfigureAfterCreate": "Du kan konfigurere autoprovisjonsinnstillingene når identitetsleverandøren er opprettet.",
|
||||||
"licenseBadge": "EE",
|
"licenseBadge": "EE",
|
||||||
"idpType": "Leverandørtype",
|
"idpType": "Leverandørtype",
|
||||||
"idpTypeDescription": "Velg typen identitetsleverandør du ønsker å konfigurere",
|
"idpTypeDescription": "Velg typen identitetsleverandør du ønsker å konfigurere",
|
||||||
@@ -867,7 +987,7 @@
|
|||||||
"defaultMappingsRole": "Standard rolletilordning",
|
"defaultMappingsRole": "Standard rolletilordning",
|
||||||
"defaultMappingsRoleDescription": "Resultatet av dette uttrykket må returnere rollenavnet slik det er definert i organisasjonen som en streng.",
|
"defaultMappingsRoleDescription": "Resultatet av dette uttrykket må returnere rollenavnet slik det er definert i organisasjonen som en streng.",
|
||||||
"defaultMappingsOrg": "Standard organisasjonstilordning",
|
"defaultMappingsOrg": "Standard organisasjonstilordning",
|
||||||
"defaultMappingsOrgDescription": "Dette uttrykket må returnere organisasjons-ID-en eller «true» for å gi brukeren tilgang til organisasjonen.",
|
"defaultMappingsOrgDescription": "Når denne er satt, må uttrykket returnere organisasjons-IDen eller «true» for at brukeren skal få tilgang til den organisasjonen. Når den ikke er satt, er det nok å definere en rolletilordning: brukeren gis tilgang så lenge en gyldig rolletilknytting kan løses for dem i organisasjonen.",
|
||||||
"defaultMappingsSubmit": "Lagre standard tilordninger",
|
"defaultMappingsSubmit": "Lagre standard tilordninger",
|
||||||
"orgPoliciesEdit": "Rediger Organisasjonspolicy",
|
"orgPoliciesEdit": "Rediger Organisasjonspolicy",
|
||||||
"org": "Organisasjon",
|
"org": "Organisasjon",
|
||||||
@@ -1014,12 +1134,12 @@
|
|||||||
"pangolinSetup": "Oppsett - Pangolin",
|
"pangolinSetup": "Oppsett - Pangolin",
|
||||||
"orgNameRequired": "Organisasjonsnavn er påkrevd",
|
"orgNameRequired": "Organisasjonsnavn er påkrevd",
|
||||||
"orgIdRequired": "Organisasjons-ID er påkrevd",
|
"orgIdRequired": "Organisasjons-ID er påkrevd",
|
||||||
|
"orgIdMaxLength": "Organisasjons-ID må maksimalt være 32 tegn",
|
||||||
"orgErrorCreate": "En feil oppstod under oppretting av organisasjon",
|
"orgErrorCreate": "En feil oppstod under oppretting av organisasjon",
|
||||||
"pageNotFound": "Siden ble ikke funnet",
|
"pageNotFound": "Siden ble ikke funnet",
|
||||||
"pageNotFoundDescription": "Oops! Siden du leter etter finnes ikke.",
|
"pageNotFoundDescription": "Oops! Siden du leter etter finnes ikke.",
|
||||||
"overview": "Oversikt",
|
"overview": "Oversikt",
|
||||||
"home": "Hjem",
|
"home": "Hjem",
|
||||||
"accessControl": "Tilgangskontroll",
|
|
||||||
"settings": "Innstillinger",
|
"settings": "Innstillinger",
|
||||||
"usersAll": "Alle brukere",
|
"usersAll": "Alle brukere",
|
||||||
"license": "Lisens",
|
"license": "Lisens",
|
||||||
@@ -1082,6 +1202,12 @@
|
|||||||
"actionGetUser": "Hent bruker",
|
"actionGetUser": "Hent bruker",
|
||||||
"actionGetOrgUser": "Hent organisasjonsbruker",
|
"actionGetOrgUser": "Hent organisasjonsbruker",
|
||||||
"actionListOrgDomains": "List opp organisasjonsdomener",
|
"actionListOrgDomains": "List opp organisasjonsdomener",
|
||||||
|
"actionGetDomain": "Få Domene",
|
||||||
|
"actionCreateOrgDomain": "Opprett domene",
|
||||||
|
"actionUpdateOrgDomain": "Oppdater domene",
|
||||||
|
"actionDeleteOrgDomain": "Slett domene",
|
||||||
|
"actionGetDNSRecords": "Hent DNS-oppføringer",
|
||||||
|
"actionRestartOrgDomain": "Omstart Domene",
|
||||||
"actionCreateSite": "Opprett område",
|
"actionCreateSite": "Opprett område",
|
||||||
"actionDeleteSite": "Slett område",
|
"actionDeleteSite": "Slett område",
|
||||||
"actionGetSite": "Hent område",
|
"actionGetSite": "Hent område",
|
||||||
@@ -1093,6 +1219,7 @@
|
|||||||
"setupTokenDescription": "Skriv inn oppsetttoken fra serverkonsollen.",
|
"setupTokenDescription": "Skriv inn oppsetttoken fra serverkonsollen.",
|
||||||
"setupTokenRequired": "Oppsetttoken er nødvendig",
|
"setupTokenRequired": "Oppsetttoken er nødvendig",
|
||||||
"actionUpdateSite": "Oppdater område",
|
"actionUpdateSite": "Oppdater område",
|
||||||
|
"actionResetSiteBandwidth": "Tilbakestill organisasjons-båndbredde",
|
||||||
"actionListSiteRoles": "List opp tillatte områderoller",
|
"actionListSiteRoles": "List opp tillatte områderoller",
|
||||||
"actionCreateResource": "Opprett ressurs",
|
"actionCreateResource": "Opprett ressurs",
|
||||||
"actionDeleteResource": "Slett ressurs",
|
"actionDeleteResource": "Slett ressurs",
|
||||||
@@ -1122,6 +1249,7 @@
|
|||||||
"actionRemoveUser": "Fjern bruker",
|
"actionRemoveUser": "Fjern bruker",
|
||||||
"actionListUsers": "List opp brukere",
|
"actionListUsers": "List opp brukere",
|
||||||
"actionAddUserRole": "Legg til brukerrolle",
|
"actionAddUserRole": "Legg til brukerrolle",
|
||||||
|
"actionSetUserOrgRoles": "Angi brukerroller",
|
||||||
"actionGenerateAccessToken": "Generer tilgangstoken",
|
"actionGenerateAccessToken": "Generer tilgangstoken",
|
||||||
"actionDeleteAccessToken": "Slett tilgangstoken",
|
"actionDeleteAccessToken": "Slett tilgangstoken",
|
||||||
"actionListAccessTokens": "List opp tilgangstokener",
|
"actionListAccessTokens": "List opp tilgangstokener",
|
||||||
@@ -1166,7 +1294,9 @@
|
|||||||
"actionViewLogs": "Vis logger",
|
"actionViewLogs": "Vis logger",
|
||||||
"noneSelected": "Ingen valgt",
|
"noneSelected": "Ingen valgt",
|
||||||
"orgNotFound2": "Ingen organisasjoner funnet.",
|
"orgNotFound2": "Ingen organisasjoner funnet.",
|
||||||
"searchProgress": "Søker...",
|
"search": "Søk…",
|
||||||
|
"searchPlaceholder": "Søk...",
|
||||||
|
"emptySearchOptions": "Ingen valg funnet",
|
||||||
"create": "Opprett",
|
"create": "Opprett",
|
||||||
"orgs": "Organisasjoner",
|
"orgs": "Organisasjoner",
|
||||||
"loginError": "En uventet feil oppstod. Vennligst prøv igjen.",
|
"loginError": "En uventet feil oppstod. Vennligst prøv igjen.",
|
||||||
@@ -1230,12 +1360,14 @@
|
|||||||
"sidebarClientResources": "Privat",
|
"sidebarClientResources": "Privat",
|
||||||
"sidebarAccessControl": "Tilgangskontroll",
|
"sidebarAccessControl": "Tilgangskontroll",
|
||||||
"sidebarLogsAndAnalytics": "Logger og analyser",
|
"sidebarLogsAndAnalytics": "Logger og analyser",
|
||||||
|
"sidebarTeam": "Lag",
|
||||||
"sidebarUsers": "Brukere",
|
"sidebarUsers": "Brukere",
|
||||||
"sidebarAdmin": "Administrator",
|
"sidebarAdmin": "Administrator",
|
||||||
"sidebarInvitations": "Invitasjoner",
|
"sidebarInvitations": "Invitasjoner",
|
||||||
"sidebarRoles": "Roller",
|
"sidebarRoles": "Roller",
|
||||||
"sidebarShareableLinks": "Lenker",
|
"sidebarShareableLinks": "Lenker",
|
||||||
"sidebarApiKeys": "API-nøkler",
|
"sidebarApiKeys": "API-nøkler",
|
||||||
|
"sidebarProvisioning": "Levering",
|
||||||
"sidebarSettings": "Innstillinger",
|
"sidebarSettings": "Innstillinger",
|
||||||
"sidebarAllUsers": "Alle brukere",
|
"sidebarAllUsers": "Alle brukere",
|
||||||
"sidebarIdentityProviders": "Identitetsleverandører",
|
"sidebarIdentityProviders": "Identitetsleverandører",
|
||||||
@@ -1247,8 +1379,167 @@
|
|||||||
"sidebarGeneral": "Administrer",
|
"sidebarGeneral": "Administrer",
|
||||||
"sidebarLogAndAnalytics": "Logg og analyser",
|
"sidebarLogAndAnalytics": "Logg og analyser",
|
||||||
"sidebarBluePrints": "Tegninger",
|
"sidebarBluePrints": "Tegninger",
|
||||||
|
"sidebarAlerting": "Varsling",
|
||||||
|
"sidebarHealthChecks": "Helsekontroller",
|
||||||
"sidebarOrganization": "Organisasjon",
|
"sidebarOrganization": "Organisasjon",
|
||||||
|
"sidebarManagement": "Administrasjon",
|
||||||
|
"sidebarBillingAndLicenses": "Fakturering & lisenser",
|
||||||
"sidebarLogsAnalytics": "Analyser",
|
"sidebarLogsAnalytics": "Analyser",
|
||||||
|
"alertingTitle": "Varsling",
|
||||||
|
"alertingDescription": "Definer kilder, triggere og handlinger for varsler",
|
||||||
|
"alertingRules": "Varslingsregler",
|
||||||
|
"alertingSearchRules": "Søk i regler…",
|
||||||
|
"alertingAddRule": "Opprett regel",
|
||||||
|
"alertingColumnSource": "Kilde",
|
||||||
|
"alertingColumnTrigger": "Utløser",
|
||||||
|
"alertingColumnActions": "Handlinger",
|
||||||
|
"alertingColumnEnabled": "Aktivert",
|
||||||
|
"alertingDeleteQuestion": "Vennligst bekreft at du vil slette denne varslingsregelen.",
|
||||||
|
"alertingDeleteRule": "Slett varslingsregel",
|
||||||
|
"alertingRuleDeleted": "Varslingsregel slettet",
|
||||||
|
"alertingRuleSaved": "Varslingsregel lagret",
|
||||||
|
"alertingRuleSavedCreatedDescription": "Din nye varslingsregel ble opprettet. Du kan fortsette å redigere den på denne siden.",
|
||||||
|
"alertingRuleSavedUpdatedDescription": "Endringene dine i denne varslingsregelen ble lagret.",
|
||||||
|
"alertingEditRule": "Rediger varslingsregel",
|
||||||
|
"alertingCreateRule": "Opprett varslingsregel",
|
||||||
|
"alertingRuleCredenzaDescription": "Velg hva som skal overvåkes, når det skal varsles, og hvordan du vil bli informert",
|
||||||
|
"alertingRuleNamePlaceholder": "Produksjonsside nede",
|
||||||
|
"alertingRuleEnabled": "Regel aktivert",
|
||||||
|
"alertingSectionSource": "Kilde",
|
||||||
|
"alertingSourceType": "Kildetype",
|
||||||
|
"alertingSourceSite": "Område",
|
||||||
|
"alertingSourceHealthCheck": "Helsekontroll",
|
||||||
|
"alertingPickSites": "Områder",
|
||||||
|
"alertingPickHealthChecks": "Helsekontroller",
|
||||||
|
"alertingPickResources": "Ressurser",
|
||||||
|
"alertingAllSites": "Alle områder",
|
||||||
|
"alertingAllSitesDescription": "Varsler for alle områder",
|
||||||
|
"alertingSpecificSites": "Spesifikke områder",
|
||||||
|
"alertingSpecificSitesDescription": "Velg spesifikke områder for overvåking",
|
||||||
|
"alertingAllHealthChecks": "Alle helsekontroller",
|
||||||
|
"alertingAllHealthChecksDescription": "Varsler for alle helsekontroller",
|
||||||
|
"alertingSpecificHealthChecks": "Spesifikke helsekontroller",
|
||||||
|
"alertingSpecificHealthChecksDescription": "Velg spesifikke helsekontroller for overvåking",
|
||||||
|
"alertingAllResources": "Alle ressurser",
|
||||||
|
"alertingAllResourcesDescription": "Varsler for alle ressurser",
|
||||||
|
"alertingSpecificResources": "Spesifikke ressurser",
|
||||||
|
"alertingSpecificResourcesDescription": "Velg spesifikke ressurser for overvåking",
|
||||||
|
"alertingSelectResources": "Velg ressurser…",
|
||||||
|
"alertingResourcesSelected": "{count} ressurser valgt",
|
||||||
|
"alertingResourcesEmpty": "Ingen ressurser med mål i de første 10 resultatene.",
|
||||||
|
"alertingSectionTrigger": "Utløser",
|
||||||
|
"alertingTrigger": "Når skal det varsles",
|
||||||
|
"alertingTriggerSiteOnline": "Nettsted er online",
|
||||||
|
"alertingTriggerSiteOffline": "Nettsted er offline",
|
||||||
|
"alertingTriggerSiteToggle": "Endringer i nettstedstatus",
|
||||||
|
"alertingTriggerHcHealthy": "Helsekontroll sunn",
|
||||||
|
"alertingTriggerHcUnhealthy": "Helsekontroll usunn",
|
||||||
|
"alertingTriggerHcToggle": "Endringer i helsekontrollstatus",
|
||||||
|
"alertingTriggerResourceHealthy": "Ressurs sunn",
|
||||||
|
"alertingTriggerResourceUnhealthy": "Ressurs usunn",
|
||||||
|
"alertingTriggerResourceDegraded": "Ressurs forringet",
|
||||||
|
"alertingSearchHealthChecks": "Søk i helsekontroller…",
|
||||||
|
"alertingHealthChecksEmpty": "Ingen tilgjengelige helsekontroller.",
|
||||||
|
"alertingTriggerResourceToggle": "Endringer i ressursstatus",
|
||||||
|
"alertingSourceResource": "Ressurs",
|
||||||
|
"alertingSectionActions": "Handlinger",
|
||||||
|
"alertingAddAction": "Legg til handling",
|
||||||
|
"alertingActionNotify": "E-post",
|
||||||
|
"alertingActionNotifyDescription": "Send e-postvarsler til brukere eller roller",
|
||||||
|
"alertingActionWebhook": "Webhook",
|
||||||
|
"alertingActionWebhookDescription": "Send en HTTP-forespørsel til et tilpasset endepunkt",
|
||||||
|
"alertingExternalIntegration": "Ekstern integrasjon",
|
||||||
|
"alertingExternalPagerDutyDescription": "Send varsler til PagerDuty for hendelseshåndtering",
|
||||||
|
"alertingExternalOpsgenieDescription": "Rute varsler til Opsgenie for vakt håndtering",
|
||||||
|
"alertingExternalServiceNowDescription": "Opprett ServiceNow hendelser fra varslingseventer",
|
||||||
|
"alertingExternalIncidentIoDescription": "Utløs Incident.io arbeidsflyter fra varsels begivenheter",
|
||||||
|
"alertingActionType": "Handlings type",
|
||||||
|
"alertingNotifyUsers": "Brukere",
|
||||||
|
"alertingNotifyRoles": "Roller",
|
||||||
|
"alertingNotifyEmails": "E-postadresser",
|
||||||
|
"alertingEmailPlaceholder": "Legg til e-post og trykk Enter",
|
||||||
|
"alertingWebhookMethod": "HTTP-metode",
|
||||||
|
"alertingWebhookSecret": "Signeringshemmelig (valgfritt)",
|
||||||
|
"alertingWebhookSecretPlaceholder": "HMAC-hemmelig",
|
||||||
|
"alertingWebhookHeaders": "Overskrifter",
|
||||||
|
"alertingAddHeader": "Legg til header",
|
||||||
|
"alertingSelectSites": "Velg områder…",
|
||||||
|
"alertingSitesSelected": "{count} områder valgt",
|
||||||
|
"alertingSelectHealthChecks": "Velg helsekontroller…",
|
||||||
|
"alertingHealthChecksSelected": "{count} helsekontroller valgt",
|
||||||
|
"alertingNoHealthChecks": "Ingen mål med helsekontroller aktivert",
|
||||||
|
"alertingHealthCheckStub": "Valg av helsekontrollkilde er ikke sluttført ennå - du kan fortsatt konfigurere triggere og handlinger.",
|
||||||
|
"alertingSelectUsers": "Velg brukere…",
|
||||||
|
"alertingUsersSelected": "{count} brukere valgt",
|
||||||
|
"alertingSelectRoles": "Velg roller…",
|
||||||
|
"alertingRolesSelected": "{count} roller valgt",
|
||||||
|
"alertingSummarySites": "Områder ({count})",
|
||||||
|
"alertingSummaryAllSites": "Alle områder",
|
||||||
|
"alertingSummaryHealthChecks": "Helsekontroller ({count})",
|
||||||
|
"alertingSummaryAllHealthChecks": "Alle helsekoner",
|
||||||
|
"alertingSummaryResources": "Ressurser ({count})",
|
||||||
|
"alertingSummaryAllResources": "Alle ressurser",
|
||||||
|
"alertingErrorNameRequired": "Skriv inn et navn",
|
||||||
|
"alertingErrorActionsMin": "Legg til minst én handling",
|
||||||
|
"alertingErrorPickSites": "Velg minst ett område",
|
||||||
|
"alertingErrorPickHealthChecks": "Velg minst én helsekontroll",
|
||||||
|
"alertingErrorPickResources": "Velg minst én ressurs",
|
||||||
|
"alertingErrorTriggerSite": "Velg en triggetjeneste for nettsted",
|
||||||
|
"alertingErrorTriggerHealth": "Velg en triggetjeneste for helsekontroll",
|
||||||
|
"alertingErrorTriggerResource": "Velg en triggetjeneste for ressurs",
|
||||||
|
"alertingErrorNotifyRecipients": "Velg brukere, roller, eller minst én e-post",
|
||||||
|
"alertingConfigureSource": "Konfigurer kilde",
|
||||||
|
"alertingConfigureTrigger": "Konfigurer trigger",
|
||||||
|
"alertingConfigureActions": "Konfigurer handlinger",
|
||||||
|
"alertingBackToRules": "Tilbake til regler",
|
||||||
|
"alertingRuleCooldown": "Nedkjøling (sekunder)",
|
||||||
|
"alertingRuleCooldownDescription": "Minimum tid mellom gjentatte varsler for samme regel. Sett til 0 for å skyte hver gang.",
|
||||||
|
"alertingDraftBadge": "Utkast - lagre for å lagre denne regelen",
|
||||||
|
"alertingSidebarHint": "Klikk på et steg på lerretet for å redigere det her.",
|
||||||
|
"alertingGraphCanvasTitle": "Regel Flyt",
|
||||||
|
"alertingGraphCanvasDescription": "Visuell oversikt over kilde, trigger og handlinger. Velg en node for å redigere den i panelet.",
|
||||||
|
"alertingNodeNotConfigured": "Ikke konfigurert ennå",
|
||||||
|
"alertingNodeActionsCount": "{count, plural, one {# handling} other {# handlinger}}",
|
||||||
|
"alertingNodeRoleSource": "Kilde",
|
||||||
|
"alertingNodeRoleTrigger": "Utløser",
|
||||||
|
"alertingNodeRoleAction": "Handling",
|
||||||
|
"alertingTabRules": "Varslingsregler",
|
||||||
|
"alertingTabHealthChecks": "Helsekontroller",
|
||||||
|
"alertingRulesBannerTitle": "Bli varslet",
|
||||||
|
"alertingRulesBannerDescription": "Hver regel binder sammen hva som skal overvåkes (et område, helsekontroll eller ressurs), når det skal varsles (for eksempel offline eller usunn), og hvordan varsle teamet ditt via e-post, webhooks eller integrasjoner. Bruk denne listen for å opprette, aktivere og administrere disse reglene.",
|
||||||
|
"alertingHealthChecksBannerTitle": "Overvåk helse & ressurser",
|
||||||
|
"alertingHealthChecksBannerDescription": "Helsekontroller er HTTP- eller TCP-monitorer du definerer én gang. Du kan deretter bruke dem som kilder i varslingsregler slik at du blir varslet når et mål blir sunt eller usunt. Helsekontroller på ressurser vises også her.",
|
||||||
|
"standaloneHcTableTitle": "Helsekontroller",
|
||||||
|
"standaloneHcSearchPlaceholder": "Søk i helsekontroller…",
|
||||||
|
"standaloneHcAddButton": "Opprett helsekontroll",
|
||||||
|
"standaloneHcCreateTitle": "Opprett helsekontroll",
|
||||||
|
"standaloneHcEditTitle": "Rediger helsekontroll",
|
||||||
|
"standaloneHcDescription": "Konfigurer en HTTP- eller TCP-helsekontroll for bruk i varslingsregler.",
|
||||||
|
"standaloneHcNameLabel": "Navn",
|
||||||
|
"standaloneHcNamePlaceholder": "Min HTTP-monitor",
|
||||||
|
"standaloneHcDeleteTitle": "Slett helsekontroll",
|
||||||
|
"standaloneHcDeleteQuestion": "Vennligst bekreft at du vil slette denne helsekontrollen.",
|
||||||
|
"standaloneHcDeleted": "Helsekontroll slettet",
|
||||||
|
"standaloneHcSaved": "Helsekontroll lagret",
|
||||||
|
"standaloneHcColumnHealth": "Helse",
|
||||||
|
"standaloneHcColumnMode": "Modus",
|
||||||
|
"standaloneHcColumnTarget": "Mål",
|
||||||
|
"standaloneHcHealthStateHealthy": "Sunn",
|
||||||
|
"standaloneHcHealthStateUnhealthy": "Usunn",
|
||||||
|
"standaloneHcHealthStateUnknown": "Ukjent",
|
||||||
|
"standaloneHcFilterAnySite": "Alle områder",
|
||||||
|
"standaloneHcFilterAnyResource": "Alle ressurser",
|
||||||
|
"standaloneHcFilterMode": "Modus",
|
||||||
|
"standaloneHcFilterModeHttp": "HTTP",
|
||||||
|
"standaloneHcFilterModeTcp": "TCP",
|
||||||
|
"standaloneHcFilterModeSnmp": "SNMP",
|
||||||
|
"standaloneHcFilterModePing": "Ping",
|
||||||
|
"standaloneHcFilterHealth": "Helse",
|
||||||
|
"standaloneHcFilterEnabled": "Aktivert",
|
||||||
|
"standaloneHcFilterEnabledOn": "Aktivert",
|
||||||
|
"standaloneHcFilterEnabledOff": "Deaktivert",
|
||||||
|
"standaloneHcFilterSiteIdFallback": "Område {id}",
|
||||||
|
"standaloneHcFilterResourceIdFallback": "Ressurs {id}",
|
||||||
"blueprints": "Tegninger",
|
"blueprints": "Tegninger",
|
||||||
"blueprintsDescription": "Bruk deklarative konfigurasjoner og vis tidligere kjøringer",
|
"blueprintsDescription": "Bruk deklarative konfigurasjoner og vis tidligere kjøringer",
|
||||||
"blueprintAdd": "Legg til blåkopi",
|
"blueprintAdd": "Legg til blåkopi",
|
||||||
@@ -1269,7 +1560,6 @@
|
|||||||
"parsedContents": "Parastinnhold (kun lese)",
|
"parsedContents": "Parastinnhold (kun lese)",
|
||||||
"enableDockerSocket": "Aktiver Docker blåkopi",
|
"enableDockerSocket": "Aktiver Docker blåkopi",
|
||||||
"enableDockerSocketDescription": "Aktiver skraping av Docker Socket for blueprint Etiketter. Socket bane må brukes for nye.",
|
"enableDockerSocketDescription": "Aktiver skraping av Docker Socket for blueprint Etiketter. Socket bane må brukes for nye.",
|
||||||
"enableDockerSocketLink": "Lær mer",
|
|
||||||
"viewDockerContainers": "Vis Docker-containere",
|
"viewDockerContainers": "Vis Docker-containere",
|
||||||
"containersIn": "Containere i {siteName}",
|
"containersIn": "Containere i {siteName}",
|
||||||
"selectContainerDescription": "Velg en hvilken som helst container for å bruke som vertsnavn for dette målet. Klikk på en port for å bruke en port.",
|
"selectContainerDescription": "Velg en hvilken som helst container for å bruke som vertsnavn for dette målet. Klikk på en port for å bruke en port.",
|
||||||
@@ -1311,7 +1601,8 @@
|
|||||||
"initialSetupDescription": "Opprett den første serveradministratorkontoen. Det kan bare finnes én serveradministrator. Du kan alltid endre denne påloggingsinformasjonen senere.",
|
"initialSetupDescription": "Opprett den første serveradministratorkontoen. Det kan bare finnes én serveradministrator. Du kan alltid endre denne påloggingsinformasjonen senere.",
|
||||||
"createAdminAccount": "Opprett administratorkonto",
|
"createAdminAccount": "Opprett administratorkonto",
|
||||||
"setupErrorCreateAdmin": "En feil oppstod under opprettelsen av serveradministratorkontoen.",
|
"setupErrorCreateAdmin": "En feil oppstod under opprettelsen av serveradministratorkontoen.",
|
||||||
"certificateStatus": "Sertifikatstatus",
|
"certificateStatus": "Sertifikat",
|
||||||
|
"certificateStatusAutoRefreshHint": "Status oppdateres automatisk.",
|
||||||
"loading": "Laster inn",
|
"loading": "Laster inn",
|
||||||
"loadingAnalytics": "Laster inn analyser",
|
"loadingAnalytics": "Laster inn analyser",
|
||||||
"restart": "Start på nytt",
|
"restart": "Start på nytt",
|
||||||
@@ -1380,6 +1671,7 @@
|
|||||||
"pangolinUpdateAvailableReleaseNotes": "Se utgivelsesnotater",
|
"pangolinUpdateAvailableReleaseNotes": "Se utgivelsesnotater",
|
||||||
"newtUpdateAvailable": "Oppdatering tilgjengelig",
|
"newtUpdateAvailable": "Oppdatering tilgjengelig",
|
||||||
"newtUpdateAvailableInfo": "En ny versjon av Newt er tilgjengelig. Vennligst oppdater til den nyeste versjonen for den beste opplevelsen.",
|
"newtUpdateAvailableInfo": "En ny versjon av Newt er tilgjengelig. Vennligst oppdater til den nyeste versjonen for den beste opplevelsen.",
|
||||||
|
"pangolinNodeUpdateAvailableInfo": "En ny versjon av Pangolin Node er tilgjengelig. Vennligst oppdater til den nyeste versjonen for den beste opplevelsen.",
|
||||||
"domainPickerEnterDomain": "Domene",
|
"domainPickerEnterDomain": "Domene",
|
||||||
"domainPickerPlaceholder": "minapp.eksempel.no",
|
"domainPickerPlaceholder": "minapp.eksempel.no",
|
||||||
"domainPickerDescription": "Skriv inn hele domenet til ressursen for å se tilgjengelige alternativer.",
|
"domainPickerDescription": "Skriv inn hele domenet til ressursen for å se tilgjengelige alternativer.",
|
||||||
@@ -1397,6 +1689,7 @@
|
|||||||
"domainPickerNamespace": "Navnerom: {namespace}",
|
"domainPickerNamespace": "Navnerom: {namespace}",
|
||||||
"domainPickerShowMore": "Vis mer",
|
"domainPickerShowMore": "Vis mer",
|
||||||
"regionSelectorTitle": "Velg Region",
|
"regionSelectorTitle": "Velg Region",
|
||||||
|
"domainPickerRemoteExitNodeWarning": "Tilbudte domener støttes ikke når sider kobles til eksterne avkjøringsnoder. For ressurser som skal være tilgjengelige på eksterne noder, brukes et egendefinert domene i stedet.",
|
||||||
"regionSelectorInfo": "Å velge en region hjelper oss med å gi bedre ytelse for din lokasjon. Du trenger ikke være i samme region som serveren.",
|
"regionSelectorInfo": "Å velge en region hjelper oss med å gi bedre ytelse for din lokasjon. Du trenger ikke være i samme region som serveren.",
|
||||||
"regionSelectorPlaceholder": "Velg en region",
|
"regionSelectorPlaceholder": "Velg en region",
|
||||||
"regionSelectorComingSoon": "Kommer snart",
|
"regionSelectorComingSoon": "Kommer snart",
|
||||||
@@ -1409,6 +1702,7 @@
|
|||||||
"billingSites": "Områder",
|
"billingSites": "Områder",
|
||||||
"billingUsers": "Brukere",
|
"billingUsers": "Brukere",
|
||||||
"billingDomains": "Domener",
|
"billingDomains": "Domener",
|
||||||
|
"billingOrganizations": "Orger",
|
||||||
"billingRemoteExitNodes": "Eksterne Noder",
|
"billingRemoteExitNodes": "Eksterne Noder",
|
||||||
"billingNoLimitConfigured": "Ingen grense konfigurert",
|
"billingNoLimitConfigured": "Ingen grense konfigurert",
|
||||||
"billingEstimatedPeriod": "Estimert faktureringsperiode",
|
"billingEstimatedPeriod": "Estimert faktureringsperiode",
|
||||||
@@ -1451,6 +1745,7 @@
|
|||||||
"failed": "Mislyktes",
|
"failed": "Mislyktes",
|
||||||
"createNewOrgDescription": "Opprett en ny organisasjon",
|
"createNewOrgDescription": "Opprett en ny organisasjon",
|
||||||
"organization": "Organisasjon",
|
"organization": "Organisasjon",
|
||||||
|
"primary": "Primær",
|
||||||
"port": "Port",
|
"port": "Port",
|
||||||
"securityKeyManage": "Administrer sikkerhetsnøkler",
|
"securityKeyManage": "Administrer sikkerhetsnøkler",
|
||||||
"securityKeyDescription": "Legg til eller fjern sikkerhetsnøkler for passordløs autentisering",
|
"securityKeyDescription": "Legg til eller fjern sikkerhetsnøkler for passordløs autentisering",
|
||||||
@@ -1548,6 +1843,16 @@
|
|||||||
"billingFeatureLossWarning": "Fremhev tilgjengelig varsel",
|
"billingFeatureLossWarning": "Fremhev tilgjengelig varsel",
|
||||||
"billingFeatureLossDescription": "Ved å nedgradere vil funksjoner som ikke er tilgjengelige i den nye planen automatisk bli deaktivert. Noen innstillinger og konfigurasjoner kan gå tapt. Vennligst gjennomgå prismatrisen for å forstå hvilke funksjoner som ikke lenger vil være tilgjengelige.",
|
"billingFeatureLossDescription": "Ved å nedgradere vil funksjoner som ikke er tilgjengelige i den nye planen automatisk bli deaktivert. Noen innstillinger og konfigurasjoner kan gå tapt. Vennligst gjennomgå prismatrisen for å forstå hvilke funksjoner som ikke lenger vil være tilgjengelige.",
|
||||||
"billingUsageExceedsLimit": "Gjeldende bruk ({current}) overskrider grensen ({limit})",
|
"billingUsageExceedsLimit": "Gjeldende bruk ({current}) overskrider grensen ({limit})",
|
||||||
|
"billingPastDueTitle": "Betalingen har forfalt",
|
||||||
|
"billingPastDueDescription": "Betalingen er forfalt. Vennligst oppdater betalingsmetoden din for å fortsette å bruke den gjeldende funksjonsplanen din. Hvis du ikke har løst deg, vil abonnementet ditt avbrytes, og du vil bli tilbakestilt til gratistiden.",
|
||||||
|
"billingUnpaidTitle": "Abonnement ubetalt",
|
||||||
|
"billingUnpaidDescription": "Ditt abonnement er ubetalt og du har blitt tilbakestilt til gratis kasse. Vennligst oppdater din betalingsmetode for å gjenopprette abonnementet.",
|
||||||
|
"billingIncompleteTitle": "Betaling ufullstendig",
|
||||||
|
"billingIncompleteDescription": "Betalingen er ufullstendig. Vennligst fullfør betalingsprosessen for å aktivere abonnementet.",
|
||||||
|
"billingIncompleteExpiredTitle": "Betaling utløpt",
|
||||||
|
"billingIncompleteExpiredDescription": "Din betaling ble aldri fullført, og har utløpt. Du har blitt tilbakestilt til gratis dekk. Vennligst abonner på nytt for å gjenopprette tilgangen til betalte funksjoner.",
|
||||||
|
"billingManageSubscription": "Administrere ditt abonnement",
|
||||||
|
"billingResolvePaymentIssue": "Vennligst løs ditt betalingsproblem før du oppgraderer eller nedgraderer betalingen",
|
||||||
"signUpTerms": {
|
"signUpTerms": {
|
||||||
"IAgreeToThe": "Jeg godtar",
|
"IAgreeToThe": "Jeg godtar",
|
||||||
"termsOfService": "brukervilkårene",
|
"termsOfService": "brukervilkårene",
|
||||||
@@ -1606,6 +1911,7 @@
|
|||||||
"configureHealthCheck": "Konfigurer Helsekontroll",
|
"configureHealthCheck": "Konfigurer Helsekontroll",
|
||||||
"configureHealthCheckDescription": "Sett opp helsekontroll for {target}",
|
"configureHealthCheckDescription": "Sett opp helsekontroll for {target}",
|
||||||
"enableHealthChecks": "Aktiver Helsekontroller",
|
"enableHealthChecks": "Aktiver Helsekontroller",
|
||||||
|
"healthCheckDisabledStateDescription": "Når deaktivert, vil ikke nettstedet utføre helsekontroller, og tilstanden vil anses som ukjent.",
|
||||||
"enableHealthChecksDescription": "Overvåk helsen til dette målet. Du kan overvåke et annet endepunkt enn målet hvis nødvendig.",
|
"enableHealthChecksDescription": "Overvåk helsen til dette målet. Du kan overvåke et annet endepunkt enn målet hvis nødvendig.",
|
||||||
"healthScheme": "Metode",
|
"healthScheme": "Metode",
|
||||||
"healthSelectScheme": "Velg metode",
|
"healthSelectScheme": "Velg metode",
|
||||||
@@ -1621,6 +1927,24 @@
|
|||||||
"timeIsInSeconds": "Tid er i sekunder",
|
"timeIsInSeconds": "Tid er i sekunder",
|
||||||
"requireDeviceApproval": "Krev enhetsgodkjenning",
|
"requireDeviceApproval": "Krev enhetsgodkjenning",
|
||||||
"requireDeviceApprovalDescription": "Brukere med denne rollen trenger nye enheter godkjent av en admin før de kan koble seg og få tilgang til ressurser.",
|
"requireDeviceApprovalDescription": "Brukere med denne rollen trenger nye enheter godkjent av en admin før de kan koble seg og få tilgang til ressurser.",
|
||||||
|
"sshAccess": "SSH tilgang",
|
||||||
|
"roleAllowSsh": "Tillat SSH",
|
||||||
|
"roleAllowSshAllow": "Tillat",
|
||||||
|
"roleAllowSshDisallow": "Forby",
|
||||||
|
"roleAllowSshDescription": "Tillat brukere med denne rollen å koble til ressurser via SSH. Når deaktivert får rollen ikke tilgang til SSH.",
|
||||||
|
"sshSudoMode": "Sudo tilgang",
|
||||||
|
"sshSudoModeNone": "Ingen",
|
||||||
|
"sshSudoModeNoneDescription": "Brukeren kan ikke kjøre kommandoer med sudo.",
|
||||||
|
"sshSudoModeFull": "Full Sudo",
|
||||||
|
"sshSudoModeFullDescription": "Brukeren kan kjøre hvilken som helst kommando med sudo.",
|
||||||
|
"sshSudoModeCommands": "Kommandoer",
|
||||||
|
"sshSudoModeCommandsDescription": "Brukeren kan bare kjøre de angitte kommandoene med sudo.",
|
||||||
|
"sshSudo": "Tillat sudo",
|
||||||
|
"sshSudoCommands": "Sudo kommandoer",
|
||||||
|
"sshSudoCommandsDescription": "Kommaseparert liste med kommandoer brukeren kan kjøre med sudo.",
|
||||||
|
"sshCreateHomeDir": "Opprett hjemmappe",
|
||||||
|
"sshUnixGroups": "Unix grupper",
|
||||||
|
"sshUnixGroupsDescription": "Kommaseparerte Unix grupper for å legge brukeren til på mål-verten.",
|
||||||
"retryAttempts": "Forsøk på nytt",
|
"retryAttempts": "Forsøk på nytt",
|
||||||
"expectedResponseCodes": "Forventede svarkoder",
|
"expectedResponseCodes": "Forventede svarkoder",
|
||||||
"expectedResponseCodesDescription": "HTTP-statuskode som indikerer sunn status. Hvis den blir stående tom, regnes 200-300 som sunn.",
|
"expectedResponseCodesDescription": "HTTP-statuskode som indikerer sunn status. Hvis den blir stående tom, regnes 200-300 som sunn.",
|
||||||
@@ -1637,9 +1961,20 @@
|
|||||||
"healthCheckIntervalMin": "Sjekkeintervallet må være minst 5 sekunder",
|
"healthCheckIntervalMin": "Sjekkeintervallet må være minst 5 sekunder",
|
||||||
"healthCheckTimeoutMin": "Timeout må være minst 1 sekund",
|
"healthCheckTimeoutMin": "Timeout må være minst 1 sekund",
|
||||||
"healthCheckRetryMin": "Forsøk på nytt må være minst 1",
|
"healthCheckRetryMin": "Forsøk på nytt må være minst 1",
|
||||||
|
"healthCheckMode": "Sjekk modus",
|
||||||
|
"healthCheckStrategy": "Strategi",
|
||||||
|
"healthCheckModeDescription": "TCP-modus verifiserer kun tilkobling. HTTP-modus validerer HTTP-responsen.",
|
||||||
|
"healthyThreshold": "Sunnhets terskel",
|
||||||
|
"healthyThresholdDescription": "Suksesser på rad som kreves før man markerer som sunn.",
|
||||||
|
"unhealthyThreshold": "Usunn terskel",
|
||||||
|
"unhealthyThresholdDescription": "Feil på rad som kreves før man markerer som usunn.",
|
||||||
|
"healthCheckHealthyThresholdMin": "Sunnhet terskel må være minst 1",
|
||||||
|
"healthCheckUnhealthyThresholdMin": "Usunn terskel må være minst 1",
|
||||||
"httpMethod": "HTTP-metode",
|
"httpMethod": "HTTP-metode",
|
||||||
"selectHttpMethod": "Velg HTTP-metode",
|
"selectHttpMethod": "Velg HTTP-metode",
|
||||||
"domainPickerSubdomainLabel": "Underdomene",
|
"domainPickerSubdomainLabel": "Underdomene",
|
||||||
|
"domainPickerWildcard": "Jokertegn",
|
||||||
|
"domainPickerWildcardPaidOnly": "Jokertegnsubdomener er en betalt funksjon. Vennligst oppgrader for å få tilgang til denne funksjonen.",
|
||||||
"domainPickerBaseDomainLabel": "Grunndomene",
|
"domainPickerBaseDomainLabel": "Grunndomene",
|
||||||
"domainPickerSearchDomains": "Søk i domener...",
|
"domainPickerSearchDomains": "Søk i domener...",
|
||||||
"domainPickerNoDomainsFound": "Ingen domener funnet",
|
"domainPickerNoDomainsFound": "Ingen domener funnet",
|
||||||
@@ -1665,12 +2000,12 @@
|
|||||||
"resourcesTableAliasAddressInfo": "Denne adressen er en del av organisasjonens undernettverk. Den brukes til å løse aliasposter ved hjelp av intern DNS-oppløsning.",
|
"resourcesTableAliasAddressInfo": "Denne adressen er en del av organisasjonens undernettverk. Den brukes til å løse aliasposter ved hjelp av intern DNS-oppløsning.",
|
||||||
"resourcesTableClients": "Klienter",
|
"resourcesTableClients": "Klienter",
|
||||||
"resourcesTableAndOnlyAccessibleInternally": "og er kun tilgjengelig internt når de er koblet til med en klient.",
|
"resourcesTableAndOnlyAccessibleInternally": "og er kun tilgjengelig internt når de er koblet til med en klient.",
|
||||||
"resourcesTableNoTargets": "Ingen mål",
|
|
||||||
"resourcesTableHealthy": "Frisk",
|
"resourcesTableHealthy": "Frisk",
|
||||||
"resourcesTableDegraded": "Nedgradert",
|
"resourcesTableDegraded": "Nedgradert",
|
||||||
"resourcesTableOffline": "Frakoblet",
|
"resourcesTableUnhealthy": "Usunn",
|
||||||
"resourcesTableUnknown": "Ukjent",
|
"resourcesTableUnknown": "Ukjent",
|
||||||
"resourcesTableNotMonitored": "Ikke overvåket",
|
"resourcesTableNotMonitored": "Ikke overvåket",
|
||||||
|
"resourcesTableNoTargets": "Ingen mål",
|
||||||
"editInternalResourceDialogEditClientResource": "Rediger Private Ressurser",
|
"editInternalResourceDialogEditClientResource": "Rediger Private Ressurser",
|
||||||
"editInternalResourceDialogUpdateResourceProperties": "Oppdater ressurskonfigurasjonen og få tilgangskontroller for {resourceName}",
|
"editInternalResourceDialogUpdateResourceProperties": "Oppdater ressurskonfigurasjonen og få tilgangskontroller for {resourceName}",
|
||||||
"editInternalResourceDialogResourceProperties": "Ressursegenskaper",
|
"editInternalResourceDialogResourceProperties": "Ressursegenskaper",
|
||||||
@@ -1696,6 +2031,11 @@
|
|||||||
"editInternalResourceDialogModePort": "Port",
|
"editInternalResourceDialogModePort": "Port",
|
||||||
"editInternalResourceDialogModeHost": "Vert",
|
"editInternalResourceDialogModeHost": "Vert",
|
||||||
"editInternalResourceDialogModeCidr": "CIDR",
|
"editInternalResourceDialogModeCidr": "CIDR",
|
||||||
|
"editInternalResourceDialogModeHttp": "HTTP",
|
||||||
|
"editInternalResourceDialogModeHttps": "HTTPS",
|
||||||
|
"editInternalResourceDialogScheme": "Skjema",
|
||||||
|
"editInternalResourceDialogEnableSsl": "Aktiver SSL",
|
||||||
|
"editInternalResourceDialogEnableSslDescription": "Aktiver SSL/TLS-kryptering for sikre HTTPS-tilkoblinger til destinasjonen.",
|
||||||
"editInternalResourceDialogDestination": "Destinasjon",
|
"editInternalResourceDialogDestination": "Destinasjon",
|
||||||
"editInternalResourceDialogDestinationHostDescription": "IP-adressen eller vertsnavnet til ressursen på nettstedets nettverk.",
|
"editInternalResourceDialogDestinationHostDescription": "IP-adressen eller vertsnavnet til ressursen på nettstedets nettverk.",
|
||||||
"editInternalResourceDialogDestinationIPDescription": "IP eller vertsnavn til ressursen på nettstedets nettverk.",
|
"editInternalResourceDialogDestinationIPDescription": "IP eller vertsnavn til ressursen på nettstedets nettverk.",
|
||||||
@@ -1711,6 +2051,7 @@
|
|||||||
"createInternalResourceDialogName": "Navn",
|
"createInternalResourceDialogName": "Navn",
|
||||||
"createInternalResourceDialogSite": "Område",
|
"createInternalResourceDialogSite": "Område",
|
||||||
"selectSite": "Velg område...",
|
"selectSite": "Velg område...",
|
||||||
|
"multiSitesSelectorSitesCount": "{count, plural, one {# sted} other {# steder}}",
|
||||||
"noSitesFound": "Ingen områder funnet.",
|
"noSitesFound": "Ingen områder funnet.",
|
||||||
"createInternalResourceDialogProtocol": "Protokoll",
|
"createInternalResourceDialogProtocol": "Protokoll",
|
||||||
"createInternalResourceDialogTcp": "TCP",
|
"createInternalResourceDialogTcp": "TCP",
|
||||||
@@ -1739,11 +2080,19 @@
|
|||||||
"createInternalResourceDialogModePort": "Port",
|
"createInternalResourceDialogModePort": "Port",
|
||||||
"createInternalResourceDialogModeHost": "Vert",
|
"createInternalResourceDialogModeHost": "Vert",
|
||||||
"createInternalResourceDialogModeCidr": "CIDR",
|
"createInternalResourceDialogModeCidr": "CIDR",
|
||||||
|
"createInternalResourceDialogModeHttp": "HTTP",
|
||||||
|
"createInternalResourceDialogModeHttps": "HTTPS",
|
||||||
|
"scheme": "Skjema",
|
||||||
|
"createInternalResourceDialogScheme": "Skjema",
|
||||||
|
"createInternalResourceDialogEnableSsl": "Aktiver SSL",
|
||||||
|
"createInternalResourceDialogEnableSslDescription": "Aktiver SSL/TLS-kryptering for sikre HTTPS-tilkoblinger til destinasjonen.",
|
||||||
"createInternalResourceDialogDestination": "Destinasjon",
|
"createInternalResourceDialogDestination": "Destinasjon",
|
||||||
"createInternalResourceDialogDestinationHostDescription": "IP-adressen eller vertsnavnet til ressursen på nettstedets nettverk.",
|
"createInternalResourceDialogDestinationHostDescription": "IP-adressen eller vertsnavnet til ressursen på nettstedets nettverk.",
|
||||||
"createInternalResourceDialogDestinationCidrDescription": "CIDR-rekkevidden til ressursen på nettstedets nettverk.",
|
"createInternalResourceDialogDestinationCidrDescription": "CIDR-rekkevidden til ressursen på nettstedets nettverk.",
|
||||||
"createInternalResourceDialogAlias": "Alias",
|
"createInternalResourceDialogAlias": "Alias",
|
||||||
"createInternalResourceDialogAliasDescription": "Et valgfritt internt DNS-alias for denne ressursen.",
|
"createInternalResourceDialogAliasDescription": "Et valgfritt internt DNS-alias for denne ressursen.",
|
||||||
|
"internalResourceDownstreamSchemeRequired": "Skjema er påkrevd for HTTP-ressurser",
|
||||||
|
"internalResourceHttpPortRequired": "Destinasjonsport er nødvendig for HTTP-ressurser",
|
||||||
"siteConfiguration": "Konfigurasjon",
|
"siteConfiguration": "Konfigurasjon",
|
||||||
"siteAcceptClientConnections": "Godta klientforbindelser",
|
"siteAcceptClientConnections": "Godta klientforbindelser",
|
||||||
"siteAcceptClientConnectionsDescription": "Tillat brukere og klienter å få tilgang til ressurser på denne siden. Dette kan endres senere.",
|
"siteAcceptClientConnectionsDescription": "Tillat brukere og klienter å få tilgang til ressurser på denne siden. Dette kan endres senere.",
|
||||||
@@ -1829,6 +2178,40 @@
|
|||||||
"exitNode": "Utgangsnode",
|
"exitNode": "Utgangsnode",
|
||||||
"country": "Land",
|
"country": "Land",
|
||||||
"rulesMatchCountry": "For tiden basert på kilde IP",
|
"rulesMatchCountry": "For tiden basert på kilde IP",
|
||||||
|
"region": "Fylke",
|
||||||
|
"selectRegion": "Velg region",
|
||||||
|
"searchRegions": "Søk etter områder...",
|
||||||
|
"noRegionFound": "Ingen region funnet.",
|
||||||
|
"rulesMatchRegion": "Velg en regional gruppering av land",
|
||||||
|
"rulesErrorInvalidRegion": "Ugyldig område",
|
||||||
|
"rulesErrorInvalidRegionDescription": "Vennligst velg et gyldig område.",
|
||||||
|
"regionAfrica": "Afrika",
|
||||||
|
"regionNorthernAfrica": "[country name] Nord-Afrika",
|
||||||
|
"regionEasternAfrica": "Øst-Afrika",
|
||||||
|
"regionMiddleAfrica": "Middle Africa",
|
||||||
|
"regionSouthernAfrica": "Sør-Afrika",
|
||||||
|
"regionWesternAfrica": "[country name] Vest-Afrika",
|
||||||
|
"regionAmericas": "Amerika",
|
||||||
|
"regionCaribbean": "Karibia",
|
||||||
|
"regionCentralAmerica": "Sentral-Amerika",
|
||||||
|
"regionSouthAmerica": "Sør-Amerika",
|
||||||
|
"regionNorthernAmerica": "Nord-Amerika",
|
||||||
|
"regionAsia": "Asia",
|
||||||
|
"regionCentralAsia": "Sentral-Asia",
|
||||||
|
"regionEasternAsia": "Øst-Asia",
|
||||||
|
"regionSouthEasternAsia": "Sørøst-Asia",
|
||||||
|
"regionSouthernAsia": "Sørlige Asia",
|
||||||
|
"regionWesternAsia": "Vest-Asia",
|
||||||
|
"regionEurope": "Europa",
|
||||||
|
"regionEasternEurope": "Øst-Europa",
|
||||||
|
"regionNorthernEurope": "Nord-Europa",
|
||||||
|
"regionSouthernEurope": "Sørlige Europa",
|
||||||
|
"regionWesternEurope": "Vest-Europa",
|
||||||
|
"regionOceania": "Oceania",
|
||||||
|
"regionAustraliaAndNewZealand": "Australia og New Zealand",
|
||||||
|
"regionMelanesia": "Melanesia",
|
||||||
|
"regionMicronesia": "Micronesia",
|
||||||
|
"regionPolynesia": "Polynesia",
|
||||||
"managedSelfHosted": {
|
"managedSelfHosted": {
|
||||||
"title": "Administrert selv-hostet",
|
"title": "Administrert selv-hostet",
|
||||||
"description": "Sikre og lavvedlikeholdsservere, selvbetjente Pangolin med ekstra klokker, og understell",
|
"description": "Sikre og lavvedlikeholdsservere, selvbetjente Pangolin med ekstra klokker, og understell",
|
||||||
@@ -1867,7 +2250,7 @@
|
|||||||
},
|
},
|
||||||
"internationaldomaindetected": "Internasjonalt domene oppdaget",
|
"internationaldomaindetected": "Internasjonalt domene oppdaget",
|
||||||
"willbestoredas": "Vil bli lagret som:",
|
"willbestoredas": "Vil bli lagret som:",
|
||||||
"roleMappingDescription": "Bestem hvordan roller tilordnes brukere når innloggingen er aktivert når autog-rapportering er aktivert.",
|
"roleMappingDescription": "Bestem hvordan roller tildeles brukere når de logger inn med denne identitetsleverandøren.",
|
||||||
"selectRole": "Velg en rolle",
|
"selectRole": "Velg en rolle",
|
||||||
"roleMappingExpression": "Uttrykk",
|
"roleMappingExpression": "Uttrykk",
|
||||||
"selectRolePlaceholder": "Velg en rolle",
|
"selectRolePlaceholder": "Velg en rolle",
|
||||||
@@ -1877,6 +2260,25 @@
|
|||||||
"invalidValue": "Ugyldig verdi",
|
"invalidValue": "Ugyldig verdi",
|
||||||
"idpTypeLabel": "Identitet leverandør type",
|
"idpTypeLabel": "Identitet leverandør type",
|
||||||
"roleMappingExpressionPlaceholder": "F.eks. inneholder(grupper, 'admin') && 'Admin' ⋅'Medlem'",
|
"roleMappingExpressionPlaceholder": "F.eks. inneholder(grupper, 'admin') && 'Admin' ⋅'Medlem'",
|
||||||
|
"roleMappingModeFixedRoles": "Fast roller",
|
||||||
|
"roleMappingModeMappingBuilder": "Kartlegger bygger",
|
||||||
|
"roleMappingModeRawExpression": "Rå uttrykk",
|
||||||
|
"roleMappingFixedRolesPlaceholderSelect": "Velg en eller flere roller",
|
||||||
|
"roleMappingFixedRolesPlaceholderFreeform": "Skriv inn rollenavn (eksakt treff per organisasjon)",
|
||||||
|
"roleMappingFixedRolesDescriptionSameForAll": "Tilordne den samme rollen som er satt til hver automatisk midlertidig bruker.",
|
||||||
|
"roleMappingFixedRolesDescriptionDefaultPolicy": "For standard policyer, type rollenavn som eksisterer i hver organisasjon der brukerne tilbys. Navn må stemmer nøyaktig.",
|
||||||
|
"roleMappingClaimPath": "Krev sti",
|
||||||
|
"roleMappingClaimPathPlaceholder": "grupper",
|
||||||
|
"roleMappingClaimPathDescription": "Sti i i token nyttelast som inneholder kildeverdier (for eksempel grupper).",
|
||||||
|
"roleMappingMatchValue": "Treff verdi",
|
||||||
|
"roleMappingAssignRoles": "Tilordne roller",
|
||||||
|
"roleMappingAddMappingRule": "Legg til tilordningsregel",
|
||||||
|
"roleMappingRawExpressionResultDescription": "Uttrykk skal vurderes til en streng eller en tekststreng.",
|
||||||
|
"roleMappingRawExpressionResultDescriptionSingleRole": "Uttrykk må evaluere til en streng (en rollenavn).",
|
||||||
|
"roleMappingMatchValuePlaceholder": "Match verdi (for eksempel: admin)",
|
||||||
|
"roleMappingAssignRolesPlaceholderFreeform": "Angi rollenavn (eksakt per org)",
|
||||||
|
"roleMappingBuilderFreeformRowHint": "Rollenavn må samsvare med en rolle i hver målorganisasjon.",
|
||||||
|
"roleMappingRemoveRule": "Fjern",
|
||||||
"idpGoogleConfiguration": "Google Konfigurasjon",
|
"idpGoogleConfiguration": "Google Konfigurasjon",
|
||||||
"idpGoogleConfigurationDescription": "Konfigurer Google OAuth2 legitimasjonen",
|
"idpGoogleConfigurationDescription": "Konfigurer Google OAuth2 legitimasjonen",
|
||||||
"idpGoogleClientIdDescription": "Google OAuth2 Client ID",
|
"idpGoogleClientIdDescription": "Google OAuth2 Client ID",
|
||||||
@@ -1913,6 +2315,9 @@
|
|||||||
"authPageBrandingQuestionRemove": "Er du sikker på at du vil fjerne merkevarebyggingen for autentiseringssider?",
|
"authPageBrandingQuestionRemove": "Er du sikker på at du vil fjerne merkevarebyggingen for autentiseringssider?",
|
||||||
"authPageBrandingDeleteConfirm": "Bekreft sletting av merkevarebygging",
|
"authPageBrandingDeleteConfirm": "Bekreft sletting av merkevarebygging",
|
||||||
"brandingLogoURL": "Logo URL",
|
"brandingLogoURL": "Logo URL",
|
||||||
|
"brandingLogoURLOrPath": "Logoen URL eller sti",
|
||||||
|
"brandingLogoPathDescription": "Skriv inn en URL eller en lokal bane.",
|
||||||
|
"brandingLogoURLDescription": "Skriv inn en offentlig tilgjengelig nettadresse til din logobilde.",
|
||||||
"brandingPrimaryColor": "Primærfarge",
|
"brandingPrimaryColor": "Primærfarge",
|
||||||
"brandingLogoWidth": "Bredde (px)",
|
"brandingLogoWidth": "Bredde (px)",
|
||||||
"brandingLogoHeight": "Høyde (px)",
|
"brandingLogoHeight": "Høyde (px)",
|
||||||
@@ -1937,9 +2342,11 @@
|
|||||||
"selectDomainForOrgAuthPage": "Velg et domene for organisasjonens autentiseringsside",
|
"selectDomainForOrgAuthPage": "Velg et domene for organisasjonens autentiseringsside",
|
||||||
"domainPickerProvidedDomain": "Gitt domene",
|
"domainPickerProvidedDomain": "Gitt domene",
|
||||||
"domainPickerFreeProvidedDomain": "Gratis oppgitt domene",
|
"domainPickerFreeProvidedDomain": "Gratis oppgitt domene",
|
||||||
|
"domainPickerFreeDomainsPaidFeature": "Angitte domener er en betalingsfunksjon. Abonner for å få et domene inkludert i din plan – ingen behov for å ta med ditt eget.",
|
||||||
"domainPickerVerified": "Bekreftet",
|
"domainPickerVerified": "Bekreftet",
|
||||||
"domainPickerUnverified": "Uverifisert",
|
"domainPickerUnverified": "Uverifisert",
|
||||||
"domainPickerInvalidSubdomainStructure": "Dette underdomenet inneholder ugyldige tegn eller struktur. Det vil automatisk bli utsatt når du lagrer.",
|
"domainPickerManual": "Manuell",
|
||||||
|
"domainPickerInvalidSubdomainStructure": "Ugyldige tegn vil bli sanitert når de er lagret.",
|
||||||
"domainPickerError": "Feil",
|
"domainPickerError": "Feil",
|
||||||
"domainPickerErrorLoadDomains": "Kan ikke laste organisasjonens domener",
|
"domainPickerErrorLoadDomains": "Kan ikke laste organisasjonens domener",
|
||||||
"domainPickerErrorCheckAvailability": "Kunne ikke kontrollere domenetilgjengelighet",
|
"domainPickerErrorCheckAvailability": "Kunne ikke kontrollere domenetilgjengelighet",
|
||||||
@@ -1952,7 +2359,7 @@
|
|||||||
"orgAuthChooseIdpDescription": "Velg din identitet leverandør for å fortsette",
|
"orgAuthChooseIdpDescription": "Velg din identitet leverandør for å fortsette",
|
||||||
"orgAuthNoIdpConfigured": "Denne organisasjonen har ikke noen identitetstjeneste konfigurert. Du kan i stedet logge inn med Pangolin identiteten din.",
|
"orgAuthNoIdpConfigured": "Denne organisasjonen har ikke noen identitetstjeneste konfigurert. Du kan i stedet logge inn med Pangolin identiteten din.",
|
||||||
"orgAuthSignInWithPangolin": "Logg inn med Pangolin",
|
"orgAuthSignInWithPangolin": "Logg inn med Pangolin",
|
||||||
"orgAuthSignInToOrg": "Logg inn på en organisasjon",
|
"orgAuthSignInToOrg": "Organisasjonens identitetsleverandør (SSO)",
|
||||||
"orgAuthSelectOrgTitle": "Organisasjonsinnlogging",
|
"orgAuthSelectOrgTitle": "Organisasjonsinnlogging",
|
||||||
"orgAuthSelectOrgDescription": "Skriv inn organisasjons-ID-en din for å fortsette",
|
"orgAuthSelectOrgDescription": "Skriv inn organisasjons-ID-en din for å fortsette",
|
||||||
"orgAuthOrgIdPlaceholder": "din-organisasjon",
|
"orgAuthOrgIdPlaceholder": "din-organisasjon",
|
||||||
@@ -2168,10 +2575,10 @@
|
|||||||
},
|
},
|
||||||
"scale": {
|
"scale": {
|
||||||
"title": "Skala",
|
"title": "Skala",
|
||||||
"description": "Enterprise features, 50 brukere, 50 nettsteder og prioritetsstøtte."
|
"description": "Funksjoner for bedrifter, 50 brukere, 100 nettsteder og prioritert support."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"personalUseOnly": "Kun personlig bruk (gratis lisens - ingen utsjekking)",
|
"personalUseOnly": "Kun personlig bruk (gratis lisens - ingen kasse)",
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"continueToCheckout": "Fortsett til kassen"
|
"continueToCheckout": "Fortsett til kassen"
|
||||||
},
|
},
|
||||||
@@ -2245,6 +2652,7 @@
|
|||||||
"validPassword": "Gyldig passord",
|
"validPassword": "Gyldig passord",
|
||||||
"validEmail": "Valid email",
|
"validEmail": "Valid email",
|
||||||
"validSSO": "Valid SSO",
|
"validSSO": "Valid SSO",
|
||||||
|
"connectedClient": "Tilkoblet klient",
|
||||||
"resourceBlocked": "Ressurs blokkert",
|
"resourceBlocked": "Ressurs blokkert",
|
||||||
"droppedByRule": "Legg i regelen",
|
"droppedByRule": "Legg i regelen",
|
||||||
"noSessions": "Ingen økter",
|
"noSessions": "Ingen økter",
|
||||||
@@ -2270,6 +2678,8 @@
|
|||||||
"logRetentionAccessDescription": "Hvor lenge du vil beholde adgangslogger",
|
"logRetentionAccessDescription": "Hvor lenge du vil beholde adgangslogger",
|
||||||
"logRetentionActionLabel": "Handlings logg nytt",
|
"logRetentionActionLabel": "Handlings logg nytt",
|
||||||
"logRetentionActionDescription": "Hvor lenge handlingen skal lagres",
|
"logRetentionActionDescription": "Hvor lenge handlingen skal lagres",
|
||||||
|
"logRetentionConnectionLabel": "Logg nyhet",
|
||||||
|
"logRetentionConnectionDescription": "Hvor lenge du vil beholde tilkoblingslogger",
|
||||||
"logRetentionDisabled": "Deaktivert",
|
"logRetentionDisabled": "Deaktivert",
|
||||||
"logRetention3Days": "3 dager",
|
"logRetention3Days": "3 dager",
|
||||||
"logRetention7Days": "7 dager",
|
"logRetention7Days": "7 dager",
|
||||||
@@ -2280,8 +2690,15 @@
|
|||||||
"logRetentionEndOfFollowingYear": "Slutt på neste år",
|
"logRetentionEndOfFollowingYear": "Slutt på neste år",
|
||||||
"actionLogsDescription": "Vis historikk for handlinger som er utført i denne organisasjonen",
|
"actionLogsDescription": "Vis historikk for handlinger som er utført i denne organisasjonen",
|
||||||
"accessLogsDescription": "Vis autoriseringsforespørsler for ressurser i denne organisasjonen",
|
"accessLogsDescription": "Vis autoriseringsforespørsler for ressurser i denne organisasjonen",
|
||||||
"licenseRequiredToUse": "En Enterprise lisens er påkrevd for å bruke denne funksjonen.",
|
"connectionLogs": "Loggfiler for tilkobling",
|
||||||
"ossEnterpriseEditionRequired": "<enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> er nødvendig for å bruke denne funksjonen.",
|
"connectionLogsDescription": "Vis tilkoblingslogger for tunneler i denne organisasjonen",
|
||||||
|
"sidebarLogsConnection": "Loggfiler for tilkobling",
|
||||||
|
"sidebarLogsStreaming": "Strømming",
|
||||||
|
"sourceAddress": "Kilde adresse",
|
||||||
|
"destinationAddress": "Måladresse (Automatic Translation)",
|
||||||
|
"duration": "Varighet",
|
||||||
|
"licenseRequiredToUse": "En <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> lisens eller <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink> er påkrevd for å bruke denne funksjonen. <bookADemoLink>Bestill en demo eller POC prøveversjon</bookADemoLink>.",
|
||||||
|
"ossEnterpriseEditionRequired": "<enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> er nødvendig for å bruke denne funksjonen. Denne funksjonen er også tilgjengelig i <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>. <bookADemoLink>Bestill en demo eller POC studie</bookADemoLink>.",
|
||||||
"certResolver": "Sertifikat løser",
|
"certResolver": "Sertifikat løser",
|
||||||
"certResolverDescription": "Velg sertifikatløser som skal brukes for denne ressursen.",
|
"certResolverDescription": "Velg sertifikatløser som skal brukes for denne ressursen.",
|
||||||
"selectCertResolver": "Velg sertifikatløser",
|
"selectCertResolver": "Velg sertifikatløser",
|
||||||
@@ -2423,6 +2840,9 @@
|
|||||||
"machineClients": "Maskinklienter",
|
"machineClients": "Maskinklienter",
|
||||||
"install": "Installer",
|
"install": "Installer",
|
||||||
"run": "Kjør",
|
"run": "Kjør",
|
||||||
|
"envFile": "Miljøfil",
|
||||||
|
"serviceFile": "Tjenestefil",
|
||||||
|
"enableAndStart": "Aktiver og start",
|
||||||
"clientNameDescription": "Visningsnavnet til klienten som kan endres senere.",
|
"clientNameDescription": "Visningsnavnet til klienten som kan endres senere.",
|
||||||
"clientAddress": "Klientadresse (avansert)",
|
"clientAddress": "Klientadresse (avansert)",
|
||||||
"setupFailedToFetchSubnet": "Kunne ikke hente standard undernett",
|
"setupFailedToFetchSubnet": "Kunne ikke hente standard undernett",
|
||||||
@@ -2471,13 +2891,30 @@
|
|||||||
"editInternalResourceDialogAddClients": "Legg til klienter",
|
"editInternalResourceDialogAddClients": "Legg til klienter",
|
||||||
"editInternalResourceDialogDestinationLabel": "Destinasjon",
|
"editInternalResourceDialogDestinationLabel": "Destinasjon",
|
||||||
"editInternalResourceDialogDestinationDescription": "Spesifiser destinasjonsadressen for den interne ressursen. Dette kan være et vertsnavn, IP-adresse eller CIDR-sjikt avhengig av valgt modus. Valgfrie oppsett av intern DNS-alias for enklere identifikasjon.",
|
"editInternalResourceDialogDestinationDescription": "Spesifiser destinasjonsadressen for den interne ressursen. Dette kan være et vertsnavn, IP-adresse eller CIDR-sjikt avhengig av valgt modus. Valgfrie oppsett av intern DNS-alias for enklere identifikasjon.",
|
||||||
|
"internalResourceFormMultiSiteRoutingHelp": "Valg av flere nettsteder muliggjør motstandskraftig ruting og failover for høy tilgjengelighet.",
|
||||||
|
"internalResourceFormMultiSiteRoutingHelpLearnMore": "Lær mer",
|
||||||
"editInternalResourceDialogPortRestrictionsDescription": "Begrens tilgang til spesifikke TCP/UDP-porter eller tillate/blokkere alle porter.",
|
"editInternalResourceDialogPortRestrictionsDescription": "Begrens tilgang til spesifikke TCP/UDP-porter eller tillate/blokkere alle porter.",
|
||||||
|
"createInternalResourceDialogHttpConfiguration": "HTTP-konfigurasjon",
|
||||||
|
"createInternalResourceDialogHttpConfigurationDescription": "Velg domenet klienter vil bruke for å nå denne ressursen via HTTP eller HTTPS.",
|
||||||
|
"editInternalResourceDialogHttpConfiguration": "HTTP-konfigurasjon",
|
||||||
|
"editInternalResourceDialogHttpConfigurationDescription": "Velg domenet klienter vil bruke for å nå denne ressursen via HTTP eller HTTPS.",
|
||||||
"editInternalResourceDialogTcp": "TCP",
|
"editInternalResourceDialogTcp": "TCP",
|
||||||
"editInternalResourceDialogUdp": "UDP",
|
"editInternalResourceDialogUdp": "UDP",
|
||||||
"editInternalResourceDialogIcmp": "ICMP",
|
"editInternalResourceDialogIcmp": "ICMP",
|
||||||
"editInternalResourceDialogAccessControl": "Tilgangskontroll",
|
"editInternalResourceDialogAccessControl": "Tilgangskontroll",
|
||||||
"editInternalResourceDialogAccessControlDescription": "Kontroller hvilke roller, brukere og maskinklienter som har tilgang til denne ressursen når den er koblet til. Administratorer har alltid tilgang.",
|
"editInternalResourceDialogAccessControlDescription": "Kontroller hvilke roller, brukere og maskinklienter som har tilgang til denne ressursen når den er koblet til. Administratorer har alltid tilgang.",
|
||||||
"editInternalResourceDialogPortRangeValidationError": "Portsjiktet må være \"*\" for alle porter, eller en kommaseparert liste med porter og sjikt (f.eks. \"80,443,8000-9000\"). Porter må være mellom 1 og 65535.",
|
"editInternalResourceDialogPortRangeValidationError": "Portsjiktet må være \"*\" for alle porter, eller en kommaseparert liste med porter og sjikt (f.eks. \"80,443,8000-9000\"). Porter må være mellom 1 og 65535.",
|
||||||
|
"internalResourceAuthDaemonStrategy": "SSH Auth Daemon Sted",
|
||||||
|
"internalResourceAuthDaemonStrategyDescription": "Velg hvor SSH-autentisering daemon kjører: på nettstedet (Newt) eller på en ekstern vert.",
|
||||||
|
"internalResourceAuthDaemonDescription": "SSH-godkjenning daemon håndterer SSH-nøkkel signering og PAM autentisering for denne ressursen. Velg om den kjører på nettstedet (Newt) eller på en separat ekstern vert. Se <docsLink>dokumentasjonen</docsLink> for mer.",
|
||||||
|
"internalResourceAuthDaemonDocsUrl": "https://docs.pangolin.net",
|
||||||
|
"internalResourceAuthDaemonStrategyPlaceholder": "Velg strategi",
|
||||||
|
"internalResourceAuthDaemonStrategyLabel": "Sted",
|
||||||
|
"internalResourceAuthDaemonSite": "På nettsted",
|
||||||
|
"internalResourceAuthDaemonSiteDescription": "Autentiser daemon kjører på nettstedet (Newt).",
|
||||||
|
"internalResourceAuthDaemonRemote": "Ekstern vert",
|
||||||
|
"internalResourceAuthDaemonRemoteDescription": "Autentiser daemon kjører på en vert som ikke er nettstedet.",
|
||||||
|
"internalResourceAuthDaemonPort": "Daemon Port (valgfritt)",
|
||||||
"orgAuthWhatsThis": "Hvor kan jeg finne min organisasjons-ID?",
|
"orgAuthWhatsThis": "Hvor kan jeg finne min organisasjons-ID?",
|
||||||
"learnMore": "Lær mer",
|
"learnMore": "Lær mer",
|
||||||
"backToHome": "Gå tilbake til start",
|
"backToHome": "Gå tilbake til start",
|
||||||
@@ -2499,6 +2936,9 @@
|
|||||||
"maintenancePageMessagePlaceholder": "Vi kommer snart tilbake! Vårt nettsted gjennomgår for øyeblikket planlagt vedlikehold.",
|
"maintenancePageMessagePlaceholder": "Vi kommer snart tilbake! Vårt nettsted gjennomgår for øyeblikket planlagt vedlikehold.",
|
||||||
"maintenancePageMessageDescription": "Detaljert beskjed som forklarer vedlikeholdet",
|
"maintenancePageMessageDescription": "Detaljert beskjed som forklarer vedlikeholdet",
|
||||||
"maintenancePageTimeTitle": "Estimert ferdigstillelsestid (Valgfritt)",
|
"maintenancePageTimeTitle": "Estimert ferdigstillelsestid (Valgfritt)",
|
||||||
|
"privateMaintenanceScreenTitle": "Privat plassholder skjerm",
|
||||||
|
"privateMaintenanceScreenMessage": "Dette domenet brukes på en privatressurs. Koble til ved å bruke Pangolin-klienten for å få tilgang til denne ressursen.",
|
||||||
|
"privateMaintenanceScreenSteps": "Når du er koblet til, hvis du fortsatt ser denne meldingen, peker kanskje DNS-cachen til nettleseren din fortsatt til den gamle adressen. For å rette på dette: lukk og åpne denne fanen eller nettleseren på nytt, og naviger deretter tilbake til denne siden.",
|
||||||
"maintenanceTime": "f.eks. 2 timer, 1. november kl. 17:00",
|
"maintenanceTime": "f.eks. 2 timer, 1. november kl. 17:00",
|
||||||
"maintenanceEstimatedTimeDescription": "Når du forventer at vedlikeholdet er ferdigstilt",
|
"maintenanceEstimatedTimeDescription": "Når du forventer at vedlikeholdet er ferdigstilt",
|
||||||
"editDomain": "Rediger domene",
|
"editDomain": "Rediger domene",
|
||||||
@@ -2607,5 +3047,166 @@
|
|||||||
"approvalsEmptyStateStep2Title": "Aktiver enhetsgodkjenninger",
|
"approvalsEmptyStateStep2Title": "Aktiver enhetsgodkjenninger",
|
||||||
"approvalsEmptyStateStep2Description": "Rediger en rolle og aktiver alternativet 'Kreve enhetsgodkjenninger'. Brukere med denne rollen vil trenge administratorgodkjenning for nye enheter.",
|
"approvalsEmptyStateStep2Description": "Rediger en rolle og aktiver alternativet 'Kreve enhetsgodkjenninger'. Brukere med denne rollen vil trenge administratorgodkjenning for nye enheter.",
|
||||||
"approvalsEmptyStatePreviewDescription": "Forhåndsvisning: Når aktivert, ventende enhets forespørsler vil vises her for vurdering",
|
"approvalsEmptyStatePreviewDescription": "Forhåndsvisning: Når aktivert, ventende enhets forespørsler vil vises her for vurdering",
|
||||||
"approvalsEmptyStateButtonText": "Administrer Roller"
|
"approvalsEmptyStateButtonText": "Administrer Roller",
|
||||||
|
"domainErrorTitle": "Vi har problemer med å verifisere domenet ditt",
|
||||||
|
"idpAdminAutoProvisionPoliciesTabHint": "Konfigurer rollegartlegging og organisasjonspolicyer på <policiesTabLink>Auto leveringsinnstillinger</policiesTabLink> fanen.",
|
||||||
|
"streamingTitle": "Hendelse Strømming",
|
||||||
|
"streamingDescription": "Stream hendelser fra din organisasjon til eksterne destinasjoner i sanntid.",
|
||||||
|
"streamingUnnamedDestination": "Plassering uten navn",
|
||||||
|
"streamingNoUrlConfigured": "Ingen URL konfigurert",
|
||||||
|
"streamingAddDestination": "Legg til mål",
|
||||||
|
"streamingHttpWebhookTitle": "HTTP Webhook",
|
||||||
|
"streamingHttpWebhookDescription": "Send hendelser til alle HTTP-endepunkter med fleksibel autentisering og maling.",
|
||||||
|
"streamingS3Title": "Amazon S3",
|
||||||
|
"streamingS3Description": "Strøm hendelser til en S3-kompatibel objektlagringskjøt. Kommer snart.",
|
||||||
|
"streamingDatadogTitle": "Datadog",
|
||||||
|
"streamingDatadogDescription": "Videresend arrangementer direkte til din Datadog-konto. Kommer snart.",
|
||||||
|
"streamingTypePickerDescription": "Velg en måltype for å komme i gang.",
|
||||||
|
"streamingFailedToLoad": "Kan ikke laste inn destinasjoner",
|
||||||
|
"streamingUnexpectedError": "En uventet feil oppstod.",
|
||||||
|
"streamingFailedToUpdate": "Kunne ikke oppdatere destinasjon",
|
||||||
|
"streamingDeletedSuccess": "Målet ble slettet",
|
||||||
|
"streamingFailedToDelete": "Kunne ikke slette destinasjon",
|
||||||
|
"streamingDeleteTitle": "Slett mål",
|
||||||
|
"streamingDeleteButtonText": "Slett mål",
|
||||||
|
"streamingDeleteDialogAreYouSure": "Er du sikker på at du vil slette",
|
||||||
|
"streamingDeleteDialogThisDestination": "denne destinasjonen",
|
||||||
|
"streamingDeleteDialogPermanentlyRemoved": "? Alle konfigurasjoner vil bli slettet permanent.",
|
||||||
|
"httpDestEditTitle": "Rediger mål",
|
||||||
|
"httpDestAddTitle": "Legg til HTTP-destinasjon",
|
||||||
|
"httpDestEditDescription": "Oppdater konfigurasjonen for denne HTTP-hendelsesstrømmedestinasjonen.",
|
||||||
|
"httpDestAddDescription": "Konfigurer et nytt HTTP endepunkt for å motta organisasjonens hendelser.",
|
||||||
|
"S3DestEditTitle": "Rediger destinasjon",
|
||||||
|
"S3DestAddTitle": "Legg til S3 destinasjon",
|
||||||
|
"S3DestEditDescription": "Oppdatere konfigurasjonen for denne S3-hendelsesstrømmingsdestinasjonen.",
|
||||||
|
"S3DestAddDescription": "Konfigurer et nytt S3-endepunkt for å motta organisasjonens hendelser.",
|
||||||
|
"datadogDestEditTitle": "Rediger destinasjon",
|
||||||
|
"datadogDestAddTitle": "Legg til Datadog destinasjon",
|
||||||
|
"datadogDestEditDescription": "Oppdatere konfigurasjonen for denne Datadog-hendelsesstrømmingsdestinasjonen.",
|
||||||
|
"datadogDestAddDescription": "Konfigurer et nytt Datadog-endepunkt for å motta organisasjonens hendelser.",
|
||||||
|
"httpDestTabSettings": "Innstillinger",
|
||||||
|
"httpDestTabHeaders": "Overskrifter",
|
||||||
|
"httpDestTabBody": "Innhold",
|
||||||
|
"httpDestTabLogs": "Logger",
|
||||||
|
"httpDestNamePlaceholder": "Min HTTP destinasjon",
|
||||||
|
"httpDestUrlLabel": "Destinasjons URL",
|
||||||
|
"httpDestUrlErrorHttpRequired": "URL-adressen må bruke httpp eller https",
|
||||||
|
"httpDestUrlErrorHttpsRequired": "HTTPS er nødvendig for distribusjon av sky",
|
||||||
|
"httpDestUrlErrorInvalid": "Skriv inn en gyldig nettadresse (f.eks. https://eksempel.com/webhook)",
|
||||||
|
"httpDestAuthTitle": "Autentisering",
|
||||||
|
"httpDestAuthDescription": "Velg hvordan ønsker til sluttpunktet ditt er autentisert.",
|
||||||
|
"httpDestAuthNoneTitle": "Ingen godkjenning",
|
||||||
|
"httpDestAuthNoneDescription": "Sender forespørsler uten autorisasjonsoverskrift.",
|
||||||
|
"httpDestAuthBearerTitle": "Bærer Symbol",
|
||||||
|
"httpDestAuthBearerDescription": "Legger til en Autorisasjon: Bearer '<token>' header til hver forespørsel.",
|
||||||
|
"httpDestAuthBearerPlaceholder": "Din API-nøkkel eller token",
|
||||||
|
"httpDestAuthBasicTitle": "Standard Auth",
|
||||||
|
"httpDestAuthBasicDescription": "Legger til en Autorisasjon: Basic '<credentials>' header. Gi legitimasjon som brukernavn:passord.",
|
||||||
|
"httpDestAuthBasicPlaceholder": "brukernavn:passord",
|
||||||
|
"httpDestAuthCustomTitle": "Egendefinert topptekst",
|
||||||
|
"httpDestAuthCustomDescription": "Angi et egendefinert HTTP headers navn og verdi for autentisering (f.eks X-API-Key).",
|
||||||
|
"httpDestAuthCustomHeaderNamePlaceholder": "Topptekst navn (f.eks X-API-Key)",
|
||||||
|
"httpDestAuthCustomHeaderValuePlaceholder": "Header verdi",
|
||||||
|
"httpDestCustomHeadersTitle": "Egendefinerte HTTP-overskrifter",
|
||||||
|
"httpDestCustomHeadersDescription": "Legg til egendefinerte overskrifter til hver utgående forespørsel. Nyttig for statisk tokens eller en egendefinert innholdstype. Som standard blir innholdstype: applikasjon/json sendt.",
|
||||||
|
"httpDestNoHeadersConfigured": "Ingen egendefinerte overskrifter konfigurert. Klikk \"Legg til topptekst\" for å legge til en.",
|
||||||
|
"httpDestHeaderNamePlaceholder": "Navn på topptekst",
|
||||||
|
"httpDestHeaderValuePlaceholder": "Verdi",
|
||||||
|
"httpDestAddHeader": "Legg til topptekst",
|
||||||
|
"httpDestBodyTemplateTitle": "Egendefinert hovedmal",
|
||||||
|
"httpDestBodyTemplateDescription": "Kontroller JSON nyttelaststrukturen sendt til ditt endepunkt. Hvis deaktivert, sendes et standard JSON-objekt for hver hendelse.",
|
||||||
|
"httpDestEnableBodyTemplate": "Aktiver egendefinert meldingsmal",
|
||||||
|
"httpDestBodyTemplateLabel": "Kroppsmal (JSON)",
|
||||||
|
"httpDestBodyTemplateHint": "Bruk designmal variabler for å referere til eventfelt i din betaling.",
|
||||||
|
"httpDestPayloadFormatTitle": "Mål format",
|
||||||
|
"httpDestPayloadFormatDescription": "Hvordan blir hendelser serialisert inn i hver forespørselsorgan.",
|
||||||
|
"httpDestFormatJsonArrayTitle": "JSON liste",
|
||||||
|
"httpDestFormatJsonArrayDescription": "Én forespørsel per batch, innholdet er en JSON-liste. Kompatibel med de mest generiske webhooks og Datadog.",
|
||||||
|
"httpDestFormatNdjsonTitle": "NDJSON",
|
||||||
|
"httpDestFormatNdjsonDescription": "Én forespørsel per sats, innholdet er nytt avgrenset JSON - et objekt per linje, ingen ytterarray. Kreves av Splunk HEC, Elastisk/OpenSearch, og Grafana Loki.",
|
||||||
|
"httpDestFormatSingleTitle": "En hendelse per forespørsel",
|
||||||
|
"httpDestFormatSingleDescription": "Sender en separat HTTP POST for hver enkelt hendelse. Bruk bare for endepunkter som ikke kan håndtere batcher.",
|
||||||
|
"httpDestLogTypesTitle": "Logg typer",
|
||||||
|
"httpDestLogTypesDescription": "Velg hvilke loggtyper som blir videresendt til dette målet. Bare aktiverte loggtyper vil bli strømmet.",
|
||||||
|
"httpDestAccessLogsTitle": "Tilgangslogger (Automatic Translation)",
|
||||||
|
"httpDestAccessLogsDescription": "Adgangsforsøk for ressurser, inkludert godkjente og nektet forespørsler.",
|
||||||
|
"httpDestActionLogsTitle": "Handlingslogger",
|
||||||
|
"httpDestActionLogsDescription": "Administrative tiltak som utføres av brukere innenfor organisasjonen.",
|
||||||
|
"httpDestConnectionLogsTitle": "Loggfiler for tilkobling",
|
||||||
|
"httpDestConnectionLogsDescription": "Utstyrs- og tunneltilkoblingshendelser, inkludert forbindelser og frakobling.",
|
||||||
|
"httpDestRequestLogsTitle": "Forespørselslogger (Automatic Translation)",
|
||||||
|
"httpDestRequestLogsDescription": "HTTP-forespørsel logger for bekreftede ressurser, inkludert metode, bane og responskode.",
|
||||||
|
"httpDestSaveChanges": "Lagre endringer",
|
||||||
|
"httpDestCreateDestination": "Opprett mål",
|
||||||
|
"httpDestUpdatedSuccess": "Målet er oppdatert",
|
||||||
|
"httpDestCreatedSuccess": "Målet er opprettet",
|
||||||
|
"httpDestUpdateFailed": "Kunne ikke oppdatere destinasjon",
|
||||||
|
"httpDestCreateFailed": "Kan ikke opprette mål",
|
||||||
|
"followRedirects": "Følg videresendinger",
|
||||||
|
"followRedirectsDescription": "Følg automatisk HTTP-videresendinger for forespørsler.",
|
||||||
|
"alertingErrorWebhookUrl": "Vennligst skriv inn en gyldig URL for webhooken.",
|
||||||
|
"healthCheckStrategyHttp": "Validerer tilkobling og sjekker HTTP-responsstatus.",
|
||||||
|
"healthCheckStrategyTcp": "Bekrefter kun TCP-tilkobling, uten å inspisere responsen.",
|
||||||
|
"healthCheckStrategySnmp": "Utfører en SNMP get-forespørsel for å sjekke helsen til nettverksenheter og infrastruktur.",
|
||||||
|
"healthCheckStrategyIcmp": "Bruker ICMP ekko forespørsler (ping) for å sjekke om en ressurs er tilgjengelig og responsiv.",
|
||||||
|
"healthCheckTabStrategy": "Strategi",
|
||||||
|
"healthCheckTabConnection": "Tilkobling",
|
||||||
|
"healthCheckTabAdvanced": "Avansert",
|
||||||
|
"healthCheckStrategyNotAvailable": "Denne strategien er ikke tilgjengelig. Vennligst kontakt salgsavdelingen for å aktivere denne funksjonen.",
|
||||||
|
"uptime30d": "Oppetid (30d)",
|
||||||
|
"idpAddActionCreateNew": "Opprett ny identitetsleverandør",
|
||||||
|
"idpAddActionImportFromOrg": "Importer fra en annen organisasjon",
|
||||||
|
"idpImportDialogTitle": "Importer identitetsleverandør",
|
||||||
|
"idpImportDialogDescription": "Velg en identitetsleverandør fra en organisasjon der du er admin. Den vil bli knyttet til denne organisasjonen.",
|
||||||
|
"idpImportSearchPlaceholder": "Søk etter organisasjons- eller leverandørnavn...",
|
||||||
|
"idpImportEmpty": "Ingen identitetsleverandører funnet.",
|
||||||
|
"idpImportedDescription": "Identitetsleverandøren ble importert vellykket.",
|
||||||
|
"idpDeleteGlobalQuestion": "Er du sikker på at du vil slette denne identitetsleverandøren permanent?",
|
||||||
|
"idpDeleteGlobalDescription": "Dette vil slette identitetsleverandøren permanent fra alle organisasjoner den er tilknyttet.",
|
||||||
|
"idpUnassociateTitle": "Frakoble identitetsleverandør",
|
||||||
|
"idpUnassociateQuestion": "Er du sikker på at du vil frakoble denne identitetsleverandøren fra denne organisasjonen?",
|
||||||
|
"idpUnassociateDescription": "Alle brukere knyttet til denne identitetsleverandøren vil bli fjernet fra denne organisasjonen, men identitetsleverandøren vil fortsatt eksistere for andre tilknyttede organisasjoner.",
|
||||||
|
"idpUnassociateConfirm": "Bekreft frakobling av identitetsleverandør",
|
||||||
|
"idpUnassociateWarning": "Dette kan ikke angres for denne organisasjonen.",
|
||||||
|
"idpUnassociatedDescription": "Identitetsleverandør er vellykket frakoblet fra denne organisasjonen",
|
||||||
|
"idpUnassociateMenu": "Frakoble",
|
||||||
|
"idpDeleteAllOrgsMenu": "Slett",
|
||||||
|
"publicIpEndpoint": "Endepunkt",
|
||||||
|
"lastTriggeredAt": "Siste utløste",
|
||||||
|
"reject": "Avvis",
|
||||||
|
"uptimeDaysAgo": "{count} days ago",
|
||||||
|
"uptimeToday": "I dag",
|
||||||
|
"uptimeNoDataAvailable": "Ingen data tilgjengelig",
|
||||||
|
"uptimeSuffix": "oppetid",
|
||||||
|
"uptimeDowntimeSuffix": "nedetid",
|
||||||
|
"uptimeTooltipUptimeLabel": "Oppetid",
|
||||||
|
"uptimeTooltipDowntimeLabel": "Nedetid",
|
||||||
|
"uptimeOngoing": "pågående",
|
||||||
|
"uptimeNoMonitoringData": "Ingen overvåkingsdata",
|
||||||
|
"uptimeNoData": "Ingen data",
|
||||||
|
"uptimeMiniBarDown": "Nede",
|
||||||
|
"uptimeSectionTitle": "Oppetid",
|
||||||
|
"uptimeSectionDescription": "Tilgjengelighet de siste {days} dagene",
|
||||||
|
"uptimeAddAlert": "Legg til varsling",
|
||||||
|
"uptimeViewAlerts": "Vis varsler",
|
||||||
|
"uptimeCreateEmailAlert": "Opprett e-postvarsel",
|
||||||
|
"uptimeAlertDescriptionSite": "Få beskjed på e-post når dette nettstedet går offline eller kommer tilbake online.",
|
||||||
|
"uptimeAlertDescriptionResource": "Få beskjed på e-post når denne ressursen går offline eller kommer tilbake online.",
|
||||||
|
"uptimeAlertNamePlaceholder": "Varslingsnavn",
|
||||||
|
"uptimeAdditionalEmails": "Flere e-poster",
|
||||||
|
"uptimeCreateAlert": "Opprett varsling",
|
||||||
|
"uptimeAlertNoRecipients": "Ingen mottakere",
|
||||||
|
"uptimeAlertNoRecipientsDescription": "Vennligst legg til minst én bruker, rolle, eller e-post for å varsle.",
|
||||||
|
"uptimeAlertCreated": "Varsel opprettet",
|
||||||
|
"uptimeAlertCreatedDescription": "Du vil bli varslet når dette endrer status.",
|
||||||
|
"uptimeAlertCreateFailed": "Kunne ikke opprette varsel",
|
||||||
|
"webhookUrlLabel": "URL",
|
||||||
|
"webhookHeaderKeyPlaceholder": "Nøkkel",
|
||||||
|
"webhookHeaderValuePlaceholder": "Verdi",
|
||||||
|
"alertLabel": "Varsel",
|
||||||
|
"domainPickerWildcardSubdomainNotAllowed": "Jokertegnsubdomener er ikke tillatt.",
|
||||||
|
"domainPickerWildcardCertWarning": "Jokertegnressurser kan kreve ekstra konfigurasjon for å fungere skikkelig.",
|
||||||
|
"domainPickerWildcardCertWarningLink": "Lær mer",
|
||||||
|
"health": "Helse",
|
||||||
|
"domainPendingErrorTitle": "Verifiseringsproblem"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
{
|
{
|
||||||
|
"contactSalesEnable": "Neem contact op met de verkoopafdeling om deze functie in te schakelen.",
|
||||||
|
"contactSalesBookDemo": "Boek een demo",
|
||||||
|
"contactSalesOr": "of",
|
||||||
|
"contactSalesContactUs": "neem contact met ons op",
|
||||||
"setupCreate": "Maak de organisatie, site en bronnen aan",
|
"setupCreate": "Maak de organisatie, site en bronnen aan",
|
||||||
"headerAuthCompatibilityInfo": "Schakel dit in om een 401 Niet Geautoriseerd antwoord af te dwingen wanneer een authenticatietoken ontbreekt. Dit is vereist voor browsers of specifieke HTTP-bibliotheken die geen referenties verzenden zonder een serveruitdaging.",
|
"headerAuthCompatibilityInfo": "Schakel dit in om een 401 Niet Geautoriseerd antwoord af te dwingen wanneer een authenticatietoken ontbreekt. Dit is vereist voor browsers of specifieke HTTP-bibliotheken die geen referenties verzenden zonder een serveruitdaging.",
|
||||||
"headerAuthCompatibility": "Uitgebreide compatibiliteit",
|
"headerAuthCompatibility": "Uitgebreide compatibiliteit",
|
||||||
@@ -19,6 +23,18 @@
|
|||||||
"componentsInvalidKey": "Ongeldige of verlopen licentiesleutels gedetecteerd. Volg de licentievoorwaarden om alle functies te blijven gebruiken.",
|
"componentsInvalidKey": "Ongeldige of verlopen licentiesleutels gedetecteerd. Volg de licentievoorwaarden om alle functies te blijven gebruiken.",
|
||||||
"dismiss": "Uitschakelen",
|
"dismiss": "Uitschakelen",
|
||||||
"subscriptionViolationMessage": "U overschrijdt uw huidige abonnement. Corrigeer het probleem door sites, gebruikers of andere bronnen te verwijderen om binnen uw plan te blijven.",
|
"subscriptionViolationMessage": "U overschrijdt uw huidige abonnement. Corrigeer het probleem door sites, gebruikers of andere bronnen te verwijderen om binnen uw plan te blijven.",
|
||||||
|
"trialBannerMessage": "Uw proefversie verloopt over {countdown}. Upgrade om toegang te behouden.",
|
||||||
|
"trialBannerExpired": "Uw proefperiode is verlopen. Upgrade nu om toegang te herstellen.",
|
||||||
|
"billingTrialBannerTitle": "Proefperiode Actief",
|
||||||
|
"billingTrialBannerDescription": "Je bent momenteel bezig met een gratis proefperiode op het zakelijke niveau. Wanneer de proefperiode eindigt, wordt je account automatisch teruggezet naar de functies en limieten van het Basic-niveau. Upgrade op elk moment om toegang te houden tot de functies van je huidige plan.",
|
||||||
|
"billingTrialBannerUpgrade": "Nu Upgraden",
|
||||||
|
"billingTrialBadge": "Gratis Proefversie",
|
||||||
|
"trialActive": "Gratis proefversie actief",
|
||||||
|
"trialExpired": "Proefversie verlopen",
|
||||||
|
"trialHasEnded": "Uw proefperiode is geëindigd.",
|
||||||
|
"trialDaysRemaining": "{count, plural, one {# dag resterend} other {# dagen resterend}}",
|
||||||
|
"trialDaysLeftShort": "{days}d over in proefversie",
|
||||||
|
"trialGoToBilling": "Ga naar factureringspagina",
|
||||||
"subscriptionViolationViewBilling": "Facturering bekijken",
|
"subscriptionViolationViewBilling": "Facturering bekijken",
|
||||||
"componentsLicenseViolation": "Licentie overtreding: Deze server gebruikt {usedSites} sites die de gelicentieerde limiet van {maxSites} sites overschrijden. Volg de licentievoorwaarden om door te gaan met het gebruik van alle functies.",
|
"componentsLicenseViolation": "Licentie overtreding: Deze server gebruikt {usedSites} sites die de gelicentieerde limiet van {maxSites} sites overschrijden. Volg de licentievoorwaarden om door te gaan met het gebruik van alle functies.",
|
||||||
"componentsSupporterMessage": "Bedankt voor het ondersteunen van Pangolin als {tier}!",
|
"componentsSupporterMessage": "Bedankt voor het ondersteunen van Pangolin als {tier}!",
|
||||||
@@ -81,6 +97,8 @@
|
|||||||
"siteConfirmCopy": "Ik heb de configuratie gekopieerd",
|
"siteConfirmCopy": "Ik heb de configuratie gekopieerd",
|
||||||
"searchSitesProgress": "Sites zoeken...",
|
"searchSitesProgress": "Sites zoeken...",
|
||||||
"siteAdd": "Site toevoegen",
|
"siteAdd": "Site toevoegen",
|
||||||
|
"sitesTableViewPublicResources": "Openbare bronnen bekijken",
|
||||||
|
"sitesTableViewPrivateResources": "Privébronnen bekijken",
|
||||||
"siteInstallNewt": "Installeer Newt",
|
"siteInstallNewt": "Installeer Newt",
|
||||||
"siteInstallNewtDescription": "Laat Newt draaien op uw systeem",
|
"siteInstallNewtDescription": "Laat Newt draaien op uw systeem",
|
||||||
"WgConfiguration": "WireGuard Configuratie",
|
"WgConfiguration": "WireGuard Configuratie",
|
||||||
@@ -98,6 +116,21 @@
|
|||||||
"siteUpdatedDescription": "De site is bijgewerkt.",
|
"siteUpdatedDescription": "De site is bijgewerkt.",
|
||||||
"siteGeneralDescription": "Algemene instellingen voor deze site configureren",
|
"siteGeneralDescription": "Algemene instellingen voor deze site configureren",
|
||||||
"siteSettingDescription": "Configureer de instellingen van de site",
|
"siteSettingDescription": "Configureer de instellingen van de site",
|
||||||
|
"siteResourcesTab": "Bronnen",
|
||||||
|
"siteResourcesNoneOnSite": "Deze site heeft nog geen openbare of privébronnen.",
|
||||||
|
"siteResourcesSectionPublic": "Openbare bronnen",
|
||||||
|
"siteResourcesSectionPrivate": "Privébronnen",
|
||||||
|
"siteResourcesSectionPublicDescription": "Bronnen extern blootgesteld via domeinen of poorten.",
|
||||||
|
"siteResourcesSectionPrivateDescription": "Bronnen beschikbaar op uw privénetwerk via de site.",
|
||||||
|
"siteResourcesViewAllPublic": "Bekijk alle bronnen",
|
||||||
|
"siteResourcesViewAllPrivate": "Bekijk alle bronnen",
|
||||||
|
"siteResourcesDialogDescription": "Overzicht van openbare en privébronnen die geassocieerd zijn met deze site.",
|
||||||
|
"siteResourcesShowMore": "Meer weergeven",
|
||||||
|
"siteResourcesPermissionDenied": "U heeft geen toestemming om deze bronnen te vermelden.",
|
||||||
|
"siteResourcesEmptyPublic": "Geen openbare bronnen richten zich nog op deze site.",
|
||||||
|
"siteResourcesEmptyPrivate": "Er zijn nog geen privébronnen gekoppeld aan deze site.",
|
||||||
|
"siteResourcesHowToAccess": "Hoe te openen",
|
||||||
|
"siteResourcesTargetsOnSite": "Doelen op deze site",
|
||||||
"siteSetting": "{siteName} instellingen",
|
"siteSetting": "{siteName} instellingen",
|
||||||
"siteNewtTunnel": "Nieuwste site (Aanbevolen)",
|
"siteNewtTunnel": "Nieuwste site (Aanbevolen)",
|
||||||
"siteNewtTunnelDescription": "Makkelijkste manier om een ingangspunt in een netwerk te maken. Geen extra opzet.",
|
"siteNewtTunnelDescription": "Makkelijkste manier om een ingangspunt in een netwerk te maken. Geen extra opzet.",
|
||||||
@@ -148,6 +181,11 @@
|
|||||||
"createLink": "Koppeling aanmaken",
|
"createLink": "Koppeling aanmaken",
|
||||||
"resourcesNotFound": "Geen bronnen gevonden",
|
"resourcesNotFound": "Geen bronnen gevonden",
|
||||||
"resourceSearch": "Zoek bronnen",
|
"resourceSearch": "Zoek bronnen",
|
||||||
|
"machineSearch": "Zoek machines",
|
||||||
|
"machinesSearch": "Zoek machine-clients...",
|
||||||
|
"machineNotFound": "Geen machines gevonden",
|
||||||
|
"userDeviceSearch": "Gebruikersapparaten zoeken",
|
||||||
|
"userDevicesSearch": "Gebruikersapparaten zoeken...",
|
||||||
"openMenu": "Menu openen",
|
"openMenu": "Menu openen",
|
||||||
"resource": "Bron",
|
"resource": "Bron",
|
||||||
"title": "Aanspreektitel",
|
"title": "Aanspreektitel",
|
||||||
@@ -175,6 +213,7 @@
|
|||||||
"resourceHTTPDescription": "Proxyverzoeken via HTTPS met een volledig gekwalificeerde domeinnaam.",
|
"resourceHTTPDescription": "Proxyverzoeken via HTTPS met een volledig gekwalificeerde domeinnaam.",
|
||||||
"resourceRaw": "TCP/UDP bron",
|
"resourceRaw": "TCP/UDP bron",
|
||||||
"resourceRawDescription": "Proxyverzoeken via ruwe TCP/UDP met een poortnummer.",
|
"resourceRawDescription": "Proxyverzoeken via ruwe TCP/UDP met een poortnummer.",
|
||||||
|
"resourceRawDescriptionCloud": "Proxy verzoeken over rauwe TCP/UDP met behulp van een poortnummer. Vereist sites om verbinding te maken met een remote node.",
|
||||||
"resourceCreate": "Bron maken",
|
"resourceCreate": "Bron maken",
|
||||||
"resourceCreateDescription": "Volg de onderstaande stappen om een nieuwe bron te maken",
|
"resourceCreateDescription": "Volg de onderstaande stappen om een nieuwe bron te maken",
|
||||||
"resourceSeeAll": "Alle bronnen bekijken",
|
"resourceSeeAll": "Alle bronnen bekijken",
|
||||||
@@ -201,6 +240,7 @@
|
|||||||
"protocolSelect": "Selecteer een protocol",
|
"protocolSelect": "Selecteer een protocol",
|
||||||
"resourcePortNumber": "Nummer van poort",
|
"resourcePortNumber": "Nummer van poort",
|
||||||
"resourcePortNumberDescription": "Het externe poortnummer naar proxyverzoeken.",
|
"resourcePortNumberDescription": "Het externe poortnummer naar proxyverzoeken.",
|
||||||
|
"back": "Achterzijde",
|
||||||
"cancel": "Annuleren",
|
"cancel": "Annuleren",
|
||||||
"resourceConfig": "Configuratie tekstbouwstenen",
|
"resourceConfig": "Configuratie tekstbouwstenen",
|
||||||
"resourceConfigDescription": "Kopieer en plak deze configuratie-snippets om de TCP/UDP-bron in te stellen",
|
"resourceConfigDescription": "Kopieer en plak deze configuratie-snippets om de TCP/UDP-bron in te stellen",
|
||||||
@@ -246,11 +286,25 @@
|
|||||||
"orgErrorDeleteMessage": "Er is een fout opgetreden tijdens het verwijderen van de organisatie.",
|
"orgErrorDeleteMessage": "Er is een fout opgetreden tijdens het verwijderen van de organisatie.",
|
||||||
"orgDeleted": "Organisatie verwijderd",
|
"orgDeleted": "Organisatie verwijderd",
|
||||||
"orgDeletedMessage": "De organisatie en haar gegevens zijn verwijderd.",
|
"orgDeletedMessage": "De organisatie en haar gegevens zijn verwijderd.",
|
||||||
|
"deleteAccount": "Verwijder account",
|
||||||
|
"deleteAccountDescription": "Verwijdert permanent uw account, alle organisaties die u bezit, en alle gegevens binnen deze organisaties. Dit kan niet ongedaan worden gemaakt.",
|
||||||
|
"deleteAccountButton": "Verwijder account",
|
||||||
|
"deleteAccountConfirmTitle": "Verwijder account",
|
||||||
|
"deleteAccountConfirmMessage": "Dit zal uw account permanent wissen, alle organisaties die u bezit, en alle gegevens binnen deze organisaties. Dit kan niet ongedaan worden gemaakt.",
|
||||||
|
"deleteAccountConfirmString": "verwijder account",
|
||||||
|
"deleteAccountSuccess": "Account verwijderd",
|
||||||
|
"deleteAccountSuccessMessage": "Uw account is verwijderd.",
|
||||||
|
"deleteAccountError": "Kan account niet verwijderen",
|
||||||
|
"deleteAccountPreviewAccount": "Uw account",
|
||||||
|
"deleteAccountPreviewOrgs": "Organisaties die je bezit (en al hun gegevens)",
|
||||||
"orgMissing": "Organisatie-ID ontbreekt",
|
"orgMissing": "Organisatie-ID ontbreekt",
|
||||||
"orgMissingMessage": "Niet in staat om de uitnodiging te regenereren zonder organisatie-ID.",
|
"orgMissingMessage": "Niet in staat om de uitnodiging te regenereren zonder organisatie-ID.",
|
||||||
"accessUsersManage": "Gebruikers beheren",
|
"accessUsersManage": "Gebruikers beheren",
|
||||||
|
"accessUserManage": "Beheer gebruiker",
|
||||||
"accessUsersDescription": "Nodig uit en beheer gebruikers met toegang tot deze organisatie",
|
"accessUsersDescription": "Nodig uit en beheer gebruikers met toegang tot deze organisatie",
|
||||||
"accessUsersSearch": "Gebruikers zoeken...",
|
"accessUsersSearch": "Gebruikers zoeken...",
|
||||||
|
"accessUsersRoleFilterCount": "{count, plural, one {# rol} other {# rollen}}",
|
||||||
|
"accessUsersRoleFilterClear": "Rolfilters wissen",
|
||||||
"accessUserCreate": "Gebruiker aanmaken",
|
"accessUserCreate": "Gebruiker aanmaken",
|
||||||
"accessUserRemove": "Gebruiker verwijderen",
|
"accessUserRemove": "Gebruiker verwijderen",
|
||||||
"username": "Gebruikersnaam",
|
"username": "Gebruikersnaam",
|
||||||
@@ -310,6 +364,54 @@
|
|||||||
"apiKeysDelete": "API-sleutel verwijderen",
|
"apiKeysDelete": "API-sleutel verwijderen",
|
||||||
"apiKeysManage": "API-sleutels beheren",
|
"apiKeysManage": "API-sleutels beheren",
|
||||||
"apiKeysDescription": "API-sleutels worden gebruikt om te verifiëren met de integratie-API",
|
"apiKeysDescription": "API-sleutels worden gebruikt om te verifiëren met de integratie-API",
|
||||||
|
"provisioningKeysTitle": "Vertrekkende sleutel",
|
||||||
|
"provisioningKeysManage": "Beheren van Provisioning Sleutels",
|
||||||
|
"provisioningKeysDescription": "Provisionerende sleutels worden gebruikt om geautomatiseerde sitebepaling voor uw organisatie te verifiëren.",
|
||||||
|
"provisioningManage": "Provisie",
|
||||||
|
"provisioningDescription": "Voorzieningssleutels beheren en sites beoordelen in afwachting van goedkeuring.",
|
||||||
|
"pendingSites": "Openstaande sites",
|
||||||
|
"siteApproveSuccess": "Site succesvol goedgekeurd",
|
||||||
|
"siteApproveError": "Fout bij goedkeuren website",
|
||||||
|
"provisioningKeys": "Verhelderende sleutels",
|
||||||
|
"searchProvisioningKeys": "Zoek provisioningsleutels ...",
|
||||||
|
"provisioningKeysAdd": "Genereer Provisioning Sleutel",
|
||||||
|
"provisioningKeysErrorDelete": "Fout bij verwijderen provisioning sleutel",
|
||||||
|
"provisioningKeysErrorDeleteMessage": "Fout bij verwijderen provisioning sleutel",
|
||||||
|
"provisioningKeysQuestionRemove": "Weet u zeker dat u deze proefsleutel van de organisatie wilt verwijderen?",
|
||||||
|
"provisioningKeysMessageRemove": "Eenmaal verwijderd, kan de sleutel niet meer worden gebruikt voor site-instructie.",
|
||||||
|
"provisioningKeysDeleteConfirm": "Bevestig Verwijderen Provisione-sleutel",
|
||||||
|
"provisioningKeysDelete": "Provisione-sleutel verwijderen",
|
||||||
|
"provisioningKeysCreate": "Genereer Provisioning Sleutel",
|
||||||
|
"provisioningKeysCreateDescription": "Een nieuwe provisioningsleutel voor de organisatie genereren",
|
||||||
|
"provisioningKeysSeeAll": "Bekijk alle provisioning sleutels",
|
||||||
|
"provisioningKeysSave": "Sla de provisioning sleutel op",
|
||||||
|
"provisioningKeysSaveDescription": "Je kunt dit slechts één keer zien. Kopieer het naar een veilige plaats.",
|
||||||
|
"provisioningKeysErrorCreate": "Fout bij aanmaken provisioning sleutel",
|
||||||
|
"provisioningKeysList": "Nieuwe provisioning sleutel",
|
||||||
|
"provisioningKeysMaxBatchSize": "Maximale batchgrootte",
|
||||||
|
"provisioningKeysUnlimitedBatchSize": "Onbeperkte batchgrootte (geen limiet)",
|
||||||
|
"provisioningKeysMaxBatchUnlimited": "Onbeperkt",
|
||||||
|
"provisioningKeysMaxBatchSizeInvalid": "Voer een geldige maximale batchgrootte in (1–1.000,000).",
|
||||||
|
"provisioningKeysValidUntil": "Geldig tot",
|
||||||
|
"provisioningKeysValidUntilHint": "Laat leeg voor geen vervaldatum.",
|
||||||
|
"provisioningKeysValidUntilInvalid": "Voer een geldige datum en tijd in.",
|
||||||
|
"provisioningKeysNumUsed": "Aantal keer gebruikt",
|
||||||
|
"provisioningKeysLastUsed": "Laatst gebruikt",
|
||||||
|
"provisioningKeysNoExpiry": "Geen vervaldatum",
|
||||||
|
"provisioningKeysNeverUsed": "Nooit",
|
||||||
|
"provisioningKeysEdit": "Wijzig Provisioning Sleutel",
|
||||||
|
"provisioningKeysEditDescription": "Werk de maximale batchgrootte en verlooptijd voor deze sleutel bij.",
|
||||||
|
"provisioningKeysApproveNewSites": "Goedkeuren van nieuwe sites",
|
||||||
|
"provisioningKeysApproveNewSitesDescription": "Automatisch sites goedkeuren die zich registreren met deze sleutel.",
|
||||||
|
"provisioningKeysUpdateError": "Fout tijdens bijwerken provisioning sleutel",
|
||||||
|
"provisioningKeysUpdated": "Provisie sleutel bijgewerkt",
|
||||||
|
"provisioningKeysUpdatedDescription": "Uw wijzigingen zijn opgeslagen.",
|
||||||
|
"provisioningKeysBannerTitle": "Bewerkingssleutels voor websites",
|
||||||
|
"provisioningKeysBannerDescription": "Genereer een inrichtingssleutel en gebruik deze met de Newt-connector om automatisch sites te maken bij de eerste opstart - er is geen behoefte om aparte inloggegevens voor elke site in te stellen.",
|
||||||
|
"provisioningKeysBannerButtonText": "Meer informatie",
|
||||||
|
"pendingSitesBannerTitle": "Openstaande sites",
|
||||||
|
"pendingSitesBannerDescription": "Sites die verbinding maken met een inrichtingssleutel verschijnen hier voor beoordeling.",
|
||||||
|
"pendingSitesBannerButtonText": "Meer informatie",
|
||||||
"apiKeysSettings": "{apiKeyName} instellingen",
|
"apiKeysSettings": "{apiKeyName} instellingen",
|
||||||
"userTitle": "Alle gebruikers beheren",
|
"userTitle": "Alle gebruikers beheren",
|
||||||
"userDescription": "Bekijk en beheer alle gebruikers in het systeem",
|
"userDescription": "Bekijk en beheer alle gebruikers in het systeem",
|
||||||
@@ -339,6 +441,10 @@
|
|||||||
"licenseErrorKeyActivate": "Licentiesleutel activeren mislukt",
|
"licenseErrorKeyActivate": "Licentiesleutel activeren mislukt",
|
||||||
"licenseErrorKeyActivateDescription": "Er is een fout opgetreden tijdens het activeren van de licentiesleutel.",
|
"licenseErrorKeyActivateDescription": "Er is een fout opgetreden tijdens het activeren van de licentiesleutel.",
|
||||||
"licenseAbout": "Over licenties",
|
"licenseAbout": "Over licenties",
|
||||||
|
"licenseBannerTitle": "Activeer Uw Enterprise Licentie",
|
||||||
|
"licenseBannerDescription": "Ontgrendel enterprise-functies voor uw zelf-gehoste Pangolin-instantie. Koop een licentiesleutel om premium mogelijkheden te activeren, voeg deze vervolgens hieronder toe.",
|
||||||
|
"licenseBannerGetLicense": "Koop een Licentie",
|
||||||
|
"licenseBannerViewDocs": "Bekijk Documentatie",
|
||||||
"communityEdition": "Community editie",
|
"communityEdition": "Community editie",
|
||||||
"licenseAboutDescription": "Dit geldt voor gebruikers van bedrijven en ondernemingen die Pangolin in gebruiken in een commerciële omgeving. Als u Pangolin gebruikt voor persoonlijk gebruik, kunt u dit gedeelte negeren.",
|
"licenseAboutDescription": "Dit geldt voor gebruikers van bedrijven en ondernemingen die Pangolin in gebruiken in een commerciële omgeving. Als u Pangolin gebruikt voor persoonlijk gebruik, kunt u dit gedeelte negeren.",
|
||||||
"licenseKeyActivated": "Licentiesleutel geactiveerd",
|
"licenseKeyActivated": "Licentiesleutel geactiveerd",
|
||||||
@@ -461,6 +567,8 @@
|
|||||||
"filterByApprovalState": "Filter op goedkeuringsstatus",
|
"filterByApprovalState": "Filter op goedkeuringsstatus",
|
||||||
"approvalListEmpty": "Geen goedkeuringen",
|
"approvalListEmpty": "Geen goedkeuringen",
|
||||||
"approvalState": "Goedkeuring status",
|
"approvalState": "Goedkeuring status",
|
||||||
|
"approvalLoadMore": "Meer laden",
|
||||||
|
"loadingApprovals": "Goedkeuringen laden",
|
||||||
"approve": "Goedkeuren",
|
"approve": "Goedkeuren",
|
||||||
"approved": "Goedgekeurd",
|
"approved": "Goedgekeurd",
|
||||||
"denied": "Geweigerd",
|
"denied": "Geweigerd",
|
||||||
@@ -494,9 +602,12 @@
|
|||||||
"userSaved": "Gebruiker opgeslagen",
|
"userSaved": "Gebruiker opgeslagen",
|
||||||
"userSavedDescription": "De gebruiker is bijgewerkt.",
|
"userSavedDescription": "De gebruiker is bijgewerkt.",
|
||||||
"autoProvisioned": "Automatisch bevestigen",
|
"autoProvisioned": "Automatisch bevestigen",
|
||||||
|
"autoProvisionSettings": "Auto Provisie Instellingen",
|
||||||
"autoProvisionedDescription": "Toestaan dat deze gebruiker automatisch wordt beheerd door een identiteitsprovider",
|
"autoProvisionedDescription": "Toestaan dat deze gebruiker automatisch wordt beheerd door een identiteitsprovider",
|
||||||
"accessControlsDescription": "Beheer wat deze gebruiker toegang heeft tot en doet in de organisatie",
|
"accessControlsDescription": "Beheer wat deze gebruiker toegang heeft tot en doet in de organisatie",
|
||||||
"accessControlsSubmit": "Bewaar Toegangsbesturing",
|
"accessControlsSubmit": "Bewaar Toegangsbesturing",
|
||||||
|
"singleRolePerUserPlanNotice": "Uw plan ondersteunt slechts één rol per gebruiker.",
|
||||||
|
"singleRolePerUserEditionNotice": "Deze editie ondersteunt slechts één rol per gebruiker.",
|
||||||
"roles": "Rollen",
|
"roles": "Rollen",
|
||||||
"accessUsersRoles": "Beheer Gebruikers & Rollen",
|
"accessUsersRoles": "Beheer Gebruikers & Rollen",
|
||||||
"accessUsersRolesDescription": "Nodig gebruikers uit en voeg ze toe aan de rollen om toegang tot de organisatie te beheren",
|
"accessUsersRolesDescription": "Nodig gebruikers uit en voeg ze toe aan de rollen om toegang tot de organisatie te beheren",
|
||||||
@@ -553,6 +664,8 @@
|
|||||||
"targetErrorInvalidPortDescription": "Voer een geldig poortnummer in",
|
"targetErrorInvalidPortDescription": "Voer een geldig poortnummer in",
|
||||||
"targetErrorNoSite": "Geen site geselecteerd",
|
"targetErrorNoSite": "Geen site geselecteerd",
|
||||||
"targetErrorNoSiteDescription": "Selecteer een site voor het doel",
|
"targetErrorNoSiteDescription": "Selecteer een site voor het doel",
|
||||||
|
"targetTargetsCleared": "Doelen gewist",
|
||||||
|
"targetTargetsClearedDescription": "Alle doelen zijn verwijderd van deze bron",
|
||||||
"targetCreated": "Doel aangemaakt",
|
"targetCreated": "Doel aangemaakt",
|
||||||
"targetCreatedDescription": "Doel is succesvol aangemaakt",
|
"targetCreatedDescription": "Doel is succesvol aangemaakt",
|
||||||
"targetErrorCreate": "Kan doel niet aanmaken",
|
"targetErrorCreate": "Kan doel niet aanmaken",
|
||||||
@@ -636,6 +749,7 @@
|
|||||||
"resourcesErrorUpdate": "Bron wisselen mislukt",
|
"resourcesErrorUpdate": "Bron wisselen mislukt",
|
||||||
"resourcesErrorUpdateDescription": "Er is een fout opgetreden tijdens het bijwerken van het document",
|
"resourcesErrorUpdateDescription": "Er is een fout opgetreden tijdens het bijwerken van het document",
|
||||||
"access": "Toegangsrechten",
|
"access": "Toegangsrechten",
|
||||||
|
"accessControl": "Toegangs controle",
|
||||||
"shareLink": "{resource} Share link",
|
"shareLink": "{resource} Share link",
|
||||||
"resourceSelect": "Selecteer resource",
|
"resourceSelect": "Selecteer resource",
|
||||||
"shareLinks": "Links delen",
|
"shareLinks": "Links delen",
|
||||||
@@ -653,6 +767,7 @@
|
|||||||
"newtEndpoint": "Endpoint",
|
"newtEndpoint": "Endpoint",
|
||||||
"newtId": "ID",
|
"newtId": "ID",
|
||||||
"newtSecretKey": "Geheim",
|
"newtSecretKey": "Geheim",
|
||||||
|
"newtVersion": "Versie",
|
||||||
"architecture": "Architectuur",
|
"architecture": "Architectuur",
|
||||||
"sites": "Sites",
|
"sites": "Sites",
|
||||||
"siteWgAnyClients": "Gebruik een willekeurige WireGuard client om verbinding te maken. Je zult interne bronnen moeten aanspreken met behulp van de peer IP.",
|
"siteWgAnyClients": "Gebruik een willekeurige WireGuard client om verbinding te maken. Je zult interne bronnen moeten aanspreken met behulp van de peer IP.",
|
||||||
@@ -776,6 +891,7 @@
|
|||||||
"accessRoleRemoved": "Rol verwijderd",
|
"accessRoleRemoved": "Rol verwijderd",
|
||||||
"accessRoleRemovedDescription": "De rol is succesvol verwijderd.",
|
"accessRoleRemovedDescription": "De rol is succesvol verwijderd.",
|
||||||
"accessRoleRequiredRemove": "Voordat u deze rol verwijdert, selecteer een nieuwe rol om bestaande leden aan te dragen.",
|
"accessRoleRequiredRemove": "Voordat u deze rol verwijdert, selecteer een nieuwe rol om bestaande leden aan te dragen.",
|
||||||
|
"network": "Netwerk",
|
||||||
"manage": "Beheren",
|
"manage": "Beheren",
|
||||||
"sitesNotFound": "Geen sites gevonden.",
|
"sitesNotFound": "Geen sites gevonden.",
|
||||||
"pangolinServerAdmin": "Serverbeheer - Pangolin",
|
"pangolinServerAdmin": "Serverbeheer - Pangolin",
|
||||||
@@ -791,6 +907,9 @@
|
|||||||
"sitestCountIncrease": "Toename van site vergroten",
|
"sitestCountIncrease": "Toename van site vergroten",
|
||||||
"idpManage": "Identiteitsaanbieders beheren",
|
"idpManage": "Identiteitsaanbieders beheren",
|
||||||
"idpManageDescription": "Identiteitsaanbieders in het systeem bekijken en beheren",
|
"idpManageDescription": "Identiteitsaanbieders in het systeem bekijken en beheren",
|
||||||
|
"idpGlobalModeBanner": "Identiteitsaanbieders (IdPs) per organisatie zijn uitgeschakeld op deze server. Het gebruikt globale IdPs (gedeeld tussen alle organisaties). Beheer globale IdPs in het <adminPanelLink>beheerderspaneel</adminPanelLink>. Om IdPs per organisatie in te schakelen, bewerk de server configuratie en zet IdP modus op org. <configDocsLink>Zie de documenten</configDocsLink>. Als je globale IdPs wilt blijven gebruiken en dit uit de organisatie-instellingen wilt laten verdwijnen, zet dan expliciet de modus naar globaal in de config.",
|
||||||
|
"idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organisatie zijn uitgeschakeld op deze server. Het gebruikt globale IdPs (gedeeld in alle organisaties) Beheer globale IdPs in het <adminPanelLink>beheerderspaneel</adminPanelLink>. Om identiteitsproviders per organisatie te gebruiken, moet u upgraden naar de Enterprise editie.",
|
||||||
|
"idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organisatie zijn uitgeschakeld op deze server. Het gebruikt globale IdPs (gedeeld in alle organisaties) Beheer globale IdPs in het <adminPanelLink>beheerderspaneel</adminPanelLink>. Om identiteitsaanbieders per organisatie te gebruiken, is een Enterprise-licentie vereist.",
|
||||||
"idpDeletedDescription": "Identity provider succesvol verwijderd",
|
"idpDeletedDescription": "Identity provider succesvol verwijderd",
|
||||||
"idpOidc": "OAuth2/OIDC",
|
"idpOidc": "OAuth2/OIDC",
|
||||||
"idpQuestionRemove": "Weet u zeker dat u de identiteitsprovider permanent wilt verwijderen?",
|
"idpQuestionRemove": "Weet u zeker dat u de identiteitsprovider permanent wilt verwijderen?",
|
||||||
@@ -816,6 +935,7 @@
|
|||||||
"idpDisplayName": "Een weergavenaam voor deze identiteitsprovider",
|
"idpDisplayName": "Een weergavenaam voor deze identiteitsprovider",
|
||||||
"idpAutoProvisionUsers": "Auto Provisie Gebruikers",
|
"idpAutoProvisionUsers": "Auto Provisie Gebruikers",
|
||||||
"idpAutoProvisionUsersDescription": "Wanneer ingeschakeld, worden gebruikers automatisch in het systeem aangemaakt wanneer ze de eerste keer inloggen met de mogelijkheid om gebruikers toe te wijzen aan rollen en organisaties.",
|
"idpAutoProvisionUsersDescription": "Wanneer ingeschakeld, worden gebruikers automatisch in het systeem aangemaakt wanneer ze de eerste keer inloggen met de mogelijkheid om gebruikers toe te wijzen aan rollen en organisaties.",
|
||||||
|
"idpAutoProvisionConfigureAfterCreate": "U kunt automatische voorzieningsinstellingen configureren zodra de identiteitsprovider is aangemaakt.",
|
||||||
"licenseBadge": "EE",
|
"licenseBadge": "EE",
|
||||||
"idpType": "Type provider",
|
"idpType": "Type provider",
|
||||||
"idpTypeDescription": "Selecteer het type identiteitsprovider dat u wilt configureren",
|
"idpTypeDescription": "Selecteer het type identiteitsprovider dat u wilt configureren",
|
||||||
@@ -867,7 +987,7 @@
|
|||||||
"defaultMappingsRole": "Standaard Rol Toewijzing",
|
"defaultMappingsRole": "Standaard Rol Toewijzing",
|
||||||
"defaultMappingsRoleDescription": "Het resultaat van deze uitdrukking moet de rolnaam zoals gedefinieerd in de organisatie als tekenreeks teruggeven.",
|
"defaultMappingsRoleDescription": "Het resultaat van deze uitdrukking moet de rolnaam zoals gedefinieerd in de organisatie als tekenreeks teruggeven.",
|
||||||
"defaultMappingsOrg": "Standaard organisatie mapping",
|
"defaultMappingsOrg": "Standaard organisatie mapping",
|
||||||
"defaultMappingsOrgDescription": "Deze expressie moet de org-ID teruggeven of waar om de gebruiker toegang te geven tot de organisatie.",
|
"defaultMappingsOrgDescription": "Wanneer ingesteld, moet deze expressie de organisatie-ID of waar retourneren voor de gebruiker om toegang te krijgen tot die organisatie. Als het niet is ingesteld, is het definiëren van een roltoewijzing voldoende: de gebruiker is toegestaan zolang een geldige roltoewijzing voor hen binnen de organisatie kan worden opgelost.",
|
||||||
"defaultMappingsSubmit": "Standaard toewijzingen opslaan",
|
"defaultMappingsSubmit": "Standaard toewijzingen opslaan",
|
||||||
"orgPoliciesEdit": "Organisatie beleid bewerken",
|
"orgPoliciesEdit": "Organisatie beleid bewerken",
|
||||||
"org": "Organisatie",
|
"org": "Organisatie",
|
||||||
@@ -1014,12 +1134,12 @@
|
|||||||
"pangolinSetup": "Instellen - Pangolin",
|
"pangolinSetup": "Instellen - Pangolin",
|
||||||
"orgNameRequired": "Organisatienaam is vereist",
|
"orgNameRequired": "Organisatienaam is vereist",
|
||||||
"orgIdRequired": "Organisatie-ID is vereist",
|
"orgIdRequired": "Organisatie-ID is vereist",
|
||||||
|
"orgIdMaxLength": "Organisatie-ID mag maximaal 32 tekens lang zijn",
|
||||||
"orgErrorCreate": "Fout opgetreden tijdens het aanmaken org",
|
"orgErrorCreate": "Fout opgetreden tijdens het aanmaken org",
|
||||||
"pageNotFound": "Pagina niet gevonden",
|
"pageNotFound": "Pagina niet gevonden",
|
||||||
"pageNotFoundDescription": "Oeps! De pagina die je zoekt bestaat niet.",
|
"pageNotFoundDescription": "Oeps! De pagina die je zoekt bestaat niet.",
|
||||||
"overview": "Overzicht.",
|
"overview": "Overzicht.",
|
||||||
"home": "Startpagina",
|
"home": "Startpagina",
|
||||||
"accessControl": "Toegangs controle",
|
|
||||||
"settings": "Instellingen",
|
"settings": "Instellingen",
|
||||||
"usersAll": "Alle gebruikers",
|
"usersAll": "Alle gebruikers",
|
||||||
"license": "Licentie",
|
"license": "Licentie",
|
||||||
@@ -1082,6 +1202,12 @@
|
|||||||
"actionGetUser": "Gebruiker ophalen",
|
"actionGetUser": "Gebruiker ophalen",
|
||||||
"actionGetOrgUser": "Krijg organisatie-gebruiker",
|
"actionGetOrgUser": "Krijg organisatie-gebruiker",
|
||||||
"actionListOrgDomains": "Lijst organisatie domeinen",
|
"actionListOrgDomains": "Lijst organisatie domeinen",
|
||||||
|
"actionGetDomain": "Domein verkrijgen",
|
||||||
|
"actionCreateOrgDomain": "Domein aanmaken",
|
||||||
|
"actionUpdateOrgDomain": "Domein bijwerken",
|
||||||
|
"actionDeleteOrgDomain": "Domein verwijderen",
|
||||||
|
"actionGetDNSRecords": "Krijg DNS Records",
|
||||||
|
"actionRestartOrgDomain": "Domein opnieuw starten",
|
||||||
"actionCreateSite": "Site aanmaken",
|
"actionCreateSite": "Site aanmaken",
|
||||||
"actionDeleteSite": "Site verwijderen",
|
"actionDeleteSite": "Site verwijderen",
|
||||||
"actionGetSite": "Site ophalen",
|
"actionGetSite": "Site ophalen",
|
||||||
@@ -1093,6 +1219,7 @@
|
|||||||
"setupTokenDescription": "Voer het setup-token in vanaf de serverconsole.",
|
"setupTokenDescription": "Voer het setup-token in vanaf de serverconsole.",
|
||||||
"setupTokenRequired": "Setup-token is vereist",
|
"setupTokenRequired": "Setup-token is vereist",
|
||||||
"actionUpdateSite": "Site bijwerken",
|
"actionUpdateSite": "Site bijwerken",
|
||||||
|
"actionResetSiteBandwidth": "Reset organisatieschandbreedte",
|
||||||
"actionListSiteRoles": "Toon toegestane sitenollen",
|
"actionListSiteRoles": "Toon toegestane sitenollen",
|
||||||
"actionCreateResource": "Bron maken",
|
"actionCreateResource": "Bron maken",
|
||||||
"actionDeleteResource": "Document verwijderen",
|
"actionDeleteResource": "Document verwijderen",
|
||||||
@@ -1122,6 +1249,7 @@
|
|||||||
"actionRemoveUser": "Gebruiker verwijderen",
|
"actionRemoveUser": "Gebruiker verwijderen",
|
||||||
"actionListUsers": "Gebruikers weergeven",
|
"actionListUsers": "Gebruikers weergeven",
|
||||||
"actionAddUserRole": "Gebruikersrol toevoegen",
|
"actionAddUserRole": "Gebruikersrol toevoegen",
|
||||||
|
"actionSetUserOrgRoles": "Stel gebruikersrollen in",
|
||||||
"actionGenerateAccessToken": "Genereer Toegangstoken",
|
"actionGenerateAccessToken": "Genereer Toegangstoken",
|
||||||
"actionDeleteAccessToken": "Verwijder toegangstoken",
|
"actionDeleteAccessToken": "Verwijder toegangstoken",
|
||||||
"actionListAccessTokens": "Lijst toegangstokens",
|
"actionListAccessTokens": "Lijst toegangstokens",
|
||||||
@@ -1166,7 +1294,9 @@
|
|||||||
"actionViewLogs": "Logboeken bekijken",
|
"actionViewLogs": "Logboeken bekijken",
|
||||||
"noneSelected": "Niet geselecteerd",
|
"noneSelected": "Niet geselecteerd",
|
||||||
"orgNotFound2": "Geen organisaties gevonden.",
|
"orgNotFound2": "Geen organisaties gevonden.",
|
||||||
"searchProgress": "Zoeken...",
|
"search": "Zoeken…",
|
||||||
|
"searchPlaceholder": "Zoeken...",
|
||||||
|
"emptySearchOptions": "Geen opties gevonden",
|
||||||
"create": "Aanmaken",
|
"create": "Aanmaken",
|
||||||
"orgs": "Organisaties",
|
"orgs": "Organisaties",
|
||||||
"loginError": "Er is een onverwachte fout opgetreden. Probeer het opnieuw.",
|
"loginError": "Er is een onverwachte fout opgetreden. Probeer het opnieuw.",
|
||||||
@@ -1230,12 +1360,14 @@
|
|||||||
"sidebarClientResources": "Privé",
|
"sidebarClientResources": "Privé",
|
||||||
"sidebarAccessControl": "Toegangs controle",
|
"sidebarAccessControl": "Toegangs controle",
|
||||||
"sidebarLogsAndAnalytics": "Logs & Analytics",
|
"sidebarLogsAndAnalytics": "Logs & Analytics",
|
||||||
|
"sidebarTeam": "Team",
|
||||||
"sidebarUsers": "Gebruikers",
|
"sidebarUsers": "Gebruikers",
|
||||||
"sidebarAdmin": "Beheerder",
|
"sidebarAdmin": "Beheerder",
|
||||||
"sidebarInvitations": "Uitnodigingen",
|
"sidebarInvitations": "Uitnodigingen",
|
||||||
"sidebarRoles": "Rollen",
|
"sidebarRoles": "Rollen",
|
||||||
"sidebarShareableLinks": "Koppelingen",
|
"sidebarShareableLinks": "Koppelingen",
|
||||||
"sidebarApiKeys": "API sleutels",
|
"sidebarApiKeys": "API sleutels",
|
||||||
|
"sidebarProvisioning": "Provisie",
|
||||||
"sidebarSettings": "Instellingen",
|
"sidebarSettings": "Instellingen",
|
||||||
"sidebarAllUsers": "Alle gebruikers",
|
"sidebarAllUsers": "Alle gebruikers",
|
||||||
"sidebarIdentityProviders": "Identiteit aanbieders",
|
"sidebarIdentityProviders": "Identiteit aanbieders",
|
||||||
@@ -1247,8 +1379,167 @@
|
|||||||
"sidebarGeneral": "Beheren",
|
"sidebarGeneral": "Beheren",
|
||||||
"sidebarLogAndAnalytics": "Log & Analytics",
|
"sidebarLogAndAnalytics": "Log & Analytics",
|
||||||
"sidebarBluePrints": "Blauwdrukken",
|
"sidebarBluePrints": "Blauwdrukken",
|
||||||
|
"sidebarAlerting": "Waarschuwingen",
|
||||||
|
"sidebarHealthChecks": "Gezondheidscontroles",
|
||||||
"sidebarOrganization": "Organisatie",
|
"sidebarOrganization": "Organisatie",
|
||||||
|
"sidebarManagement": "Beheer",
|
||||||
|
"sidebarBillingAndLicenses": "Facturatie & Licenties",
|
||||||
"sidebarLogsAnalytics": "Analyses",
|
"sidebarLogsAnalytics": "Analyses",
|
||||||
|
"alertingTitle": "Waarschuwingen",
|
||||||
|
"alertingDescription": "Definieer bronnen, triggers en acties voor meldingen",
|
||||||
|
"alertingRules": "Waarschuwingsregels",
|
||||||
|
"alertingSearchRules": "Zoek regels…",
|
||||||
|
"alertingAddRule": "Regel aanmaken",
|
||||||
|
"alertingColumnSource": "Bron",
|
||||||
|
"alertingColumnTrigger": "Trigger",
|
||||||
|
"alertingColumnActions": "Acties",
|
||||||
|
"alertingColumnEnabled": "Ingeschakeld",
|
||||||
|
"alertingDeleteQuestion": "Bevestig alstublieft dat u deze waarschuwingsregel wilt verwijderen.",
|
||||||
|
"alertingDeleteRule": "Verwijder waarschuwingsregel",
|
||||||
|
"alertingRuleDeleted": "Waarschuwingsregel verwijderd",
|
||||||
|
"alertingRuleSaved": "Waarschuwingsregel opgeslagen",
|
||||||
|
"alertingRuleSavedCreatedDescription": "Uw nieuwe waarschuwingsregel is aangemaakt. U kunt deze op deze pagina blijven bewerken.",
|
||||||
|
"alertingRuleSavedUpdatedDescription": "Uw wijzigingen in deze waarschuwingsregel zijn opgeslagen.",
|
||||||
|
"alertingEditRule": "Bewerk waarschuwingsregel",
|
||||||
|
"alertingCreateRule": "Waarschuwingsregel aanmaken",
|
||||||
|
"alertingRuleCredenzaDescription": "Kies wat te bekijken, wanneer het moet gebeuren en hoe te waarschuwen",
|
||||||
|
"alertingRuleNamePlaceholder": "Productiesite offline",
|
||||||
|
"alertingRuleEnabled": "Regel ingeschakeld",
|
||||||
|
"alertingSectionSource": "Bron",
|
||||||
|
"alertingSourceType": "Brontype",
|
||||||
|
"alertingSourceSite": "Site",
|
||||||
|
"alertingSourceHealthCheck": "Gezondheidscontrole",
|
||||||
|
"alertingPickSites": "Sites",
|
||||||
|
"alertingPickHealthChecks": "Gezondheidscontroles",
|
||||||
|
"alertingPickResources": "Bronnen",
|
||||||
|
"alertingAllSites": "Alle sites",
|
||||||
|
"alertingAllSitesDescription": "Waarschuwing voor elke site",
|
||||||
|
"alertingSpecificSites": "Specifieke sites",
|
||||||
|
"alertingSpecificSitesDescription": "Kies specifieke sites om in de gaten te houden",
|
||||||
|
"alertingAllHealthChecks": "Alle Gezondheidscontroles",
|
||||||
|
"alertingAllHealthChecksDescription": "Waarschuwing voor elke gezondheidscontrole",
|
||||||
|
"alertingSpecificHealthChecks": "Specifieke Gezondheidscontroles",
|
||||||
|
"alertingSpecificHealthChecksDescription": "Kies specifieke gezondheidscontroles om in de gaten te houden",
|
||||||
|
"alertingAllResources": "Alle bronnen",
|
||||||
|
"alertingAllResourcesDescription": "Waarschuwing voor elke bron",
|
||||||
|
"alertingSpecificResources": "Specifieke bronnen",
|
||||||
|
"alertingSpecificResourcesDescription": "Kies specifieke bronnen om in de gaten te houden",
|
||||||
|
"alertingSelectResources": "Selecteer bronnen…",
|
||||||
|
"alertingResourcesSelected": "{count} bronnen geselecteerd",
|
||||||
|
"alertingResourcesEmpty": "Geen bronnen met doelen in de eerste 10 resultaten.",
|
||||||
|
"alertingSectionTrigger": "Trigger",
|
||||||
|
"alertingTrigger": "Wanneer te waarschuwen",
|
||||||
|
"alertingTriggerSiteOnline": "Site online",
|
||||||
|
"alertingTriggerSiteOffline": "Site offline",
|
||||||
|
"alertingTriggerSiteToggle": "Site status wijzigt",
|
||||||
|
"alertingTriggerHcHealthy": "Gezondheidscontrole gezond",
|
||||||
|
"alertingTriggerHcUnhealthy": "Gezondheidscontrole ongezond",
|
||||||
|
"alertingTriggerHcToggle": "Gezondheidscontrole status verandert",
|
||||||
|
"alertingTriggerResourceHealthy": "Bron gezond",
|
||||||
|
"alertingTriggerResourceUnhealthy": "Bron ongezond",
|
||||||
|
"alertingTriggerResourceDegraded": "Bron gedegradeerd",
|
||||||
|
"alertingSearchHealthChecks": "Zoek gezondheidscontroles…",
|
||||||
|
"alertingHealthChecksEmpty": "Geen gezondheidscontroles beschikbaar.",
|
||||||
|
"alertingTriggerResourceToggle": "Bronstatus wijzigt",
|
||||||
|
"alertingSourceResource": "Bron",
|
||||||
|
"alertingSectionActions": "Acties",
|
||||||
|
"alertingAddAction": "Actie toevoegen",
|
||||||
|
"alertingActionNotify": "E-mail",
|
||||||
|
"alertingActionNotifyDescription": "Stuur e-mailmeldingen naar gebruikers of rollen",
|
||||||
|
"alertingActionWebhook": "Webhook",
|
||||||
|
"alertingActionWebhookDescription": "Stuur een HTTP-verzoek naar een aangepast eindpunt",
|
||||||
|
"alertingExternalIntegration": "Externe integratie",
|
||||||
|
"alertingExternalPagerDutyDescription": "Stuur waarschuwingen naar PagerDuty voor incidentbeheer",
|
||||||
|
"alertingExternalOpsgenieDescription": "Routeer waarschuwingen naar Opsgenie voor wachtdienstbeheer",
|
||||||
|
"alertingExternalServiceNowDescription": "Maak ServiceNow-incidenten aan vanuit waarschuwingsgebeurtenissen",
|
||||||
|
"alertingExternalIncidentIoDescription": "Trigger Incident.io workflows van waarschuwingsgebeurtenissen",
|
||||||
|
"alertingActionType": "Actietype",
|
||||||
|
"alertingNotifyUsers": "Gebruikers",
|
||||||
|
"alertingNotifyRoles": "Rollen",
|
||||||
|
"alertingNotifyEmails": "E-mailadressen",
|
||||||
|
"alertingEmailPlaceholder": "Voeg e-mail toe en druk op Enter",
|
||||||
|
"alertingWebhookMethod": "HTTP-methode",
|
||||||
|
"alertingWebhookSecret": "Ondertekengeheim (optioneel)",
|
||||||
|
"alertingWebhookSecretPlaceholder": "HMAC-geheim",
|
||||||
|
"alertingWebhookHeaders": "Headers",
|
||||||
|
"alertingAddHeader": "Header toevoegen",
|
||||||
|
"alertingSelectSites": "Selecteer sites…",
|
||||||
|
"alertingSitesSelected": "{count} sites geselecteerd",
|
||||||
|
"alertingSelectHealthChecks": "Selecteer gezondheidscontroles…",
|
||||||
|
"alertingHealthChecksSelected": "{count} gezondheidscontroles geselecteerd",
|
||||||
|
"alertingNoHealthChecks": "Geen doelen met ingeschakelde gezondheidscontroles",
|
||||||
|
"alertingHealthCheckStub": "Gezondheidscontrole brondeselectie is nog niet gekoppeld - u kunt nog steeds triggers en acties configureren.",
|
||||||
|
"alertingSelectUsers": "Selecteer gebruikers…",
|
||||||
|
"alertingUsersSelected": "{count} gebruikers geselecteerd",
|
||||||
|
"alertingSelectRoles": "Selecteer rollen…",
|
||||||
|
"alertingRolesSelected": "{count} rollen geselecteerd",
|
||||||
|
"alertingSummarySites": "Sites ({count})",
|
||||||
|
"alertingSummaryAllSites": "Alle sites",
|
||||||
|
"alertingSummaryHealthChecks": "Gezondheidscontroles ({count})",
|
||||||
|
"alertingSummaryAllHealthChecks": "Alle gezondheidscontroles",
|
||||||
|
"alertingSummaryResources": "Bronnen ({count})",
|
||||||
|
"alertingSummaryAllResources": "Alle bronnen",
|
||||||
|
"alertingErrorNameRequired": "Voer een naam in",
|
||||||
|
"alertingErrorActionsMin": "Voeg minimaal één actie toe",
|
||||||
|
"alertingErrorPickSites": "Selecteer minimaal één site",
|
||||||
|
"alertingErrorPickHealthChecks": "Selecteer minimaal één gezondheidscontrole",
|
||||||
|
"alertingErrorPickResources": "Selecteer minimaal één bron",
|
||||||
|
"alertingErrorTriggerSite": "Kies een site-trigger",
|
||||||
|
"alertingErrorTriggerHealth": "Kies een gezondheidscontrole-trigger",
|
||||||
|
"alertingErrorTriggerResource": "Kies een bron-trigger",
|
||||||
|
"alertingErrorNotifyRecipients": "Kies gebruikers, rollen of ten minste één e-mail",
|
||||||
|
"alertingConfigureSource": "Bron configureren",
|
||||||
|
"alertingConfigureTrigger": "Trigger configureren",
|
||||||
|
"alertingConfigureActions": "Acties configureren",
|
||||||
|
"alertingBackToRules": "Terug naar regels",
|
||||||
|
"alertingRuleCooldown": "Aflkoelperiode (seconden)",
|
||||||
|
"alertingRuleCooldownDescription": "Minimale tijd tussen herhaalwaarschuwingen voor dezelfde regel. Zet op 0 om elke keer te laten vuren.",
|
||||||
|
"alertingDraftBadge": "Concept - opslaan om deze regel op te slaan",
|
||||||
|
"alertingSidebarHint": "Klik op een stap in het canvas om deze hier te bewerken.",
|
||||||
|
"alertingGraphCanvasTitle": "Regelstroom",
|
||||||
|
"alertingGraphCanvasDescription": "Visueel overzicht van bron, trigger en acties. Selecteer een node om deze in het paneel te bewerken.",
|
||||||
|
"alertingNodeNotConfigured": "Nog niet geconfigureerd",
|
||||||
|
"alertingNodeActionsCount": "{count, plural, one {# actie} other {# acties}}",
|
||||||
|
"alertingNodeRoleSource": "Bron",
|
||||||
|
"alertingNodeRoleTrigger": "Trigger",
|
||||||
|
"alertingNodeRoleAction": "Actie",
|
||||||
|
"alertingTabRules": "Waarschuwingsregels",
|
||||||
|
"alertingTabHealthChecks": "Gezondheidscontroles",
|
||||||
|
"alertingRulesBannerTitle": "Meldingen ontvangen",
|
||||||
|
"alertingRulesBannerDescription": "Elke regel koppelt wat te bekijken (een site, gezondheidscontrole of bron), wanneer te vuren (bijvoorbeeld offline of ongezond), en hoe uw team te waarschuwen via e-mail, webhooks of integraties. Gebruik deze lijst om die regels te maken, in te schakelen en te beheren.",
|
||||||
|
"alertingHealthChecksBannerTitle": "Gezondheid & bronnen bewaken",
|
||||||
|
"alertingHealthChecksBannerDescription": "Gezondheidscontroles zijn HTTP- of TCP-monitoren die u één keer definieert. U kunt ze vervolgens als bronnen in waarschuwingsregels gebruiken, zodat u meldingen krijgt wanneer een doelwit gezond of ongezond wordt. Gezondheidscontroles van bronnen verschijnen ook hier.",
|
||||||
|
"standaloneHcTableTitle": "Gezondheidscontroles",
|
||||||
|
"standaloneHcSearchPlaceholder": "Zoek gezondheidscontroles…",
|
||||||
|
"standaloneHcAddButton": "Gezondheidscontrole aanmaken",
|
||||||
|
"standaloneHcCreateTitle": "Gezondheidscontrole aanmaken",
|
||||||
|
"standaloneHcEditTitle": "Gezondheidscontrole bewerken",
|
||||||
|
"standaloneHcDescription": "Configureer een HTTP- of TCP-gezondheidscontrole voor gebruik in waarschuwingsregels.",
|
||||||
|
"standaloneHcNameLabel": "Naam",
|
||||||
|
"standaloneHcNamePlaceholder": "Mijn HTTP-monitor",
|
||||||
|
"standaloneHcDeleteTitle": "Gezondheidscontrole verwijderen",
|
||||||
|
"standaloneHcDeleteQuestion": "Bevestig alstublieft dat u deze gezondheidscontrole wilt verwijderen.",
|
||||||
|
"standaloneHcDeleted": "Gezondheidscontrole verwijderd",
|
||||||
|
"standaloneHcSaved": "Gezondheidscontrole opgeslagen",
|
||||||
|
"standaloneHcColumnHealth": "Gezondheid",
|
||||||
|
"standaloneHcColumnMode": "Modus",
|
||||||
|
"standaloneHcColumnTarget": "Doelwit",
|
||||||
|
"standaloneHcHealthStateHealthy": "Gezond",
|
||||||
|
"standaloneHcHealthStateUnhealthy": "Ongezond",
|
||||||
|
"standaloneHcHealthStateUnknown": "Onbekend",
|
||||||
|
"standaloneHcFilterAnySite": "Alle sites",
|
||||||
|
"standaloneHcFilterAnyResource": "Alle bronnen",
|
||||||
|
"standaloneHcFilterMode": "Modus",
|
||||||
|
"standaloneHcFilterModeHttp": "HTTP",
|
||||||
|
"standaloneHcFilterModeTcp": "TCP",
|
||||||
|
"standaloneHcFilterModeSnmp": "SNMP",
|
||||||
|
"standaloneHcFilterModePing": "Ping",
|
||||||
|
"standaloneHcFilterHealth": "Gezondheid",
|
||||||
|
"standaloneHcFilterEnabled": "Ingeschakeld",
|
||||||
|
"standaloneHcFilterEnabledOn": "Ingeschakeld",
|
||||||
|
"standaloneHcFilterEnabledOff": "Uitgeschakeld",
|
||||||
|
"standaloneHcFilterSiteIdFallback": "Site {id}",
|
||||||
|
"standaloneHcFilterResourceIdFallback": "Bron {id}",
|
||||||
"blueprints": "Blauwdrukken",
|
"blueprints": "Blauwdrukken",
|
||||||
"blueprintsDescription": "Gebruik declaratieve configuraties en bekijk vorige uitvoeringen.",
|
"blueprintsDescription": "Gebruik declaratieve configuraties en bekijk vorige uitvoeringen.",
|
||||||
"blueprintAdd": "Blauwdruk toevoegen",
|
"blueprintAdd": "Blauwdruk toevoegen",
|
||||||
@@ -1269,7 +1560,6 @@
|
|||||||
"parsedContents": "Geparseerde inhoud (alleen lezen)",
|
"parsedContents": "Geparseerde inhoud (alleen lezen)",
|
||||||
"enableDockerSocket": "Schakel Docker Blauwdruk in",
|
"enableDockerSocket": "Schakel Docker Blauwdruk in",
|
||||||
"enableDockerSocketDescription": "Schakel Docker Socket label in voor blauwdruk labels. Pad naar Nieuw.",
|
"enableDockerSocketDescription": "Schakel Docker Socket label in voor blauwdruk labels. Pad naar Nieuw.",
|
||||||
"enableDockerSocketLink": "Meer informatie",
|
|
||||||
"viewDockerContainers": "Bekijk Docker containers",
|
"viewDockerContainers": "Bekijk Docker containers",
|
||||||
"containersIn": "Containers in {siteName}",
|
"containersIn": "Containers in {siteName}",
|
||||||
"selectContainerDescription": "Selecteer een container om als hostnaam voor dit doel te gebruiken. Klik op een poort om een poort te gebruiken.",
|
"selectContainerDescription": "Selecteer een container om als hostnaam voor dit doel te gebruiken. Klik op een poort om een poort te gebruiken.",
|
||||||
@@ -1311,7 +1601,8 @@
|
|||||||
"initialSetupDescription": "Maak het eerste serverbeheeraccount aan. Er kan slechts één serverbeheerder bestaan. U kunt deze inloggegevens later altijd wijzigen.",
|
"initialSetupDescription": "Maak het eerste serverbeheeraccount aan. Er kan slechts één serverbeheerder bestaan. U kunt deze inloggegevens later altijd wijzigen.",
|
||||||
"createAdminAccount": "Maak een beheeraccount aan",
|
"createAdminAccount": "Maak een beheeraccount aan",
|
||||||
"setupErrorCreateAdmin": "Er is een fout opgetreden bij het maken van het serverbeheerdersaccount.",
|
"setupErrorCreateAdmin": "Er is een fout opgetreden bij het maken van het serverbeheerdersaccount.",
|
||||||
"certificateStatus": "Certificaatstatus",
|
"certificateStatus": "Certificaat",
|
||||||
|
"certificateStatusAutoRefreshHint": "Status ververst automatisch.",
|
||||||
"loading": "Bezig met laden",
|
"loading": "Bezig met laden",
|
||||||
"loadingAnalytics": "Laden van Analytics",
|
"loadingAnalytics": "Laden van Analytics",
|
||||||
"restart": "Herstarten",
|
"restart": "Herstarten",
|
||||||
@@ -1380,6 +1671,7 @@
|
|||||||
"pangolinUpdateAvailableReleaseNotes": "Uitgaveopmerkingen bekijken",
|
"pangolinUpdateAvailableReleaseNotes": "Uitgaveopmerkingen bekijken",
|
||||||
"newtUpdateAvailable": "Update beschikbaar",
|
"newtUpdateAvailable": "Update beschikbaar",
|
||||||
"newtUpdateAvailableInfo": "Er is een nieuwe versie van Newt beschikbaar. Update naar de nieuwste versie voor de beste ervaring.",
|
"newtUpdateAvailableInfo": "Er is een nieuwe versie van Newt beschikbaar. Update naar de nieuwste versie voor de beste ervaring.",
|
||||||
|
"pangolinNodeUpdateAvailableInfo": "Er is een nieuwe versie van Pangolin Node beschikbaar. Update naar de nieuwste versie voor de beste ervaring.",
|
||||||
"domainPickerEnterDomain": "Domein",
|
"domainPickerEnterDomain": "Domein",
|
||||||
"domainPickerPlaceholder": "mijnapp.voorbeeld.nl",
|
"domainPickerPlaceholder": "mijnapp.voorbeeld.nl",
|
||||||
"domainPickerDescription": "Voer de volledige domein van de bron in om beschikbare opties te zien.",
|
"domainPickerDescription": "Voer de volledige domein van de bron in om beschikbare opties te zien.",
|
||||||
@@ -1397,6 +1689,7 @@
|
|||||||
"domainPickerNamespace": "Naamruimte: {namespace}",
|
"domainPickerNamespace": "Naamruimte: {namespace}",
|
||||||
"domainPickerShowMore": "Meer weergeven",
|
"domainPickerShowMore": "Meer weergeven",
|
||||||
"regionSelectorTitle": "Selecteer Regio",
|
"regionSelectorTitle": "Selecteer Regio",
|
||||||
|
"domainPickerRemoteExitNodeWarning": "Opgegeven domeinen worden niet ondersteund wanneer websites verbinding maken met externe sluitnodes. Gebruik in plaats daarvan een aangepast domein. Om bronnen beschikbaar te maken op externe nodes.",
|
||||||
"regionSelectorInfo": "Het selecteren van een regio helpt ons om betere prestaties te leveren voor uw locatie. U hoeft niet in dezelfde regio als uw server te zijn.",
|
"regionSelectorInfo": "Het selecteren van een regio helpt ons om betere prestaties te leveren voor uw locatie. U hoeft niet in dezelfde regio als uw server te zijn.",
|
||||||
"regionSelectorPlaceholder": "Kies een regio",
|
"regionSelectorPlaceholder": "Kies een regio",
|
||||||
"regionSelectorComingSoon": "Komt binnenkort",
|
"regionSelectorComingSoon": "Komt binnenkort",
|
||||||
@@ -1409,6 +1702,7 @@
|
|||||||
"billingSites": "Sites",
|
"billingSites": "Sites",
|
||||||
"billingUsers": "Gebruikers",
|
"billingUsers": "Gebruikers",
|
||||||
"billingDomains": "Domeinen",
|
"billingDomains": "Domeinen",
|
||||||
|
"billingOrganizations": "Ordenen",
|
||||||
"billingRemoteExitNodes": "Externe knooppunten",
|
"billingRemoteExitNodes": "Externe knooppunten",
|
||||||
"billingNoLimitConfigured": "Geen limiet ingesteld",
|
"billingNoLimitConfigured": "Geen limiet ingesteld",
|
||||||
"billingEstimatedPeriod": "Geschatte Facturatie Periode",
|
"billingEstimatedPeriod": "Geschatte Facturatie Periode",
|
||||||
@@ -1451,6 +1745,7 @@
|
|||||||
"failed": "Mislukt",
|
"failed": "Mislukt",
|
||||||
"createNewOrgDescription": "Maak een nieuwe organisatie",
|
"createNewOrgDescription": "Maak een nieuwe organisatie",
|
||||||
"organization": "Organisatie",
|
"organization": "Organisatie",
|
||||||
|
"primary": "Primair",
|
||||||
"port": "Poort",
|
"port": "Poort",
|
||||||
"securityKeyManage": "Beveiligingssleutels beheren",
|
"securityKeyManage": "Beveiligingssleutels beheren",
|
||||||
"securityKeyDescription": "Voeg beveiligingssleutels toe of verwijder ze voor wachtwoordloze authenticatie",
|
"securityKeyDescription": "Voeg beveiligingssleutels toe of verwijder ze voor wachtwoordloze authenticatie",
|
||||||
@@ -1548,6 +1843,16 @@
|
|||||||
"billingFeatureLossWarning": "Kennisgeving beschikbaarheid",
|
"billingFeatureLossWarning": "Kennisgeving beschikbaarheid",
|
||||||
"billingFeatureLossDescription": "Door downgraden worden functies die niet beschikbaar zijn in het nieuwe abonnement automatisch uitgeschakeld. Sommige instellingen en configuraties kunnen verloren gaan. Raadpleeg de prijsmatrix om te begrijpen welke functies niet langer beschikbaar zijn.",
|
"billingFeatureLossDescription": "Door downgraden worden functies die niet beschikbaar zijn in het nieuwe abonnement automatisch uitgeschakeld. Sommige instellingen en configuraties kunnen verloren gaan. Raadpleeg de prijsmatrix om te begrijpen welke functies niet langer beschikbaar zijn.",
|
||||||
"billingUsageExceedsLimit": "Huidig gebruik ({current}) overschrijdt limiet ({limit})",
|
"billingUsageExceedsLimit": "Huidig gebruik ({current}) overschrijdt limiet ({limit})",
|
||||||
|
"billingPastDueTitle": "Vervaldatum betaling",
|
||||||
|
"billingPastDueDescription": "Uw betaling is verlopen. Werk uw betaalmethode bij om uw huidige abonnementsfuncties te blijven gebruiken. Als dit niet is opgelost, zal je abonnement worden geannuleerd en zal je worden teruggezet naar de vrije rang.",
|
||||||
|
"billingUnpaidTitle": "Abonnement Onbetaald",
|
||||||
|
"billingUnpaidDescription": "Uw abonnement is niet betaald en u bent teruggekeerd naar het gratis niveau. Update uw betalingsmethode om uw abonnement te herstellen.",
|
||||||
|
"billingIncompleteTitle": "Betaling onvolledig",
|
||||||
|
"billingIncompleteDescription": "Uw betaling is onvolledig. Voltooi alstublieft het betalingsproces om uw abonnement te activeren.",
|
||||||
|
"billingIncompleteExpiredTitle": "Betaling verlopen",
|
||||||
|
"billingIncompleteExpiredDescription": "Uw betaling is nooit voltooid en verlopen. U bent teruggekeerd naar de gratis niveaus. Abonneer u opnieuw om de toegang tot betaalde functies te herstellen.",
|
||||||
|
"billingManageSubscription": "Beheer uw abonnement",
|
||||||
|
"billingResolvePaymentIssue": "Gelieve uw betalingsprobleem op te lossen voor het upgraden of downgraden",
|
||||||
"signUpTerms": {
|
"signUpTerms": {
|
||||||
"IAgreeToThe": "Ik ga akkoord met de",
|
"IAgreeToThe": "Ik ga akkoord met de",
|
||||||
"termsOfService": "servicevoorwaarden",
|
"termsOfService": "servicevoorwaarden",
|
||||||
@@ -1606,6 +1911,7 @@
|
|||||||
"configureHealthCheck": "Configureer Gezondheidscontrole",
|
"configureHealthCheck": "Configureer Gezondheidscontrole",
|
||||||
"configureHealthCheckDescription": "Stel gezondheid monitor voor {target} in",
|
"configureHealthCheckDescription": "Stel gezondheid monitor voor {target} in",
|
||||||
"enableHealthChecks": "Inschakelen Gezondheidscontroles",
|
"enableHealthChecks": "Inschakelen Gezondheidscontroles",
|
||||||
|
"healthCheckDisabledStateDescription": "Wanneer uitgeschakeld, zal de site geen gezondheidscontroles uitvoeren en wordt de staat als onbekend beschouwd.",
|
||||||
"enableHealthChecksDescription": "Controleer de gezondheid van dit doel. U kunt een ander eindpunt monitoren dan het doel indien vereist.",
|
"enableHealthChecksDescription": "Controleer de gezondheid van dit doel. U kunt een ander eindpunt monitoren dan het doel indien vereist.",
|
||||||
"healthScheme": "Methode",
|
"healthScheme": "Methode",
|
||||||
"healthSelectScheme": "Selecteer methode",
|
"healthSelectScheme": "Selecteer methode",
|
||||||
@@ -1621,6 +1927,24 @@
|
|||||||
"timeIsInSeconds": "Tijd is in seconden",
|
"timeIsInSeconds": "Tijd is in seconden",
|
||||||
"requireDeviceApproval": "Vereist goedkeuring van apparaat",
|
"requireDeviceApproval": "Vereist goedkeuring van apparaat",
|
||||||
"requireDeviceApprovalDescription": "Gebruikers met deze rol hebben nieuwe apparaten nodig die door een beheerder zijn goedgekeurd voordat ze verbinding kunnen maken met bronnen en deze kunnen gebruiken.",
|
"requireDeviceApprovalDescription": "Gebruikers met deze rol hebben nieuwe apparaten nodig die door een beheerder zijn goedgekeurd voordat ze verbinding kunnen maken met bronnen en deze kunnen gebruiken.",
|
||||||
|
"sshAccess": "SSH toegang",
|
||||||
|
"roleAllowSsh": "SSH toestaan",
|
||||||
|
"roleAllowSshAllow": "Toestaan",
|
||||||
|
"roleAllowSshDisallow": "Weigeren",
|
||||||
|
"roleAllowSshDescription": "Sta gebruikers met deze rol toe om verbinding te maken met bronnen via SSH. Indien uitgeschakeld kan de rol geen gebruik maken van SSH toegang.",
|
||||||
|
"sshSudoMode": "Sudo toegang",
|
||||||
|
"sshSudoModeNone": "geen",
|
||||||
|
"sshSudoModeNoneDescription": "Gebruiker kan geen commando's uitvoeren met sudo.",
|
||||||
|
"sshSudoModeFull": "Volledige Sudo",
|
||||||
|
"sshSudoModeFullDescription": "Gebruiker kan elk commando uitvoeren met een sudo.",
|
||||||
|
"sshSudoModeCommands": "Opdrachten",
|
||||||
|
"sshSudoModeCommandsDescription": "Gebruiker kan alleen de opgegeven commando's uitvoeren met de sudo.",
|
||||||
|
"sshSudo": "sudo toestaan",
|
||||||
|
"sshSudoCommands": "Sudo Commando's",
|
||||||
|
"sshSudoCommandsDescription": "Komma's gescheiden lijst van commando's waar de gebruiker een sudo mee mag uitvoeren.",
|
||||||
|
"sshCreateHomeDir": "Maak Home Directory",
|
||||||
|
"sshUnixGroups": "Unix groepen",
|
||||||
|
"sshUnixGroupsDescription": "Door komma's gescheiden Unix-groepen om de gebruiker toe te voegen aan de doelhost.",
|
||||||
"retryAttempts": "Herhaal Pogingen",
|
"retryAttempts": "Herhaal Pogingen",
|
||||||
"expectedResponseCodes": "Verwachte Reactiecodes",
|
"expectedResponseCodes": "Verwachte Reactiecodes",
|
||||||
"expectedResponseCodesDescription": "HTTP-statuscode die gezonde status aangeeft. Indien leeg wordt 200-300 als gezond beschouwd.",
|
"expectedResponseCodesDescription": "HTTP-statuscode die gezonde status aangeeft. Indien leeg wordt 200-300 als gezond beschouwd.",
|
||||||
@@ -1637,9 +1961,20 @@
|
|||||||
"healthCheckIntervalMin": "Controle interval moet minimaal 5 seconden zijn",
|
"healthCheckIntervalMin": "Controle interval moet minimaal 5 seconden zijn",
|
||||||
"healthCheckTimeoutMin": "Timeout moet minimaal 1 seconde zijn",
|
"healthCheckTimeoutMin": "Timeout moet minimaal 1 seconde zijn",
|
||||||
"healthCheckRetryMin": "Herhaal pogingen moet minimaal 1 zijn",
|
"healthCheckRetryMin": "Herhaal pogingen moet minimaal 1 zijn",
|
||||||
|
"healthCheckMode": "Controlemodus",
|
||||||
|
"healthCheckStrategy": "Strategie",
|
||||||
|
"healthCheckModeDescription": "TCP-modus verifieert alleen connectiviteit. HTTP-modus valideert de HTTP-respons.",
|
||||||
|
"healthyThreshold": "Gezonde drempel",
|
||||||
|
"healthyThresholdDescription": "Opeenvolgende successen vereist voordat gemarkeerd wordt als gezond.",
|
||||||
|
"unhealthyThreshold": "Ongezonde drempel",
|
||||||
|
"unhealthyThresholdDescription": "Opeenvolgende fouten vereist voordat gemarkeerd wordt als ongezond.",
|
||||||
|
"healthCheckHealthyThresholdMin": "Gezonde drempel moet minimaal 1 zijn",
|
||||||
|
"healthCheckUnhealthyThresholdMin": "Ongezonde drempel moet minimaal 1 zijn",
|
||||||
"httpMethod": "HTTP-methode",
|
"httpMethod": "HTTP-methode",
|
||||||
"selectHttpMethod": "Selecteer HTTP-methode",
|
"selectHttpMethod": "Selecteer HTTP-methode",
|
||||||
"domainPickerSubdomainLabel": "Subdomein",
|
"domainPickerSubdomainLabel": "Subdomein",
|
||||||
|
"domainPickerWildcard": "Wildcard",
|
||||||
|
"domainPickerWildcardPaidOnly": "Wildcard-subdomeinen zijn een betaalde functie. Upgrade om deze functie te gebruiken.",
|
||||||
"domainPickerBaseDomainLabel": "Basisdomein",
|
"domainPickerBaseDomainLabel": "Basisdomein",
|
||||||
"domainPickerSearchDomains": "Zoek domeinen...",
|
"domainPickerSearchDomains": "Zoek domeinen...",
|
||||||
"domainPickerNoDomainsFound": "Geen domeinen gevonden",
|
"domainPickerNoDomainsFound": "Geen domeinen gevonden",
|
||||||
@@ -1665,12 +2000,12 @@
|
|||||||
"resourcesTableAliasAddressInfo": "Dit adres is onderdeel van het hulpprogramma subnet van de organisatie. Het wordt gebruikt om aliasrecords op te lossen met behulp van interne DNS-resolutie.",
|
"resourcesTableAliasAddressInfo": "Dit adres is onderdeel van het hulpprogramma subnet van de organisatie. Het wordt gebruikt om aliasrecords op te lossen met behulp van interne DNS-resolutie.",
|
||||||
"resourcesTableClients": "Clienten",
|
"resourcesTableClients": "Clienten",
|
||||||
"resourcesTableAndOnlyAccessibleInternally": "en zijn alleen intern toegankelijk wanneer verbonden met een client.",
|
"resourcesTableAndOnlyAccessibleInternally": "en zijn alleen intern toegankelijk wanneer verbonden met een client.",
|
||||||
"resourcesTableNoTargets": "Geen doelen",
|
|
||||||
"resourcesTableHealthy": "Gezond",
|
"resourcesTableHealthy": "Gezond",
|
||||||
"resourcesTableDegraded": "Verminderde",
|
"resourcesTableDegraded": "Verminderde",
|
||||||
"resourcesTableOffline": "Offline",
|
"resourcesTableUnhealthy": "Ongezond",
|
||||||
"resourcesTableUnknown": "onbekend",
|
"resourcesTableUnknown": "onbekend",
|
||||||
"resourcesTableNotMonitored": "Niet gecontroleerd",
|
"resourcesTableNotMonitored": "Niet gecontroleerd",
|
||||||
|
"resourcesTableNoTargets": "Geen doelen",
|
||||||
"editInternalResourceDialogEditClientResource": "Privépagina bewerken",
|
"editInternalResourceDialogEditClientResource": "Privépagina bewerken",
|
||||||
"editInternalResourceDialogUpdateResourceProperties": "Update de resource configuratie en access control voor {resourceName}",
|
"editInternalResourceDialogUpdateResourceProperties": "Update de resource configuratie en access control voor {resourceName}",
|
||||||
"editInternalResourceDialogResourceProperties": "Bron eigenschappen",
|
"editInternalResourceDialogResourceProperties": "Bron eigenschappen",
|
||||||
@@ -1696,6 +2031,11 @@
|
|||||||
"editInternalResourceDialogModePort": "Poort",
|
"editInternalResourceDialogModePort": "Poort",
|
||||||
"editInternalResourceDialogModeHost": "Hostnaam",
|
"editInternalResourceDialogModeHost": "Hostnaam",
|
||||||
"editInternalResourceDialogModeCidr": "CIDR",
|
"editInternalResourceDialogModeCidr": "CIDR",
|
||||||
|
"editInternalResourceDialogModeHttp": "HTTP",
|
||||||
|
"editInternalResourceDialogModeHttps": "HTTPS",
|
||||||
|
"editInternalResourceDialogScheme": "Schema",
|
||||||
|
"editInternalResourceDialogEnableSsl": "SSL inschakelen",
|
||||||
|
"editInternalResourceDialogEnableSslDescription": "Schakel SSL/TLS-encryptie in voor beveiligde HTTPS-verbindingen met de bestemming.",
|
||||||
"editInternalResourceDialogDestination": "Bestemming",
|
"editInternalResourceDialogDestination": "Bestemming",
|
||||||
"editInternalResourceDialogDestinationHostDescription": "Het IP-adres of de hostnaam van de bron op het netwerk van de site.",
|
"editInternalResourceDialogDestinationHostDescription": "Het IP-adres of de hostnaam van de bron op het netwerk van de site.",
|
||||||
"editInternalResourceDialogDestinationIPDescription": "Het IP of hostnaam adres van de bron op het netwerk van de site.",
|
"editInternalResourceDialogDestinationIPDescription": "Het IP of hostnaam adres van de bron op het netwerk van de site.",
|
||||||
@@ -1711,6 +2051,7 @@
|
|||||||
"createInternalResourceDialogName": "Naam",
|
"createInternalResourceDialogName": "Naam",
|
||||||
"createInternalResourceDialogSite": "Site",
|
"createInternalResourceDialogSite": "Site",
|
||||||
"selectSite": "Selecteer site...",
|
"selectSite": "Selecteer site...",
|
||||||
|
"multiSitesSelectorSitesCount": "{count, plural, one {# site} other {# sites}}",
|
||||||
"noSitesFound": "Geen sites gevonden.",
|
"noSitesFound": "Geen sites gevonden.",
|
||||||
"createInternalResourceDialogProtocol": "Protocol",
|
"createInternalResourceDialogProtocol": "Protocol",
|
||||||
"createInternalResourceDialogTcp": "TCP",
|
"createInternalResourceDialogTcp": "TCP",
|
||||||
@@ -1739,11 +2080,19 @@
|
|||||||
"createInternalResourceDialogModePort": "Poort",
|
"createInternalResourceDialogModePort": "Poort",
|
||||||
"createInternalResourceDialogModeHost": "Hostnaam",
|
"createInternalResourceDialogModeHost": "Hostnaam",
|
||||||
"createInternalResourceDialogModeCidr": "CIDR",
|
"createInternalResourceDialogModeCidr": "CIDR",
|
||||||
|
"createInternalResourceDialogModeHttp": "HTTP",
|
||||||
|
"createInternalResourceDialogModeHttps": "HTTPS",
|
||||||
|
"scheme": "Schema",
|
||||||
|
"createInternalResourceDialogScheme": "Schema",
|
||||||
|
"createInternalResourceDialogEnableSsl": "SSL inschakelen",
|
||||||
|
"createInternalResourceDialogEnableSslDescription": "Schakel SSL/TLS-encryptie in voor beveiligde HTTPS-verbindingen met de bestemming.",
|
||||||
"createInternalResourceDialogDestination": "Bestemming",
|
"createInternalResourceDialogDestination": "Bestemming",
|
||||||
"createInternalResourceDialogDestinationHostDescription": "Het IP-adres of de hostnaam van de bron op het netwerk van de site.",
|
"createInternalResourceDialogDestinationHostDescription": "Het IP-adres of de hostnaam van de bron op het netwerk van de site.",
|
||||||
"createInternalResourceDialogDestinationCidrDescription": "Het CIDR-bereik van het document op het netwerk van de site.",
|
"createInternalResourceDialogDestinationCidrDescription": "Het CIDR-bereik van het document op het netwerk van de site.",
|
||||||
"createInternalResourceDialogAlias": "Alias",
|
"createInternalResourceDialogAlias": "Alias",
|
||||||
"createInternalResourceDialogAliasDescription": "Een optionele interne DNS-alias voor dit document.",
|
"createInternalResourceDialogAliasDescription": "Een optionele interne DNS-alias voor dit document.",
|
||||||
|
"internalResourceDownstreamSchemeRequired": "Schema is vereist voor HTTP-bronnen",
|
||||||
|
"internalResourceHttpPortRequired": "Bestemmingspoort is vereist voor HTTP-bronnen",
|
||||||
"siteConfiguration": "Configuratie",
|
"siteConfiguration": "Configuratie",
|
||||||
"siteAcceptClientConnections": "Accepteer clientverbindingen",
|
"siteAcceptClientConnections": "Accepteer clientverbindingen",
|
||||||
"siteAcceptClientConnectionsDescription": "Sta gebruikersapparaten en clients toegang toe tot bronnen op deze site. Dit kan later worden gewijzigd.",
|
"siteAcceptClientConnectionsDescription": "Sta gebruikersapparaten en clients toegang toe tot bronnen op deze site. Dit kan later worden gewijzigd.",
|
||||||
@@ -1829,6 +2178,40 @@
|
|||||||
"exitNode": "Exit Node",
|
"exitNode": "Exit Node",
|
||||||
"country": "Land",
|
"country": "Land",
|
||||||
"rulesMatchCountry": "Momenteel gebaseerd op bron IP",
|
"rulesMatchCountry": "Momenteel gebaseerd op bron IP",
|
||||||
|
"region": "Regio",
|
||||||
|
"selectRegion": "Selecteer regio",
|
||||||
|
"searchRegions": "Zoek regio's...",
|
||||||
|
"noRegionFound": "Geen regio gevonden.",
|
||||||
|
"rulesMatchRegion": "Selecteer een regionale groepering van landen",
|
||||||
|
"rulesErrorInvalidRegion": "Ongeldige regio",
|
||||||
|
"rulesErrorInvalidRegionDescription": "Selecteer een geldige regio.",
|
||||||
|
"regionAfrica": "Afrika",
|
||||||
|
"regionNorthernAfrica": "Noord-Afrika",
|
||||||
|
"regionEasternAfrica": "Oost Afrika",
|
||||||
|
"regionMiddleAfrica": "Midden Afrika",
|
||||||
|
"regionSouthernAfrica": "Zuidelijk Afrika",
|
||||||
|
"regionWesternAfrica": "Westelijk Afrika",
|
||||||
|
"regionAmericas": "Amerika's",
|
||||||
|
"regionCaribbean": "Caraïben",
|
||||||
|
"regionCentralAmerica": "Midden-Amerika",
|
||||||
|
"regionSouthAmerica": "Zuid Amerika",
|
||||||
|
"regionNorthernAmerica": "Noord-Amerika",
|
||||||
|
"regionAsia": "Azië",
|
||||||
|
"regionCentralAsia": "Centraal-Azië",
|
||||||
|
"regionEasternAsia": "Oost-Azië",
|
||||||
|
"regionSouthEasternAsia": "Zuid-Oost-Azië",
|
||||||
|
"regionSouthernAsia": "Zuid-Azië",
|
||||||
|
"regionWesternAsia": "Westelijk Azië",
|
||||||
|
"regionEurope": "Europa",
|
||||||
|
"regionEasternEurope": "Oost-Europa",
|
||||||
|
"regionNorthernEurope": "Noord-Europa",
|
||||||
|
"regionSouthernEurope": "Zuid-Europa",
|
||||||
|
"regionWesternEurope": "West-Europa",
|
||||||
|
"regionOceania": "Oceania",
|
||||||
|
"regionAustraliaAndNewZealand": "Australië en Nieuw-Zeeland",
|
||||||
|
"regionMelanesia": "Melanesia",
|
||||||
|
"regionMicronesia": "Micronesia",
|
||||||
|
"regionPolynesia": "Polynesia",
|
||||||
"managedSelfHosted": {
|
"managedSelfHosted": {
|
||||||
"title": "Beheerde Self-Hosted",
|
"title": "Beheerde Self-Hosted",
|
||||||
"description": "betrouwbaardere en slecht onderhouden Pangolin server met extra klokken en klokkenluiders",
|
"description": "betrouwbaardere en slecht onderhouden Pangolin server met extra klokken en klokkenluiders",
|
||||||
@@ -1867,7 +2250,7 @@
|
|||||||
},
|
},
|
||||||
"internationaldomaindetected": "Internationaal Domein Gedetecteerd",
|
"internationaldomaindetected": "Internationaal Domein Gedetecteerd",
|
||||||
"willbestoredas": "Zal worden opgeslagen als:",
|
"willbestoredas": "Zal worden opgeslagen als:",
|
||||||
"roleMappingDescription": "Bepaal hoe rollen worden toegewezen aan gebruikers wanneer ze inloggen wanneer Auto Provision is ingeschakeld.",
|
"roleMappingDescription": "Bepaal hoe rollen aan gebruikers worden toegewezen wanneer ze zich aanmelden met deze identiteitsprovider.",
|
||||||
"selectRole": "Selecteer een rol",
|
"selectRole": "Selecteer een rol",
|
||||||
"roleMappingExpression": "Expressie",
|
"roleMappingExpression": "Expressie",
|
||||||
"selectRolePlaceholder": "Kies een rol",
|
"selectRolePlaceholder": "Kies een rol",
|
||||||
@@ -1877,6 +2260,25 @@
|
|||||||
"invalidValue": "Ongeldige waarde",
|
"invalidValue": "Ongeldige waarde",
|
||||||
"idpTypeLabel": "Identiteit provider type",
|
"idpTypeLabel": "Identiteit provider type",
|
||||||
"roleMappingExpressionPlaceholder": "bijvoorbeeld bevat (groepen, 'admin') && 'Admin' ½ 'Member'",
|
"roleMappingExpressionPlaceholder": "bijvoorbeeld bevat (groepen, 'admin') && 'Admin' ½ 'Member'",
|
||||||
|
"roleMappingModeFixedRoles": "Vaste rollen",
|
||||||
|
"roleMappingModeMappingBuilder": "Toewijzing Bouwer",
|
||||||
|
"roleMappingModeRawExpression": "Ruwe expressie",
|
||||||
|
"roleMappingFixedRolesPlaceholderSelect": "Selecteer één of meer rollen",
|
||||||
|
"roleMappingFixedRolesPlaceholderFreeform": "Typ rolnamen (exacte overeenkomst per organisatie)",
|
||||||
|
"roleMappingFixedRolesDescriptionSameForAll": "Wijs dezelfde rolset toe aan elke auto-provisioned gebruiker.",
|
||||||
|
"roleMappingFixedRolesDescriptionDefaultPolicy": "Voor standaardbeleid, typ rolnamen die bestaan in elke organisatie waar gebruikers worden opgegeven. Namen moeten exact overeenkomen.",
|
||||||
|
"roleMappingClaimPath": "Claim pad",
|
||||||
|
"roleMappingClaimPathPlaceholder": "Groepen",
|
||||||
|
"roleMappingClaimPathDescription": "Pad in de token payload die bronwaarden bevat (bijvoorbeeld groepen).",
|
||||||
|
"roleMappingMatchValue": "Kies een waarde",
|
||||||
|
"roleMappingAssignRoles": "Rollen toewijzen",
|
||||||
|
"roleMappingAddMappingRule": "Toewijzingsregel toevoegen",
|
||||||
|
"roleMappingRawExpressionResultDescription": "Expressie moet een tekenreeks of tekenreeks evalueren.",
|
||||||
|
"roleMappingRawExpressionResultDescriptionSingleRole": "Expressie moet evalueren naar een tekenreeks (een naam met één rol).",
|
||||||
|
"roleMappingMatchValuePlaceholder": "Overeenkomende waarde (bijvoorbeeld: admin)",
|
||||||
|
"roleMappingAssignRolesPlaceholderFreeform": "Typ rolnamen (exact per org)",
|
||||||
|
"roleMappingBuilderFreeformRowHint": "Rol namen moeten overeenkomen met een rol in elke doelorganisatie.",
|
||||||
|
"roleMappingRemoveRule": "Verwijderen",
|
||||||
"idpGoogleConfiguration": "Google Configuratie",
|
"idpGoogleConfiguration": "Google Configuratie",
|
||||||
"idpGoogleConfigurationDescription": "Configureer de Google OAuth2-referenties",
|
"idpGoogleConfigurationDescription": "Configureer de Google OAuth2-referenties",
|
||||||
"idpGoogleClientIdDescription": "Google OAuth2 Client ID",
|
"idpGoogleClientIdDescription": "Google OAuth2 Client ID",
|
||||||
@@ -1913,6 +2315,9 @@
|
|||||||
"authPageBrandingQuestionRemove": "Weet u zeker dat u de branding voor Auth-pagina's wilt verwijderen?",
|
"authPageBrandingQuestionRemove": "Weet u zeker dat u de branding voor Auth-pagina's wilt verwijderen?",
|
||||||
"authPageBrandingDeleteConfirm": "Bevestig verwijder Branding",
|
"authPageBrandingDeleteConfirm": "Bevestig verwijder Branding",
|
||||||
"brandingLogoURL": "Het logo-URL",
|
"brandingLogoURL": "Het logo-URL",
|
||||||
|
"brandingLogoURLOrPath": "Logo URL of pad",
|
||||||
|
"brandingLogoPathDescription": "Voer een URL of een lokaal pad in.",
|
||||||
|
"brandingLogoURLDescription": "Voer een openbaar toegankelijke URL in voor uw logo afbeelding.",
|
||||||
"brandingPrimaryColor": "Primaire kleur",
|
"brandingPrimaryColor": "Primaire kleur",
|
||||||
"brandingLogoWidth": "Breedte (px)",
|
"brandingLogoWidth": "Breedte (px)",
|
||||||
"brandingLogoHeight": "Hoogte (px)",
|
"brandingLogoHeight": "Hoogte (px)",
|
||||||
@@ -1937,9 +2342,11 @@
|
|||||||
"selectDomainForOrgAuthPage": "Selecteer een domein voor de authenticatiepagina van de organisatie",
|
"selectDomainForOrgAuthPage": "Selecteer een domein voor de authenticatiepagina van de organisatie",
|
||||||
"domainPickerProvidedDomain": "Opgegeven domein",
|
"domainPickerProvidedDomain": "Opgegeven domein",
|
||||||
"domainPickerFreeProvidedDomain": "Gratis verstrekt domein",
|
"domainPickerFreeProvidedDomain": "Gratis verstrekt domein",
|
||||||
|
"domainPickerFreeDomainsPaidFeature": "Geleverde domeinen zijn een betaalde functie. Abonneer je om een domein bij je plan te krijgen - je hoeft er zelf geen mee te brengen.",
|
||||||
"domainPickerVerified": "Geverifieerd",
|
"domainPickerVerified": "Geverifieerd",
|
||||||
"domainPickerUnverified": "Ongeverifieerd",
|
"domainPickerUnverified": "Ongeverifieerd",
|
||||||
"domainPickerInvalidSubdomainStructure": "Dit subdomein bevat ongeldige tekens of structuur. Het zal automatisch worden gesaneerd wanneer u opslaat.",
|
"domainPickerManual": "Handleiding",
|
||||||
|
"domainPickerInvalidSubdomainStructure": "Ongeldige tekens worden gesaneerd bij het opslaan.",
|
||||||
"domainPickerError": "Foutmelding",
|
"domainPickerError": "Foutmelding",
|
||||||
"domainPickerErrorLoadDomains": "Fout bij het laden van organisatiedomeinen",
|
"domainPickerErrorLoadDomains": "Fout bij het laden van organisatiedomeinen",
|
||||||
"domainPickerErrorCheckAvailability": "Kan domein beschikbaarheid niet controleren",
|
"domainPickerErrorCheckAvailability": "Kan domein beschikbaarheid niet controleren",
|
||||||
@@ -1952,7 +2359,7 @@
|
|||||||
"orgAuthChooseIdpDescription": "Kies uw identiteitsprovider om door te gaan",
|
"orgAuthChooseIdpDescription": "Kies uw identiteitsprovider om door te gaan",
|
||||||
"orgAuthNoIdpConfigured": "Deze organisatie heeft geen identiteitsproviders geconfigureerd. Je kunt in plaats daarvan inloggen met je Pangolin-identiteit.",
|
"orgAuthNoIdpConfigured": "Deze organisatie heeft geen identiteitsproviders geconfigureerd. Je kunt in plaats daarvan inloggen met je Pangolin-identiteit.",
|
||||||
"orgAuthSignInWithPangolin": "Log in met Pangolin",
|
"orgAuthSignInWithPangolin": "Log in met Pangolin",
|
||||||
"orgAuthSignInToOrg": "Log in bij een organisatie",
|
"orgAuthSignInToOrg": "Organisatie Identiteitsprovider (SSO)",
|
||||||
"orgAuthSelectOrgTitle": "Organisatie Inloggen",
|
"orgAuthSelectOrgTitle": "Organisatie Inloggen",
|
||||||
"orgAuthSelectOrgDescription": "Voer je organisatie-ID in om verder te gaan",
|
"orgAuthSelectOrgDescription": "Voer je organisatie-ID in om verder te gaan",
|
||||||
"orgAuthOrgIdPlaceholder": "jouw-organisatie",
|
"orgAuthOrgIdPlaceholder": "jouw-organisatie",
|
||||||
@@ -2168,10 +2575,10 @@
|
|||||||
},
|
},
|
||||||
"scale": {
|
"scale": {
|
||||||
"title": "Schaal",
|
"title": "Schaal",
|
||||||
"description": "Enterprise functies, 50 gebruikers, 50 sites en prioriteit ondersteuning."
|
"description": "Enterprise-functies, 50 gebruikers, 100 sites en prioritaire ondersteuning."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"personalUseOnly": "Alleen persoonlijk gebruik (gratis licentie - geen afrekenen)",
|
"personalUseOnly": "Alleen voor persoonlijk gebruik (gratis licentie - geen afrekening)",
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"continueToCheckout": "Doorgaan naar afrekenen"
|
"continueToCheckout": "Doorgaan naar afrekenen"
|
||||||
},
|
},
|
||||||
@@ -2245,6 +2652,7 @@
|
|||||||
"validPassword": "Geldig wachtwoord",
|
"validPassword": "Geldig wachtwoord",
|
||||||
"validEmail": "Valid email",
|
"validEmail": "Valid email",
|
||||||
"validSSO": "Valid SSO",
|
"validSSO": "Valid SSO",
|
||||||
|
"connectedClient": "Verbonden Client",
|
||||||
"resourceBlocked": "Bron geblokkeerd",
|
"resourceBlocked": "Bron geblokkeerd",
|
||||||
"droppedByRule": "Achtergelaten door regel",
|
"droppedByRule": "Achtergelaten door regel",
|
||||||
"noSessions": "Geen sessies",
|
"noSessions": "Geen sessies",
|
||||||
@@ -2270,6 +2678,8 @@
|
|||||||
"logRetentionAccessDescription": "Hoe lang de toegangslogboeken behouden blijven",
|
"logRetentionAccessDescription": "Hoe lang de toegangslogboeken behouden blijven",
|
||||||
"logRetentionActionLabel": "Actie log bewaring",
|
"logRetentionActionLabel": "Actie log bewaring",
|
||||||
"logRetentionActionDescription": "Hoe lang de action logs behouden moeten blijven",
|
"logRetentionActionDescription": "Hoe lang de action logs behouden moeten blijven",
|
||||||
|
"logRetentionConnectionLabel": "Connectie log bewaring",
|
||||||
|
"logRetentionConnectionDescription": "Hoe lang de verbindingslogs onderhouden",
|
||||||
"logRetentionDisabled": "Uitgeschakeld",
|
"logRetentionDisabled": "Uitgeschakeld",
|
||||||
"logRetention3Days": "3 dagen",
|
"logRetention3Days": "3 dagen",
|
||||||
"logRetention7Days": "7 dagen",
|
"logRetention7Days": "7 dagen",
|
||||||
@@ -2280,8 +2690,15 @@
|
|||||||
"logRetentionEndOfFollowingYear": "Einde van volgend jaar",
|
"logRetentionEndOfFollowingYear": "Einde van volgend jaar",
|
||||||
"actionLogsDescription": "Bekijk een geschiedenis van acties die worden uitgevoerd in deze organisatie",
|
"actionLogsDescription": "Bekijk een geschiedenis van acties die worden uitgevoerd in deze organisatie",
|
||||||
"accessLogsDescription": "Toegangsverificatieverzoeken voor resources in deze organisatie bekijken",
|
"accessLogsDescription": "Toegangsverificatieverzoeken voor resources in deze organisatie bekijken",
|
||||||
"licenseRequiredToUse": "Een Enterprise-licentie is vereist om deze functie te gebruiken.",
|
"connectionLogs": "Connectie Logs",
|
||||||
"ossEnterpriseEditionRequired": "De <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> is vereist om deze functie te gebruiken.",
|
"connectionLogsDescription": "Toon verbindingslogs voor tunnels in deze organisatie",
|
||||||
|
"sidebarLogsConnection": "Connectie Logs",
|
||||||
|
"sidebarLogsStreaming": "Streamen",
|
||||||
|
"sourceAddress": "Bron adres",
|
||||||
|
"destinationAddress": "Adres bestemming",
|
||||||
|
"duration": "Duur",
|
||||||
|
"licenseRequiredToUse": "Een <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> licentie of <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink> is vereist om deze functie te gebruiken. <bookADemoLink>Boek een demo of POC trial</bookADemoLink>.",
|
||||||
|
"ossEnterpriseEditionRequired": "De <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> is vereist om deze functie te gebruiken. Deze functie is ook beschikbaar in <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>. <bookADemoLink>Boek een demo of POC trial</bookADemoLink>.",
|
||||||
"certResolver": "Certificaat Resolver",
|
"certResolver": "Certificaat Resolver",
|
||||||
"certResolverDescription": "Selecteer de certificaat resolver die moet worden gebruikt voor deze resource.",
|
"certResolverDescription": "Selecteer de certificaat resolver die moet worden gebruikt voor deze resource.",
|
||||||
"selectCertResolver": "Certificaat Resolver selecteren",
|
"selectCertResolver": "Certificaat Resolver selecteren",
|
||||||
@@ -2423,6 +2840,9 @@
|
|||||||
"machineClients": "Machine Clienten",
|
"machineClients": "Machine Clienten",
|
||||||
"install": "Installeren",
|
"install": "Installeren",
|
||||||
"run": "Uitvoeren",
|
"run": "Uitvoeren",
|
||||||
|
"envFile": "Omgevingsbestand",
|
||||||
|
"serviceFile": "Servicebestand",
|
||||||
|
"enableAndStart": "Inschakelen en Starten",
|
||||||
"clientNameDescription": "De weergavenaam van de client die later gewijzigd kan worden.",
|
"clientNameDescription": "De weergavenaam van de client die later gewijzigd kan worden.",
|
||||||
"clientAddress": "Klant adres (Geavanceerd)",
|
"clientAddress": "Klant adres (Geavanceerd)",
|
||||||
"setupFailedToFetchSubnet": "Kan standaard subnet niet ophalen",
|
"setupFailedToFetchSubnet": "Kan standaard subnet niet ophalen",
|
||||||
@@ -2471,13 +2891,30 @@
|
|||||||
"editInternalResourceDialogAddClients": "Clienten toevoegen",
|
"editInternalResourceDialogAddClients": "Clienten toevoegen",
|
||||||
"editInternalResourceDialogDestinationLabel": "Bestemming",
|
"editInternalResourceDialogDestinationLabel": "Bestemming",
|
||||||
"editInternalResourceDialogDestinationDescription": "Specificeer het bestemmingsadres voor de interne bron. Dit kan een hostnaam, IP-adres of CIDR-bereik zijn, afhankelijk van de geselecteerde modus. Stel optioneel een interne DNS-alias in voor eenvoudigere identificatie.",
|
"editInternalResourceDialogDestinationDescription": "Specificeer het bestemmingsadres voor de interne bron. Dit kan een hostnaam, IP-adres of CIDR-bereik zijn, afhankelijk van de geselecteerde modus. Stel optioneel een interne DNS-alias in voor eenvoudigere identificatie.",
|
||||||
|
"internalResourceFormMultiSiteRoutingHelp": "Selecteren van meerdere sites maakt veerkrachtige routing en failover mogelijk voor hoge beschikbaarheid.",
|
||||||
|
"internalResourceFormMultiSiteRoutingHelpLearnMore": "Meer informatie",
|
||||||
"editInternalResourceDialogPortRestrictionsDescription": "Beperk toegang tot specifieke TCP/UDP-poorten of sta alle poorten toe/blokkeer.",
|
"editInternalResourceDialogPortRestrictionsDescription": "Beperk toegang tot specifieke TCP/UDP-poorten of sta alle poorten toe/blokkeer.",
|
||||||
|
"createInternalResourceDialogHttpConfiguration": "HTTP-configuratie",
|
||||||
|
"createInternalResourceDialogHttpConfigurationDescription": "Kies het domein dat cliënten zullen gebruiken om deze bron via HTTP of HTTPS te bereiken.",
|
||||||
|
"editInternalResourceDialogHttpConfiguration": "HTTP-configuratie",
|
||||||
|
"editInternalResourceDialogHttpConfigurationDescription": "Kies het domein dat cliënten zullen gebruiken om deze bron via HTTP of HTTPS te bereiken.",
|
||||||
"editInternalResourceDialogTcp": "TCP",
|
"editInternalResourceDialogTcp": "TCP",
|
||||||
"editInternalResourceDialogUdp": "UDP",
|
"editInternalResourceDialogUdp": "UDP",
|
||||||
"editInternalResourceDialogIcmp": "ICMP",
|
"editInternalResourceDialogIcmp": "ICMP",
|
||||||
"editInternalResourceDialogAccessControl": "Toegangs controle",
|
"editInternalResourceDialogAccessControl": "Toegangs controle",
|
||||||
"editInternalResourceDialogAccessControlDescription": "Beheer welke rollen, gebruikers en machineclients toegang hebben tot deze bron wanneer ze zijn verbonden. Beheerders hebben altijd toegang.",
|
"editInternalResourceDialogAccessControlDescription": "Beheer welke rollen, gebruikers en machineclients toegang hebben tot deze bron wanneer ze zijn verbonden. Beheerders hebben altijd toegang.",
|
||||||
"editInternalResourceDialogPortRangeValidationError": "Poortbereik moet \"*\" zijn voor alle poorten, of een komma-gescheiden lijst van poorten en bereiken (bijv. \"80,443,8000-9000\"). Poorten moeten tussen 1 en 65535 zijn.",
|
"editInternalResourceDialogPortRangeValidationError": "Poortbereik moet \"*\" zijn voor alle poorten, of een komma-gescheiden lijst van poorten en bereiken (bijv. \"80,443,8000-9000\"). Poorten moeten tussen 1 en 65535 zijn.",
|
||||||
|
"internalResourceAuthDaemonStrategy": "SSH Auth Daemon locatie",
|
||||||
|
"internalResourceAuthDaemonStrategyDescription": "Kies waar de SSH authenticatie daemon wordt uitgevoerd: op de website (Newt) of op een externe host.",
|
||||||
|
"internalResourceAuthDaemonDescription": "De SSH authenticatie daemon zorgt voor SSH sleutelondertekening en PAM authenticatie voor deze resource. Kies of het wordt uitgevoerd op de website (Nieuw) of op een afzonderlijke externe host. Zie <docsLink>de documentatie</docsLink> voor meer.",
|
||||||
|
"internalResourceAuthDaemonDocsUrl": "https://docs.pangolin.net",
|
||||||
|
"internalResourceAuthDaemonStrategyPlaceholder": "Selecteer strategie",
|
||||||
|
"internalResourceAuthDaemonStrategyLabel": "Locatie",
|
||||||
|
"internalResourceAuthDaemonSite": "In de site",
|
||||||
|
"internalResourceAuthDaemonSiteDescription": "Auth daemon draait op de site (Newt).",
|
||||||
|
"internalResourceAuthDaemonRemote": "Externe host",
|
||||||
|
"internalResourceAuthDaemonRemoteDescription": "Authenticatiedaemon draait op een host die niet de site is.",
|
||||||
|
"internalResourceAuthDaemonPort": "Daemon poort (optioneel)",
|
||||||
"orgAuthWhatsThis": "Waar kan ik mijn organisatie-ID vinden?",
|
"orgAuthWhatsThis": "Waar kan ik mijn organisatie-ID vinden?",
|
||||||
"learnMore": "Meer informatie",
|
"learnMore": "Meer informatie",
|
||||||
"backToHome": "Ga terug naar startpagina",
|
"backToHome": "Ga terug naar startpagina",
|
||||||
@@ -2499,6 +2936,9 @@
|
|||||||
"maintenancePageMessagePlaceholder": "We keren snel terug! Onze site ondergaat momenteel gepland onderhoud.",
|
"maintenancePageMessagePlaceholder": "We keren snel terug! Onze site ondergaat momenteel gepland onderhoud.",
|
||||||
"maintenancePageMessageDescription": "Gedetailleerd bericht dat het onderhoud uitlegt",
|
"maintenancePageMessageDescription": "Gedetailleerd bericht dat het onderhoud uitlegt",
|
||||||
"maintenancePageTimeTitle": "Geschatte voltooiingstijd (optioneel)",
|
"maintenancePageTimeTitle": "Geschatte voltooiingstijd (optioneel)",
|
||||||
|
"privateMaintenanceScreenTitle": "Privéscherm maintenance screen",
|
||||||
|
"privateMaintenanceScreenMessage": "Dit domein wordt gebruikt op een privébron. Verbind met de Pangolin client om toegang te krijgen tot deze bron.",
|
||||||
|
"privateMaintenanceScreenSteps": "Eenmaal verbonden, als u dit bericht nog steeds ziet, kan het DNS-cache van uw browser nog steeds naar het oude adres wijzen. Om dit te corrigeren: sluit en heropen dit tabblad, of uw browser, dan navigeer weer naar deze pagina.",
|
||||||
"maintenanceTime": "bijv. 2 uur, 1 nov om 17:00",
|
"maintenanceTime": "bijv. 2 uur, 1 nov om 17:00",
|
||||||
"maintenanceEstimatedTimeDescription": "Wanneer u verwacht dat het onderhoud voltooid is",
|
"maintenanceEstimatedTimeDescription": "Wanneer u verwacht dat het onderhoud voltooid is",
|
||||||
"editDomain": "Domein bewerken",
|
"editDomain": "Domein bewerken",
|
||||||
@@ -2607,5 +3047,166 @@
|
|||||||
"approvalsEmptyStateStep2Title": "Toestel goedkeuringen inschakelen",
|
"approvalsEmptyStateStep2Title": "Toestel goedkeuringen inschakelen",
|
||||||
"approvalsEmptyStateStep2Description": "Bewerk een rol en schakel de optie 'Vereist Apparaat Goedkeuringen' in. Gebruikers met deze rol hebben admin goedkeuring nodig voor nieuwe apparaten.",
|
"approvalsEmptyStateStep2Description": "Bewerk een rol en schakel de optie 'Vereist Apparaat Goedkeuringen' in. Gebruikers met deze rol hebben admin goedkeuring nodig voor nieuwe apparaten.",
|
||||||
"approvalsEmptyStatePreviewDescription": "Voorbeeld: Indien ingeschakeld, zullen in afwachting van apparaatverzoeken hier verschijnen om te beoordelen",
|
"approvalsEmptyStatePreviewDescription": "Voorbeeld: Indien ingeschakeld, zullen in afwachting van apparaatverzoeken hier verschijnen om te beoordelen",
|
||||||
"approvalsEmptyStateButtonText": "Rollen beheren"
|
"approvalsEmptyStateButtonText": "Rollen beheren",
|
||||||
|
"domainErrorTitle": "We ondervinden problemen bij het controleren van uw domein",
|
||||||
|
"idpAdminAutoProvisionPoliciesTabHint": "Configureer rolverrekening en organisatie beleid in het <policiesTabLink>Auto Provision Settings</policiesTabLink> tab.",
|
||||||
|
"streamingTitle": "Event streaming",
|
||||||
|
"streamingDescription": "Stream events van uw organisatie naar externe bestemmingen in realtime.",
|
||||||
|
"streamingUnnamedDestination": "Naamloze bestemming",
|
||||||
|
"streamingNoUrlConfigured": "Geen URL ingesteld",
|
||||||
|
"streamingAddDestination": "Bestemming toevoegen",
|
||||||
|
"streamingHttpWebhookTitle": "HTTP Webhook",
|
||||||
|
"streamingHttpWebhookDescription": "Stuur gebeurtenissen naar elk HTTP eindpunt met flexibele authenticatie en template.",
|
||||||
|
"streamingS3Title": "Amazon S3",
|
||||||
|
"streamingS3Description": "Stream events naar een S3-compatibele object-opslagemmer. Binnenkort beschikbaar.",
|
||||||
|
"streamingDatadogTitle": "Datadog",
|
||||||
|
"streamingDatadogDescription": "Stuur gebeurtenissen rechtstreeks door naar je Datadog account. Binnenkort beschikbaar.",
|
||||||
|
"streamingTypePickerDescription": "Kies een bestemmingstype om te beginnen.",
|
||||||
|
"streamingFailedToLoad": "Laden van bestemmingen mislukt",
|
||||||
|
"streamingUnexpectedError": "Er is een onverwachte fout opgetreden.",
|
||||||
|
"streamingFailedToUpdate": "Bijwerken bestemming mislukt",
|
||||||
|
"streamingDeletedSuccess": "Bestemming succesvol verwijderd",
|
||||||
|
"streamingFailedToDelete": "Verwijderen van bestemming mislukt",
|
||||||
|
"streamingDeleteTitle": "Verwijder bestemming",
|
||||||
|
"streamingDeleteButtonText": "Verwijder bestemming",
|
||||||
|
"streamingDeleteDialogAreYouSure": "Weet u zeker dat u wilt verwijderen",
|
||||||
|
"streamingDeleteDialogThisDestination": "deze bestemming",
|
||||||
|
"streamingDeleteDialogPermanentlyRemoved": "? Alle configuratie zal permanent worden verwijderd.",
|
||||||
|
"httpDestEditTitle": "Bewerk bestemming",
|
||||||
|
"httpDestAddTitle": "Voeg HTTP bestemming toe",
|
||||||
|
"httpDestEditDescription": "Werk de configuratie voor deze HTTP-event streaming bestemming bij.",
|
||||||
|
"httpDestAddDescription": "Configureer een nieuw HTTP-eindpunt om de gebeurtenissen van uw organisatie te ontvangen.",
|
||||||
|
"S3DestEditTitle": "Bestemming bewerken",
|
||||||
|
"S3DestAddTitle": "S3-bestemming toevoegen",
|
||||||
|
"S3DestEditDescription": "Werk de configuratie bij voor deze S3-gebeurtenisstreamingbestemming.",
|
||||||
|
"S3DestAddDescription": "Configureer een nieuw S3-eindpunt om de gebeurtenissen van uw organisatie te ontvangen.",
|
||||||
|
"datadogDestEditTitle": "Bestemming bewerken",
|
||||||
|
"datadogDestAddTitle": "Datadog-bestemming toevoegen",
|
||||||
|
"datadogDestEditDescription": "Werk de configuratie bij voor deze Datadog-gebeurtenisstreamingbestemming.",
|
||||||
|
"datadogDestAddDescription": "Configureer een nieuw Datadog-eindpunt om de gebeurtenissen van uw organisatie te ontvangen.",
|
||||||
|
"httpDestTabSettings": "Instellingen",
|
||||||
|
"httpDestTabHeaders": "Kopteksten",
|
||||||
|
"httpDestTabBody": "Lichaam",
|
||||||
|
"httpDestTabLogs": "Logboeken",
|
||||||
|
"httpDestNamePlaceholder": "Mijn HTTP-bestemming",
|
||||||
|
"httpDestUrlLabel": "Bestemming URL",
|
||||||
|
"httpDestUrlErrorHttpRequired": "URL moet http of https gebruiken",
|
||||||
|
"httpDestUrlErrorHttpsRequired": "HTTPS is vereist op cloud implementaties",
|
||||||
|
"httpDestUrlErrorInvalid": "Voer een geldige URL in (bijv. https://example.com/webhook)",
|
||||||
|
"httpDestAuthTitle": "Authenticatie",
|
||||||
|
"httpDestAuthDescription": "Kies hoe verzoeken voor uw eindpunt zijn geverifieerd.",
|
||||||
|
"httpDestAuthNoneTitle": "Geen authenticatie",
|
||||||
|
"httpDestAuthNoneDescription": "Stuurt verzoeken zonder toestemmingskop.",
|
||||||
|
"httpDestAuthBearerTitle": "Betere Token",
|
||||||
|
"httpDestAuthBearerDescription": "Voegt een Authorization: Bearer '<token>' header toe aan elk verzoek.",
|
||||||
|
"httpDestAuthBearerPlaceholder": "Uw API-sleutel of -token",
|
||||||
|
"httpDestAuthBasicTitle": "Basis authenticatie",
|
||||||
|
"httpDestAuthBasicDescription": "Voegt een Authorization: Basic '<credentials>' header toe. Verstrek inloggegevens als gebruikersnaam:wachtwoord.",
|
||||||
|
"httpDestAuthBasicPlaceholder": "Gebruikersnaam:wachtwoord",
|
||||||
|
"httpDestAuthCustomTitle": "Aangepaste koptekst",
|
||||||
|
"httpDestAuthCustomDescription": "Specificeer een aangepaste HTTP header naam en waarde voor authenticatie (bijv. X-API-Key).",
|
||||||
|
"httpDestAuthCustomHeaderNamePlaceholder": "Header naam (bijv. X-API-Key)",
|
||||||
|
"httpDestAuthCustomHeaderValuePlaceholder": "Header waarde",
|
||||||
|
"httpDestCustomHeadersTitle": "Aangepaste HTTP Headers",
|
||||||
|
"httpDestCustomHeadersDescription": "Voeg aangepaste headers toe aan elk uitgaande verzoek. Handig voor statische tokens of een aangepast Content-Type. Standaard Content-Type: application/json wordt verzonden.",
|
||||||
|
"httpDestNoHeadersConfigured": "Geen aangepaste headers geconfigureerd. Klik op \"Header\" om er een toe te voegen.",
|
||||||
|
"httpDestHeaderNamePlaceholder": "Naam koptekst",
|
||||||
|
"httpDestHeaderValuePlaceholder": "Waarde",
|
||||||
|
"httpDestAddHeader": "Koptekst toevoegen",
|
||||||
|
"httpDestBodyTemplateTitle": "Aangepaste Body Sjabloon",
|
||||||
|
"httpDestBodyTemplateDescription": "Bestuur de JSON payload structuur verzonden naar uw eindpunt. Indien uitgeschakeld, wordt een standaard JSON object verzonden voor elke event.",
|
||||||
|
"httpDestEnableBodyTemplate": "Aangepaste lichaam sjabloon inschakelen",
|
||||||
|
"httpDestBodyTemplateLabel": "Body sjabloon (JSON)",
|
||||||
|
"httpDestBodyTemplateHint": "Gebruik sjabloonvariabelen om te verwijzen naar gebeurtenisvelden in uw payload.",
|
||||||
|
"httpDestPayloadFormatTitle": "Payload formaat",
|
||||||
|
"httpDestPayloadFormatDescription": "Hoe evenementen worden geserialiseerd in elk verzoeklichaam.",
|
||||||
|
"httpDestFormatJsonArrayTitle": "JSON matrix",
|
||||||
|
"httpDestFormatJsonArrayDescription": "Eén verzoek per batch, lichaam is een JSON-array. Compatibel met de meeste algemene webhooks en Datadog.",
|
||||||
|
"httpDestFormatNdjsonTitle": "NDJSON",
|
||||||
|
"httpDestFormatNdjsonDescription": "Eén aanvraag per batch, lichaam is nieuwe JSON gescheiden - één object per regel, geen buitenste array. Vereist door Splunk HEC, Elastic / OpenSearch, en Grafana Loki.",
|
||||||
|
"httpDestFormatSingleTitle": "Eén afspraak per verzoek",
|
||||||
|
"httpDestFormatSingleDescription": "Stuurt een aparte HTTP POST voor elk individueel event. Gebruik alleen voor eindpunten die geen batches kunnen verwerken.",
|
||||||
|
"httpDestLogTypesTitle": "Log soorten",
|
||||||
|
"httpDestLogTypesDescription": "Kies welke log types doorgestuurd worden naar deze bestemming. Alleen ingeschakelde log types worden gestreden.",
|
||||||
|
"httpDestAccessLogsTitle": "Toegang tot logboek",
|
||||||
|
"httpDestAccessLogsDescription": "Hulpbrontoegangspogingen, inclusief geauthenticeerde en weigerde aanvragen.",
|
||||||
|
"httpDestActionLogsTitle": "Actie logs",
|
||||||
|
"httpDestActionLogsDescription": "Administratieve acties uitgevoerd door gebruikers binnen de organisatie.",
|
||||||
|
"httpDestConnectionLogsTitle": "Connectie Logs",
|
||||||
|
"httpDestConnectionLogsDescription": "Verbinding met de Site en tunnel maken verbroken, inclusief verbindingen en verbindingen.",
|
||||||
|
"httpDestRequestLogsTitle": "Logboeken aanvragen",
|
||||||
|
"httpDestRequestLogsDescription": "HTTP request logs voor proxied hulpmiddelen, waaronder methode, pad en response code.",
|
||||||
|
"httpDestSaveChanges": "Wijzigingen opslaan",
|
||||||
|
"httpDestCreateDestination": "Maak bestemming aan",
|
||||||
|
"httpDestUpdatedSuccess": "Bestemming succesvol bijgewerkt",
|
||||||
|
"httpDestCreatedSuccess": "Bestemming succesvol aangemaakt",
|
||||||
|
"httpDestUpdateFailed": "Bijwerken bestemming mislukt",
|
||||||
|
"httpDestCreateFailed": "Aanmaken bestemming mislukt",
|
||||||
|
"followRedirects": "Volg omleidingen",
|
||||||
|
"followRedirectsDescription": "Volg automatisch HTTP-omleidingen voor verzoeken.",
|
||||||
|
"alertingErrorWebhookUrl": "Voer een geldige URL voor de webhook in.",
|
||||||
|
"healthCheckStrategyHttp": "Valideert connectiviteit en controleert de HTTP-responsstatus.",
|
||||||
|
"healthCheckStrategyTcp": "Verifieert alleen TCP-connectiviteit zonder de respons te inspecteren.",
|
||||||
|
"healthCheckStrategySnmp": "Maakt een SNMP-verzoek om de gezondheid van netwerkapparaten en infrastructuur te controleren.",
|
||||||
|
"healthCheckStrategyIcmp": "Gebruikt ICMP-verzoeken (pings) om te controleren of een bron bereikbaar en responsief is.",
|
||||||
|
"healthCheckTabStrategy": "Strategie",
|
||||||
|
"healthCheckTabConnection": "Verbinding",
|
||||||
|
"healthCheckTabAdvanced": "Geavanceerd",
|
||||||
|
"healthCheckStrategyNotAvailable": "Deze strategie is niet beschikbaar. Neem contact op met sales om deze functie in te schakelen.",
|
||||||
|
"uptime30d": "Beschikbaarheid (30d)",
|
||||||
|
"idpAddActionCreateNew": "Nieuwe identiteitsprovider aanmaken",
|
||||||
|
"idpAddActionImportFromOrg": "Importeer vanuit een andere organisatie",
|
||||||
|
"idpImportDialogTitle": "Importeer Identiteitsprovider",
|
||||||
|
"idpImportDialogDescription": "Kies een identiteitsprovider van een organisatie waar u beheerder bent. Het wordt gekoppeld aan deze organisatie.",
|
||||||
|
"idpImportSearchPlaceholder": "Zoek op organisatie- of providernamen...",
|
||||||
|
"idpImportEmpty": "Geen identiteitsproviders gevonden.",
|
||||||
|
"idpImportedDescription": "Identiteitsprovider succesvol geïmporteerd.",
|
||||||
|
"idpDeleteGlobalQuestion": "Weet u zeker dat u deze identiteitsprovider permanent wilt verwijderen?",
|
||||||
|
"idpDeleteGlobalDescription": "Hiermee wordt de identiteitsprovider permanent verwijderd uit alle organisaties waarmee het is geassocieerd.",
|
||||||
|
"idpUnassociateTitle": "Koppel Identiteitsprovider los",
|
||||||
|
"idpUnassociateQuestion": "Weet u zeker dat u deze identiteitsprovider van deze organisatie wilt loskoppelen?",
|
||||||
|
"idpUnassociateDescription": "Alle gebruikers die aan deze identiteitsprovider zijn gekoppeld, worden uit deze organisatie verwijderd, maar de identiteitsprovider blijft bestaan voor andere gerelateerde organisaties.",
|
||||||
|
"idpUnassociateConfirm": "Bevestig ontkoppelen identiteitsprovider",
|
||||||
|
"idpUnassociateWarning": "Dit kan niet ongedaan worden gemaakt voor deze organisatie.",
|
||||||
|
"idpUnassociatedDescription": "Identiteitsprovider succesvol losgekoppeld van deze organisatie",
|
||||||
|
"idpUnassociateMenu": "Ontkoppelen",
|
||||||
|
"idpDeleteAllOrgsMenu": "Verwijderen",
|
||||||
|
"publicIpEndpoint": "Eindpunt",
|
||||||
|
"lastTriggeredAt": "Laatste Trigger",
|
||||||
|
"reject": "Afwijzen",
|
||||||
|
"uptimeDaysAgo": "{count} dagen geleden",
|
||||||
|
"uptimeToday": "Vandaag",
|
||||||
|
"uptimeNoDataAvailable": "Geen gegevens beschikbaar",
|
||||||
|
"uptimeSuffix": "werktijd",
|
||||||
|
"uptimeDowntimeSuffix": "uitvaltijd",
|
||||||
|
"uptimeTooltipUptimeLabel": "Werktijd",
|
||||||
|
"uptimeTooltipDowntimeLabel": "Uitvaltijd",
|
||||||
|
"uptimeOngoing": "lopend",
|
||||||
|
"uptimeNoMonitoringData": "Geen monitoringgegevens",
|
||||||
|
"uptimeNoData": "Geen gegevens",
|
||||||
|
"uptimeMiniBarDown": "Onder",
|
||||||
|
"uptimeSectionTitle": "Werktijd",
|
||||||
|
"uptimeSectionDescription": "Beschikbaarheid over de laatste {days} dagen",
|
||||||
|
"uptimeAddAlert": "Alarm toevoegen",
|
||||||
|
"uptimeViewAlerts": "Meldingen bekijken",
|
||||||
|
"uptimeCreateEmailAlert": "E-mailalert aanmaken",
|
||||||
|
"uptimeAlertDescriptionSite": "Ontvang een e-mailbericht wanneer deze site offline gaat of weer online komt.",
|
||||||
|
"uptimeAlertDescriptionResource": "Ontvang een e-mailbericht wanneer deze bron offline gaat of weer online komt.",
|
||||||
|
"uptimeAlertNamePlaceholder": "Waarschuwingsnaam",
|
||||||
|
"uptimeAdditionalEmails": "Extra e-mails",
|
||||||
|
"uptimeCreateAlert": "Alarm aanmaken",
|
||||||
|
"uptimeAlertNoRecipients": "Geen ontvangers",
|
||||||
|
"uptimeAlertNoRecipientsDescription": "Voeg ten minste één gebruiker, rol of e-mail toe om te melden.",
|
||||||
|
"uptimeAlertCreated": "Alarm aangemaakt",
|
||||||
|
"uptimeAlertCreatedDescription": "U wordt op de hoogte gebracht wanneer dit van status verandert.",
|
||||||
|
"uptimeAlertCreateFailed": "Kon alarm niet aanmaken",
|
||||||
|
"webhookUrlLabel": "URL",
|
||||||
|
"webhookHeaderKeyPlaceholder": "Sleutel",
|
||||||
|
"webhookHeaderValuePlaceholder": "Waarde",
|
||||||
|
"alertLabel": "Waarschuwing",
|
||||||
|
"domainPickerWildcardSubdomainNotAllowed": "Wildcard-subdomeinen zijn niet toegestaan.",
|
||||||
|
"domainPickerWildcardCertWarning": "Wildcard-bronnen hebben mogelijk extra configuratie nodig om correct te werken.",
|
||||||
|
"domainPickerWildcardCertWarningLink": "Meer informatie",
|
||||||
|
"health": "Gezondheid",
|
||||||
|
"domainPendingErrorTitle": "Verificatieprobleem"
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
4789
messages/zh-TW.json
4789
messages/zh-TW.json
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user