0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-24 22:41:28 -05:00

feat(console): add tenant loading page (#4031)

This commit is contained in:
Darcy Ye 2023-06-18 16:50:00 +08:00 committed by GitHub
parent 1236799862
commit 8770facec1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
55 changed files with 782 additions and 257 deletions

View file

@ -159,7 +159,7 @@ export const decompress = async (toPath: string, tarPath: string) => {
export const seedDatabase = async (instancePath: string, cloud: boolean) => {
try {
const pool = await createPoolAndDatabaseIfNeeded();
await seedByPool(pool);
await seedByPool(pool, cloud);
await pool.end();
} catch (error: unknown) {
consoleLog.error(error);

View file

@ -0,0 +1,110 @@
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.88165 224H40.1255C43.0059 224 44.8061 220.602 40.1255 218.337C35.4449 216.072 24.2835 214.373 15.6424 215.223C7.00129 216.072 6.64124 224 9.88165 224Z" fill="#9485C5"/>
<circle opacity="0.85" cx="99" cy="112" r="84" fill="url(#paint0_linear_8217_108119)"/>
<g style="mix-blend-mode:hard-light">
<path fill-rule="evenodd" clip-rule="evenodd" d="M21.6706 144.86C18.4393 150.76 17.7276 165.165 17.7276 181.998C17.7276 184.755 17.7658 187.447 17.8429 190.047C16.0016 187.933 14.2679 186.775 12.9523 186.775C9.37094 186.775 11.1471 196.538 14.8107 206.101C17.3523 212.735 20.3142 218.208 23.0239 220.504C23.6306 221.299 24.2917 221.79 25.0082 221.941C25.0049 220.59 24.9957 217.22 24.983 212.664C21.5345 206.012 14.3331 192.547 14.3331 192.547L24.9746 209.632L24.9538 202.195L24.9538 202.194L24.9538 202.187C24.9382 196.597 24.9209 190.416 24.9042 184.399C24.8868 184.415 24.8723 184.429 24.8606 184.44C24.8377 184.461 24.8261 184.472 24.8261 184.472V182.264C24.8499 182.243 24.874 182.222 24.8982 182.2C24.8597 168.284 24.8261 155.764 24.8261 154.238C24.8261 151.859 25.2659 160.57 25.5585 181.654C29.713 178.369 37.6646 174.596 37.6646 174.596C31.9599 178.247 27.3412 182.213 25.5869 183.78C25.7167 193.846 25.8112 206.534 25.8136 221.968C30.7048 221.275 33.0654 204.937 33.237 184.435C34.5472 183.411 35.925 182.128 37.337 180.716C39.6107 178.442 41.2685 175.975 42.1831 173.87C40.6338 172.447 39.1381 170.966 37.6994 169.431C36.2733 170.054 34.6831 171.017 33.1003 172.28C33.0016 169.201 32.8512 166.262 32.6436 163.512C28.2278 157.832 24.5254 151.57 21.6706 144.86Z" fill="url(#paint1_linear_8217_108119)"/>
</g>
<g filter="url(#filter0_b_8217_108119)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M140.954 172.951C125.886 171.862 114 159.293 114 143.947C114 128.218 126.487 115.406 142.088 114.883C146.304 97.7265 161.789 85 180.246 85C198.135 85 213.232 96.9546 217.983 113.311C234.439 113.836 247.613 126.999 247.613 143.161C247.613 158.81 235.261 171.648 219.538 172.923V173.125H140.954V172.951Z" fill="url(#paint2_linear_8217_108119)" fill-opacity="0.8"/>
</g>
<rect x="128" y="81" width="18" height="32" fill="#947DFF"/>
<rect width="24" height="6" rx="3" transform="matrix(1 0 0 -1 125 82)" fill="#7958FF"/>
<path d="M100 78L168 134H32L100 78Z" fill="url(#paint3_linear_8217_108119)"/>
<rect x="37" y="134" width="126" height="84" fill="#7958FF"/>
<rect x="37" y="134" width="126" height="84" fill="url(#paint4_linear_8217_108119)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M73 170C73 155.088 85.0883 143 100 143C114.912 143 127 155.088 127 170L127 218H73V170Z" fill="url(#paint5_linear_8217_108119)"/>
<path d="M100 151C90.5234 151 82.6672 157.938 81.2337 167.012C80.9751 168.648 82.3431 170 84 170H116C117.657 170 119.025 168.648 118.766 167.012C117.333 157.938 109.477 151 100 151Z" fill="url(#paint6_linear_8217_108119)"/>
<g filter="url(#filter1_i_8217_108119)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M98.577 107.87C99.6755 109.111 101.572 109.226 102.812 108.127C104.052 107.029 104.167 105.133 103.069 103.892C101.97 102.652 100.074 102.537 98.8339 103.635C97.5936 104.734 97.4785 106.63 98.577 107.87ZM105.464 111.122C103.09 113.224 99.6868 113.421 97.1255 111.827L92.6753 115.768L93.3207 116.497C93.6868 116.911 93.6485 117.543 93.235 117.909L91.7378 119.235C91.3243 119.601 90.6923 119.563 90.3262 119.149L89.6808 118.42L87.4644 120.383C86.6375 121.116 85.3735 121.039 84.6412 120.212C83.9088 119.385 83.9855 118.121 84.8124 117.389L94.4735 108.833C93.2007 106.098 93.8081 102.743 96.182 100.641C99.0762 98.0778 103.5 98.3462 106.063 101.24C108.626 104.135 108.358 108.559 105.464 111.122Z" fill="#FEDEAC"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M98.577 107.87C99.6755 109.111 101.572 109.226 102.812 108.127C104.052 107.029 104.167 105.133 103.069 103.892C101.97 102.652 100.074 102.537 98.8339 103.635C97.5936 104.734 97.4785 106.63 98.577 107.87ZM105.464 111.122C103.09 113.224 99.6868 113.421 97.1255 111.827L92.6753 115.768L93.3207 116.497C93.6868 116.911 93.6485 117.543 93.235 117.909L91.7378 119.235C91.3243 119.601 90.6923 119.563 90.3262 119.149L89.6808 118.42L87.4644 120.383C86.6375 121.116 85.3735 121.039 84.6412 120.212C83.9088 119.385 83.9855 118.121 84.8124 117.389L94.4735 108.833C93.2007 106.098 93.8081 102.743 96.182 100.641C99.0762 98.0778 103.5 98.3462 106.063 101.24C108.626 104.135 108.358 108.559 105.464 111.122Z" fill="url(#paint7_linear_8217_108119)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M98.577 107.87C99.6755 109.111 101.572 109.226 102.812 108.127C104.052 107.029 104.167 105.133 103.069 103.892C101.97 102.652 100.074 102.537 98.8339 103.635C97.5936 104.734 97.4785 106.63 98.577 107.87ZM105.464 111.122C103.09 113.224 99.6868 113.421 97.1255 111.827L92.6753 115.768L93.3207 116.497C93.6868 116.911 93.6485 117.543 93.235 117.909L91.7378 119.235C91.3243 119.601 90.6923 119.563 90.3262 119.149L89.6808 118.42L87.4644 120.383C86.6375 121.116 85.3735 121.039 84.6412 120.212C83.9088 119.385 83.9855 118.121 84.8124 117.389L94.4735 108.833C93.2007 106.098 93.8081 102.743 96.182 100.641C99.0762 98.0778 103.5 98.3462 106.063 101.24C108.626 104.135 108.358 108.559 105.464 111.122Z" fill="url(#paint8_linear_8217_108119)"/>
</g>
<rect x="81" y="176" width="40" height="34" rx="3" fill="url(#paint9_linear_8217_108119)"/>
<rect x="113" y="183" width="4" height="9" rx="2" fill="#7958FF"/>
<path d="M33 224H171V221C171 219.343 169.657 218 168 218H36C34.3431 218 33 219.343 33 221V224Z" fill="url(#paint10_linear_8217_108119)"/>
<g style="mix-blend-mode:multiply" opacity="0.5">
<path d="M134 156H135.5C136.605 156 137.5 156.895 137.5 158V216C137.5 217.105 136.605 218 135.5 218H88L134 156Z" fill="url(#paint11_linear_8217_108119)"/>
</g>
<rect x="137" y="153" width="94" height="68" rx="5" fill="#CABEFF"/>
<rect x="137" y="153" width="94" height="68" rx="5" stroke="#7958FF" stroke-width="6"/>
<rect x="137" y="153" width="94" height="68" rx="5" stroke="url(#paint12_linear_8217_108119)" stroke-width="6"/>
<rect x="162" y="203" width="44" height="6" rx="2" fill="#AF9EFF"/>
<rect x="162" y="193" width="44" height="6" rx="2" fill="#AF9EFF"/>
<rect x="172" y="165" width="24" height="24" rx="8" fill="#F5EEFF"/>
<circle cx="184" cy="172" r="4" fill="#FDCF90"/>
<path d="M178 181C178 178.791 179.791 177 182 177H186C188.209 177 190 178.791 190 181V183C190 184.105 189.105 185 188 185H180C178.895 185 178 184.105 178 183V181Z" fill="#7958FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M95.7671 71.8462L96.8238 70.9798C97.9297 70.0731 99.5219 70.0731 100.628 70.9798L101.684 71.846L171.5 129C172.802 130.067 172.971 131.997 171.874 133.275L170.572 134.791C169.51 136.027 167.655 136.19 166.394 135.156L100.628 81.322C99.5219 80.4153 97.9297 80.4153 96.8238 81.322L31.106 135.156C29.8453 136.19 27.9901 136.027 26.9281 134.791L25.626 133.275C24.5291 131.997 24.6978 130.067 25.9999 129L95.7671 71.8462Z" fill="#7958FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M212.346 34.5009C212.38 34.4412 212.457 34.421 212.517 34.4558L214.463 35.5911C214.523 35.6259 214.543 35.7026 214.508 35.7623L212.51 39.1869L216.475 39.2048C216.544 39.2051 216.6 39.2614 216.6 39.3305L216.59 41.5835C216.589 41.6526 216.533 41.7084 216.464 41.7081L212.498 41.6902L214.465 45.1334C214.5 45.1934 214.479 45.2699 214.419 45.3042L212.463 46.4219C212.403 46.4562 212.326 46.4353 212.292 46.3753L210.325 42.9327L208.327 46.3574C208.292 46.4172 208.215 46.4373 208.156 46.4025L206.21 45.2672C206.15 45.2324 206.13 45.1557 206.165 45.096L208.163 41.6706L204.198 41.6528C204.129 41.6525 204.073 41.5962 204.073 41.527L204.083 39.2741C204.084 39.2049 204.14 39.1492 204.209 39.1495L208.174 39.1673L206.207 35.7248C206.172 35.6648 206.193 35.5884 206.253 35.5541L208.209 34.4364C208.269 34.4021 208.346 34.4229 208.38 34.483L210.347 37.9261L212.346 34.5009Z" fill="#CABEFF"/>
<path d="M237.791 81L236.596 83.564C235.937 84.98 234.254 85.5933 232.838 84.9338V84.9338C231.422 84.2743 229.74 84.8876 229.08 86.3036V86.3036C228.421 87.7197 226.738 88.333 225.322 87.6735V87.6735C223.906 87.0139 222.223 87.6272 221.564 89.0433L220.37 91.6073" stroke="#CABEFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<rect width="6.42101" height="6.42101" rx="2" transform="matrix(-0.758703 -0.651436 -0.651436 0.758703 25.0547 55.1826)" fill="#CABEFF"/>
<defs>
<filter id="filter0_b_8217_108119" x="64" y="35" width="233.613" height="188.125" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feGaussianBlur in="BackgroundImageFix" stdDeviation="25"/>
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_8217_108119"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_8217_108119" result="shape"/>
</filter>
<filter id="filter1_i_8217_108119" x="84.1382" y="98.8809" width="23.6846" height="24.0049" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="1"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_8217_108119"/>
</filter>
<linearGradient id="paint0_linear_8217_108119" x1="34.0421" y1="59.0394" x2="197.175" y2="112" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFDDB5"/>
<stop offset="1" stop-color="#FFEEDC" stop-opacity="0.7"/>
</linearGradient>
<linearGradient id="paint1_linear_8217_108119" x1="26.8611" y1="223.869" x2="41.6494" y2="140.844" gradientUnits="userSpaceOnUse">
<stop stop-color="#C7BDE6"/>
<stop offset="1" stop-color="#E4DBFF" stop-opacity="0.16"/>
</linearGradient>
<linearGradient id="paint2_linear_8217_108119" x1="180.807" y1="85" x2="180.807" y2="232.5" gradientUnits="userSpaceOnUse">
<stop stop-color="#F9F4FF"/>
<stop offset="1" stop-color="#FAF7FF" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint3_linear_8217_108119" x1="137" y1="94.5" x2="59" y2="134" gradientUnits="userSpaceOnUse">
<stop stop-color="#947DFF"/>
<stop offset="1" stop-color="#AF9EFF"/>
</linearGradient>
<linearGradient id="paint4_linear_8217_108119" x1="58" y1="232" x2="152" y2="134" gradientUnits="userSpaceOnUse">
<stop stop-color="#A896FF"/>
<stop offset="1" stop-color="#7958FF"/>
</linearGradient>
<linearGradient id="paint5_linear_8217_108119" x1="86" y1="145" x2="131" y2="247" gradientUnits="userSpaceOnUse">
<stop stop-color="#C1B3FF"/>
<stop offset="1" stop-color="#8F77FF"/>
</linearGradient>
<linearGradient id="paint6_linear_8217_108119" x1="128.5" y1="159.382" x2="81" y2="159.382" gradientUnits="userSpaceOnUse">
<stop stop-color="#FAD5AB"/>
<stop offset="1" stop-color="#F9C68B"/>
</linearGradient>
<linearGradient id="paint7_linear_8217_108119" x1="100.535" y1="114.507" x2="90.8162" y2="103.533" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFDDB5"/>
<stop offset="1" stop-color="#FFCE94"/>
</linearGradient>
<linearGradient id="paint8_linear_8217_108119" x1="103.574" y1="117.471" x2="91.9714" y2="104.37" gradientUnits="userSpaceOnUse">
<stop stop-color="#FAD5AB"/>
<stop offset="1" stop-color="#F9C68B"/>
</linearGradient>
<linearGradient id="paint9_linear_8217_108119" x1="131" y1="191" x2="81" y2="191" gradientUnits="userSpaceOnUse">
<stop stop-color="#FAD5AB"/>
<stop offset="1" stop-color="#F9C68B"/>
</linearGradient>
<linearGradient id="paint10_linear_8217_108119" x1="32" y1="220.5" x2="139" y2="218" gradientUnits="userSpaceOnUse">
<stop stop-color="#8366FF"/>
<stop offset="1" stop-color="#7A59FF"/>
</linearGradient>
<linearGradient id="paint11_linear_8217_108119" x1="139.409" y1="184.414" x2="94.1058" y2="215.483" gradientUnits="userSpaceOnUse">
<stop stop-color="#E1CEE8"/>
<stop offset="1" stop-color="#FFFBFF"/>
</linearGradient>
<linearGradient id="paint12_linear_8217_108119" x1="203" y1="128.5" x2="141.012" y2="295.361" gradientUnits="userSpaceOnUse">
<stop stop-color="#492EF3"/>
<stop offset="1" stop-color="#CF69FF"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,101 @@
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
<g style="mix-blend-mode:multiply">
<path fill-rule="evenodd" clip-rule="evenodd" d="M25.8136 221.968C30.7048 221.275 33.0654 204.937 33.237 184.435C34.5472 183.411 35.925 182.128 37.337 180.716C41.966 176.087 44.0418 170.658 42.4923 169.108C41.1854 167.801 37.1189 169.073 33.1003 172.28C32.5427 154.889 30.3374 142.006 25.4874 142.006C18.9528 142.006 17.7276 159.911 17.7276 181.998C17.7276 184.755 17.7658 187.447 17.8429 190.047C16.0016 187.933 14.2679 186.775 12.9523 186.775C9.37094 186.775 11.1471 196.538 14.8107 206.101C17.3523 212.735 20.3142 218.208 23.0239 220.504C23.6306 221.299 24.2917 221.79 25.0082 221.941C25.0049 220.59 24.9957 217.22 24.983 212.664C21.5345 206.012 14.3331 192.547 14.3331 192.547L24.9746 209.632L24.9538 202.195L24.9538 202.194L24.9538 202.187C24.9382 196.597 24.9209 190.416 24.9042 184.399C24.8526 184.447 24.8261 184.472 24.8261 184.472V182.264C24.8499 182.243 24.874 182.222 24.8982 182.2C24.8597 168.284 24.8261 155.764 24.8261 154.238C24.8261 151.859 25.2659 160.57 25.5585 181.654C29.713 178.369 37.6646 174.596 37.6646 174.596C31.9599 178.247 27.3412 182.213 25.5869 183.78C25.7167 193.846 25.8112 206.534 25.8136 221.968Z" fill="url(#paint0_linear_8207_107673)"/>
</g>
<path d="M9.88165 224H40.1255C43.0059 224 44.8061 220.602 40.1255 218.337C35.4449 216.072 24.2835 214.373 15.6424 215.223C7.00129 216.072 6.64124 224 9.88165 224Z" fill="#C8BEE6"/>
<circle cx="99" cy="112" r="84" fill="url(#paint1_linear_8207_107673)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M140.954 172.951C125.886 171.862 114 159.293 114 143.947C114 128.218 126.487 115.406 142.088 114.883C146.304 97.7265 161.789 85 180.246 85C198.135 85 213.232 96.9546 217.983 113.311C234.439 113.836 247.613 126.999 247.613 143.161C247.613 158.81 235.261 171.648 219.538 172.923V173.125H140.954V172.951Z" fill="url(#paint2_linear_8207_107673)"/>
<rect x="128" y="81" width="18" height="32" fill="#947DFF"/>
<rect width="24" height="6" rx="3" transform="matrix(1 0 0 -1 125 82)" fill="#7958FF"/>
<path d="M100 78L168 134H32L100 78Z" fill="url(#paint3_linear_8207_107673)"/>
<rect x="37" y="134" width="126" height="84" fill="#7958FF"/>
<rect x="37" y="134" width="126" height="84" fill="url(#paint4_linear_8207_107673)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M73 170C73 155.088 85.0883 143 100 143C114.912 143 127 155.088 127 170L127 218H73V170Z" fill="url(#paint5_linear_8207_107673)"/>
<path d="M100 151C90.5234 151 82.6672 157.938 81.2337 167.012C80.9751 168.648 82.3431 170 84 170H116C117.657 170 119.025 168.648 118.766 167.012C117.333 157.938 109.477 151 100 151Z" fill="url(#paint6_linear_8207_107673)"/>
<g filter="url(#filter0_i_8207_107673)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M98.577 107.87C99.6755 109.111 101.572 109.226 102.812 108.127C104.052 107.029 104.167 105.133 103.069 103.892C101.97 102.652 100.074 102.537 98.8339 103.635C97.5936 104.734 97.4785 106.63 98.577 107.87ZM105.464 111.122C103.09 113.224 99.6868 113.421 97.1255 111.827L92.6753 115.768L93.3207 116.497C93.6868 116.911 93.6485 117.543 93.235 117.909L91.7378 119.235C91.3243 119.601 90.6923 119.563 90.3262 119.149L89.6808 118.42L87.4644 120.383C86.6375 121.116 85.3735 121.039 84.6412 120.212C83.9088 119.385 83.9855 118.121 84.8124 117.389L94.4735 108.833C93.2007 106.098 93.8081 102.743 96.182 100.641C99.0762 98.0778 103.5 98.3462 106.063 101.24C108.626 104.135 108.358 108.559 105.464 111.122Z" fill="#FEDEAC"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M98.577 107.87C99.6755 109.111 101.572 109.226 102.812 108.127C104.052 107.029 104.167 105.133 103.069 103.892C101.97 102.652 100.074 102.537 98.8339 103.635C97.5936 104.734 97.4785 106.63 98.577 107.87ZM105.464 111.122C103.09 113.224 99.6868 113.421 97.1255 111.827L92.6753 115.768L93.3207 116.497C93.6868 116.911 93.6485 117.543 93.235 117.909L91.7378 119.235C91.3243 119.601 90.6923 119.563 90.3262 119.149L89.6808 118.42L87.4644 120.383C86.6375 121.116 85.3735 121.039 84.6412 120.212C83.9088 119.385 83.9855 118.121 84.8124 117.389L94.4735 108.833C93.2007 106.098 93.8081 102.743 96.182 100.641C99.0762 98.0778 103.5 98.3462 106.063 101.24C108.626 104.135 108.358 108.559 105.464 111.122Z" fill="url(#paint7_linear_8207_107673)"/>
</g>
<rect x="81" y="176" width="40" height="34" rx="3" fill="url(#paint8_linear_8207_107673)"/>
<rect x="113" y="183" width="4" height="9" rx="2" fill="#7958FF"/>
<path d="M33 224H171V221C171 219.343 169.657 218 168 218H36C34.3431 218 33 219.343 33 221V224Z" fill="url(#paint9_linear_8207_107673)"/>
<g style="mix-blend-mode:multiply" opacity="0.5">
<path d="M134 156H135.5C136.605 156 137.5 156.895 137.5 158V216C137.5 217.105 136.605 218 135.5 218H88L134 156Z" fill="url(#paint10_linear_8207_107673)"/>
</g>
<rect x="137" y="153" width="94" height="68" rx="5" fill="#E6DEFF"/>
<rect x="137" y="153" width="94" height="68" rx="5" stroke="#7958FF" stroke-width="6"/>
<rect x="137" y="153" width="94" height="68" rx="5" stroke="url(#paint11_linear_8207_107673)" stroke-width="6"/>
<rect x="162" y="203" width="44" height="6" rx="2" fill="#CABEFF"/>
<rect x="162" y="193" width="44" height="6" rx="2" fill="#CABEFF"/>
<rect x="172" y="165" width="24" height="24" rx="8" fill="#FBF9FF"/>
<circle cx="184" cy="172" r="4" fill="#FDCF90"/>
<path d="M178 181C178 178.791 179.791 177 182 177H186C188.209 177 190 178.791 190 181V183C190 184.105 189.105 185 188 185H180C178.895 185 178 184.105 178 183V181Z" fill="#7958FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M95.7671 71.8462L96.8238 70.9798C97.9297 70.0731 99.5219 70.0731 100.628 70.9798L101.684 71.846L171.5 129C172.802 130.067 172.971 131.997 171.874 133.275L170.572 134.791C169.51 136.027 167.655 136.19 166.394 135.156L100.628 81.322C99.5219 80.4153 97.9297 80.4153 96.8238 81.322L31.106 135.156C29.8453 136.19 27.9901 136.027 26.9281 134.791L25.626 133.275C24.5291 131.997 24.6978 130.067 25.9999 129L95.7671 71.8462Z" fill="url(#paint12_linear_8207_107673)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M212.346 34.5009C212.381 34.4412 212.457 34.4211 212.517 34.4559L214.463 35.5912C214.523 35.626 214.543 35.7026 214.508 35.7624L212.51 39.1873L216.475 39.2051C216.544 39.2055 216.6 39.2617 216.6 39.3309L216.59 41.5838C216.589 41.653 216.533 41.7088 216.464 41.7084L212.499 41.6906L214.466 45.1332C214.5 45.1932 214.479 45.2697 214.419 45.304L212.463 46.4217C212.403 46.456 212.326 46.4351 212.292 46.3751L210.325 42.9326L208.327 46.3575C208.292 46.4172 208.216 46.4374 208.156 46.4026L206.21 45.2673C206.15 45.2325 206.13 45.1558 206.165 45.0961L208.163 41.671L204.198 41.6531C204.128 41.6528 204.073 41.5965 204.073 41.5274L204.083 39.2744C204.083 39.2053 204.14 39.1495 204.209 39.1498L208.174 39.1677L206.207 35.7246C206.172 35.6646 206.193 35.5881 206.253 35.5539L208.21 34.4362C208.27 34.4019 208.346 34.4227 208.38 34.4827L210.348 37.926L212.346 34.5009Z" fill="#CABEFF"/>
<path d="M237.791 81L236.596 83.564C235.937 84.98 234.254 85.5933 232.838 84.9338V84.9338C231.422 84.2743 229.74 84.8876 229.08 86.3036V86.3036C228.421 87.7197 226.738 88.333 225.322 87.6735V87.6735C223.906 87.0139 222.223 87.6272 221.564 89.0433L220.37 91.6073" stroke="#CABEFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<rect width="6.42101" height="6.42101" rx="2" transform="matrix(-0.758703 -0.651436 -0.651436 0.758703 25.0547 55.1826)" fill="#CABEFF"/>
<defs>
<filter id="filter0_i_8207_107673" x="84.1382" y="98.8809" width="23.6846" height="24.0049" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="2"/>
<feGaussianBlur stdDeviation="1"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_8207_107673"/>
</filter>
<linearGradient id="paint0_linear_8207_107673" x1="26.8611" y1="223.869" x2="41.6494" y2="140.844" gradientUnits="userSpaceOnUse">
<stop stop-color="#C7BDE6"/>
<stop offset="1" stop-color="#E4DBFF" stop-opacity="0.16"/>
</linearGradient>
<linearGradient id="paint1_linear_8207_107673" x1="34.0421" y1="59.0394" x2="197.175" y2="112" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFDDB5"/>
<stop offset="1" stop-color="#FFEEDC" stop-opacity="0.7"/>
</linearGradient>
<linearGradient id="paint2_linear_8207_107673" x1="180.807" y1="85" x2="180.807" y2="232.5" gradientUnits="userSpaceOnUse">
<stop stop-color="#F9F4FF"/>
<stop offset="1" stop-color="#FAF7FF" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint3_linear_8207_107673" x1="137" y1="94.5" x2="59" y2="134" gradientUnits="userSpaceOnUse">
<stop stop-color="#947DFF"/>
<stop offset="1" stop-color="#AF9EFF"/>
</linearGradient>
<linearGradient id="paint4_linear_8207_107673" x1="58" y1="232" x2="152" y2="134" gradientUnits="userSpaceOnUse">
<stop stop-color="#A896FF"/>
<stop offset="1" stop-color="#7958FF"/>
</linearGradient>
<linearGradient id="paint5_linear_8207_107673" x1="86" y1="145" x2="127" y2="221" gradientUnits="userSpaceOnUse">
<stop stop-color="#CABEFF"/>
<stop offset="1" stop-color="#947DFF"/>
</linearGradient>
<linearGradient id="paint6_linear_8207_107673" x1="134.5" y1="159.382" x2="81" y2="159.382" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFDDB5"/>
<stop offset="1" stop-color="#FFCE94"/>
</linearGradient>
<linearGradient id="paint7_linear_8207_107673" x1="100.535" y1="114.507" x2="90.8162" y2="103.533" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFDDB5"/>
<stop offset="1" stop-color="#FFCE94"/>
</linearGradient>
<linearGradient id="paint8_linear_8207_107673" x1="131" y1="191" x2="81" y2="191" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFDDB5"/>
<stop offset="1" stop-color="#FFCE94"/>
</linearGradient>
<linearGradient id="paint9_linear_8207_107673" x1="32" y1="220.5" x2="139" y2="218" gradientUnits="userSpaceOnUse">
<stop stop-color="#8366FF"/>
<stop offset="1" stop-color="#7A59FF"/>
</linearGradient>
<linearGradient id="paint10_linear_8207_107673" x1="139.409" y1="184.414" x2="94.1058" y2="215.483" gradientUnits="userSpaceOnUse">
<stop stop-color="#E1CEE8"/>
<stop offset="1" stop-color="#FFFBFF"/>
</linearGradient>
<linearGradient id="paint11_linear_8207_107673" x1="203" y1="128.5" x2="141.012" y2="295.361" gradientUnits="userSpaceOnUse">
<stop stop-color="#492EF3"/>
<stop offset="1" stop-color="#CF69FF"/>
</linearGradient>
<linearGradient id="paint12_linear_8207_107673" x1="47.4035" y1="102.84" x2="148.13" y2="102.84" gradientUnits="userSpaceOnUse">
<stop stop-color="#8365FF"/>
<stop offset="1" stop-color="#6C48FF"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -15,7 +15,7 @@ type Props = {
function Redirect({ tenants, toTenantId }: Props) {
const { getAccessToken, signIn } = useLogto();
const tenant = tenants.find(({ id }) => id === toTenantId);
const { setIsSettle } = useContext(TenantsContext);
const { setIsSettle, navigate } = useContext(TenantsContext);
const href = useHref(toTenantId + '/callback');
useEffect(() => {
@ -36,7 +36,8 @@ function Redirect({ tenants, toTenantId }: Props) {
}, [getAccessToken, href, setIsSettle, signIn, tenant]);
if (!tenant) {
return <div>Forbidden</div>;
/** Fallback to another available tenant instead of showing `Forbidden`. */
navigate(tenants[0]?.id ?? '');
}
return <AppLoading />;

View file

@ -0,0 +1,31 @@
@use '@/scss/underscore' as _;
.placeholder {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
.image {
> svg {
width: 256px;
height: 256px;
}
}
.title {
font: var(--font-title-1);
}
.description {
max-width: 470px;
font: var(--font-body-2);
color: var(--color-text-secondary);
margin-top: _.unit(2);
}
.button {
margin-top: _.unit(6);
}
}

View file

@ -0,0 +1,72 @@
import { Theme } from '@logto/schemas';
import type { TenantInfo } from '@logto/schemas/models';
import classNames from 'classnames';
import { useState } from 'react';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
import Plus from '@/assets/icons/plus.svg';
import TenantLandingPageImageDark from '@/assets/images/tenant-landing-page-dark.svg';
import TenantLandingPageImage from '@/assets/images/tenant-landing-page.svg';
import Button from '@/ds-components/Button';
import DynamicT from '@/ds-components/DynamicT';
import useTenants from '@/hooks/use-tenants';
import useTheme from '@/hooks/use-theme';
import CreateTenantModal from './CreateTenantModal';
import * as styles from './index.module.scss';
type Props = {
className?: string;
};
function TenantLandingPageContent({ className }: Props) {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { tenants, mutate } = useTenants();
const theme = useTheme();
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
if (tenants?.length) {
return null;
}
return (
<>
<div className={classNames(styles.placeholder, className)}>
<div className={styles.image}>
{theme === Theme.Light ? <TenantLandingPageImage /> : <TenantLandingPageImageDark />}
</div>
<div className={styles.title}>
<DynamicT forKey="tenants.tenant_landing_page.title" />
</div>
<div className={styles.description}>
<DynamicT forKey="tenants.tenant_landing_page.description" />
</div>
<Button
title="tenants.tenant_landing_page.create_tenant_button"
type="primary"
size="large"
icon={<Plus />}
className={styles.button}
onClick={() => {
setIsCreateModalOpen(true);
}}
/>
</div>
<CreateTenantModal
isOpen={isCreateModalOpen}
onClose={async (tenant?: TenantInfo) => {
if (tenant) {
void mutate();
toast.success(t('tenants.tenant_created', { name: tenant.name }));
window.location.assign(new URL(`/${tenant.id}`, window.location.origin).toString());
}
setIsCreateModalOpen(false);
}}
/>
</>
);
}
export default TenantLandingPageContent;

View file

@ -0,0 +1,45 @@
@use '@/scss/underscore' as _;
.pageContainer {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
height: 100%;
.placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
flex-grow: 1;
transform: translateY(-32px); // Half of the topbar height, to make the placeholder vertically centered.
.image {
> svg {
width: 256px;
height: 256px;
}
}
.title {
font: var(--font-label-2);
}
.description {
max-width: 470px;
font: var(--font-body-2);
color: var(--color-text-secondary);
margin-top: _.unit(2);
}
.button {
margin-top: _.unit(6);
}
}
}
.topbar {
z-index: 1;
}

View file

@ -0,0 +1,15 @@
import Topbar from '@/containers/AppContent/components/Topbar';
import TenantLandingPageContent from './TenantLandingPageContent';
import * as styles from './index.module.scss';
function TenantLandingPage() {
return (
<div className={styles.pageContainer}>
<Topbar className={styles.topbar} />
<TenantLandingPageContent className={styles.placeholder} />
</div>
);
}
export default TenantLandingPage;

View file

@ -1,68 +0,0 @@
import { type TenantInfo, TenantTag } from '@logto/schemas/models';
import { useCallback, useContext, useEffect } from 'react';
import { useCloudApi } from '@/cloud/hooks/use-cloud-api';
import AppLoading from '@/components/AppLoading';
import { TenantsContext } from '@/contexts/TenantsProvider';
import Button from '@/ds-components/Button';
import DangerousRaw from '@/ds-components/DangerousRaw';
import * as styles from './index.module.scss';
type Props = {
data: TenantInfo[];
onAdd: (tenant: TenantInfo) => void;
};
function Tenants({ data, onAdd }: Props) {
const api = useCloudApi();
const { navigate } = useContext(TenantsContext);
const createTenant = useCallback(async () => {
onAdd(
/**
* `name` and `tag` are required for POST /tenants API, add fixed value to avoid throwing error.
* This page page will be removed in upcoming changes on multi-tenancy cloud console.
*/
await api.post('/api/tenants', { body: { name: 'My Project', tag: TenantTag.Development } })
);
}, [api, onAdd]);
useEffect(() => {
if (data.length > 1) {
return;
}
if (data[0]) {
navigate(data[0].id);
} else {
void createTenant();
}
}, [createTenant, data, navigate]);
if (data.length > 1) {
return (
<div className={styles.wrapper}>
<h3>Choose a tenant</h3>
{data.map(({ id }) => (
<a
key={id}
href={'/' + id}
onClick={(event) => {
event.preventDefault();
navigate(id);
}}
>
<Button title={<DangerousRaw>{id}</DangerousRaw>} />
</a>
))}
<h3>Create a tenant</h3>
<Button title={<DangerousRaw>Create</DangerousRaw>} onClick={createTenant} />
</div>
);
}
return <AppLoading />;
}
export default Tenants;

View file

@ -1,8 +1,7 @@
import { useLogto } from '@logto/react';
import { type TenantInfo } from '@logto/schemas/models';
import { conditional, yes } from '@silverhand/essentials';
import { HTTPError } from 'ky';
import { useCallback, useContext, useEffect, useState } from 'react';
import { useContext, useEffect, useState } from 'react';
import { useHref, useSearchParams } from 'react-router-dom';
import { useCloudApi } from '@/cloud/hooks/use-cloud-api';
@ -11,13 +10,15 @@ import AppLoading from '@/components/AppLoading';
import SessionExpired from '@/components/SessionExpired';
import { searchKeys } from '@/consts';
import { TenantsContext } from '@/contexts/TenantsProvider';
import useUserOnboardingData from '@/onboarding/hooks/use-user-onboarding-data';
import Redirect from './Redirect';
import Tenants from './Tenants';
import TenantLandingPage from './TenantLandingPage';
function Protected() {
const api = useCloudApi();
const { tenants, setTenants, currentTenantId } = useContext(TenantsContext);
const { tenants, setTenants, currentTenantId, navigate } = useContext(TenantsContext);
const { isOnboarding, isLoaded } = useUserOnboardingData();
const [error, setError] = useState<Error>();
useEffect(() => {
@ -37,12 +38,23 @@ function Protected() {
}
}, [api, setTenants, tenants]);
const onAdd = useCallback(
(tenant: TenantInfo) => {
setTenants([...(tenants ?? []), tenant]);
},
[setTenants, tenants]
);
useEffect(() => {
const createFirstTenant = async () => {
setError(undefined);
try {
const newTenant = await api.post('/api/tenants', { body: {} }); // Use DB default value.
setTenants([newTenant]);
navigate(newTenant.id);
} catch (error: unknown) {
setError(error instanceof Error ? error : new Error(String(error)));
}
};
if (isLoaded && isOnboarding && tenants?.length === 0) {
void createFirstTenant();
}
}, [api, isOnboarding, isLoaded, setTenants, tenants, navigate]);
if (error) {
if (error instanceof HTTPError && error.response.status === 401) {
@ -53,11 +65,26 @@ function Protected() {
}
if (tenants) {
if (currentTenantId) {
return <Redirect tenants={tenants} toTenantId={currentTenantId} />;
/**
* Redirect to the first tenant if the current tenant ID is not set or can not be found.
*
* `currentTenantId` can be empty string, so that Boolean is required and `??` is
* not applicable for current case.
*/
// eslint-disable-next-line no-extra-boolean-cast
const toTenantId = Boolean(currentTenantId) ? currentTenantId : tenants[0]?.id;
if (toTenantId) {
return <Redirect tenants={tenants} toTenantId={toTenantId} />;
}
return <Tenants data={tenants} onAdd={onAdd} />;
/**
* Will create a new tenant for new users that need go through onboarding process,
* but create tenant takes time, the screen will have a glance of landing page of empty tenant.
*/
if (isLoaded && !isOnboarding) {
return <TenantLandingPage />;
}
}
return <AppLoading />;

View file

@ -1,5 +1,4 @@
import { yes } from '@silverhand/essentials';
export const isProduction = process.env.NODE_ENV === 'production';
export const isCloud = yes(process.env.IS_CLOUD);
export const adminEndpoint = process.env.ADMIN_ENDPOINT;

View file

@ -0,0 +1,5 @@
.icon {
width: 20px;
height: 20px;
color: var(--color-neutral-variant-30);
}

View file

@ -4,6 +4,7 @@ import ContactIcon from '@/assets/icons/contact-us.svg';
import IconButton from '@/ds-components/IconButton';
import ContactModal from './ContactModal';
import * as styles from './index.module.scss';
function Contact() {
const [isContactOpen, setIsContactOpen] = useState(false);
@ -12,6 +13,7 @@ function Contact() {
<>
<IconButton
size="medium"
iconClassName={styles.icon}
onClick={() => {
setIsContactOpen(true);
}}

View file

@ -27,6 +27,7 @@
.icon {
width: 20px;
height: 20px;
color: var(--color-neutral-variant-30);
}
span {

View file

@ -4,7 +4,8 @@
display: flex;
align-items: center;
padding: _.unit(1);
margin-left: _.unit(5);
padding-left: _.unit(2);
margin-left: _.unit(4);
max-width: 500px;
border-radius: _.unit(2);
transition: background-color 0.2s ease-in-out;
@ -45,7 +46,12 @@
background-color: var(--color-neutral-80);
flex-shrink: 0;
position: absolute;
left: _.unit(-4);
left: _.unit(-3);
}
&:hover::before {
pointer-events: none;
cursor: default;
}
}
@ -89,7 +95,7 @@
margin-left: auto;
&.visible {
color: var(--color-brand-40);
color: var(--color-primary-40);
}
}
}
@ -116,6 +122,6 @@
.icon {
width: 20px;
height: 20px;
color: var(--color-placeholder);
color: var(--color-neutral-50);
}
}

View file

@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next';
import KeyboardArrowDown from '@/assets/icons/keyboard-arrow-down.svg';
import PlusSign from '@/assets/icons/plus.svg';
import Tick from '@/assets/icons/tick.svg';
import CreateTenantModal from '@/cloud/pages/Main/CreateTenantModal';
import CreateTenantModal from '@/cloud/pages/Main/TenantLandingPage/TenantLandingPageContent/CreateTenantModal';
import AppError from '@/components/AppError';
import Divider from '@/ds-components/Divider';
import Dropdown, { DropdownItem } from '@/ds-components/Dropdown';

View file

@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next';
import CloudLogo from '@/assets/images/cloud-logo.svg';
import Logo from '@/assets/images/logo.svg';
import { isProduction, isCloud } from '@/consts/env';
import { isCloud } from '@/consts/env';
import Spacer from '@/ds-components/Spacer';
import EarlyBirdGift from '@/onboarding/components/EarlyBirdGift';
@ -24,7 +24,7 @@ function Topbar({ className }: Props) {
return (
<div className={classNames(styles.topbar, className)}>
<LogtoLogo className={styles.logo} />
{isCloud && !isProduction && <TenantSelector />}
{isCloud && <TenantSelector />}
{!isCloud && (
<>
<div className={styles.line} />

View file

@ -9,7 +9,7 @@ import {
WebhookDetailsTabs,
TenantSettingsTabs,
} from '@/consts';
import { isCloud, isProduction } from '@/consts/env';
import { isCloud } from '@/consts/env';
import OverlayScrollbar from '@/ds-components/OverlayScrollbar';
import ApiResourceDetails from '@/pages/ApiResourceDetails';
import ApiResourcePermissions from '@/pages/ApiResourceDetails/ApiResourcePermissions';
@ -145,9 +145,7 @@ function ConsoleContent() {
{isCloud && (
<Route path="tenant-settings" element={<TenantSettings />}>
<Route index element={<Navigate replace to={TenantSettingsTabs.Settings} />} />
{!isProduction && (
<Route path={TenantSettingsTabs.Settings} element={<TenantBasicSettings />} />
)}
<Route path={TenantSettingsTabs.Settings} element={<TenantBasicSettings />} />
<Route path={TenantSettingsTabs.Domains} element={<TenantDomainSettings />} />
</Route>
)}

View file

@ -46,11 +46,20 @@ function TenantsProvider({ children }: Props) {
const navigate = useCallback((tenantId: string, options?: NavigateOptions) => {
if (options?.replace) {
window.history.replaceState(options.state ?? {}, '', '/' + tenantId);
window.history.replaceState(
options.state ?? {},
'',
new URL(`/${tenantId}`, window.location.origin).toString()
);
return;
}
window.history.pushState(options?.state ?? {}, '', '/' + tenantId);
window.history.pushState(
options?.state ?? {},
'',
new URL(`/${tenantId}`, window.location.origin).toString()
);
setCurrentTenantId(tenantId);
}, []);

View file

@ -205,8 +205,8 @@
}
.small.checked {
color: var(--color-primary);
border-color: var(--color-primary);
color: var(--color-text-link);
border-color: var(--color-text-link);
background-color: var(--color-hover-variant);
&:not(:first-child)::before {
@ -216,7 +216,7 @@
top: -1px;
left: -1px;
bottom: -1px;
background-color: var(--color-primary);
background-color: var(--color-text-link);
}
}

View file

@ -2,6 +2,8 @@ import { type TenantInfo, TenantTag } from '@logto/schemas/models';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
import { useCloudApi } from '@/cloud/hooks/use-cloud-api';
import AppError from '@/components/AppError';
@ -24,6 +26,7 @@ const tenantProfileToForm = (tenant?: TenantInfo): TenantSettingsForm => {
};
function TenantBasicSettings() {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const api = useCloudApi();
const {
currentTenant,
@ -65,6 +68,7 @@ function TenantBasicSettings() {
});
reset({ profile: { name, tag } });
void mutate();
toast.success(t('tenant_settings.profile.tenant_info_saved'));
} catch (error: unknown) {
setError(
error instanceof Error
@ -98,10 +102,7 @@ function TenantBasicSettings() {
try {
await api.delete(`/api/tenants/:tenantId`, { params: { tenantId: currentTenantId } });
setIsDeletionModalOpen(false);
await mutate();
if (tenants?.[0]?.id) {
window.open(new URL(`/${tenants[0].id}`, window.location.origin).toString(), '_self');
}
void mutate();
} catch (error: unknown) {
setError(
error instanceof Error
@ -113,6 +114,20 @@ function TenantBasicSettings() {
}
};
useEffect(() => {
/**
* Redirect to the first tenant if the current tenant is deleted;
* Redirect to Cloud console landing page if there is no tenant.
*/
if (tenants && !tenants.some(({ id }) => id === currentTenantId)) {
window.location.assign(
tenants[0]?.id
? new URL(`/${tenants[0]?.id}`, window.location.origin).toString()
: new URL(window.location.origin).toString()
);
}
}, [currentTenantId, tenants]);
if (isLoading) {
return <AppLoading />;
}

View file

@ -1,7 +1,6 @@
import { Outlet } from 'react-router-dom';
import { TenantSettingsTabs } from '@/consts';
import { isProduction } from '@/consts/env';
import CardTitle from '@/ds-components/CardTitle';
import DynamicT from '@/ds-components/DynamicT';
import TabNav, { TabNavItem } from '@/ds-components/TabNav';
@ -17,11 +16,9 @@ function TenantSettings() {
className={styles.cardTitle}
/>
<TabNav className={styles.tabs}>
{!isProduction && (
<TabNavItem href={`/tenant-settings/${TenantSettingsTabs.Settings}`}>
<DynamicT forKey="tenant_settings.tabs.settings" />
</TabNavItem>
)}
<TabNavItem href={`/tenant-settings/${TenantSettingsTabs.Settings}`}>
<DynamicT forKey="tenant_settings.tabs.settings" />
</TabNavItem>
<TabNavItem href={`/tenant-settings/${TenantSettingsTabs.Domains}`}>
<DynamicT forKey="tenant_settings.tabs.domains" />
</TabNavItem>

View file

@ -1,10 +1,12 @@
import { defaultTenantId } from '@logto/schemas';
import { appendPath } from '@silverhand/essentials';
import { setDefaultOptions } from 'expect-puppeteer';
import { logtoCloudUrl as logtoCloudUrlString, logtoConsoleUrl } from '#src/constants.js';
import { generatePassword } from '#src/utils.js';
setDefaultOptions({ timeout: 2000 });
await page.setViewport({ width: 1280, height: 720 });
setDefaultOptions({ timeout: 5000 });
/**
* NOTE: This test suite assumes test cases will run sequentially (which is Jest default).
@ -18,6 +20,8 @@ describe('smoke testing for cloud', () => {
const logtoCloudUrl = new URL(logtoCloudUrlString);
const adminTenantUrl = new URL(logtoConsoleUrl); // In dev mode, the console URL is actually for admin tenant
const createTenantName = 'new-tenant';
it('can open with app element and navigate to register page', async () => {
await page.goto(logtoCloudUrl.href);
await page.waitForNavigation({ waitUntil: 'networkidle0' });
@ -27,8 +31,6 @@ describe('smoke testing for cloud', () => {
});
it('can register the first admin account', async () => {
await expect(page).toClick('button', { text: 'Create account' });
await expect(page).toFill('input[name=identifier]', consoleUsername);
await expect(page).toClick('button[name=submit]');
@ -40,35 +42,12 @@ describe('smoke testing for cloud', () => {
confirmPassword: consolePassword,
});
await expect(page).toClick('button[name=submit]');
await page.waitForNavigation({ waitUntil: 'networkidle0' });
expect(page.url()).toBe(logtoCloudUrl.href);
});
it('shows a tenant-select page with two tenants', async () => {
const tenantsWrapper = await page.waitForSelector('div[class$=wrapper]');
await expect(tenantsWrapper).toMatchElement('a:nth-of-type(1)', { text: 'default' });
await expect(tenantsWrapper).toMatchElement('a:nth-of-type(2)', { text: 'admin' });
});
it('can create another tenant', async () => {
await expect(page).toClick('button', { text: 'Create' });
await page.waitForTimeout(1000);
const tenants = await page.$$('div[class$=wrapper] > a');
expect(tenants.length).toBe(3);
});
it('can enter the tenant just created', async () => {
const button = await page.waitForSelector('div[class$=wrapper] > a:last-of-type');
const tenantId = await button.evaluate((element) => element.textContent);
await button.click();
// Wait for our beautiful logo to show up
await page.waitForSelector('div[class$=topbar] > svg[viewbox][class$=logo]');
expect(page.url()).toBe(new URL(`/${tenantId ?? ''}/onboarding/welcome`, logtoCloudUrl).href);
expect(page.url()).toBe(
appendPath(logtoCloudUrl, `/${defaultTenantId}/onboarding/welcome`).href
);
});
it('can complete the onboarding welcome process and enter the user survey page', async () => {
@ -129,6 +108,7 @@ describe('smoke testing for cloud', () => {
await expect(page).toClick('div[class$=content] >button');
// Wait for the admin console to load
await page.waitForNavigation({ waitUntil: 'networkidle0' });
const mainContent = await page.waitForSelector('div[class$=main]:has(div[class$=title])');
await expect(mainContent).toMatchElement('div[class$=title]', {
text: 'Something to explore to help you succeed',
@ -137,15 +117,84 @@ describe('smoke testing for cloud', () => {
expect(new URL(page.url()).pathname.endsWith('/get-started')).toBeTruthy();
});
it('can create a new tenant using tenant dropdown', async () => {
// Click 'current tenant card' locates in topbar
const currentTenantCard = await page.waitForSelector(
'div[class$=topbar] > div[class$=currentTenantCard][role=button]:has(div[class$=name])'
);
await expect(currentTenantCard).toMatchElement('div[class$=name]', { text: 'My Project' });
await currentTenantCard.click();
await page.waitForTimeout(500);
const createTenantButton = await page.waitForSelector(
'div[class$=ReactModalPortal] div[class$=dropdownContainer] > div[class$=dropdown] > div[class$=createTenantButton][role=button]:has(div)'
);
await expect(createTenantButton).toMatchElement('div', { text: 'Create tenant' });
await createTenantButton.click();
// Create tenant with name 'new-tenant' and tag 'production'
await page.waitForTimeout(500);
await page.waitForSelector(
'div[class$=ReactModalPortal] div[class*=card][class$=medium] input[type=text][name=name]'
);
await page.waitForSelector(
'div[class$=ReactModalPortal] div[class*=radioGroup][class$=small] div[class*=radio][class$=small][role=radio] > div[class$=content]:has(input[value=production])'
);
await expect(page).toFill(
'div[class$=ReactModalPortal] div[class*=card][class$=medium] input[type=text][name=name]',
createTenantName
);
await expect(page).toClick(
'div[class$=ReactModalPortal] div[class*=radioGroup][class$=small] div[class*=radio][class$=small][role=radio] > div[class$=content]:has(input[value=production])'
);
// Click create button
await page.waitForTimeout(500);
await expect(page).toClick(
'div[class$=ReactModalPortal] div[class*=card][class$=medium] div[class$=footer] button[type=submit]'
);
expect(new URL(page.url()).pathname.endsWith(`${defaultTenantId}/get-started`)).toBeTruthy();
});
it('check current tenant list and switch to new tenant', async () => {
// Wait for toast to disappear.
await page.waitForTimeout(5000);
// Click 'current tenant card' locates in topbar
const currentTenantCard = await page.waitForSelector(
'div[class$=topbar] > div[class$=currentTenantCard][role=button]:has(div[class$=name])'
);
await expect(currentTenantCard).toMatchElement('div[class$=name]', { text: 'My Project' });
await currentTenantCard.click();
const newTenant = await page.waitForSelector(
'div[class$=ReactModalPortal] div[class$=dropdownContainer] div[class$=dropdownItem]:first-child'
);
await expect(newTenant).toMatchElement('div[class$=dropdownName]', { text: createTenantName });
await newTenant.click();
await page.waitForNavigation({ waitUntil: 'networkidle0' });
});
it('can sign out of admin console', async () => {
await expect(page).toClick('div[class$=topbar] > div[class$=container]');
// Check if the current tenant is switched to new tenant.
const currentTenantCard = await page.waitForSelector(
'div[class$=topbar] > div[class$=currentTenantCard][role=button]:has(div[class$=name])'
);
await expect(currentTenantCard).toMatchElement('div[class$=name]', { text: createTenantName });
const userInfoButton = await page.waitForSelector('div[class$=topbar] > div[class$=container]');
await userInfoButton.click();
// Try awaiting for 500ms before clicking sign-out button
await page.waitForTimeout(500);
await expect(page).toClick(
'.ReactModalPortal div[class$=dropdownContainer] div[class$=dropdownItem]:last-child'
const signOutButton = await page.waitForSelector(
'div[class$=ReactModalPortal] div[class$=dropdownContainer] div[class$=dropdownItem]:last-child'
);
await signOutButton.click();
await page.waitForNavigation({ waitUntil: 'networkidle0' });
expect(page.url()).toBe(new URL('sign-in', logtoConsoleUrl).href);
@ -169,9 +218,9 @@ describe('smoke testing for cloud', () => {
});
await expect(page).toClick('button[name=submit]');
await page.waitForNavigation({ waitUntil: 'networkidle0', timeout: 5000 });
await page.waitForNavigation({ waitUntil: 'networkidle0' });
expect(page.url().startsWith(logtoCloudUrl.href)).toBeTruthy();
expect(new URL(page.url()).pathname.endsWith('/onboarding/welcome')).toBeTruthy();
expect(page.url().startsWith(logtoCloudUrl.origin)).toBeTruthy();
expect(page.url().endsWith('/onboarding/welcome')).toBeTruthy();
});
});

View file

@ -12,10 +12,11 @@ const tenant_settings = {
tenant_name: 'Mietername',
environment_tag: 'Umgebungsmarke',
environment_tag_description:
'Verwenden Sie Tags, um Mieter-Nutzungsumgebungen zu unterscheiden. Services innerhalb jedes Tags sind identisch und gewährleisten Konsistenz.',
environment_tag_development: 'Entwicklung',
environment_tag_staging: 'Inszenierung',
environment_tag_production: 'Produktion',
'Die Dienste mit unterschiedlichen Tags sind identisch. Es funktioniert als Suffix, um Ihrem Team Umgebungen zu unterscheiden.',
environment_tag_development: 'Dev',
environment_tag_staging: 'Staging',
environment_tag_production: 'Prod',
tenant_info_saved: 'Mieterinformationen erfolgreich gespeichert.',
},
deletion_card: {
title: 'LÖSCHEN',

View file

@ -7,10 +7,10 @@ const tenants = {
tenant_name_placeholder: 'Mein Mieter',
environment_tag: 'Umwelt Tag',
environment_tag_description:
'Verwenden Sie Tags, um die Verwendungsumgebungen von Mieter zu unterscheiden. Dienste innerhalb jeder Marke sind identisch und sorgen für Konsistenz.',
environment_tag_development: 'Entwicklung',
'Die Dienste mit unterschiedlichen Tags sind identisch. Es funktioniert als Suffix, um Ihrem Team Umgebungen zu unterscheiden.',
environment_tag_development: 'Dev',
environment_tag_staging: 'Staging',
environment_tag_production: 'Produktion',
environment_tag_production: 'Prod',
},
delete_modal: {
title: 'Mieter löschen',
@ -22,6 +22,12 @@ const tenants = {
'Wenn Sie fortfahren möchten, geben Sie bitte den Mieter-Namen "<span>{{name}}</span>" zur Bestätigung ein.',
delete_button: 'Dauerhaft löschen',
},
tenant_landing_page: {
title: 'Du hast noch keinen Mandanten erstellt',
description:
'Um Ihr Projekt mit Logto zu konfigurieren, erstellen Sie bitte einen neuen Mandanten. Wenn Sie sich abmelden oder Ihr Konto löschen möchten, klicken Sie einfach auf die Avatar-Taste in der oberen rechten Ecke.',
create_tenant_button: 'Mandanten erstellen',
},
tenant_created: "Mieter '{{name}}' erfolgreich erstellt.",
};

View file

@ -12,10 +12,11 @@ const tenant_settings = {
tenant_name: 'Tenant Name',
environment_tag: 'Environment Tag',
environment_tag_description:
'Use tags to differentiate tenant usage environments. Services within each tag are identical, ensuring consistency.',
environment_tag_development: 'Development',
'The services with different tags are identical. It functions as a suffix to help your team differentiate environments.',
environment_tag_development: 'Dev',
environment_tag_staging: 'Staging',
environment_tag_production: 'Production',
environment_tag_production: 'Prod',
tenant_info_saved: 'Tenant information saved successfully.',
},
deletion_card: {
title: 'DELETE',

View file

@ -7,10 +7,10 @@ const tenants = {
tenant_name_placeholder: 'My tenant',
environment_tag: 'Environment Tag',
environment_tag_description:
'Use tags to differentiate tenant usage environments. Services within each tag are identical, ensuring consistency.',
environment_tag_development: 'Development',
'The services with different tags are identical. It functions as a suffix to help your team differentiate environments.',
environment_tag_development: 'Dev',
environment_tag_staging: 'Staging',
environment_tag_production: 'Production',
environment_tag_production: 'Prod',
},
delete_modal: {
title: 'Delete tenant',
@ -22,6 +22,12 @@ const tenants = {
'If you would like to proceed, please enter the tenant name "<span>{{name}}</span>" to confirm.',
delete_button: 'Permanently delete',
},
tenant_landing_page: {
title: 'You havent created a tenant yet',
description:
'To start configuring your project with Logto, please create a new tenant. If you need to log out or delete your account, just click on the avatar button in the top right corner.',
create_tenant_button: 'Create tenant',
},
tenant_created: "Tenant '{{name}}' created successfully.",
};

View file

@ -12,10 +12,11 @@ const tenant_settings = {
tenant_name: 'Nombre del inquilino',
environment_tag: 'Etiqueta del entorno',
environment_tag_description:
'Use etiquetas para diferenciar entre los entornos de uso del inquilino. Los servicios dentro de cada etiqueta son idénticos, lo que garantiza la consistencia.',
environment_tag_development: 'Desarrollo',
environment_tag_staging: 'Puesta en escena',
environment_tag_production: 'Producción',
'Los servicios con diferentes etiquetas son idénticos. Funciona como sufijo para ayudar a su equipo a diferenciar entornos.',
environment_tag_development: 'Dev',
environment_tag_staging: 'Staging',
environment_tag_production: 'Prod',
tenant_info_saved: 'Información del inquilino guardada correctamente.',
},
deletion_card: {
title: 'ELIMINAR',

View file

@ -7,10 +7,10 @@ const tenants = {
tenant_name_placeholder: 'Mi inquilino',
environment_tag: 'Etiqueta de ambiente',
environment_tag_description:
'Use etiquetas para diferenciar los ambientes de uso del inquilino. Los servicios dentro de cada etiqueta son idénticos, lo que garantiza la coherencia.',
environment_tag_development: 'Desarrollo',
environment_tag_staging: 'Puesta en escena',
environment_tag_production: 'Producción',
'Los servicios con diferentes etiquetas son idénticos. Funciona como sufijo para ayudar a su equipo a diferenciar entornos.',
environment_tag_development: 'Dev',
environment_tag_staging: 'Staging',
environment_tag_production: 'Prod',
},
delete_modal: {
title: 'Eliminar inquilino',
@ -22,6 +22,12 @@ const tenants = {
'Si desea continuar, ingrese el nombre del inquilino "<span>{{name}}</span>" para confirmar.',
delete_button: 'Eliminar permanentemente',
},
tenant_landing_page: {
title: 'Todavía no has creado un tenant',
description:
'Para empezar a configurar tu proyecto con Logto, por favor crea un nuevo tenant. Si necesitas cerrar la sesión o eliminar tu cuenta, simplemente haz clic en el botón de avatar en la esquina superior derecha.',
create_tenant_button: 'Crear tenant',
},
tenant_created: "El inquilino '{{name}}' se ha creado correctamente.",
};

View file

@ -12,10 +12,11 @@ const tenant_settings = {
tenant_name: 'Nom du locataire',
environment_tag: "Tag de l'environnement",
environment_tag_description:
"Utilisez des tags pour différencier les environnements d'utilisation du locataire. Les services au sein de chaque tag sont identiques, assurant ainsi la cohérence.",
environment_tag_development: 'Développement',
environment_tag_staging: 'Mise en scène',
environment_tag_production: 'Production',
'Les services avec différentes balises sont identiques. Il fonctionne comme un suffixe pour aider votre équipe à différencier les environnements.',
environment_tag_development: 'Dev',
environment_tag_staging: 'Staging',
environment_tag_production: 'Prod',
tenant_info_saved: 'Les informations du locataire ont été enregistrées avec succès.',
},
deletion_card: {
title: 'SUPPRIMER',

View file

@ -7,10 +7,10 @@ const tenants = {
tenant_name_placeholder: 'Mon locataire',
environment_tag: 'Balise environnement',
environment_tag_description:
"Utilisez des balises pour différencier les environnements d'utilisation des locataires. Les services dans chaque balise sont identiques, assurant ainsi la cohérence.",
environment_tag_development: 'Développement',
environment_tag_staging: 'Mise en scène',
environment_tag_production: 'Production',
'Les services avec différentes balises sont identiques. Il fonctionne comme un suffixe pour aider votre équipe à différencier les environnements.',
environment_tag_development: 'Dev',
environment_tag_staging: 'Staging',
environment_tag_production: 'Prod',
},
delete_modal: {
title: 'Supprimer le locataire',
@ -22,6 +22,12 @@ const tenants = {
'Si vous souhaitez continuer, veuillez entrer le nom du locataire "<span>{{name}}</span>" pour confirmer.',
delete_button: 'Supprimer définitivement',
},
tenant_landing_page: {
title: "Vous n'avez pas encore créé de locataire",
description:
"Pour commencer à configurer votre projet avec Logto, veuillez créer un nouveau locataire. Si vous devez vous déconnecter ou supprimer votre compte, cliquez simplement sur le bouton d'avatar dans le coin supérieur droit.",
create_tenant_button: 'Créer un locataire',
},
tenant_created: "Locataire '{{name}}' créé avec succès.",
};

View file

@ -12,10 +12,11 @@ const tenant_settings = {
tenant_name: 'Nome Tenant',
environment_tag: 'Tag Ambiente',
environment_tag_description:
"Utilizza i tag per differenziare gli ambienti di utilizzo del tenant. I servizi all'interno di ogni tag sono identici, garantendo la coerenza.",
environment_tag_development: 'Sviluppo',
'I servizi con tag diversi sono identici. Funziona come suffisso per aiutare il tuo team a differenziare gli ambienti.',
environment_tag_development: 'Dev',
environment_tag_staging: 'Staging',
environment_tag_production: 'Produzione',
environment_tag_production: 'Prod',
tenant_info_saved: "Le informazioni dell'inquilino sono state salvate correttamente.",
},
deletion_card: {
title: 'ELIMINA',

View file

@ -7,10 +7,10 @@ const tenants = {
tenant_name_placeholder: 'Il mio tenant',
environment_tag: 'Tag ambiente',
environment_tag_description:
"Usa i tag per differenziare gli ambienti di utilizzo del tenant. I servizi all'interno di ogni tag sono identici, garantendo la coerenza.",
environment_tag_development: 'Sviluppo',
environment_tag_staging: 'Sperimentale',
environment_tag_production: 'Produzione',
'I servizi con tag diversi sono identici. Funziona come suffisso per aiutare il tuo team a differenziare gli ambienti.',
environment_tag_development: 'Dev',
environment_tag_staging: 'Staging',
environment_tag_production: 'Prod',
},
delete_modal: {
title: 'Elimina tenant',
@ -22,6 +22,12 @@ const tenants = {
'Se vuoi procedere, inserisci il nome del tenant "<span>{{name}}</span>" per confermare.',
delete_button: 'Elimina definitivamente',
},
tenant_landing_page: {
title: 'Non hai ancora creato un tenant',
description:
'Per iniziare a configurare il tuo progetto con Logto, crea un nuovo tenant. Se hai bisogno di uscire o eliminare il tuo account, clicca sul pulsante avatar in alto a destra.',
create_tenant_button: 'Crea tenant',
},
tenant_created: "Tenant '{{name}}' creato con successo.",
};

View file

@ -12,10 +12,11 @@ const tenant_settings = {
tenant_name: 'テナント名',
environment_tag: '環境タグ',
environment_tag_description:
'タグを使用してテナント使用環境を区別します。 各タグ内のサービスは同一で、一貫性が保たれます。',
environment_tag_development: '開発',
environment_tag_staging: 'ステージング',
environment_tag_production: 'プロダクション',
'タグの異なるサービスは同一です。環境を区別するためにチームを支援する接尾辞として機能します。',
environment_tag_development: 'Dev',
environment_tag_staging: 'Staging',
environment_tag_production: 'Prod',
tenant_info_saved: 'テナント情報は正常に保存されました。',
},
deletion_card: {
title: '削除',

View file

@ -7,10 +7,10 @@ const tenants = {
tenant_name_placeholder: '私のテナント',
environment_tag: '環境タグ',
environment_tag_description:
'タグを使用して、テナント使用環境を区別します。各タグ内のサービスは同一であり、一貫性が保たれます。',
environment_tag_development: '開発',
environment_tag_staging: 'ステージング',
environment_tag_production: 'プロダクション',
'タグの異なるサービスは同一です。環境を区別するためにチームを支援する接尾辞として機能します。',
environment_tag_development: 'Dev',
environment_tag_staging: 'Staging',
environment_tag_production: 'Prod',
},
delete_modal: {
title: 'テナントを削除します',
@ -22,6 +22,12 @@ const tenants = {
'続行する場合は、テナント名 "<span>{{name}}</span>" を入力して確認してください。',
delete_button: '完全に削除する',
},
tenant_landing_page: {
title: 'まだテナントを作成していません',
description:
'Logto でプロジェクトを設定するには、新しいテナントを作成してください。ログアウトまたはアカウントを削除する必要がある場合は、右上隅のアバターボタンをクリックしてください。',
create_tenant_button: 'テナントを作成',
},
tenant_created: "{{name}}'のテナントが正常に作成されました。",
};

View file

@ -11,10 +11,11 @@ const tenant_settings = {
tenant_name: '테넌트 이름',
environment_tag: '환경 태그',
environment_tag_description:
'태그를 사용하여 테넌트 사용 환경을 구분합니다. 각 태그에 대한 서비스는 동일하므로 일관성이 유지됩니다.',
environment_tag_development: '개발',
environment_tag_staging: '스테이징',
environment_tag_production: '프로덕션',
'태그가 다른 서비스는 동일합니다. 환경을 구분하는 데 팀을 돕는 접미사로 기능합니다.',
environment_tag_development: 'Dev',
environment_tag_staging: 'Staging',
environment_tag_production: 'Prod',
tenant_info_saved: '세입자 정보가 성공적으로 저장되었습니다.',
},
deletion_card: {
title: '삭제',

View file

@ -7,10 +7,10 @@ const tenants = {
tenant_name_placeholder: '내 테넌트',
environment_tag: '환경 태그',
environment_tag_description:
'태그를 사용하여 테넌트 사용 환경을 구분하세요. 각 태그 내의 서비스는 동일하여 일관성을 보장합니다.',
environment_tag_development: '개발',
environment_tag_staging: '스테이징',
environment_tag_production: '프로덕션',
'태그가 다른 서비스는 동일합니다. 환경을 구분하는 데 팀을 돕는 접미사로 기능합니다.',
environment_tag_development: 'Dev',
environment_tag_staging: 'Staging',
environment_tag_production: 'Prod',
},
delete_modal: {
title: '테넌트 삭제',
@ -22,6 +22,12 @@ const tenants = {
'삭제하려는 테넌트 이름 "<span>{{name}}</span>"을(를) 입력하여 확인하십시오.',
delete_button: '영구 삭제',
},
tenant_landing_page: {
title: '아직 테넌트를 만들지 않았습니다.',
description:
'Logto 를 사용하여 프로젝트를 구성하려면 새 테넌트를 만드세요. 로그아웃하거나 계정을 삭제하려면 오른쪽 상단 모서리에있는 아바타 버튼을 클릭하세요.',
create_tenant_button: '테넌트 만들기',
},
tenant_created: "테넌트 '{{name}}'가(이) 성공적으로 만들어졌습니다.",
};

View file

@ -12,10 +12,11 @@ const tenant_settings = {
tenant_name: 'Nazwa Najemcy',
environment_tag: 'Tag Środowiska',
environment_tag_description:
'Użyj tagów, aby rozróżnić środowiska użytkowe najemcy. Usługi w każdym tagu są identyczne, co zapewnia spójność.',
environment_tag_development: 'Rozwój',
'Usługi z różnymi tagami są identyczne. Działa jako przyrostek, aby pomóc Twojemu zespołowi w różnicowaniu środowisk.',
environment_tag_development: 'Dev',
environment_tag_staging: 'Staging',
environment_tag_production: 'Produkcja',
environment_tag_production: 'Prod',
tenant_info_saved: 'Informacje o najemcy zostały pomyślnie zapisane.',
},
deletion_card: {
title: 'USUWANIE',

View file

@ -7,10 +7,10 @@ const tenants = {
tenant_name_placeholder: 'Mój najemca',
environment_tag: 'Tag środowiska',
environment_tag_description:
'Użyj tagów do odróżniania środowisk wykorzystania najemcy. Usługi w każdym tagu są identyczne, zapewniając spójność.',
environment_tag_development: 'Rozwój',
'Usługi z różnymi tagami są identyczne. Działa jako przyrostek, aby pomóc Twojemu zespołowi w różnicowaniu środowisk.',
environment_tag_development: 'Dev',
environment_tag_staging: 'Staging',
environment_tag_production: 'Produkcja',
environment_tag_production: 'Prod',
},
delete_modal: {
title: 'Usuń najemcę',
@ -22,6 +22,12 @@ const tenants = {
'Jeśli chcesz kontynuować, wprowadź nazwę najemcy "<span>{{name}}</span>" w celu potwierdzenia.',
delete_button: 'Usuń na stałe',
},
tenant_landing_page: {
title: 'Nie utworzyłeś jeszcze najemcy',
description:
'Aby rozpocząć konfigurowanie projektu z Logto, utwórz nowego najemcę. Jeśli musisz się wylogować lub usunąć swoje konto, wystarczy kliknąć przycisk awatara w prawym górnym rogu.',
create_tenant_button: 'Utwórz najemcę',
},
tenant_created: "Najemca '{{name}}' utworzony pomyślnie.",
};

View file

@ -12,10 +12,11 @@ const tenant_settings = {
tenant_name: 'Nome do Locatário',
environment_tag: 'Tag do Ambiente',
environment_tag_description:
'Use tags para diferenciar os ambientes de uso do locatário. Os serviços em cada tag são os mesmos, garantindo consistência.',
environment_tag_development: 'Desenvolvimento',
environment_tag_staging: 'Teste',
environment_tag_production: 'Produção',
'Os serviços com diferentes tags são idênticos. Funciona como um sufixo para ajudar sua equipe a diferenciar ambientes.',
environment_tag_development: 'Dev',
environment_tag_staging: 'Staging',
environment_tag_production: 'Prod',
tenant_info_saved: 'As informações do locatário foram salvas com sucesso.',
},
deletion_card: {
title: 'EXCLUIR',

View file

@ -7,10 +7,10 @@ const tenants = {
tenant_name_placeholder: 'Meu inquilino',
environment_tag: 'Tag de ambiente',
environment_tag_description:
'Use tags para diferenciar ambientes de uso de inquilino. Serviços dentro de cada tag são idênticos, garantindo consistência.',
environment_tag_development: 'Desenvolvimento',
'Os serviços com diferentes tags são idênticos. Funciona como um sufixo para ajudar sua equipe a diferenciar ambientes.',
environment_tag_development: 'Dev',
environment_tag_staging: 'Staging',
environment_tag_production: 'Produção',
environment_tag_production: 'Prod',
},
delete_modal: {
title: 'Excluir locatário',
@ -22,6 +22,12 @@ const tenants = {
'Se você deseja continuar, digite o nome do locatário "<span>{{name}}</span>" para confirmar.',
delete_button: 'Excluir permanentemente',
},
tenant_landing_page: {
title: 'Você ainda não criou um inquilino',
description:
'Para começar a configurar seu projeto com o Logto, crie um novo inquilino. Se você precisar fazer logout ou excluir sua conta, basta clicar no botão de avatar no canto superior direito.',
create_tenant_button: 'Criar inquilino',
},
tenant_created: "Inquilino '{{name}}' criado com sucesso.",
};

View file

@ -12,10 +12,11 @@ const tenant_settings = {
tenant_name: 'Nome do Inquilino',
environment_tag: 'Tag de Ambiente',
environment_tag_description:
'Use tags para diferenciar os ambientes de uso do inquilino. Os serviços dentro de cada tag são idênticos, garantindo consistência.',
environment_tag_development: 'Desenvolvimento',
'Os serviços com etiquetas diferentes são idênticos. Funciona como um sufixo para ajudar a sua equipa a diferenciar ambientes.',
environment_tag_development: 'Dev',
environment_tag_staging: 'Staging',
environment_tag_production: 'Produção',
environment_tag_production: 'Prod',
tenant_info_saved: 'A informação do arrendatário foi guardada com sucesso.',
},
deletion_card: {
title: 'ELIMINAR',

View file

@ -7,10 +7,10 @@ const tenants = {
tenant_name_placeholder: 'Meu inquilino',
environment_tag: 'Etiqueta de ambiente',
environment_tag_description:
'Use etiquetas para diferenciar os ambientes de utilização do inquilino. Os serviços em cada etiqueta são idênticos, garantindo consistência.',
environment_tag_development: 'Desenvolvimento',
'Os serviços com etiquetas diferentes são idênticos. Funciona como um sufixo para ajudar a sua equipa a diferenciar ambientes.',
environment_tag_development: 'Dev',
environment_tag_staging: 'Staging',
environment_tag_production: 'Produção',
environment_tag_production: 'Prod',
},
delete_modal: {
title: 'Eliminar inquilino',
@ -22,6 +22,12 @@ const tenants = {
'Se desejar continuar, introduza o nome do inquilino "<span>{{name}}</span>" para confirmar.',
delete_button: 'Eliminar permanentemente',
},
tenant_landing_page: {
title: 'Ainda não criou um inquilino',
description:
'Para começar a configurar o seu projeto com o Logto, crie um novo inquilino. Se precisar de fazer logout ou excluir a sua conta, basta clicar no botão avatar no canto superior direito.',
create_tenant_button: 'Criar inquilino',
},
tenant_created: "Inquilino '{{name}}' criado com sucesso.",
};

View file

@ -12,10 +12,11 @@ const tenant_settings = {
tenant_name: 'Имя арендатора',
environment_tag: 'Тег окружения',
environment_tag_description:
'Используйте теги для различения окружений использования арендаторов. Сервисы в каждом теге идентичны, обеспечивая согласованность.',
environment_tag_development: 'Разработка',
environment_tag_staging: 'Стадия',
environment_tag_production: 'Производство',
'Сервисы с разными тегами идентичны. Он работает как суффикс, чтобы помочь вашей команде различать среды.',
environment_tag_development: 'Dev',
environment_tag_staging: 'Staging',
environment_tag_production: 'Prod',
tenant_info_saved: 'Информация о квартиросъемщике успешно сохранена.',
},
deletion_card: {
title: 'УДАЛИТЬ',

View file

@ -7,10 +7,10 @@ const tenants = {
tenant_name_placeholder: 'Мой арендатор',
environment_tag: 'Тег окружения',
environment_tag_description:
'Используйте теги для отделения сред сред использования арендаторов. Сервисы в каждом теге идентичны, обеспечивая согласованность.',
environment_tag_development: 'Разработка',
environment_tag_staging: 'Стадия',
environment_tag_production: 'Производство',
'Сервисы с разными тегами идентичны. Он работает как суффикс, чтобы помочь вашей команде различать среды.',
environment_tag_development: 'Dev',
environment_tag_staging: 'Staging',
environment_tag_production: 'Prod',
},
delete_modal: {
title: 'Удалить арендатора',
@ -22,6 +22,12 @@ const tenants = {
'Если вы хотите продолжить, введите название арендатора "<span>{{name}}</span>" для подтверждения.',
delete_button: 'Навсегда удалить',
},
tenant_landing_page: {
title: 'Вы еще не создали арендатора',
description:
'Чтобы начать настройку вашего проекта с помощью Logto, создайте нового арендатора. Если вам нужно выйти из системы или удалить свою учетную запись, просто нажмите на кнопку аватара в правом верхнем углу.',
create_tenant_button: 'Создать арендатора',
},
tenant_created: "Арендатор '{{name}}' успешно создан.",
};

View file

@ -12,10 +12,11 @@ const tenant_settings = {
tenant_name: 'Kiracı Adı',
environment_tag: 'Çevre Etiketi',
environment_tag_description:
'Etiketleri kullanarak kiracı kullanım ortamlarını farklılaştırın. Her etiketin içindeki hizmetler aynıdır, tutarlılığı sağlar.',
environment_tag_development: 'Geliştirme',
'Farklı etiketlere sahip hizmetler aynıdır. Ortamları ayırt etmek için ekibinize yardımcı olmak için bir sonek görevi görür.',
environment_tag_development: 'Dev',
environment_tag_staging: 'Staging',
environment_tag_production: 'Üretim',
environment_tag_production: 'Prod',
tenant_info_saved: 'Kiracı bilgileri başarıyla kaydedildi.',
},
deletion_card: {
title: 'SİL',

View file

@ -7,10 +7,10 @@ const tenants = {
tenant_name_placeholder: 'Benim kiracım',
environment_tag: 'Çevre Etiketi',
environment_tag_description:
'Kiracı kullanım ortamlarını ayırt etmek için etiketleri kullanın. Her etiket içindeki hizmetler aynıdır, tutarlılığı sağlar.',
environment_tag_development: 'Geliştirme',
environment_tag_staging: 'Daha Yüksek Birlik',
environment_tag_production: 'Üretim',
'Farklı etiketlere sahip hizmetler aynıdır. Ortamları ayırt etmek için ekibinize yardımcı olmak için bir sonek görevi görür.',
environment_tag_development: 'Dev',
environment_tag_staging: 'Staging',
environment_tag_production: 'Prod',
},
delete_modal: {
title: 'Kiracıyı Sil',
@ -22,6 +22,12 @@ const tenants = {
'Devam etmek isterseniz, "<span>{{name}}</span>" kiracı adını onaylamak için yazın.',
delete_button: 'Kalıcı olarak sil',
},
tenant_landing_page: {
title: 'Henüz bir kiracı oluşturmadınız',
description:
'Logto ile projenizi yapılandırmaya başlamak için lütfen yeni bir kiracı oluşturun. Hesabınızdan çıkış yapmanız veya hesabınızı silmeniz gerekiyorsa, sağ üst köşedeki avatar düğmesine tıklayın.',
create_tenant_button: 'Kiracı oluştur',
},
tenant_created: "Kiracı '{{name}}' başarıyla oluşturuldu.",
};

View file

@ -10,10 +10,12 @@ const tenant_settings = {
tenant_id: '租户 ID',
tenant_name: '租户名称',
environment_tag: '环境标签',
environment_tag_description: '使用标签区分租户使用环境。每个标签中的服务是相同的,确保一致性。',
environment_tag_development: '开发',
environment_tag_staging: '暂存',
environment_tag_production: '生产',
environment_tag_description:
'携带不同标签的服务完全相同。它充当后缀的作用,以帮助您的团队区分不同的环境。',
environment_tag_development: 'Dev',
environment_tag_staging: 'Staging',
environment_tag_production: 'Prod',
tenant_info_saved: '租户信息成功保存。',
},
deletion_card: {
title: '删除',

View file

@ -6,10 +6,11 @@ const tenants = {
tenant_name: '租户名称',
tenant_name_placeholder: '我的租户',
environment_tag: '环境标签',
environment_tag_description: '使用标签区分租户使用环境。每个标签内的服务相同,确保一致性。',
environment_tag_development: '开发环境',
environment_tag_staging: '暂存环境',
environment_tag_production: '生产环境',
environment_tag_description:
'携带不同标签的服务完全相同。它充当后缀的作用,以帮助您的团队区分不同的环境。',
environment_tag_development: 'Dev',
environment_tag_staging: 'Staging',
environment_tag_production: 'Prod',
},
delete_modal: {
title: '删除租户',
@ -20,6 +21,12 @@ const tenants = {
description_line3: '如果你想继续,请输入租户名 "<span>{{name}}</span>" 确认。',
delete_button: '永久删除',
},
tenant_landing_page: {
title: '你还没有创建租户',
description:
'要开始使用 Logto 配置项目,请创建一个新租户。如果您需要注销或删除您的帐户,只需单击右上角的头像按钮。',
create_tenant_button: '创建租户',
},
tenant_created: "租户'{{name}}'创建成功。",
};

View file

@ -11,10 +11,11 @@ const tenant_settings = {
tenant_name: '租户名称',
environment_tag: '环境标识',
environment_tag_description:
'使用标签区分租户使用环境。在每个标签中的服务是相同的,以确保一致性。',
environment_tag_development: '开发',
environment_tag_staging: '暂存',
environment_tag_production: '生产',
'攜帶不同標籤的服務完全相同。它充當後綴的作用,以幫助您的團隊區分不同的環境。',
environment_tag_development: 'Dev',
environment_tag_staging: 'Staging',
environment_tag_production: 'Prod',
tenant_info_saved: '租戶信息成功保存。',
},
deletion_card: {
title: '刪除',

View file

@ -6,10 +6,11 @@ const tenants = {
tenant_name: '租戶名稱',
tenant_name_placeholder: '我的租戶',
environment_tag: '環境標籤',
environment_tag_description: '使用標籤區分租戶環境。每個標籤中的服務均相同,確保一致性。',
environment_tag_development: '開發',
environment_tag_staging: '測試',
environment_tag_production: '生產',
environment_tag_description:
'攜帶不同標籤的服務完全相同。它充當後綴的作用,以幫助您的團隊區分不同的環境。',
environment_tag_development: 'Dev',
environment_tag_staging: 'Staging',
environment_tag_production: 'Prod',
},
delete_modal: {
title: '刪除租戶',
@ -20,6 +21,12 @@ const tenants = {
description_line3: '如果您確定要繼續,請輸入租戶名稱 "<span>{{name}}</span>" 以進行確認。',
delete_button: '永久刪除',
},
tenant_landing_page: {
title: '您尚未建立租戶',
description:
'要開始使用 Logto 配置您的項目,請創建一個新的租戶。如果您需要退出或刪除您的帳戶,只需單擊右上角的頭像按鈕。',
create_tenant_button: '創建租戶',
},
tenant_created: '成功創建租戶「{{name}}」。',
};

View file

@ -10,10 +10,12 @@ const tenant_settings = {
tenant_id: '租戶 ID',
tenant_name: '租戶名稱',
environment_tag: '環境標籤',
environment_tag_description: '使用標籤區分租戶使用環境。每個標籤中的服務均相同,確保一致性。',
environment_tag_development: '開發',
environment_tag_staging: '暫存',
environment_tag_production: '生產',
environment_tag_description:
'帶有不同標籤的服務完全相同。它充當後綴的作用,以幫助您的團隊區分不同的環境。',
environment_tag_development: 'Dev',
environment_tag_staging: 'Staging',
environment_tag_production: 'Prod',
tenant_info_saved: '租戶資訊成功儲存。',
},
deletion_card: {
title: '刪除',

View file

@ -6,10 +6,11 @@ const tenants = {
tenant_name: '租戶名稱',
tenant_name_placeholder: '我的租戶',
environment_tag: '環境標籤',
environment_tag_description: '使用標籤區分租戶使用環境,每個標籤的服務相同,確保一致性。',
environment_tag_development: '開發',
environment_tag_staging: '測試',
environment_tag_production: '生產',
environment_tag_description:
'帶有不同標籤的服務完全相同。它充當後綴的作用,以幫助您的團隊區分不同的環境。',
environment_tag_development: 'Dev',
environment_tag_staging: 'Staging',
environment_tag_production: 'Prod',
},
delete_modal: {
title: '刪除租戶',
@ -20,6 +21,12 @@ const tenants = {
description_line3: '如果您確定要繼續,請輸入租戶名稱 "<span>{{name}}</span>" 以確認。',
delete_button: '永久刪除',
},
tenant_landing_page: {
title: '您尚未建立租戶',
description:
'要開始使用Logto配置您的項目請創建一個新租戶。如果您需要登出或刪除您的帳戶只需點擊右上角的頭像按鈕。',
create_tenant_button: '創建租戶',
},
tenant_created: "租戶 '{{name}}' 成功建立。",
};