feat: license desktop page (#3969)
* feat license desktop page Signed-off-by: jingyang <3161362058@qq.com> * delete log --------- Signed-off-by: jingyang <3161362058@qq.com>
@@ -20,4 +20,8 @@ SEALOS_CLOUD_DOMAIN="cloud.sealos.io"
|
||||
# WECHAT_ENABLED=
|
||||
# GITHUB_ENABLED=
|
||||
# PASSWORD_ENABLED=
|
||||
# SMS_ENABLED=
|
||||
# SMS_ENABLED=
|
||||
|
||||
# costcenter
|
||||
STRIPE_ENABLED=
|
||||
STRIPE_PUB=
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.2952 14.1663L13.6285 10.8329L12.4619 9.66626L11.1285 10.9579V7.49959H9.46188V10.9579L8.12854 9.66626L6.96188 10.8329L10.2952 14.1663ZM5.29521 18.3329C4.83687 18.3329 4.44437 18.1696 4.11771 17.8429C3.79104 17.5163 3.62799 17.124 3.62854 16.6663V6.66626L8.62854 1.66626H15.2952C15.7535 1.66626 16.146 1.82959 16.4727 2.15626C16.7994 2.48293 16.9624 2.87515 16.9619 3.33293V16.6663C16.9619 17.1246 16.7985 17.5171 16.4719 17.8438C16.1452 18.1704 15.753 18.3335 15.2952 18.3329H5.29521ZM5.29521 16.6663H15.2952V3.33293H9.33688L5.29521 7.37459V16.6663Z" fill="#219BF4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 683 B |
@@ -0,0 +1,5 @@
|
||||
<svg width="48" height="49" viewBox="0 0 48 49" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="24" cy="24.2092" r="23.7" stroke="#9CA2A8" stroke-width="0.6" stroke-dasharray="2.4 2.4"/>
|
||||
<path d="M14.2118 33.7125L14.1837 33.7119C14.0213 33.7067 13.1773 33.6525 12.6151 33.1254C12.0386 32.5847 11.9698 31.8073 11.9619 31.6569C11.9611 31.6427 11.9607 31.6286 11.9607 31.6144V27.3805C11.9523 27.2061 11.9411 26.6169 12.1385 25.9625C12.3276 25.3351 12.5613 24.9232 12.6283 24.8121L14.7538 21.0516C14.7645 21.0327 14.7757 21.0145 14.7878 20.9968C14.854 20.8984 15.2103 20.3923 15.6988 20.0884C16.2129 19.7683 16.835 19.7435 17.0136 19.7435H30.4403C30.4576 19.7435 30.4747 19.744 30.4923 19.7451C30.6258 19.7537 31.3189 19.8122 31.8552 20.119C32.3674 20.4123 32.7796 20.9249 32.857 21.0245C32.8727 21.0449 32.8875 21.066 32.9012 21.0878L35.4257 25.0881C35.4365 25.1057 35.4474 25.1241 35.4573 25.143C35.5057 25.2359 35.7552 25.7268 35.8816 26.2042C36.0064 26.6765 36.0365 27.1985 36.0411 27.2982L36.0417 27.3323V31.5668C36.0446 31.7907 36.0034 32.5591 35.366 33.13C34.7624 33.6704 33.9498 33.7122 33.7151 33.7122H14.2118V33.7125ZM13.9955 25.6702C13.949 25.7528 13.8046 26.026 13.6831 26.4285C13.5548 26.8541 13.5701 27.2519 13.5721 27.296C13.5733 27.315 13.5739 27.3341 13.5742 27.3532V31.5865L13.5758 31.5982C13.5858 31.6743 13.6322 31.8587 13.7123 31.9419L13.7231 31.9534L13.7358 31.9627C13.8479 32.0453 14.1257 32.0927 14.2227 32.0986L33.7164 32.0989C33.8781 32.097 34.1608 32.0431 34.2889 31.9283C34.4035 31.8256 34.4292 31.6493 34.4292 31.621L34.4277 31.5721V27.357C34.4189 27.1926 34.3871 26.8649 34.3211 26.6166C34.2464 26.3329 34.0793 25.9931 34.0465 25.9281L34.0374 25.9123L31.56 21.9861L31.5523 21.9768C31.4584 21.8637 31.2332 21.6221 31.0534 21.519C30.8977 21.4299 30.5806 21.3729 30.4173 21.3576L30.4004 21.3568H17.0164C16.8911 21.3579 16.6584 21.3911 16.5509 21.4583C16.3995 21.5527 16.2202 21.7683 16.1464 21.869L16.1401 21.8777L14.0278 25.6145L14.005 25.6529L14.0053 25.6519C14.0047 25.6538 13.9955 25.6702 13.9955 25.6702ZM23.9242 18.5604C23.7103 18.5602 23.5052 18.4751 23.3539 18.3239C23.2027 18.1727 23.1176 17.9676 23.1173 17.7537V13.1127C23.1176 12.8989 23.2026 12.6939 23.3538 12.5426C23.505 12.3914 23.71 12.3063 23.9239 12.306C24.1378 12.3062 24.3429 12.3913 24.4942 12.5425C24.6455 12.6938 24.7306 12.8988 24.7309 13.1127V17.7536C24.7307 17.9675 24.6456 18.1726 24.4944 18.3238C24.3431 18.4751 24.1381 18.5601 23.9242 18.5603V18.5604ZM29.3331 18.5298C29.1192 18.5295 28.9142 18.4444 28.7629 18.2931C28.6117 18.1419 28.5267 17.9368 28.5264 17.7229C28.5266 17.5379 28.5902 17.3585 28.7066 17.2147L30.5508 14.9403C30.6264 14.8467 30.722 14.7713 30.8306 14.7197C30.9392 14.668 31.058 14.6413 31.1782 14.6416C31.3647 14.6416 31.5402 14.7039 31.6854 14.8217C31.768 14.8881 31.8366 14.9704 31.8871 15.0636C31.9377 15.1568 31.9692 15.2591 31.9799 15.3646C31.9912 15.4699 31.9816 15.5765 31.9515 15.6781C31.9213 15.7797 31.8713 15.8743 31.8043 15.9564L29.9602 18.2309C29.8849 18.3244 29.7896 18.3998 29.6813 18.4515C29.573 18.5032 29.4544 18.5299 29.3344 18.5296H29.3331V18.5298ZM18.6059 18.4681C18.4847 18.4684 18.365 18.4413 18.2559 18.3887C18.1467 18.336 18.0509 18.2593 17.9757 18.1643L16.1628 15.89C16.0296 15.7226 15.9683 15.5092 15.9923 15.2966C16.0162 15.0841 16.1236 14.8897 16.2907 14.7562C16.4331 14.6418 16.6104 14.5797 16.793 14.5802C17.0403 14.5802 17.2704 14.691 17.4244 14.8842L19.2374 17.1584C19.3707 17.3258 19.432 17.5391 19.4081 17.7517C19.3842 17.9642 19.2769 18.1586 19.1098 18.2921C18.9669 18.4058 18.7898 18.4678 18.6072 18.4681H18.6059Z" fill="#7B838B"/>
|
||||
<path d="M23.9563 30.3933C22.1589 30.3933 20.5973 29.1883 20.159 27.4634L20.1237 27.3243L12.9591 27.3201C12.7451 27.3196 12.5401 27.2344 12.3889 27.083C12.2377 26.9317 12.1526 26.7266 12.1523 26.5127C12.1527 26.2989 12.2378 26.0939 12.389 25.9428C12.5402 25.7916 12.7452 25.7066 12.9591 25.7063L20.8484 25.7113C21.0622 25.7119 21.2671 25.7971 21.4182 25.9484C21.5693 26.0996 21.6544 26.3046 21.6547 26.5184C21.6547 27.7865 22.6656 28.7797 23.9563 28.7797C25.2423 28.7797 26.2498 27.7865 26.2498 26.5184C26.2502 26.3046 26.3353 26.0996 26.4865 25.9484C26.6377 25.7971 26.8426 25.7119 27.0565 25.7113L34.8728 25.7064C35.3178 25.7064 35.6795 26.0678 35.6798 26.5125C35.6801 26.9572 35.3185 27.3196 34.8737 27.3202L27.7816 27.3245L27.7462 27.4635C27.3085 29.1884 25.7502 30.3933 23.9563 30.3933Z" fill="#7B838B"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 4.55992C1.5 3.93032 2.0104 3.41992 2.64 3.41992H21.36C21.9896 3.41992 22.5 3.93032 22.5 4.55992V19.4399C22.5 20.0695 21.9896 20.5799 21.36 20.5799H2.64C2.01039 20.5799 1.5 20.0695 1.5 19.4399V4.55992ZM3.3 5.21992V18.7799H20.7V5.21992H3.3ZM4.60635 7.73H5.50635H10.1588H11.0588V9.53H10.1588H5.50635H4.60635V7.73ZM4.60635 11.33H5.50635H8.7353H9.6353V13.13H8.7353H5.50635H4.60635V11.33ZM5.50635 14.93H4.60635V16.73H5.50635H11.3461H12.2461V14.93H11.3461H5.50635ZM16.1411 6.05415L16.5841 6.39978L17.2928 6.9528L18.1849 7.06289L18.7425 7.13169L18.8113 7.68929L18.9214 8.58147L19.4745 9.29018L19.8201 9.73311L19.4745 10.176L18.9214 10.8848L18.8113 11.7769L18.7425 12.3345L18.2473 12.3956V16.3476V17.3506C18.2473 17.4626 18.1165 17.5236 18.0307 17.4517L17.2619 16.8075L16.2317 15.9443C16.1829 15.9034 16.1119 15.9032 16.0629 15.9438L15.018 16.8096L14.2512 17.445C14.1653 17.5163 14.0352 17.4551 14.0352 17.3435V16.3476V12.3957L13.5397 12.3345L13.4709 11.7769L13.3608 10.8848L12.8078 10.176L12.4622 9.73311L12.8078 9.29018L13.3608 8.58147L13.4709 7.68929L13.5397 7.13169L14.0973 7.06289L14.9895 6.9528L15.6982 6.39978L16.1411 6.05415ZM16.5841 13.0664L17.0473 12.705L17.0473 14.7795C17.0473 14.8915 16.9165 14.9525 16.8307 14.8806L16.535 14.6328L16.236 14.3823C16.1872 14.3414 16.1162 14.3412 16.0672 14.3818L15.7668 14.6307L15.4512 14.8922C15.3653 14.9634 15.2352 14.9023 15.2352 14.7906V12.7052L15.6982 13.0664L16.1411 13.4121L16.5841 13.0664ZM16.1411 7.88068L15.7183 8.21062L15.5612 8.33317L15.3635 8.35756L14.8313 8.42324L14.7656 8.95553L14.7412 9.15324L14.6186 9.31029L14.2887 9.73311L14.6186 10.1559L14.7412 10.313L14.7656 10.5107L14.8313 11.043L15.3635 11.1087L15.5612 11.1331L15.7183 11.2556L16.1411 11.5855L16.5639 11.2556L16.721 11.1331L16.9187 11.1087L17.451 11.043L17.5167 10.5107L17.5411 10.313L17.6636 10.1559L17.9936 9.73311L17.6636 9.31029L17.5411 9.15324L17.5167 8.95553L17.451 8.42324L16.9187 8.35756L16.721 8.33317L16.5639 8.21062L16.1411 7.88068Z" fill="#485058"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="33" height="33" fill="none" viewBox="0 0 33 33"><path fill="#66CBCA" d="M21.6029 11.3839C22.1201 11.3839 22.624 11.4236 23.1147 11.4899C22.2129 7.55135 18.0754 4.56757 13.1024 4.56757C7.46637 4.56757 2.89124 8.38681 2.89124 13.0946C2.89124 15.8131 4.41628 18.2134 6.79005 19.7782L5.45066 22.4703L9.11077 20.8922C9.89318 21.1707 10.7154 21.3961 11.5906 21.5155C11.4713 20.9983 11.405 20.4678 11.405 19.9108C11.3917 15.2164 15.9668 11.3839 21.6029 11.3839ZM16.4973 8.40007C16.6645 8.40007 16.83 8.433 16.9845 8.49698C17.1389 8.56096 17.2793 8.65473 17.3975 8.77295C17.5157 8.89116 17.6095 9.03151 17.6735 9.18596C17.7374 9.34042 17.7704 9.50597 17.7704 9.67315C17.7704 9.84033 17.7374 10.0059 17.6735 10.1603C17.6095 10.3148 17.5157 10.4551 17.3975 10.5734C17.2793 10.6916 17.1389 10.7853 16.9845 10.8493C16.83 10.9133 16.6645 10.9462 16.4973 10.9462C16.1596 10.9462 15.8358 10.8121 15.5971 10.5734C15.3583 10.3346 15.2242 10.0108 15.2242 9.67315C15.2242 9.33551 15.3583 9.0117 15.5971 8.77295C15.8358 8.5342 16.1596 8.40007 16.4973 8.40007ZM9.69426 10.9595C9.35662 10.9595 9.03281 10.8254 8.79406 10.5866C8.55531 10.3479 8.42118 10.0241 8.42118 9.68641C8.42118 9.34877 8.55531 9.02496 8.79406 8.78621C9.03281 8.54746 9.35662 8.41333 9.69426 8.41333C10.0319 8.41333 10.3557 8.54746 10.5945 8.78621C10.8332 9.02496 10.9673 9.34877 10.9673 9.68641C10.9673 10.0241 10.8332 10.3479 10.5945 10.5866C10.3557 10.8254 10.0319 10.9595 9.69426 10.9595Z"/><path fill="#66CBCA" d="M30.1034 19.9108C30.1034 16.1446 26.2974 13.0946 21.6029 13.0946C16.9084 13.0946 13.1024 16.1446 13.1024 19.9108C13.1024 23.677 16.9084 26.7271 21.6029 26.7271C22.372 26.7271 23.1147 26.621 23.8175 26.4619L28.4059 28.4378L26.8146 25.2551C28.8038 24.0086 30.1034 22.099 30.1034 19.9108ZM19.0435 19.4865C18.7917 19.4865 18.5455 19.4118 18.3362 19.2719C18.1268 19.132 17.9636 18.9332 17.8673 18.7006C17.7709 18.468 17.7457 18.212 17.7948 17.965C17.844 17.7181 17.9652 17.4912 18.1433 17.3132C18.3213 17.1352 18.5481 17.0139 18.7951 16.9648C19.0421 16.9157 19.298 16.9409 19.5306 17.0372C19.7633 17.1336 19.9621 17.2968 20.102 17.5061C20.2419 17.7155 20.3165 17.9616 20.3165 18.2134C20.3298 18.9163 19.7463 19.4865 19.0435 19.4865ZM24.149 19.4865C23.8114 19.4865 23.4876 19.3524 23.2488 19.1136C23.0101 18.8749 22.876 18.551 22.876 18.2134C22.876 17.8758 23.0101 17.5519 23.2488 17.3132C23.4876 17.0745 23.8114 16.9403 24.149 16.9403C24.4867 16.9403 24.8105 17.0745 25.0493 17.3132C25.288 17.5519 25.4221 17.8758 25.4221 18.2134C25.4221 18.551 25.288 18.8749 25.0493 19.1136C24.8105 19.3524 24.4867 19.4865 24.149 19.4865Z"/></svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.7817 5.27939C12.7817 5.27939 12.5563 4.19299 11.3725 4.00055C11.3725 4.00055 10.8033 1.57375 8.65861 2.50441C8.65861 2.50441 7.09246 0.281563 5.42773 2.50441C5.42773 2.50441 3.44218 1.45657 2.61833 4.00429C2.61833 4.00429 1.17837 4.04106 1.16666 6.00901C1.15496 7.97698 3.9909 10.1056 4.39552 10.2913C4.39552 10.2913 3.50522 11.2573 4.59583 11.928C6.10568 12.7192 8.95848 12.4312 9.65348 11.8108C10.0844 11.4262 10.0507 10.8354 9.65348 10.3499C9.65181 10.3516 13.3067 8.20121 12.7817 5.27939ZM10.3742 3.66134C9.82448 2.76795 8.74839 3.35834 8.74839 3.35834C9.15012 6.60704 7.87515 9.78259 7.87515 9.78259C9.08369 9.40436 11.0373 4.73893 10.3742 3.66134ZM6.89832 2.38927C6.89832 2.38927 6.02133 2.44458 5.87338 3.64706C5.72543 4.84955 6.07658 9.51854 7.01419 9.85574C7.01419 9.85574 7.6755 10.0288 8.13004 5.93963C8.73076 1.96735 6.89832 2.38927 6.89832 2.38927ZM5.26448 3.35834C5.26448 3.35834 4.32931 2.70485 3.65948 3.66134C2.99817 4.73893 4.95182 9.40436 6.15858 9.78259C6.15858 9.78259 4.90472 6.94622 5.26448 3.35834ZM2.79238 4.8151C2.79238 4.8151 1.87898 4.8151 1.97679 6.08772C2.09979 7.68806 4.14358 9.38622 4.90472 9.6681C4.90292 9.6681 3.19206 7.2753 2.79238 4.8151ZM4.86984 11.3794C5.12477 11.8271 8.05802 11.9622 9.20061 11.4573C9.20061 11.4573 9.55941 11.0929 9.01061 10.3664C8.99101 10.3405 8.79227 10.3597 8.69575 10.3542C8.59923 10.3487 8.5044 10.3252 8.39208 10.3022C8.39208 10.3022 8.03889 10.5554 7.4812 10.3664C7.4812 10.3664 7.0871 10.5867 6.62738 10.3664C6.62738 10.3664 6.0585 10.5606 5.71652 10.3022C5.71652 10.3022 5.47633 10.3677 5.12392 10.3022C5.12392 10.3022 4.62231 10.9447 4.86984 11.3794ZM11.235 4.81773C10.7971 7.54644 9.15012 9.6704 9.15012 9.6704C10.146 9.45942 11.8414 7.36034 12.0605 6.08772C12.2796 4.8151 11.235 4.81773 11.235 4.81773Z" fill="#5A646E"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.30493 1.86719C3.62465 1.86719 2.97223 2.13743 2.4912 2.61846C2.01017 3.09949 1.73993 3.75191 1.73993 4.43219L1.73993 19.8222C1.73993 20.5025 2.01017 21.1549 2.4912 21.6359C2.97223 22.1169 3.62465 22.3872 4.30493 22.3872H19.6949C20.3752 22.3872 21.0276 22.1169 21.5087 21.6359C21.9897 21.1549 22.2599 20.5025 22.2599 19.8222V4.43219C22.2599 3.75191 21.9897 3.09949 21.5087 2.61846C21.0276 2.13743 20.3752 1.86719 19.6949 1.86719H4.30493ZM12.2898 8.77345C11.5408 8.77345 11.0881 8.98378 11.0881 9.53397C11.0881 10.1342 11.8666 10.3984 12.8323 10.7267C14.4072 11.2589 16.4797 11.9617 16.4887 14.5652C16.4887 17.0879 14.4675 18.5397 11.5254 18.5397C10.198 18.5355 8.88528 18.2624 7.66636 17.7368V14.3818C8.85396 15.0308 10.3532 15.5104 11.5267 15.5104C12.318 15.5104 12.8836 15.2988 12.8836 14.6499C12.8836 13.9855 12.0397 13.6816 11.0201 13.3148C9.46827 12.7556 7.51118 12.0502 7.51118 9.70326C7.51118 7.2088 9.41954 5.71469 12.2898 5.71469C13.4893 5.70364 14.6797 5.92352 15.7961 6.36235V9.67505C14.7214 9.09792 13.3645 8.77345 12.2898 8.77345Z" fill="#9893FF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,10 @@
|
||||
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_5_5440)">
|
||||
<path d="M6.29452 3.16663C3.34785 3.16663 0.961182 5.55329 0.961182 8.49996C0.961182 11.4466 3.34785 13.8333 6.29452 13.8333C9.24118 13.8333 11.6278 11.4466 11.6278 8.49996C11.6278 5.55329 9.24118 3.16663 6.29452 3.16663ZM6.29452 12.5C4.08785 12.5 2.29452 10.7066 2.29452 8.49996C2.29452 6.29329 4.08785 4.49996 6.29452 4.49996C8.50118 4.49996 10.2945 6.29329 10.2945 8.49996C10.2945 10.7066 8.50118 12.5 6.29452 12.5ZM8.29452 7.49996H6.96118V10.8333H5.62785V7.49996H4.29452V6.49996H8.29452V7.49996ZM13.7945 2.99996L15.6278 3.83329L13.7945 4.66663L12.9612 6.49996L12.1278 4.66663L10.2945 3.83329L12.1278 2.99996L12.9612 1.16663L13.7945 2.99996ZM13.7945 12.3333L15.6278 13.1666L13.7945 14L12.9612 15.8333L12.1278 14L10.2945 13.1666L12.1278 12.3333L12.9612 10.5L13.7945 12.3333Z" fill="#485058"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_5_5440">
|
||||
<rect width="16" height="16" fill="white" transform="translate(0.294556 0.5)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.3502 8.2672C16.7402 8.2672 17.1202 8.2972 17.4902 8.3472C16.8102 5.3772 13.6902 3.1272 9.94023 3.1272C5.69023 3.1272 2.24023 6.0072 2.24023 9.5572C2.24023 11.6072 3.39023 13.4172 5.18023 14.5972L4.17023 16.6272L6.93023 15.4372C7.52023 15.6472 8.14023 15.8172 8.80023 15.9072C8.71023 15.5172 8.66023 15.1172 8.66023 14.6972C8.65023 11.1572 12.1002 8.2672 16.3502 8.2672ZM12.5002 6.0172C12.6263 6.0172 12.7511 6.04203 12.8676 6.09027C12.9841 6.13852 13.0899 6.20923 13.1791 6.29837C13.2682 6.38752 13.3389 6.49335 13.3872 6.60982C13.4354 6.72629 13.4602 6.85113 13.4602 6.9772C13.4602 7.10327 13.4354 7.2281 13.3872 7.34457C13.3389 7.46105 13.2682 7.56688 13.1791 7.65602C13.0899 7.74516 12.9841 7.81588 12.8676 7.86412C12.7511 7.91237 12.6263 7.9372 12.5002 7.9372C12.2456 7.9372 12.0014 7.83605 11.8214 7.65602C11.6414 7.47598 11.5402 7.2318 11.5402 6.9772C11.5402 6.72259 11.6414 6.47841 11.8214 6.29837C12.0014 6.11834 12.2456 6.0172 12.5002 6.0172ZM7.37023 7.9472C7.11563 7.9472 6.87145 7.84606 6.69141 7.66602C6.51138 7.48598 6.41023 7.24181 6.41023 6.9872C6.41023 6.73259 6.51138 6.48841 6.69141 6.30837C6.87145 6.12834 7.11563 6.0272 7.37023 6.0272C7.62484 6.0272 7.86902 6.12834 8.04906 6.30837C8.22909 6.48841 8.33023 6.73259 8.33023 6.9872C8.33023 7.24181 8.22909 7.48598 8.04906 7.66602C7.86902 7.84606 7.62484 7.9472 7.37023 7.9472Z" fill="#33BABB"/>
|
||||
<path d="M22.7603 14.6973C22.7603 11.8573 19.8903 9.55725 16.3503 9.55725C12.8103 9.55725 9.94034 11.8573 9.94034 14.6973C9.94034 17.5373 12.8103 19.8372 16.3503 19.8372C16.9303 19.8372 17.4903 19.7573 18.0203 19.6373L21.4803 21.1273L20.2803 18.7273C21.7803 17.7873 22.7603 16.3473 22.7603 14.6973ZM14.4203 14.3773C14.2305 14.3773 14.0449 14.3209 13.887 14.2155C13.7291 14.11 13.6061 13.96 13.5334 13.7846C13.4608 13.6092 13.4417 13.4162 13.4788 13.23C13.5158 13.0437 13.6073 12.8727 13.7415 12.7384C13.8758 12.6042 14.0468 12.5127 14.2331 12.4757C14.4193 12.4387 14.6123 12.4577 14.7877 12.5303C14.9631 12.603 15.1131 12.726 15.2185 12.8839C15.324 13.0418 15.3803 13.2274 15.3803 13.4173C15.3903 13.9473 14.9503 14.3773 14.4203 14.3773ZM18.2703 14.3773C18.0157 14.3773 17.7716 14.2761 17.5915 14.0961C17.4115 13.916 17.3103 13.6719 17.3103 13.4173C17.3103 13.1626 17.4115 12.9185 17.5915 12.7384C17.7716 12.5584 18.0157 12.4573 18.2703 12.4573C18.5249 12.4573 18.7691 12.5584 18.9492 12.7384C19.1292 12.9185 19.2303 13.1626 19.2303 13.4173C19.2303 13.6719 19.1292 13.916 18.9492 14.0961C18.7691 14.2761 18.5249 14.3773 18.2703 14.3773Z" fill="#33BABB"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -79,5 +79,14 @@
|
||||
"Dissovle Tips": "Dissolving the team will clear all resources. Are you sure you want to disband",
|
||||
"Enter Confirm.": "Please enter {{value}} to confirm",
|
||||
"Accept Invitation": "Accept Invitation",
|
||||
"Recive Tips": "{{managerName}} invite you join in {{teamName}} as {{role}}"
|
||||
"Recive Tips": "{{managerName}} invite you join in {{teamName}} as {{role}}",
|
||||
"pay with stripe": "Pay With Stripe",
|
||||
"pay with wechat": "Pay With Wechat",
|
||||
"License Buy": "License Buy",
|
||||
"Purchase History": "Purchase History",
|
||||
"Purchase License": "Purchase License",
|
||||
"Remaining Time": "Remaining Time: ",
|
||||
"Please read and agree to the agreement": "Please read and agree to the agreement",
|
||||
"Purchase Link Error": "Purchase Link Error",
|
||||
"You have not purchased the License": "You have not purchased the License"
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
{
|
||||
"More Apps": "更多应用",
|
||||
"Message Center": "消息中心",
|
||||
"Have Read": "已读",
|
||||
"Unread": "未读",
|
||||
"Read All": "全部已读",
|
||||
"Username": "用户名",
|
||||
"Log In": "登录",
|
||||
"Loading": "加载中",
|
||||
"Log Out": "退出账号",
|
||||
"From": "来自",
|
||||
"Balance": "余额",
|
||||
"verify code tips": "6位验证码",
|
||||
"phone number tips": "手机号码",
|
||||
"Password Login":"密码登录",
|
||||
"Password":"密码",
|
||||
"Verify password": "确认密码",
|
||||
"Verification Code Login":"手机号登录",
|
||||
"password tips": "密码为8位以上字符",
|
||||
"username tips":"用户名为3-16位的英文或数字的字符",
|
||||
"Invalid username or password": "用户名或密码错误",
|
||||
"Invalid phone number":"无效的手机号码",
|
||||
"Invalid verification code": "无效的验证码",
|
||||
"Get code failed": "获取验证码失败",
|
||||
"Read and agree": "请阅读并同意下方协议",
|
||||
"agree policy": "我已阅读并同意",
|
||||
"and": "和",
|
||||
"Service Agreement": "服务协议",
|
||||
"Privacy Policy": "隐私政策",
|
||||
"Get Code": "获取验证码",
|
||||
"Bonus": "赠",
|
||||
"Payment Result": "支付结果",
|
||||
"Payment Successful": "支付成功",
|
||||
"In Payment": "支付中 ...",
|
||||
"Recharge Amount": "充值金额",
|
||||
"Select Amount": "选择金额",
|
||||
"View Discount Rules": "查看优惠规则",
|
||||
"Payment Status": "支付状态",
|
||||
"Scan with WeChat": "微信扫码支付",
|
||||
"Charge": "充值",
|
||||
"Order Number": "订单号",
|
||||
"Confirm": "确认"
|
||||
}
|
||||
@@ -73,5 +73,14 @@
|
||||
"Dissovle Tips": "解散团队会清空所有资源,确定要解散吗?",
|
||||
"Enter Confirm.": "请输入 {{value}} 确认",
|
||||
"Accept Invitation": "接受邀请",
|
||||
"Recive Tips": "{{managerName}} 邀请你到 {{teamName}} 当 {{role}}"
|
||||
"Recive Tips": "{{managerName}} 邀请你到 {{teamName}} 当 {{role}}",
|
||||
"pay with wechat": "微信支付",
|
||||
"pay with stripe": "Stripe 支付",
|
||||
"License Buy": "License 购买",
|
||||
"Purchase History": "购买记录",
|
||||
"Purchase License": "购买 License",
|
||||
"Remaining Time": "剩余激活时间: ",
|
||||
"Please read and agree to the agreement": "请阅读并同意协议",
|
||||
"Purchase Link Error": "购买链接错误",
|
||||
"You have not purchased the License": "您还没有购买 License"
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { setCookie } from '@/utils/cookieUtils';
|
||||
import { Flex, FlexProps } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { EVENT_NAME } from 'sealos-desktop-sdk';
|
||||
import { masterApp } from 'sealos-desktop-sdk/master';
|
||||
|
||||
export default function LangSelectSimple(props: FlexProps) {
|
||||
const { t, i18n } = useTranslation();
|
||||
|
||||
return (
|
||||
<Flex
|
||||
userSelect={'none'}
|
||||
ml="auto"
|
||||
w="32px"
|
||||
h="32px"
|
||||
borderRadius={'50%'}
|
||||
justifyContent={'center'}
|
||||
alignItems={'center'}
|
||||
backgroundColor={'#FFF'}
|
||||
color={'#152539'}
|
||||
cursor={'pointer'}
|
||||
fontWeight={500}
|
||||
{...props}
|
||||
onClick={() => {
|
||||
masterApp?.sendMessageToAll({
|
||||
apiName: 'event-bus',
|
||||
eventName: EVENT_NAME.CHANGE_I18N,
|
||||
data: {
|
||||
currentLanguage: i18n?.language === 'en' ? 'zh' : 'en'
|
||||
}
|
||||
});
|
||||
setCookie('NEXT_LOCALE', i18n?.language === 'en' ? 'zh' : 'en', {
|
||||
expires: 30,
|
||||
sameSite: 'None',
|
||||
secure: true
|
||||
});
|
||||
i18n?.changeLanguage(i18n?.language === 'en' ? 'zh' : 'en');
|
||||
}}
|
||||
>
|
||||
{i18n?.language === 'en' ? 'En' : '中'}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import LangSelect from '@/components/LangSelect';
|
||||
import LangSelectSimple from '@/components/LangSelect/simple';
|
||||
import { Box, Flex, UseDisclosureReturn } from '@chakra-ui/react';
|
||||
import { I18n } from 'next-i18next';
|
||||
|
||||
@@ -14,19 +15,12 @@ const Language = ({ disclosure, i18n }: LanguageType) => {
|
||||
cursor={'pointer'}
|
||||
gap={'16px'}
|
||||
>
|
||||
<Flex
|
||||
<LangSelectSimple
|
||||
w="36px"
|
||||
h="36px"
|
||||
borderRadius={'50%'}
|
||||
background={'rgba(244, 246, 248, 0.7)'}
|
||||
justifyContent={'center'}
|
||||
alignItems={'center'}
|
||||
position={'relative'}
|
||||
boxShadow={'0px 1.2px 2.3px rgba(0, 0, 0, 0.2)'}
|
||||
>
|
||||
<Box onClick={() => disclosure.onOpen()}>{i18n?.language === 'en' ? 'en' : '中'}</Box>
|
||||
<LangSelect disclosure={disclosure} i18n={i18n} />
|
||||
</Flex>
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Checkbox, Flex, Link, Text } from '@chakra-ui/react';
|
||||
import { Checkbox, Flex, Link, Text, TextProps } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useState } from 'react';
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import Account from '@/components/account';
|
||||
import Notification from '@/components/notification';
|
||||
import useSessionStore from '@/stores/session';
|
||||
import { Box, Flex, Image, useDisclosure } from '@chakra-ui/react';
|
||||
import { i18n } from 'next-i18next';
|
||||
import { Box, Flex, FlexProps, Image, useDisclosure } from '@chakra-ui/react';
|
||||
import { useState } from 'react';
|
||||
import LangSelect from '../LangSelect';
|
||||
import LangSelectSimple from '../LangSelect/simple';
|
||||
import Iconfont from '../iconfont';
|
||||
|
||||
enum UserMenuKeys {
|
||||
@@ -13,25 +12,31 @@ enum UserMenuKeys {
|
||||
Account
|
||||
}
|
||||
|
||||
export default function Index() {
|
||||
export default function Index(props: { userMenuStyleProps?: FlexProps }) {
|
||||
const [notificationAmount, setNotificationAmount] = useState(0);
|
||||
const accountDisclosure = useDisclosure();
|
||||
const showDisclosure = useDisclosure();
|
||||
const switchLangDisclosure = useDisclosure();
|
||||
const userInfo = useSessionStore((state) => state.getSession());
|
||||
if (!userInfo) return null;
|
||||
|
||||
const {
|
||||
userMenuStyleProps = {
|
||||
alignItems: 'center',
|
||||
position: 'absolute',
|
||||
top: '42px',
|
||||
right: '42px',
|
||||
cursor: 'pointer',
|
||||
gap: '16px'
|
||||
}
|
||||
} = props;
|
||||
|
||||
const buttonList: {
|
||||
click?: () => void;
|
||||
button: JSX.Element;
|
||||
content: JSX.Element;
|
||||
key: UserMenuKeys;
|
||||
}[] = [
|
||||
{
|
||||
key: UserMenuKeys.LangSelect,
|
||||
button: <Box>{i18n?.language === 'en' ? 'en' : '中'}</Box>,
|
||||
click: () => switchLangDisclosure.onOpen(),
|
||||
content: <LangSelect disclosure={switchLangDisclosure} i18n={i18n} key={'langselect'} />
|
||||
},
|
||||
{
|
||||
key: UserMenuKeys.Notification,
|
||||
button: (
|
||||
@@ -63,7 +68,13 @@ export default function Index() {
|
||||
}
|
||||
];
|
||||
return (
|
||||
<Flex alignItems={'center'} position={'absolute'} top={'42px'} right={'42px'} gap={'16px'}>
|
||||
<Flex {...userMenuStyleProps}>
|
||||
<LangSelectSimple
|
||||
w="36px"
|
||||
h="36px"
|
||||
background={'rgba(244, 246, 248, 0.7)'}
|
||||
boxShadow={'0px 1.2px 2.3px rgba(0, 0, 0, 0.2)'}
|
||||
/>
|
||||
{buttonList.map((item, index) => (
|
||||
<Flex
|
||||
w="36px"
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import request from '@/services/request';
|
||||
import { ApiResp } from '@/types';
|
||||
import { Flex, Text } from '@chakra-ui/react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import CurrencySymbol from '../pages/license/components/CurrencySymbol';
|
||||
|
||||
export default function useBonusBox() {
|
||||
const [selectAmountIndex, setSelectAmountIndex] = useState(0);
|
||||
|
||||
const { data: bonuses, isSuccess } = useQuery(
|
||||
['bonus'],
|
||||
() =>
|
||||
request.get<
|
||||
any,
|
||||
ApiResp<{
|
||||
steps: string;
|
||||
ratios: string;
|
||||
}>
|
||||
>('/api/price/bonus'),
|
||||
{}
|
||||
);
|
||||
|
||||
const { ratios, steps } = useMemo(() => {
|
||||
return {
|
||||
ratios: (bonuses?.data?.ratios || '').split(',').map((v) => +v),
|
||||
steps: (bonuses?.data?.steps || '').split(',').map((v) => +v)
|
||||
};
|
||||
}, [bonuses?.data]);
|
||||
|
||||
const BonusBox = useCallback(
|
||||
() => (
|
||||
<Flex wrap={'wrap'} gap={'16px'} minW="460px">
|
||||
{steps.map((amount, index) => (
|
||||
<Flex
|
||||
key={amount}
|
||||
width="140px"
|
||||
height="92px"
|
||||
justify={'center'}
|
||||
align={'center'}
|
||||
{...(selectAmountIndex === index
|
||||
? {
|
||||
color: '#36ADEF',
|
||||
border: '1.5px solid #36ADEF'
|
||||
}
|
||||
: {
|
||||
border: '1px solid #EFF0F1'
|
||||
})}
|
||||
bg={'#f4f6f8'}
|
||||
borderRadius="4px"
|
||||
position={'relative'}
|
||||
flexGrow="0"
|
||||
cursor={'pointer'}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setSelectAmountIndex(index);
|
||||
}}
|
||||
>
|
||||
<Flex align={'center'}>
|
||||
<CurrencySymbol w="24px" type={'shellCoin'} />
|
||||
<Text ml="4px" fontStyle="normal" fontWeight="500" fontSize="24px">
|
||||
{amount}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
),
|
||||
[selectAmountIndex, steps]
|
||||
);
|
||||
|
||||
return {
|
||||
BonusBox,
|
||||
selectAmount: steps[selectAmountIndex]
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { authSession } from '@/services/backend/auth';
|
||||
import { createLicenseRecord } from '@/services/backend/db/license';
|
||||
import { jsonRes } from '@/services/backend/response';
|
||||
import { LicensePayload } from '@/types';
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
|
||||
export default async function handler(req: NextApiRequest, resp: NextApiResponse) {
|
||||
try {
|
||||
const payload = await authSession(req.headers);
|
||||
if (!payload) return jsonRes(resp, { code: 401, message: 'token verify error' });
|
||||
|
||||
const { token, orderID, amount, quota, paymentMethod } = req.body as LicensePayload;
|
||||
|
||||
const record = {
|
||||
uid: payload.user.nsid,
|
||||
amount: amount,
|
||||
token: token,
|
||||
orderID: orderID,
|
||||
quota: quota,
|
||||
paymentMethod: paymentMethod
|
||||
};
|
||||
|
||||
const result = await createLicenseRecord(record);
|
||||
|
||||
return jsonRes(resp, {
|
||||
data: result
|
||||
});
|
||||
} catch (error) {
|
||||
jsonRes(resp, { code: 500, data: error });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { authSession } from '@/services/backend/auth';
|
||||
import { createLicenseRecord, getLicenseRecordsByUid } from '@/services/backend/db/license';
|
||||
import { jsonRes } from '@/services/backend/response';
|
||||
import { LicensePayload } from '@/types';
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
|
||||
export default async function handler(req: NextApiRequest, resp: NextApiResponse) {
|
||||
try {
|
||||
const payload = await authSession(req.headers);
|
||||
if (!payload) return jsonRes(resp, { code: 401, message: 'token verify error' });
|
||||
|
||||
const { page = 1, pageSize = 10 } = req.body as {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
};
|
||||
|
||||
const result = await getLicenseRecordsByUid({
|
||||
uid: payload.user.nsid,
|
||||
page: page,
|
||||
pageSize: pageSize
|
||||
});
|
||||
|
||||
return jsonRes(resp, {
|
||||
data: result
|
||||
});
|
||||
} catch (error) {
|
||||
jsonRes(resp, { code: 500, data: error });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import { authSession } from '@/services/backend/auth';
|
||||
import { ApplyYaml } from '@/services/backend/kubernetes/user';
|
||||
import { jsonRes } from '@/services/backend/response';
|
||||
import { LicensePaymentForm } from '@/types';
|
||||
import yaml from 'js-yaml';
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
|
||||
export const generateLicenseCrd = (form: LicensePaymentForm) => {
|
||||
const paymentCrd = {
|
||||
apiVersion: 'infostream.sealos.io/v1',
|
||||
kind: 'Payment',
|
||||
metadata: {
|
||||
name: form.paymentName,
|
||||
namespace: form.namespace
|
||||
},
|
||||
spec: {
|
||||
userID: form.userId,
|
||||
amount: form.amount, // weixin
|
||||
paymentMethod: form.paymentMethod,
|
||||
service: {
|
||||
amt: form.quota, // Actual value
|
||||
hid: form.hashID
|
||||
}
|
||||
}
|
||||
};
|
||||
try {
|
||||
const result = yaml.dump(paymentCrd);
|
||||
return result;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export default async function handler(req: NextApiRequest, resp: NextApiResponse) {
|
||||
try {
|
||||
const payload = await authSession(req.headers);
|
||||
if (!payload) return jsonRes(resp, { code: 401, message: 'token verify error' });
|
||||
|
||||
const { amount, paymentMethod, hid, quota } = req.body as {
|
||||
amount: number;
|
||||
paymentMethod: 'wechat' | 'stripe';
|
||||
hid: string;
|
||||
quota: number;
|
||||
};
|
||||
console.log(amount, quota, paymentMethod, hid);
|
||||
if (!hid) {
|
||||
return jsonRes(resp, {
|
||||
code: 400,
|
||||
message: 'Missing hid parameter'
|
||||
});
|
||||
}
|
||||
if (amount <= 0) {
|
||||
return jsonRes(resp, {
|
||||
code: 400,
|
||||
message: 'Amount cannot be less than 0'
|
||||
});
|
||||
}
|
||||
|
||||
const paymentName = crypto.randomUUID();
|
||||
const form: LicensePaymentForm = {
|
||||
namespace: payload.user.nsid,
|
||||
paymentName: paymentName,
|
||||
userId: payload.user.k8s_username,
|
||||
amount: amount,
|
||||
quota: quota,
|
||||
paymentMethod: paymentMethod,
|
||||
hashID: hid
|
||||
};
|
||||
|
||||
const LicenseCrd = generateLicenseCrd(form);
|
||||
|
||||
const res = await ApplyYaml(payload.kc, LicenseCrd);
|
||||
return jsonRes(resp, {
|
||||
data: {
|
||||
paymentName: paymentName,
|
||||
extra: res[0]
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
jsonRes(resp, { code: 500, data: error });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { authSession } from '@/services/backend/auth';
|
||||
import { GetCRD } from '@/services/backend/kubernetes/user';
|
||||
import { jsonRes } from '@/services/backend/response';
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
|
||||
export default async function handler(req: NextApiRequest, resp: NextApiResponse) {
|
||||
try {
|
||||
const payload = await authSession(req.headers);
|
||||
if (!payload) return jsonRes(resp, { code: 401, message: 'token verify error' });
|
||||
|
||||
const { paymentName } = req.query as {
|
||||
paymentName: string;
|
||||
};
|
||||
|
||||
if (typeof paymentName !== 'string' || paymentName === '') {
|
||||
return jsonRes(resp, { code: 400, message: 'payment name cannot be empty' });
|
||||
}
|
||||
|
||||
const paymentM = {
|
||||
group: 'infostream.sealos.io',
|
||||
version: 'v1',
|
||||
namespace: payload.user.nsid,
|
||||
plural: 'payments'
|
||||
};
|
||||
const paymentDesc = await GetCRD(payload.kc, paymentM, paymentName);
|
||||
console.log(paymentDesc?.body?.status, 'payment');
|
||||
return jsonRes(resp, { data: paymentDesc?.body?.status });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
jsonRes(resp, { code: 500, data: error });
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,10 @@ import {
|
||||
enableWechat,
|
||||
enablePassword,
|
||||
enableSms,
|
||||
enableGoogle
|
||||
enableGoogle,
|
||||
enableStripe,
|
||||
enableWechatRecharge,
|
||||
enableLicense
|
||||
} from '@/services/enable';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
@@ -20,6 +23,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
const needSms = enableSms();
|
||||
const needGoogle = enableGoogle();
|
||||
const callback_url = process.env.CALLBACK_URL;
|
||||
const stripeEnabled = enableStripe();
|
||||
const wechatEnabledRecharge = enableWechatRecharge();
|
||||
const licenseEnabled = enableLicense();
|
||||
|
||||
jsonRes(res, {
|
||||
data: {
|
||||
@@ -34,7 +40,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
needSms,
|
||||
needGithub,
|
||||
needWechat,
|
||||
needGoogle
|
||||
needGoogle,
|
||||
stripeEnabled,
|
||||
wechatEnabledRecharge,
|
||||
licenseEnabled
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { authSession } from '@/services/backend/auth';
|
||||
import { GetConfigMap } from '@/services/backend/kubernetes/user';
|
||||
import { jsonRes } from '@/services/backend/response';
|
||||
import { enableRecharge } from '@/services/enable';
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
|
||||
export default async function handler(req: NextApiRequest, resp: NextApiResponse) {
|
||||
try {
|
||||
const payload = await authSession(req.headers);
|
||||
if (!payload) {
|
||||
return jsonRes(resp, { code: 401, message: 'token verify error' });
|
||||
}
|
||||
const result = await GetConfigMap(payload.kc, 'sealos', 'recharge-gift');
|
||||
if (!result.body.data) {
|
||||
return jsonRes(resp, { code: 404, message: 'not found' });
|
||||
}
|
||||
return jsonRes(resp, {
|
||||
code: 200,
|
||||
data: {
|
||||
ratios: result.body.data.ratios,
|
||||
steps: result.body.data.steps
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
jsonRes(resp, { code: 500, message: 'get price error' });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { authSession } from '@/services/backend/auth';
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import * as yaml from 'js-yaml';
|
||||
import { jsonRes } from '@/services/backend/response';
|
||||
import { CRDMeta, ValuationBillingRecord, ValuationData } from '@/types';
|
||||
import { ApplyYaml, GetCRD } from '@/services/backend/kubernetes/user';
|
||||
import { IncomingMessage } from 'http';
|
||||
|
||||
export default async function handler(req: NextApiRequest, resp: NextApiResponse) {
|
||||
try {
|
||||
const payload = await authSession(req.headers);
|
||||
if (!payload) {
|
||||
return jsonRes(resp, { code: 401, message: 'token verify error' });
|
||||
}
|
||||
const namespace = payload.user.nsid;
|
||||
const name = 'prices';
|
||||
const crdSchema = {
|
||||
apiVersion: `account.sealos.io/v1`,
|
||||
kind: 'PriceQuery',
|
||||
metadata: {
|
||||
name,
|
||||
namespace
|
||||
},
|
||||
spec: {}
|
||||
};
|
||||
const meta: CRDMeta = {
|
||||
group: 'account.sealos.io',
|
||||
version: 'v1',
|
||||
namespace,
|
||||
plural: 'pricequeries'
|
||||
};
|
||||
try {
|
||||
await ApplyYaml(payload.kc, yaml.dump(crdSchema));
|
||||
await new Promise<void>((resolve) => setTimeout(() => resolve(), 1000));
|
||||
} finally {
|
||||
const crd = (await GetCRD(payload.kc, meta, name)) as {
|
||||
response: IncomingMessage;
|
||||
body: ValuationData;
|
||||
};
|
||||
const billingRecords = crd?.body?.status?.billingRecords || [];
|
||||
return jsonRes<{ billingRecords: ValuationBillingRecord[] }>(resp, {
|
||||
code: 200,
|
||||
data: {
|
||||
billingRecords
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
jsonRes(resp, { code: 500, message: 'get price error' });
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,6 @@ export async function getSystemConfig(): Promise<SystemConfigType> {
|
||||
const res = JSON.parse(readFileSync(filename, 'utf-8'));
|
||||
return res;
|
||||
} catch (error) {
|
||||
console.log('get system config error, set default');
|
||||
return defaultConfig;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Text, Icon } from '@chakra-ui/react';
|
||||
export default function currencysymbol({
|
||||
type = 'shellCoin',
|
||||
...props
|
||||
}: {
|
||||
type?: 'shellCoin' | 'cny' | 'usd';
|
||||
} & Pick<Parameters<typeof Icon>[0], 'w' | 'h' | 'color'>) {
|
||||
return type === 'shellCoin' ? (
|
||||
<Icon h="auto" {...props} viewBox="0 0 43 43">
|
||||
<path
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M38.8452 16.3383C38.8452 16.3383 38.169 13.0791 34.6177 12.5018C34.6177 12.5018 32.91 5.22138 26.476 8.01336C26.476 8.01336 21.7775 1.34481 16.7833 8.01336C16.7833 8.01336 10.8267 4.86984 8.35511 12.513C8.35511 12.513 4.03522 12.6233 4.00011 18.5272C3.96499 24.4311 12.4728 30.8168 13.6867 31.3741C13.6867 31.3741 11.0158 34.2719 14.2876 36.284C18.8172 38.6577 27.3756 37.7936 29.4606 35.9326C30.7532 34.7789 30.6523 33.0064 29.4606 31.5498C29.4556 31.5548 40.4202 25.1038 38.8452 16.3383ZM31.6228 11.4841C29.9736 8.80397 26.7453 10.5751 26.7453 10.5751C27.9505 20.3212 24.1256 29.8479 24.1256 29.8479C27.7512 28.7132 33.6121 14.7169 31.6228 11.4841ZM21.1951 7.66792C21.1951 7.66792 18.5641 7.83387 18.1203 11.4413C17.6764 15.0488 18.7299 29.0557 21.5427 30.0673C21.5427 30.0673 23.5266 30.5865 24.8902 18.319C26.6924 6.40218 21.1951 7.66792 21.1951 7.66792ZM16.2936 10.5751C16.2936 10.5751 13.488 8.61467 11.4786 11.4841C9.49465 14.7169 15.3556 28.7132 18.9759 29.8479C18.9759 29.8479 15.2143 21.3388 16.2936 10.5751ZM8.87726 14.9454C8.87726 14.9454 6.13705 14.9454 6.43049 18.7633C6.79949 23.5643 12.9309 28.6588 15.2143 29.5044C15.2089 29.5044 10.0763 22.326 8.87726 14.9454ZM15.1097 34.6383C15.8744 35.9813 24.6742 36.3866 28.102 34.8719C28.102 34.8719 29.1783 33.7787 27.532 31.5994C27.4731 31.5216 26.8769 31.5791 26.5874 31.5627C26.2978 31.5463 26.0133 31.4758 25.6763 31.4068C25.6763 31.4068 24.6168 32.1662 22.9437 31.5994C22.9437 31.5994 21.7614 32.2601 20.3823 31.5994C20.3823 31.5994 18.6756 32.1821 17.6497 31.4068C17.6497 31.4068 16.9291 31.6032 15.8719 31.4068C15.8719 31.4068 14.3671 33.3342 15.1097 34.6383ZM34.2051 14.9533C32.8914 23.1395 27.9505 29.5113 27.9505 29.5113C30.9382 28.8784 36.0244 22.5811 36.6816 18.7633C37.3388 14.9454 34.2051 14.9533 34.2051 14.9533Z"
|
||||
/>
|
||||
</Icon>
|
||||
) : type === 'cny' ? (
|
||||
<Text {...props}>¥</Text>
|
||||
) : (
|
||||
<Text {...props}>$</Text>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Flex, Img, Link } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
export default function Index({ text, href }: { text: string; href?: string }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Flex align={'center'}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="17"
|
||||
height="17"
|
||||
viewBox="0 0 17 17"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M8.50006 1.75153C7.18152 1.75153 5.89259 2.14252 4.79626 2.87506C3.69993 3.6076 2.84545 4.6488 2.34086 5.86697C1.83628 7.08514 1.70426 8.42559 1.96149 9.71879C2.21873 11.012 2.85366 12.1999 3.78601 13.1322C4.71836 14.0646 5.90625 14.6995 7.19946 14.9568C8.49266 15.214 9.83311 15.082 11.0513 14.5774C12.2695 14.0728 13.3106 13.2183 14.0432 12.122C14.7757 11.0257 15.1667 9.73673 15.1667 8.41819C15.1648 6.65068 14.4618 4.95611 13.212 3.70629C11.9621 2.45647 10.2676 1.75347 8.50006 1.75153ZM8.50006 13.7515C7.44523 13.7515 6.41408 13.4387 5.53702 12.8527C4.65996 12.2667 3.97637 11.4337 3.5727 10.4592C3.16903 9.48463 3.06342 8.41228 3.2692 7.37771C3.47499 6.34315 3.98294 5.39284 4.72882 4.64696C5.4747 3.90108 6.42501 3.39313 7.45958 3.18734C8.49414 2.98155 9.5665 3.08717 10.541 3.49084C11.5156 3.8945 12.3485 4.57809 12.9346 5.45515C13.5206 6.33221 13.8334 7.36336 13.8334 8.41819C13.8316 9.83214 13.2692 11.1877 12.2693 12.1875C11.2695 13.1873 9.91401 13.7498 8.50006 13.7515ZM8.50006 8.08486C8.32325 8.08486 8.15368 8.1551 8.02866 8.28012C7.90363 8.40515 7.83339 8.57471 7.83339 8.75153V10.7515C7.83339 10.9283 7.90363 11.0979 8.02866 11.2229C8.15368 11.348 8.32325 11.4182 8.50006 11.4182C8.67687 11.4182 8.84644 11.348 8.97146 11.2229C9.09649 11.0979 9.16673 10.9283 9.16673 10.7515V8.75153C9.16673 8.57471 9.09649 8.40515 8.97146 8.28012C8.84644 8.1551 8.67687 8.08486 8.50006 8.08486ZM8.50006 5.41819C8.33524 5.41819 8.17413 5.46707 8.03708 5.55863C7.90004 5.6502 7.79323 5.78035 7.73016 5.93262C7.66709 6.08489 7.65058 6.25245 7.68274 6.4141C7.71489 6.57575 7.79426 6.72424 7.9108 6.84078C8.02735 6.95732 8.17583 7.03669 8.33748 7.06885C8.49913 7.101 8.66669 7.0845 8.81896 7.02142C8.97123 6.95835 9.10138 6.85154 9.19295 6.7145C9.28452 6.57746 9.33339 6.41634 9.33339 6.25153C9.33339 6.03051 9.2456 5.81855 9.08931 5.66227C8.93303 5.50599 8.72107 5.41819 8.50006 5.41819Z"
|
||||
fill="#48A7EC"
|
||||
/>
|
||||
</svg>
|
||||
<Link
|
||||
fontStyle="normal"
|
||||
fontWeight="400"
|
||||
fontSize="12px"
|
||||
color="#1D8CDC"
|
||||
{...(href
|
||||
? {
|
||||
href: href
|
||||
}
|
||||
: {})}
|
||||
>
|
||||
{text}
|
||||
</Link>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import { Flex, Text, Image, FlexProps, Icon, Box } from '@chakra-ui/react';
|
||||
import { useState } from 'react';
|
||||
type PaginationProps = {
|
||||
totalItems: number;
|
||||
itemsPerPage: number;
|
||||
onPageChange: any;
|
||||
};
|
||||
|
||||
export default function Pagination({ totalItems, itemsPerPage, onPageChange }: PaginationProps) {
|
||||
const totalPage = Math.ceil(totalItems / itemsPerPage);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
const goToPage = (page: number) => {
|
||||
if (page >= 1 && page <= totalPage) {
|
||||
setCurrentPage(page);
|
||||
onPageChange(page);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePrevPage = () => {
|
||||
goToPage(currentPage - 1);
|
||||
};
|
||||
|
||||
const handleNextPage = () => {
|
||||
goToPage(currentPage + 1);
|
||||
};
|
||||
|
||||
const buttonStyle: FlexProps = {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
w: '24px',
|
||||
h: '24px',
|
||||
borderRadius: '50%'
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex h="32px" ml="auto" align="center" mt="20px">
|
||||
<Text>Total:</Text>
|
||||
<Flex w="40px">{totalItems}</Flex>
|
||||
<Flex gap="8px" alignItems={'center'}>
|
||||
<Flex
|
||||
onClick={handlePrevPage}
|
||||
{...buttonStyle}
|
||||
{...{
|
||||
bg: currentPage === 1 ? '#EDF0F2' : '#EDEFF1',
|
||||
cursor: currentPage === 1 ? 'not-allowed' : 'pointer',
|
||||
opacity: currentPage === 1 ? '0.5' : '1'
|
||||
}}
|
||||
>
|
||||
<Icon viewBox="0 0 12 12" fill={currentPage === 1 ? 'black' : '#262A32'}>
|
||||
<path d="M9.205 8.295L6.91 6L9.205 3.705L8.5 3L5.5 6L8.5 9L9.205 8.295ZM3 3H4V9H3V3Z" />
|
||||
</Icon>
|
||||
</Flex>
|
||||
<Flex
|
||||
{...buttonStyle}
|
||||
{...{
|
||||
bg: currentPage === 1 ? '#EDF0F2' : '#EDEFF1',
|
||||
cursor: currentPage === 1 ? 'not-allowed' : 'pointer',
|
||||
opacity: currentPage === 1 ? '0.5' : '1'
|
||||
}}
|
||||
onClick={() => goToPage(1)}
|
||||
>
|
||||
<Icon viewBox="0 0 12 12" fill={currentPage === 1 ? 'black' : '#262A32'}>
|
||||
<path
|
||||
d="M5.414 5.99999L7.889 8.47499L7.182 9.18199L4 5.99999L7.182 2.81799L7.889 3.52499L5.414 5.99999Z"
|
||||
fill="#262A32"
|
||||
/>
|
||||
</Icon>
|
||||
</Flex>
|
||||
|
||||
<Text>
|
||||
{currentPage}/{totalPage}
|
||||
</Text>
|
||||
|
||||
<Flex
|
||||
{...buttonStyle}
|
||||
{...{
|
||||
bg: currentPage === totalPage ? '#EDF0F2' : '#EDEFF1',
|
||||
cursor: currentPage === totalPage ? 'not-allowed' : 'pointer',
|
||||
opacity: currentPage === totalPage ? '0.5' : '1'
|
||||
}}
|
||||
onClick={handleNextPage}
|
||||
>
|
||||
<Icon
|
||||
viewBox="0 0 12 12"
|
||||
fill={currentPage === 1 ? 'black' : '#262A32'}
|
||||
transform="rotate(180deg)"
|
||||
>
|
||||
<path
|
||||
d="M5.414 5.99999L7.889 8.47499L7.182 9.18199L4 5.99999L7.182 2.81799L7.889 3.52499L5.414 5.99999Z"
|
||||
fill="#262A32"
|
||||
/>
|
||||
</Icon>
|
||||
</Flex>
|
||||
<Flex
|
||||
mr="10px"
|
||||
{...buttonStyle}
|
||||
{...{
|
||||
bg: currentPage === totalPage ? '#EDF0F2' : '#EDEFF1',
|
||||
cursor: currentPage === totalPage ? 'not-allowed' : 'pointer',
|
||||
opacity: currentPage === totalPage ? '0.5' : '1'
|
||||
}}
|
||||
onClick={() => goToPage(totalPage)}
|
||||
>
|
||||
<Icon
|
||||
viewBox="0 0 12 12"
|
||||
transform="rotate(180deg)"
|
||||
fill={currentPage === 1 ? 'black' : '#262A32'}
|
||||
>
|
||||
<path d="M9.205 8.295L6.91 6L9.205 3.705L8.5 3L5.5 6L8.5 9L9.205 8.295ZM3 3H4V9H3V3Z" />
|
||||
</Icon>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Text>{itemsPerPage}</Text>
|
||||
<Text>/Page</Text>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
import useBonusBox from '@/hooks/useBonusBox';
|
||||
import { useCustomToast } from '@/hooks/useCustomToast';
|
||||
import request from '@/services/request';
|
||||
import { ApiResp, LicensePayStatus, LicensePayload, Payment, SystemEnv } from '@/types';
|
||||
import { deFormatMoney } from '@/utils/format';
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Flex,
|
||||
Image,
|
||||
Link,
|
||||
Modal,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
Text,
|
||||
useDisclosure
|
||||
} from '@chakra-ui/react';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useState } from 'react';
|
||||
import WechatPayment from './WechatPayment';
|
||||
|
||||
export default function RechargeComponent() {
|
||||
const { t } = useTranslation();
|
||||
const [isAgree, setIsAgree] = useState(false);
|
||||
const [isInvalid, setIsInvalid] = useState(false);
|
||||
const { BonusBox, selectAmount } = useBonusBox();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
// 整个流程跑通需要状态管理, 0 初始态, 1 创建支付单, 2 支付中, 3 支付成功
|
||||
const [complete, setComplete] = useState<0 | 1 | 2 | 3>(0);
|
||||
// 0 是微信,1 是stripe
|
||||
const [payType, setPayType] = useState<'wechat' | 'stripe'>('wechat');
|
||||
const [paymentName, setPaymentName] = useState('');
|
||||
const queryClient = useQueryClient();
|
||||
const { toast } = useCustomToast();
|
||||
const { query } = useRouter();
|
||||
const [hid, setHid] = useState(''); // license key
|
||||
|
||||
// handle hid
|
||||
useEffect(() => {
|
||||
if (query?.hid && typeof query.hid === 'string') {
|
||||
const decodedHid = decodeURIComponent(query.hid);
|
||||
setHid(decodedHid);
|
||||
} else {
|
||||
toast({
|
||||
status: 'error',
|
||||
title: 'Purchase Link Error',
|
||||
isClosable: true,
|
||||
position: 'top'
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onModalClose = () => {
|
||||
setComplete(0);
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleWechatConfirm = () => {
|
||||
if (isAgree) {
|
||||
setComplete(1);
|
||||
createPaymentLicense.mutate();
|
||||
onOpen();
|
||||
} else {
|
||||
toast({
|
||||
status: 'error',
|
||||
title: t('Please read and agree to the agreement'),
|
||||
isClosable: true,
|
||||
position: 'top'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const { data: platformEnv } = useQuery(['getPlatformEnv'], () =>
|
||||
request<any, ApiResp<SystemEnv>>('/api/platform/getEnv')
|
||||
);
|
||||
|
||||
const createPaymentLicense = useMutation(
|
||||
() =>
|
||||
request.post<any, ApiResp<Payment>>('/api/license/pay', {
|
||||
amount: deFormatMoney(selectAmount), //weixin
|
||||
quota: selectAmount,
|
||||
paymentMethod: payType,
|
||||
hid: hid
|
||||
}),
|
||||
{
|
||||
onSuccess(data) {
|
||||
console.log(data);
|
||||
setPaymentName((data?.data?.paymentName as string).trim());
|
||||
setComplete(2);
|
||||
},
|
||||
onError(err: any) {
|
||||
toast({
|
||||
status: 'error',
|
||||
title: err?.message || '',
|
||||
isClosable: true,
|
||||
position: 'top'
|
||||
});
|
||||
setComplete(0);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const createLicenseRecord = useMutation(
|
||||
(payload: LicensePayload) =>
|
||||
request.post<any, ApiResp>('/api/license/createLicenseRecord', payload),
|
||||
{
|
||||
onSuccess(data) {
|
||||
console.log(data);
|
||||
queryClient.invalidateQueries(['getLicenseActive']);
|
||||
},
|
||||
onError(err: any) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const { data } = useQuery(
|
||||
['getLicenseResult', paymentName],
|
||||
() =>
|
||||
request<any, ApiResp<LicensePayStatus>>('/api/license/result', {
|
||||
params: {
|
||||
paymentName: paymentName
|
||||
}
|
||||
}),
|
||||
{
|
||||
refetchInterval: complete === 2 ? 1000 : false,
|
||||
enabled: complete === 2 && !!paymentName,
|
||||
cacheTime: 0,
|
||||
staleTime: 0,
|
||||
onSuccess(data) {
|
||||
if (data?.data?.status === 'Completed') {
|
||||
onModalClose();
|
||||
toast({
|
||||
status: 'success',
|
||||
title: t('Payment Successful'),
|
||||
isClosable: true,
|
||||
position: 'top'
|
||||
});
|
||||
createLicenseRecord.mutate({
|
||||
uid: '',
|
||||
amount: deFormatMoney(selectAmount),
|
||||
quota: selectAmount,
|
||||
token: data.data?.token,
|
||||
orderID: data?.data?.tradeNO,
|
||||
paymentMethod: 'wechat'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
flex={1}
|
||||
flexDirection="column"
|
||||
// justifyContent="center"
|
||||
alignItems="center"
|
||||
pt="64px"
|
||||
pb="40px"
|
||||
px={{
|
||||
base: '56px',
|
||||
xl: '130px'
|
||||
}}
|
||||
>
|
||||
<Text alignSelf={'start'} color="#262A32" fontSize={'28px'} fontWeight={600}>
|
||||
{t('Purchase License')}
|
||||
</Text>
|
||||
<Text mt={'60px'} alignSelf={'start'} color="#7B838B" fontWeight={'normal'} mb={'16px'}>
|
||||
{t('Select Amount')}
|
||||
</Text>
|
||||
<BonusBox />
|
||||
<Flex alignSelf={'flex-start'} align={'center'} mt={'46px'}>
|
||||
<Checkbox
|
||||
isInvalid={isInvalid}
|
||||
mr={'8px'}
|
||||
isChecked={isAgree}
|
||||
variant={'unstyled'}
|
||||
onChange={(e) => {
|
||||
setIsInvalid(false);
|
||||
setIsAgree(e.target.checked);
|
||||
}}
|
||||
/>
|
||||
<Text
|
||||
fontStyle="normal"
|
||||
fontWeight="400"
|
||||
fontSize="12px"
|
||||
lineHeight="140%"
|
||||
color={'#1D8CDC'}
|
||||
>
|
||||
{t('agree policy')}
|
||||
<Link
|
||||
isExternal
|
||||
href={platformEnv?.data?.service_protocol}
|
||||
_hover={{
|
||||
color: 'rgba(94, 189, 242, 1)',
|
||||
borderBottom: '1px solid rgba(94, 189, 242, 1)'
|
||||
}}
|
||||
px="4px"
|
||||
>
|
||||
{t('Service Agreement')}
|
||||
</Link>
|
||||
{t('and')}
|
||||
<Link
|
||||
isExternal
|
||||
href={platformEnv?.data?.private_protocol}
|
||||
_hover={{
|
||||
color: 'rgba(94, 189, 242, 1)',
|
||||
borderBottom: '1px solid rgba(94, 189, 242, 1)'
|
||||
}}
|
||||
px="4px"
|
||||
>
|
||||
{t('Privacy Policy')}
|
||||
</Link>
|
||||
</Text>
|
||||
{/* <OuterLink text={t('View Discount Rules')}></OuterLink> */}
|
||||
</Flex>
|
||||
<Flex gap={'16px'} width={'full'} mt="36px">
|
||||
{/* {platformEnv?.data?.stripeEnabled && (
|
||||
<Button size="primary" variant="primary" mt="20px" w="full" h="auto" py="14px" px="34px">
|
||||
<Image alt="stripe" src="icons/stripe.svg" w="24px" h="24px" />
|
||||
<Text ml="12px">{t('pay with stripe')}</Text>
|
||||
</Button>
|
||||
)} */}
|
||||
|
||||
<Button
|
||||
size="primary"
|
||||
variant="primary"
|
||||
mt="20px"
|
||||
w="full"
|
||||
h="auto"
|
||||
py="14px"
|
||||
px="34px"
|
||||
onClick={() => handleWechatConfirm()}
|
||||
>
|
||||
<Image alt="weixin" src="icons/wechat.svg" w="24px" h="24px" />
|
||||
<Text ml="12px">{t('pay with wechat')}</Text>
|
||||
</Button>
|
||||
</Flex>
|
||||
<Modal isOpen={isOpen} onClose={onModalClose}>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>充值金额</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
|
||||
<WechatPayment
|
||||
complete={complete}
|
||||
codeURL={data?.data?.codeURL}
|
||||
tradeNO={data?.data?.tradeNO}
|
||||
/>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
import request from '@/services/request';
|
||||
import { ApiResp, LicenseRecord } from '@/types';
|
||||
import download from '@/utils/downloadFIle';
|
||||
import { formatMoney, getRemainingTime } from '@/utils/format';
|
||||
import { Box, Flex, Image, Text } from '@chakra-ui/react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useState } from 'react';
|
||||
import Pagination from './Pagination';
|
||||
|
||||
export default function History() {
|
||||
const { t } = useTranslation();
|
||||
const [page, setPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
|
||||
const { data } = useQuery(['getLicenseActive', page, pageSize], () =>
|
||||
request.post<any, ApiResp<{ total: number; records: LicenseRecord[] }>>(
|
||||
'/api/license/getLicenseRecord',
|
||||
{
|
||||
page,
|
||||
pageSize
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const downloadToken = (token: string) => {
|
||||
const result = Buffer.from(token, 'binary').toString('base64');
|
||||
download('token.txt', result);
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex
|
||||
flex={1}
|
||||
flexDirection={'column'}
|
||||
alignItems={'center'}
|
||||
pt="64px"
|
||||
pb="40px"
|
||||
pl="130px"
|
||||
position={'relative'}
|
||||
>
|
||||
<Text mb="20px" color={'#262A32'} fontSize={'28px'} fontWeight={600} alignSelf={'flex-start'}>
|
||||
{t('Purchase History')}
|
||||
</Text>
|
||||
{data?.data?.records && data?.data?.records?.length > 0 ? (
|
||||
<Box w="100%" flex={1} overflowY={'auto'} pb="20px" pr="12px">
|
||||
{data?.data?.records.map((item) => (
|
||||
<Flex
|
||||
key={item?._id ?? item.iat}
|
||||
w="100%"
|
||||
minW={'350px'}
|
||||
p="12px 0 12px 16px"
|
||||
border={'1px solid #EFF0F1'}
|
||||
borderRadius={'4px'}
|
||||
background={'#F8FAFB'}
|
||||
mt="16px"
|
||||
>
|
||||
<Box w="100%">
|
||||
<Flex alignItems={'center'} mb="8px">
|
||||
<Image src={'/icons/license.svg'} w={'24px'} h={'24px'} alt="token"></Image>
|
||||
<Text color={'#485058'} fontSize={'16px'} fontWeight={500} mx="10px">
|
||||
License
|
||||
</Text>
|
||||
<Image
|
||||
src={'/icons/shell_coin.svg'}
|
||||
w={'14px'}
|
||||
h={'14px'}
|
||||
alt="token"
|
||||
mr="6px"
|
||||
></Image>
|
||||
<Text color={'#5A646E'} fontSize={'12px'} fontWeight={500}>
|
||||
{formatMoney(item.amount)}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Text color={'#5A646E'} fontSize={'12px'} fontWeight={500}>
|
||||
{t('Remaining Time')} {getRemainingTime(item.exp)}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box h="100%" w="1px" border={'1px solid #EFF0F1'}></Box>
|
||||
<Flex justifyContent={'center'} alignItems={'center'} ml="auto" w="160px">
|
||||
<Image src={'/icons/token.svg'} w={'16px'} h={'16px'} alt="token"></Image>
|
||||
<Text color={'#485058'} fontSize={'12px'} fontWeight={500} px="8px">
|
||||
Token
|
||||
</Text>
|
||||
<Image
|
||||
cursor={'pointer'}
|
||||
onClick={() => downloadToken(item.token)}
|
||||
src={'/icons/download.svg'}
|
||||
w={'20px'}
|
||||
h={'20px'}
|
||||
alt="download"
|
||||
></Image>
|
||||
</Flex>
|
||||
</Flex>
|
||||
))}
|
||||
</Box>
|
||||
) : (
|
||||
<Flex
|
||||
w="100%"
|
||||
flex={1}
|
||||
overflowY={'auto'}
|
||||
pb="20px"
|
||||
pr="12px"
|
||||
justifyContent={'center'}
|
||||
alignItems={'center'}
|
||||
flexDirection={'column'}
|
||||
>
|
||||
<Image src="/icons/empty.svg" alt="empty" w="48px" h="48px"></Image>
|
||||
<Text mt="20px" color={'#5A646E'} fontWeight={500} fontSize={'14px'}>
|
||||
{t('You have not purchased the License')}
|
||||
</Text>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
<Flex ml="auto" mt={'auto'} pr="20px">
|
||||
<Pagination
|
||||
totalItems={data?.data?.total || 0}
|
||||
itemsPerPage={pageSize}
|
||||
onPageChange={() => {}}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Flex, Box, Text } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
|
||||
export default function WechatPayment(props: {
|
||||
complete: number;
|
||||
codeURL?: string;
|
||||
tradeNO?: string;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Flex
|
||||
flexDirection="column"
|
||||
px="37px"
|
||||
py="60px"
|
||||
justify={'center'}
|
||||
align={'center'}
|
||||
display={'flex'}
|
||||
justifyContent={'center'}
|
||||
alignItems={'center'}
|
||||
position={'relative'}
|
||||
>
|
||||
<Flex
|
||||
width={'267px'}
|
||||
height={'295px'}
|
||||
direction={'column'}
|
||||
align="center"
|
||||
justify={'space-between'}
|
||||
>
|
||||
<Text color="#7B838B" mb="8px" textAlign="center">
|
||||
{t('Scan with WeChat')}
|
||||
</Text>
|
||||
{props.complete === 2 && !!props.codeURL ? (
|
||||
<QRCodeSVG
|
||||
size={185}
|
||||
value={props.codeURL}
|
||||
style={{ margin: '0 auto' }}
|
||||
imageSettings={{
|
||||
// 二维码中间的logo图片
|
||||
src: 'icons/pay_wechat.svg',
|
||||
height: 40,
|
||||
width: 40,
|
||||
excavate: true // 中间图片所在的位置是否镂空
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Box>waiting...</Box>
|
||||
)}
|
||||
<Box mt="8px">
|
||||
<Text color="#717D8A" fontSize="12px" fontWeight="normal">
|
||||
{t('Order Number')}: {props.tradeNO || ''}
|
||||
</Text>
|
||||
<Text color="#717D8A" fontSize="12px">
|
||||
{t('Payment Result')}:{props.complete === 3 ? t('Payment Successful') : t('In Payment')}
|
||||
</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import LangSelectSimple from '@/components/LangSelect/simple';
|
||||
import { Flex, Image, Text } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
||||
import RechargeComponent from './components/Recharge';
|
||||
import LicenseRecord from './components/Record';
|
||||
import { useRouter } from 'next/router';
|
||||
import request from '@/services/request';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { ApiResp, SystemEnv } from '@/types';
|
||||
|
||||
export default function LicensePage() {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const goHome = () => router.replace('/');
|
||||
|
||||
const { data: platformEnv } = useQuery(
|
||||
['getPlatformEnv'],
|
||||
() => request<any, ApiResp<SystemEnv>>('/api/platform/getEnv'),
|
||||
{
|
||||
onSuccess(data) {
|
||||
console.log(data.data?.licenseEnabled, 'licenseEnabled');
|
||||
if (!data.data?.licenseEnabled) {
|
||||
goHome();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex w="100%" h="100%" flexDirection={'column'}>
|
||||
<Flex w="100%" alignItems={'center'} background={'#24282C'} h="56px" px="48px">
|
||||
<Image
|
||||
cursor={'pointer'}
|
||||
onClick={goHome}
|
||||
p="2px"
|
||||
width="36px"
|
||||
height="36px"
|
||||
src={'/images/sealos.svg'}
|
||||
fallbackSrc="/images/sealos.svg"
|
||||
alt="logo"
|
||||
/>
|
||||
<Text
|
||||
ml="6px"
|
||||
mr="12px"
|
||||
fontSize={20}
|
||||
fontWeight={700}
|
||||
color={'#fff'}
|
||||
cursor={'pointer'}
|
||||
onClick={goHome}
|
||||
>
|
||||
Sealos
|
||||
</Text>
|
||||
<Text fontSize={16} fontWeight={600} color={'#fff'}>
|
||||
| {t('License Buy')}
|
||||
</Text>
|
||||
<LangSelectSimple />
|
||||
</Flex>
|
||||
<Flex flex={1} h={0} bg="#fefefe" overflowX={'auto'}>
|
||||
<LicenseRecord />
|
||||
<RechargeComponent />
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps({ req, res, locales }: any) {
|
||||
const local = req?.cookies?.NEXT_LOCALE || 'en';
|
||||
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(local, undefined, null, locales || []))
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { LicensePayload, LicenseRecord } from '@/types';
|
||||
import { connectToDatabase } from './mongodb';
|
||||
|
||||
async function connectLicenseRecordCollection() {
|
||||
const client = await connectToDatabase();
|
||||
const collection = client.db().collection<LicenseRecord>('licenseRecord');
|
||||
return collection;
|
||||
}
|
||||
|
||||
export async function createLicenseRecord({
|
||||
uid,
|
||||
amount,
|
||||
token,
|
||||
orderID,
|
||||
quota,
|
||||
paymentMethod
|
||||
}: LicensePayload) {
|
||||
const collection = await connectLicenseRecordCollection();
|
||||
|
||||
const now = Math.floor(Date.now() / 1000); // Get current timestamp in seconds
|
||||
const oneDayInSeconds = 24 * 60 * 60; // One day in seconds
|
||||
|
||||
const record: LicenseRecord = {
|
||||
uid: uid,
|
||||
token: token,
|
||||
orderID: orderID,
|
||||
paymentMethod: paymentMethod,
|
||||
service: {
|
||||
quota: quota
|
||||
},
|
||||
iat: now, // Store the current timestamp as iat
|
||||
exp: now + oneDayInSeconds, // Set expiration to one day from now (in seconds)
|
||||
amount: amount
|
||||
};
|
||||
|
||||
const result = await collection.insertOne(record);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function getLicenseRecordsByUid({
|
||||
uid,
|
||||
page,
|
||||
pageSize
|
||||
}: {
|
||||
uid: string;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}) {
|
||||
const collection = await connectLicenseRecordCollection();
|
||||
|
||||
const skip = (page - 1) * pageSize;
|
||||
|
||||
const query = { uid: uid };
|
||||
const options = {
|
||||
skip: skip,
|
||||
limit: pageSize
|
||||
};
|
||||
|
||||
// Find records for the specified uid, skip records based on pagination, and limit the result to pageSize
|
||||
const records = await collection.find(query, options).sort({ iat: -1 }).toArray();
|
||||
|
||||
// Calculate the total count of records for the given uid
|
||||
const totalCount = await collection.countDocuments(query);
|
||||
|
||||
const result = {
|
||||
records: records,
|
||||
total: totalCount
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -22,3 +22,11 @@ export const enableGoogle = () =>
|
||||
export const enableRecharge = () => {
|
||||
return process.env.RECHARGE_ENABLED === 'true';
|
||||
};
|
||||
// costcenter
|
||||
export const enableStripe = () =>
|
||||
process.env['STRIPE_ENABLED'] === 'true' && !!process.env['STRIPE_PUB'];
|
||||
export const enableWechatRecharge = () => process.env['WECHAT_ENABLED'] === 'true';
|
||||
// license
|
||||
export const enableLicense = () => {
|
||||
return process.env.LICENSE_ENABLED === 'true';
|
||||
};
|
||||
|
||||
@@ -52,3 +52,22 @@ input:-webkit-autofill {
|
||||
background-image: none;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
div {
|
||||
&::-webkit-scrollbar-thumb,
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: transparent !important;
|
||||
border-radius: 10px;
|
||||
transition: 1s;
|
||||
}
|
||||
&:hover {
|
||||
&::-webkit-scrollbar-thumb,
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: rgba(189, 193, 197, 0.5) !important;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb:hover,
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(189, 193, 197, 1) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ export * from './crd';
|
||||
export * from './payment';
|
||||
export * from './system';
|
||||
export * from './login';
|
||||
export * from './valuation';
|
||||
export * from './license';
|
||||
|
||||
declare global {
|
||||
var mongodb: MongoClient | null;
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
export type LicenseCrd = {
|
||||
apiVersion: 'infostream.sealos.io/v1';
|
||||
kind: 'Payment';
|
||||
metadata: {
|
||||
name: string;
|
||||
namespace: string;
|
||||
};
|
||||
spec: {
|
||||
userID: string;
|
||||
amount: number;
|
||||
paymentMethod: 'wechat' | 'stripe';
|
||||
service: {
|
||||
amt: number;
|
||||
objType: string; // 'external,internal'
|
||||
};
|
||||
};
|
||||
status: {
|
||||
tradeNO: string;
|
||||
codeURL: string;
|
||||
status: 'Created' | 'Completed';
|
||||
token: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type LicensePaymentForm = {
|
||||
paymentName: string;
|
||||
namespace: string;
|
||||
userId: string;
|
||||
amount: number;
|
||||
paymentMethod: 'wechat' | 'stripe';
|
||||
hashID: string;
|
||||
quota: number;
|
||||
};
|
||||
|
||||
export type LicensePayStatus = {
|
||||
tradeNO: string;
|
||||
codeURL: string;
|
||||
status: 'Created' | 'Completed';
|
||||
token: string;
|
||||
};
|
||||
|
||||
export type LicenseRecord = {
|
||||
_id?: string;
|
||||
uid: string; // user id
|
||||
token: string; // license token
|
||||
orderID: string; // order number
|
||||
paymentMethod: 'wechat' | 'stripe';
|
||||
service: {
|
||||
quota: number; // 额度
|
||||
};
|
||||
iat: number; // 签发日期
|
||||
exp: number; // 有效期
|
||||
amount: number; // 消费金额
|
||||
};
|
||||
|
||||
export type LicensePayload = {
|
||||
uid: string;
|
||||
amount: number;
|
||||
token: string;
|
||||
orderID: string;
|
||||
quota: number;
|
||||
paymentMethod: 'wechat' | 'stripe';
|
||||
};
|
||||
@@ -23,4 +23,7 @@ export type LoginProps = {
|
||||
|
||||
export type SystemEnv = {
|
||||
SEALOS_CLOUD_DOMAIN: string;
|
||||
stripeEnabled: boolean;
|
||||
wechatEnabledRecharge: boolean;
|
||||
licenseEnabled: boolean;
|
||||
} & LoginProps;
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
export type ValuationStandard = {
|
||||
name: string;
|
||||
unit: string;
|
||||
price: string;
|
||||
};
|
||||
export type ValuationBillingRecord = {
|
||||
price: number;
|
||||
resourceType: string;
|
||||
};
|
||||
export type ValuationData = {
|
||||
apiVersion: 'account.sealos.io/v1';
|
||||
kind: 'PriceQuery';
|
||||
metadata: any;
|
||||
spec: {};
|
||||
status: {
|
||||
billingRecords: ValuationBillingRecord[];
|
||||
};
|
||||
};
|
||||
@@ -36,3 +36,19 @@ export const parseOpenappQuery = (openapp: string) => {
|
||||
appQuery
|
||||
};
|
||||
};
|
||||
|
||||
export const getRemainingTime = (expirationTime: number) => {
|
||||
const currentTime = Math.floor(Date.now() / 1000);
|
||||
|
||||
if (currentTime >= expirationTime) {
|
||||
return 'expired';
|
||||
}
|
||||
|
||||
const remainingTimeInSeconds = expirationTime - currentTime;
|
||||
const hours = Math.floor(remainingTimeInSeconds / 3600);
|
||||
const minutes = Math.floor((remainingTimeInSeconds % 3600) / 60);
|
||||
const seconds = remainingTimeInSeconds % 60;
|
||||
|
||||
const formattedTime = `${hours}小时${minutes}分钟`;
|
||||
return formattedTime;
|
||||
};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
NEXT_PUBLIC_MOCK_USER=
|
||||
SEALOS_DOMAIN="cloud.sealos.io"
|
||||
MONGODB_URI=
|
||||
MONGODB_URI=
|
||||
# Same as desktop important
|
||||
PASSWORD_SALT=
|
||||
@@ -14,8 +14,4 @@ spec:
|
||||
name: license
|
||||
type: iframe
|
||||
displayType: normal
|
||||
i18n:
|
||||
zh:
|
||||
name: 定时任务
|
||||
zh-Hans:
|
||||
name: 定时任务
|
||||
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
labels:
|
||||
app: license-frontend
|
||||
name: license-frontend
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: license-frontend-config
|
||||
namespace: license-frontend
|
||||
namespace: sealos
|
||||
data:
|
||||
config.yaml: |-
|
||||
addr: :3000
|
||||
@@ -18,7 +11,7 @@ apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: license-frontend
|
||||
namespace: license-frontend
|
||||
namespace: sealos
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
@@ -39,6 +32,16 @@ spec:
|
||||
env:
|
||||
- name: SEALOS_DOMAIN
|
||||
value: {{ .cloudDomain }}
|
||||
- name: MONGODB_URI
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
key: mongodb_uri
|
||||
name: license-frontend-secret
|
||||
- name: PASSWORD_SALT
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
key: password_salt
|
||||
name: license-frontend-secret
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1001
|
||||
@@ -54,7 +57,7 @@ spec:
|
||||
cpu: 10m
|
||||
memory: 128Mi
|
||||
# do not modify this image, it is used for CI/CD
|
||||
image: ghcr.io/labring/sealos-license-frontend:latest
|
||||
image: zhujingyang/sealos-license-frontend:1.0.1
|
||||
imagePullPolicy: Always
|
||||
volumeMounts:
|
||||
- name: license-frontend-volume
|
||||
@@ -71,7 +74,7 @@ metadata:
|
||||
labels:
|
||||
app: license-frontend
|
||||
name: license-frontend
|
||||
namespace: license-frontend
|
||||
namespace: sealos
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
|
||||
@@ -14,7 +14,7 @@ metadata:
|
||||
add_header Cache-Control "public";
|
||||
}
|
||||
name: license-frontend
|
||||
namespace: license-frontend
|
||||
namespace: sealos
|
||||
spec:
|
||||
rules:
|
||||
- host: license.{{ .cloudDomain }}
|
||||
@@ -30,4 +30,4 @@ spec:
|
||||
tls:
|
||||
- hosts:
|
||||
- license.{{ .cloudDomain }}
|
||||
secretName: {{ .certSecretName }}
|
||||
secretName: wildcard-cert
|
||||
|
||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
@@ -4,6 +4,9 @@
|
||||
"Activation Record": "Activation Record",
|
||||
"Purchase License": "Purchase License",
|
||||
"Please go to": "Please go to",
|
||||
"Buy": "Buy",
|
||||
"Activation Time": "Activation time: "
|
||||
"Purchase": "purchase a license.",
|
||||
"Activation Time": "Activation time: ",
|
||||
"Go to the message center to see the results": "Go to the message center to see the results",
|
||||
"token does not exist": "token does not exist",
|
||||
"No Record": "No Record"
|
||||
}
|
||||
@@ -3,7 +3,10 @@
|
||||
"Activate License": "激活 License",
|
||||
"Activation Record": "激活记录",
|
||||
"Purchase License": "购买 License",
|
||||
"Please go to": "请前往",
|
||||
"Buy": "购买",
|
||||
"Activation Time": "激活时间: "
|
||||
"Please go to": "请到",
|
||||
"Purchase": "购买 license",
|
||||
"Activation Time": "激活时间: ",
|
||||
"Go to the message center to see the results": "前往消息中心查看结果",
|
||||
"token does not exist": "token 不存在",
|
||||
"No Record": "暂无记录"
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.021 1.021H16.021V3.021H10.021V1.021ZM20.04 7.41L21.474 5.976L20.06 4.562L18.627 5.995C17.0569 4.73681 15.1101 4.04175 13.0982 4.02109C11.0863 4.00042 9.12562 4.65533 7.53001 5.881L8.95001 7.321C10.1364 6.47836 11.5548 6.02418 13.01 6.021L13.02 6.022V13.002L17.973 17.96C16.9952 18.9424 15.7478 19.6124 14.3888 19.8852C13.0299 20.158 11.6205 20.0212 10.3393 19.4923C9.05817 18.9634 7.96283 18.066 7.19211 16.914C6.4214 15.7619 6.00999 14.4071 6.01001 13.021H9.01001L5.01001 9.021L1.01001 13.021H4.01001C4.00865 14.4592 4.35195 15.8767 5.01117 17.1549C5.67038 18.4331 6.62631 19.5347 7.79887 20.3674C8.97143 21.2001 10.3265 21.7397 11.7505 21.9409C13.1745 22.1422 14.626 21.9993 15.9834 21.5242C17.3408 21.049 18.5646 20.2555 19.5522 19.2101C20.5398 18.1647 21.2626 16.8978 21.6598 15.5156C22.0571 14.1334 22.1173 12.6761 21.8355 11.2658C21.5537 9.85556 20.938 8.53336 20.04 7.41Z" fill="#24282C"/>
|
||||
<svg width="49" height="48" viewBox="0 0 49 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.77734 6.80408C4.67277 6.80408 3.77734 7.69951 3.77734 8.80408V39.196C3.77734 40.3005 4.67277 41.196 5.77734 41.196H43.7773C44.8819 41.196 45.7773 40.3005 45.7773 39.196V8.80408C45.7773 7.69951 44.8819 6.80408 43.7773 6.80408H5.77734ZM33.4131 12.903L32.674 12.3263L31.9349 12.903L30.4635 14.0512L28.6111 14.2797L27.6808 14.3945L27.5659 15.3249L27.3374 17.1773L26.1892 18.6487L25.6125 19.3878L26.1892 20.1269L27.3374 21.5983L27.5659 23.4507L27.6808 24.381L28.6111 24.4958L28.6973 24.5065L28.6973 32.632V34.1148C28.6973 34.3383 28.9577 34.4608 29.1298 34.3181L30.2716 33.372L32.5179 31.5106C32.616 31.4293 32.7583 31.4297 32.856 31.5116L35.0721 33.3686L36.2169 34.3278C36.3887 34.4718 36.6505 34.3497 36.6505 34.1254V32.632V24.5065L36.7369 24.4958L37.6673 24.381L37.7821 23.4507L38.0106 21.5983L39.1588 20.1269L39.7355 19.3878L39.1588 18.6487L38.0106 17.1773L37.7821 15.3249L37.6673 14.3945L36.7369 14.2797L34.8845 14.0512L33.4131 12.903ZM34.7283 24.8463L33.4131 25.8726L32.674 26.4493L31.9349 25.8726L30.6195 24.8461V30.0256C30.6195 30.2492 30.8799 30.3716 31.052 30.2289L32.0775 29.3792L32.5247 29.0086C32.6229 28.9272 32.7651 28.9276 32.8628 29.0095L33.308 29.3825L34.2947 30.2093C34.4665 30.3533 34.7283 30.2311 34.7283 30.0069V24.8463ZM31.6796 16.1499L32.674 15.3739L33.6684 16.1499L33.9305 16.3544L34.2604 16.3951L35.5122 16.5496L35.6667 17.8014L35.7074 18.1313L35.9119 18.3934L36.6879 19.3878L35.9119 20.3822L35.7074 20.6443L35.6667 20.9742L35.5122 22.226L34.2604 22.3805L33.9305 22.4212L33.6684 22.6257L32.674 23.4016L31.6796 22.6257L31.4175 22.4212L31.0876 22.3805L29.8358 22.226L29.6813 20.9742L29.6406 20.6443L29.4361 20.3822L28.6602 19.3878L29.4361 18.3934L29.6406 18.1313L29.6813 17.8014L29.8358 16.5496L31.0876 16.3951L31.4175 16.3544L31.6796 16.1499ZM9.81874 15.6173H11.3805H20.6961H22.2579V18.7408H20.6961H11.3805H9.81874V15.6173ZM9.81874 22.8254H11.3805H17.8458H19.4076V25.949H17.8458H11.3805H9.81874V22.8254ZM11.3805 30.0336H9.81874V33.1572H11.3805H23.0733H24.6351V30.0336H23.0733H11.3805Z" fill="#24282C"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1013 B After Width: | Height: | Size: 2.1 KiB |
@@ -182,8 +182,9 @@ const FileSelect = ({ fileExtension, setFiles, files, ...props }: Props) => {
|
||||
{t('Upload Token File')}
|
||||
</Text>
|
||||
<Box
|
||||
w="106px"
|
||||
h="50px"
|
||||
w={'auto'}
|
||||
maxW={'200px'}
|
||||
h="35px"
|
||||
bottom={'0'}
|
||||
right={'0'}
|
||||
fontWeight={400}
|
||||
|
||||
@@ -34,7 +34,7 @@ export default function Pagination({ totalItems, itemsPerPage, onPageChange }: P
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex minW="370px" h="32px" ml="auto" align="center" mt="20px" justifyContent={'end'}>
|
||||
<Flex minW="350px" pr="8px" h="32px" ml="auto" align="center" mt="20px" justifyContent={'end'}>
|
||||
<Text>Total:</Text>
|
||||
<Flex w="40px">{totalItems}</Flex>
|
||||
<Flex gap="8px" alignItems={'center'}>
|
||||
|
||||
@@ -233,7 +233,6 @@ export const theme = extendTheme({
|
||||
color: 'myGray.900',
|
||||
fontSize: 'md',
|
||||
height: '100%',
|
||||
overflowY: 'hidden',
|
||||
fontWeight: 400
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ function App({ Component, pageProps }: AppProps) {
|
||||
const lastLang = getLangStore();
|
||||
const newLang = data.currentLanguage;
|
||||
if (lastLang !== newLang) {
|
||||
i18n.changeLanguage(newLang);
|
||||
i18n?.changeLanguage?.(newLang);
|
||||
setLangStore(newLang);
|
||||
setRefresh((state) => !state);
|
||||
}
|
||||
|
||||
@@ -3,15 +3,16 @@ import { jsonRes } from '@/services/backend/response';
|
||||
import { connectToLicenseCollection } from '@/services/db/mongodb';
|
||||
import { getK8s } from '@/services/backend/kubernetes';
|
||||
import { authSession } from '@/services/backend/auth';
|
||||
import { getUserId } from '@/utils/user';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
const { page = '1', pageSize = '10' } = req.query as { page: string; pageSize: string };
|
||||
const { namespace } = await getK8s({
|
||||
const { kube_user } = await getK8s({
|
||||
kubeconfig: await authSession(req)
|
||||
});
|
||||
const userId = getUserId();
|
||||
const userId = kube_user.name;
|
||||
console.log(userId, 'userId');
|
||||
|
||||
const skip = (parseInt(page) - 1) * parseInt(pageSize);
|
||||
|
||||
const license_collection = await connectToLicenseCollection();
|
||||
@@ -38,6 +39,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err, 'getlicense----');
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import { jsonRes } from '@/services/backend/response';
|
||||
import { ApiResp } from '@/services/kubernet';
|
||||
import { hashCrypto } from '@/utils/crypto';
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
|
||||
export type Response = {
|
||||
domain?: string;
|
||||
env_storage_className?: string;
|
||||
hid: string; // PASSWORD_SALT
|
||||
};
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<ApiResp>) {
|
||||
jsonRes<Response>(res, {
|
||||
data: {
|
||||
domain: process.env.SEALOS_DOMAIN || 'cloud.sealos.io',
|
||||
env_storage_className: process.env.STORAGE_CLASSNAME || ''
|
||||
hid: hashCrypto(process.env.PASSWORD_SALT || '')
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import { applyLicense, getLicenseRecord } from '@/api/license';
|
||||
import { getPlatformEnv } from '@/api/platform';
|
||||
import CurrencySymbol from '@/components/CurrencySymbol';
|
||||
import FileSelect, { FileItemType } from '@/components/FileSelect';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import Pagination from '@/components/Pagination';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
import download from '@/utils/downloadFIle';
|
||||
import { serviceSideProps } from '@/utils/i18n';
|
||||
import { json2License } from '@/utils/json2Yaml';
|
||||
import { Box, Flex, Icon, Image, Link, Text, Button } from '@chakra-ui/react';
|
||||
import { useCopyData } from '@/utils/tools';
|
||||
import { Box, Flex, Icon, Image, Text } from '@chakra-ui/react';
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { debounce } from 'lodash';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function LicenseApp() {
|
||||
const { t } = useTranslation();
|
||||
@@ -16,16 +21,38 @@ export default function LicenseApp() {
|
||||
const { toast } = useToast();
|
||||
const [page, setPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(5);
|
||||
const { copyData } = useCopyData();
|
||||
const [purchaseLink, setPurchaseLink] = useState('');
|
||||
|
||||
const licenseMutation = useMutation({
|
||||
mutationFn: (yamlList: string[]) => applyLicense(yamlList, 'create'),
|
||||
onSuccess(data) {
|
||||
console.log(data);
|
||||
toast({
|
||||
title: t('Go to the message center to see the results'),
|
||||
status: 'success'
|
||||
});
|
||||
},
|
||||
onError(error) {
|
||||
console.log(error);
|
||||
}
|
||||
});
|
||||
|
||||
useQuery(['getPlatformEnv'], () => getPlatformEnv(), {
|
||||
onSuccess(data) {
|
||||
const hid = data?.hid;
|
||||
if (!hid) {
|
||||
return toast({
|
||||
title: 'env hid error',
|
||||
status: 'error'
|
||||
});
|
||||
}
|
||||
const encodedHid = encodeURIComponent(hid);
|
||||
const main = data?.domain ? data.domain : 'cloud.sealos.io';
|
||||
const link = `https:/${main}/license?hid=${encodedHid}`;
|
||||
setPurchaseLink(link);
|
||||
}
|
||||
});
|
||||
|
||||
const { data } = useQuery(['getLicenseActive', page, pageSize], () =>
|
||||
getLicenseRecord({ page: page, pageSize: pageSize })
|
||||
);
|
||||
@@ -33,7 +60,7 @@ export default function LicenseApp() {
|
||||
const activeLicense = debounce(async () => {
|
||||
if (files.length < 1) {
|
||||
return toast({
|
||||
title: 'token 不存在',
|
||||
title: t('token does not exist'),
|
||||
status: 'error'
|
||||
});
|
||||
}
|
||||
@@ -41,40 +68,68 @@ export default function LicenseApp() {
|
||||
licenseMutation.mutate(yamlList);
|
||||
}, 500);
|
||||
|
||||
const copyLicenseLink = () => {
|
||||
if (!purchaseLink) {
|
||||
return toast({
|
||||
title: 'env hid error',
|
||||
status: 'error'
|
||||
});
|
||||
}
|
||||
copyData(purchaseLink);
|
||||
};
|
||||
|
||||
const downloadToken = (token: string) => {
|
||||
const result = Buffer.from(token, 'binary').toString('base64');
|
||||
download('token.txt', result);
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex w="100%" h="100%">
|
||||
<Flex
|
||||
w="50%"
|
||||
m="24px 108px 34px 48px"
|
||||
overflow={'hidden'}
|
||||
justifyContent={'center'}
|
||||
alignItems={'center'}
|
||||
position={'relative'}
|
||||
>
|
||||
<Box flexShrink={0} textAlign="center">
|
||||
<Flex
|
||||
w="100%"
|
||||
minH={'100%'}
|
||||
minW={'1190px'}
|
||||
overflow={'scroll'}
|
||||
justifyContent={'center'}
|
||||
alignItems={'center'}
|
||||
py="20px"
|
||||
>
|
||||
<Flex w="50%" pl="48px" justifyContent="center" alignItems="center" position={'relative'}>
|
||||
<Box position="relative">
|
||||
<Image
|
||||
alt="license"
|
||||
draggable="false"
|
||||
w="616px"
|
||||
h="636px"
|
||||
src="/icons/license-bg.svg"
|
||||
objectFit="contain"
|
||||
maxH="100%" // 设置图片的最大高度为100%以适应空间
|
||||
alt="license"
|
||||
objectFit="cover"
|
||||
/>
|
||||
</Box>
|
||||
<Box position={'absolute'} color={'#FFF'}>
|
||||
<Text fontSize={'32px'} fontWeight={600}>
|
||||
{t('Purchase License')}
|
||||
</Text>
|
||||
<Box mt="45px">
|
||||
{t('Please go to')}
|
||||
<Link px="4px" color={'#36ADEF'} href="https://cloud.sealos.io/license" isExternal>
|
||||
https://cloud.sealos.io/license
|
||||
</Link>
|
||||
{t('Buy')}
|
||||
<Box position={'absolute'} color={'#FFF'} top={'180px'} left={'80px'}>
|
||||
<Text fontSize={'32px'} fontWeight={600}>
|
||||
{t('Purchase License')}
|
||||
</Text>
|
||||
<Flex mt="45px" cursor={'copy'} onClick={copyLicenseLink}>
|
||||
{t('Please go to')}
|
||||
<Text
|
||||
px="4px"
|
||||
w="220px"
|
||||
color={'#36ADEF'}
|
||||
textOverflow="ellipsis"
|
||||
whiteSpace="nowrap"
|
||||
overflow="hidden"
|
||||
>
|
||||
{purchaseLink}
|
||||
</Text>
|
||||
{t('Purchase')}
|
||||
</Flex>
|
||||
</Box>
|
||||
<Flex position={'absolute'} bottom={'20px'} right={'48px'}>
|
||||
<Image alt="license" src="/icons/license-sealos.svg" />
|
||||
</Flex>
|
||||
</Box>
|
||||
</Flex>
|
||||
{/* rihgt */}
|
||||
<Box w="50%" pr="70px">
|
||||
<Text color={'#262A32'} fontSize={'24px'} fontWeight={600} mt="34px">
|
||||
{/* right */}
|
||||
<Box w="50%" pl="108px" pr="70px">
|
||||
<Text color={'#262A32'} fontSize={'24px'} fontWeight={600}>
|
||||
{t('Activate License')}
|
||||
</Text>
|
||||
<FileSelect fileExtension={'.txt'} files={files} setFiles={setFiles} />
|
||||
@@ -99,48 +154,71 @@ export default function LicenseApp() {
|
||||
<Text mt="26px" color={'#262A32'} fontSize={'20px'} fontWeight={600}>
|
||||
{t('Activation Record')}
|
||||
</Text>
|
||||
<Box mt="12px" height={'300px'} overflowY={'scroll'}>
|
||||
{data?.items?.map((license, i) => (
|
||||
<Flex
|
||||
key={license._id}
|
||||
minW={'350px'}
|
||||
p="12px 0 12px 16px"
|
||||
border={'1px solid #EFF0F1'}
|
||||
borderRadius={'4px'}
|
||||
background={'#F8FAFB'}
|
||||
alignItems={'center'}
|
||||
mb="12px"
|
||||
>
|
||||
<Image src={'/icons/license.svg'} w={'24px'} h={'24px'} alt="token" />
|
||||
<Text color={'#485058'} fontSize={'16px'} fontWeight={500} ml="10px" mr="16px">
|
||||
License
|
||||
</Text>
|
||||
<CurrencySymbol />
|
||||
<Text ml="6px" color={'#5A646E'} fontSize={'14px'} fontWeight={500}>
|
||||
{license.payload?.amt}
|
||||
</Text>
|
||||
<Text color={'#5A646E'} fontSize={'12px'} fontWeight={500} ml="auto">
|
||||
{t('Activation time')} {license.meta.createTime}
|
||||
</Text>
|
||||
|
||||
{data?.items && data?.items?.length > 0 ? (
|
||||
<Box mt="12px" minW={'350px'} height={'300px'} overflowY={'auto'}>
|
||||
{data?.items?.map((license, i) => (
|
||||
<Flex
|
||||
w="100%"
|
||||
key={license._id}
|
||||
p="12px 0 12px 16px"
|
||||
border={'1px solid #EFF0F1'}
|
||||
borderRadius={'4px'}
|
||||
background={'#F8FAFB'}
|
||||
alignItems={'center'}
|
||||
mx={{
|
||||
sm: '8px',
|
||||
md: '24px'
|
||||
}}
|
||||
cursor={'pointer'}
|
||||
mb="12px"
|
||||
>
|
||||
<Text color={'#1D8CDC'} fontSize={'14px'} fontWeight={600} px="8px">
|
||||
Token
|
||||
<Image src={'/icons/license.svg'} w={'24px'} h={'24px'} alt="token" />
|
||||
<Text color={'#485058'} fontSize={'16px'} fontWeight={500} ml="10px" mr="16px">
|
||||
License
|
||||
</Text>
|
||||
<Icon fill="#1D8CDC" viewBox="0 0 16 16">
|
||||
<path d="M4.76693 14.0667C4.60026 13.9 4.51693 13.7027 4.51693 13.4747C4.51693 13.2471 4.60026 13.05 4.76693 12.8833L9.65026 8L4.75026 3.1C4.59471 2.94444 4.51693 2.75 4.51693 2.51666C4.51693 2.28333 4.60026 2.08333 4.76693 1.91666C4.93359 1.75 5.13093 1.66666 5.35893 1.66666C5.58648 1.66666 5.78359 1.75 5.95026 1.91666L11.5503 7.53333C11.6169 7.6 11.6643 7.67222 11.6923 7.75C11.7198 7.82778 11.7336 7.91111 11.7336 8C11.7336 8.08889 11.7198 8.17222 11.6923 8.25C11.6643 8.32778 11.6169 8.4 11.5503 8.46666L5.93359 14.0833C5.77804 14.2389 5.58648 14.3167 5.35893 14.3167C5.13093 14.3167 4.93359 14.2333 4.76693 14.0667Z" />
|
||||
</Icon>
|
||||
<CurrencySymbol />
|
||||
<Text ml="6px" color={'#5A646E'} fontSize={'14px'} fontWeight={500}>
|
||||
{license.payload?.amt}
|
||||
</Text>
|
||||
<Text color={'#5A646E'} fontSize={'12px'} fontWeight={500} ml="auto">
|
||||
{t('Activation time')} {license.meta.createTime}
|
||||
</Text>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
mx={{
|
||||
sm: '8px',
|
||||
md: '24px'
|
||||
}}
|
||||
cursor={'pointer'}
|
||||
>
|
||||
<Text
|
||||
color={'#1D8CDC'}
|
||||
fontSize={'14px'}
|
||||
fontWeight={600}
|
||||
px="8px"
|
||||
onClick={() => downloadToken(license?.meta?.token)}
|
||||
>
|
||||
Token
|
||||
</Text>
|
||||
<Icon fill="#1D8CDC" viewBox="0 0 16 16">
|
||||
<path d="M4.76693 14.0667C4.60026 13.9 4.51693 13.7027 4.51693 13.4747C4.51693 13.2471 4.60026 13.05 4.76693 12.8833L9.65026 8L4.75026 3.1C4.59471 2.94444 4.51693 2.75 4.51693 2.51666C4.51693 2.28333 4.60026 2.08333 4.76693 1.91666C4.93359 1.75 5.13093 1.66666 5.35893 1.66666C5.58648 1.66666 5.78359 1.75 5.95026 1.91666L11.5503 7.53333C11.6169 7.6 11.6643 7.67222 11.6923 7.75C11.7198 7.82778 11.7336 7.91111 11.7336 8C11.7336 8.08889 11.7198 8.17222 11.6923 8.25C11.6643 8.32778 11.6169 8.4 11.5503 8.46666L5.93359 14.0833C5.77804 14.2389 5.58648 14.3167 5.35893 14.3167C5.13093 14.3167 4.93359 14.2333 4.76693 14.0667Z" />
|
||||
</Icon>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
))}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
) : (
|
||||
<Flex
|
||||
mt="12px"
|
||||
minW={'350px'}
|
||||
height={'300px'}
|
||||
justifyContent={'center'}
|
||||
alignItems={'center'}
|
||||
flexDirection={'column'}
|
||||
>
|
||||
<MyIcon name="noEvents" w={'48px'} h={'48px'} color={'transparent'} />
|
||||
<Text mt="12px" color={'#5A646E'} fontWeight={500} fontSize={'14px'}>
|
||||
{t('No Record')}
|
||||
</Text>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
<Pagination
|
||||
totalItems={data?.totalCount || 0}
|
||||
itemsPerPage={pageSize}
|
||||
@@ -150,3 +228,11 @@ export default function LicenseApp() {
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps(content: any) {
|
||||
return {
|
||||
props: {
|
||||
...(await serviceSideProps(content))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import * as crypto from 'crypto';
|
||||
|
||||
export const hashCrypto = (text: string): string => {
|
||||
const hash = crypto.createHash('sha256');
|
||||
hash.update(text);
|
||||
return hash.digest('hex');
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
function download(filename: string, text: string) {
|
||||
var element = document.createElement('a');
|
||||
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
|
||||
element.setAttribute('download', filename);
|
||||
|
||||
element.style.display = 'none';
|
||||
document.body.appendChild(element);
|
||||
|
||||
element.click();
|
||||
|
||||
document.body.removeChild(element);
|
||||
}
|
||||
|
||||
export default download;
|
||||
@@ -1,20 +1,24 @@
|
||||
import yaml from 'js-yaml';
|
||||
import { getUserId } from './user';
|
||||
import { getUserId, getUserNamespace } from './user';
|
||||
|
||||
export const json2License = (token: string) => {
|
||||
const namespace = getUserNamespace();
|
||||
const userId = getUserId();
|
||||
const license_name = crypto.randomUUID();
|
||||
const decodedData = Buffer.from(token, 'base64').toString('binary');
|
||||
|
||||
const template = {
|
||||
apiVersion: 'infostream.sealos.io/v1',
|
||||
kind: 'License',
|
||||
metadata: {
|
||||
name: 'license',
|
||||
namespace: 'ns-admin'
|
||||
name: license_name,
|
||||
namespace: namespace
|
||||
},
|
||||
spec: {
|
||||
uid: userId,
|
||||
token: token
|
||||
token: decodedData
|
||||
}
|
||||
};
|
||||
|
||||
console.log(template, 'license');
|
||||
return yaml.dump(template);
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// edge
|
||||
import yaml from 'js-yaml';
|
||||
|
||||
type KC = {
|
||||
@@ -47,5 +48,5 @@ export const getUserServiceAccount = () => {
|
||||
export const getUserId = () => {
|
||||
const kubeConfig = getUserKubeConfig();
|
||||
const json = yaml.load(kubeConfig) as KC;
|
||||
return json?.contexts[0]?.context?.user || json.users[0].name;
|
||||
return json?.contexts[0]?.context?.user || json?.users[0]?.name;
|
||||
};
|
||||
|
||||