diff --git a/package.json b/package.json index e4d92c243..df1e1d19c 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,13 @@ "@mui/material": "^5.2.5", "@mui/x-tree-view": "^6.17.0", "@octokit/core": "^3.5.1", + "@radix-ui/react-checkbox": "^1.1.3", + "@radix-ui/react-collapsible": "^1.1.2", + "@radix-ui/react-radio-group": "^1.2.2", "@radix-ui/react-select": "^2.1.1", "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-switch": "^1.1.2", + "@radix-ui/react-tooltip": "^1.1.6", "@zilliz/toolkit": "^0.1.11", "axios": "^0.28.0", "class-variance-authority": "^0.7.0", @@ -34,6 +39,7 @@ "cropperjs": "^1.5.12", "cssnano-preset-lite": "^2.0.2", "d3-format": "^3.1.0", + "d3-scale": "^4.0.2", "dayjs": "^1.10.7", "gestalt": "^146.5.4", "gray-matter": "^4.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 18784aec3..db5beff45 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,12 +50,27 @@ importers: '@octokit/core': specifier: ^3.5.1 version: 3.6.0 + '@radix-ui/react-checkbox': + specifier: ^1.1.3 + version: 1.1.3(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-collapsible': + specifier: ^1.1.2 + version: 1.1.2(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-radio-group': + specifier: ^1.2.2 + version: 1.2.2(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@radix-ui/react-select': specifier: ^2.1.1 version: 2.1.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@radix-ui/react-slot': specifier: ^1.0.2 version: 1.0.2(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-switch': + specifier: ^1.1.2 + version: 1.1.2(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-tooltip': + specifier: ^1.1.6 + version: 1.1.6(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@zilliz/toolkit': specifier: ^0.1.11 version: 0.1.11 @@ -77,6 +92,9 @@ importers: d3-format: specifier: ^3.1.0 version: 3.1.0 + d3-scale: + specifier: ^4.0.2 + version: 4.0.2 dayjs: specifier: ^1.10.7 version: 1.11.11 @@ -1131,6 +1149,9 @@ packages: '@radix-ui/primitive@1.1.0': resolution: {integrity: sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==} + '@radix-ui/primitive@1.1.1': + resolution: {integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==} + '@radix-ui/react-arrow@1.1.0': resolution: {integrity: sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==} peerDependencies: @@ -1144,6 +1165,45 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-arrow@1.1.1': + resolution: {integrity: sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-checkbox@1.1.3': + resolution: {integrity: sha512-HD7/ocp8f1B3e6OHygH0n7ZKjONkhciy1Nh0yuBgObqThc3oyx+vuMfFHKAknXRHHWVE9XvXStxJFyjUmB8PIw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collapsible@1.1.2': + resolution: {integrity: sha512-PliMB63vxz7vggcyq0IxNYk8vGDrLXVWw4+W4B8YnwI1s18x7YZYqlG9PLX7XxAJUi0g2DxP4XKJMFHh/iVh9A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-collection@1.1.0': resolution: {integrity: sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==} peerDependencies: @@ -1157,6 +1217,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-collection@1.1.1': + resolution: {integrity: sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-compose-refs@1.0.1': resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} peerDependencies: @@ -1175,6 +1248,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-compose-refs@1.1.1': + resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-context@1.1.0': resolution: {integrity: sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==} peerDependencies: @@ -1184,6 +1266,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-context@1.1.1': + resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-direction@1.1.0': resolution: {integrity: sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==} peerDependencies: @@ -1206,6 +1297,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-dismissable-layer@1.1.3': + resolution: {integrity: sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-focus-guards@1.1.0': resolution: {integrity: sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw==} peerDependencies: @@ -1250,6 +1354,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-popper@1.2.1': + resolution: {integrity: sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-portal@1.1.1': resolution: {integrity: sha512-A3UtLk85UtqhzFqtoC8Q0KvR2GbXF3mtPgACSazajqq6A41mEQgo53iPzY4i6BwDxlIFqWIhiQ2G729n+2aw/g==} peerDependencies: @@ -1263,6 +1380,32 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-portal@1.1.3': + resolution: {integrity: sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.2': + resolution: {integrity: sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-primitive@2.0.0': resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==} peerDependencies: @@ -1276,6 +1419,45 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-primitive@2.0.1': + resolution: {integrity: sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-radio-group@1.2.2': + resolution: {integrity: sha512-E0MLLGfOP0l8P/NxgVzfXJ8w3Ch8cdO6UDzJfDChu4EJDy+/WdO5LqpdY8PYnCErkmZH3gZhDL1K7kQ41fAHuQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-roving-focus@1.1.1': + resolution: {integrity: sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-select@2.1.1': resolution: {integrity: sha512-8iRDfyLtzxlprOo9IicnzvpsO1wNCkuwzzCM+Z5Rb5tNOpCdMvcc2AkzX0Fz+Tz9v6NJ5B/7EEgyZveo4FBRfQ==} peerDependencies: @@ -1307,6 +1489,41 @@ packages: '@types/react': optional: true + '@radix-ui/react-slot@1.1.1': + resolution: {integrity: sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-switch@1.1.2': + resolution: {integrity: sha512-zGukiWHjEdBCRyXvKR6iXAQG6qXm2esuAD6kDOi9Cn+1X6ev3ASo4+CsYaD6Fov9r/AQFekqnD/7+V0Cs6/98g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tooltip@1.1.6': + resolution: {integrity: sha512-TLB5D8QLExS1uDn7+wH/bjEmRurNMTzNrtq7IjaS4kjion9NtzsTGkvR5+i7yc9q01Pi2KMM2cN3f8UG4IvvXA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-use-callback-ref@1.1.0': resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==} peerDependencies: @@ -1383,6 +1600,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-visually-hidden@1.1.1': + resolution: {integrity: sha512-vVfA2IZ9q/J+gEamvj761Oq1FpWgCDaNOOIfbPVp2MVPLEomUr5+Vf7kJGwQ24YxZSlQVar7Bes8kyTo5Dshpg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/rect@1.1.0': resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==} @@ -2356,10 +2586,34 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + d3-format@3.1.0: resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} engines: {node: '>=12'} + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} @@ -3094,6 +3348,10 @@ packages: resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} engines: {node: '>= 0.4'} + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} @@ -6271,6 +6529,8 @@ snapshots: '@radix-ui/primitive@1.1.0': {} + '@radix-ui/primitive@1.1.1': {} + '@radix-ui/react-arrow@1.1.0(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@radix-ui/react-primitive': 2.0.0(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -6279,6 +6539,44 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 + '@radix-ui/react-arrow@1.1.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.0.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + + '@radix-ui/react-checkbox@1.1.3(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-presence': 1.1.2(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-primitive': 2.0.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.3)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + + '@radix-ui/react-collapsible@1.1.2(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-presence': 1.1.2(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-primitive': 2.0.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + '@radix-ui/react-collection@1.1.0(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.3)(react@18.2.0) @@ -6290,6 +6588,17 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 + '@radix-ui/react-collection@1.1.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-primitive': 2.0.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-slot': 1.1.1(@types/react@18.3.3)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + '@radix-ui/react-compose-refs@1.0.1(@types/react@18.3.3)(react@18.2.0)': dependencies: '@babel/runtime': 7.24.6 @@ -6303,12 +6612,24 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 + '@radix-ui/react-compose-refs@1.1.1(@types/react@18.3.3)(react@18.2.0)': + dependencies: + react: 18.2.0 + optionalDependencies: + '@types/react': 18.3.3 + '@radix-ui/react-context@1.1.0(@types/react@18.3.3)(react@18.2.0)': dependencies: react: 18.2.0 optionalDependencies: '@types/react': 18.3.3 + '@radix-ui/react-context@1.1.1(@types/react@18.3.3)(react@18.2.0)': + dependencies: + react: 18.2.0 + optionalDependencies: + '@types/react': 18.3.3 + '@radix-ui/react-direction@1.1.0(@types/react@18.3.3)(react@18.2.0)': dependencies: react: 18.2.0 @@ -6327,6 +6648,18 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 + '@radix-ui/react-dismissable-layer@1.1.3(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-primitive': 2.0.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.3)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + '@radix-ui/react-focus-guards@1.1.0(@types/react@18.3.3)(react@18.2.0)': dependencies: react: 18.2.0 @@ -6367,6 +6700,23 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 + '@radix-ui/react-popper@1.2.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@floating-ui/react-dom': 2.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-arrow': 1.1.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-primitive': 2.0.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-use-rect': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/rect': 1.1.0 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + '@radix-ui/react-portal@1.1.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@radix-ui/react-primitive': 2.0.0(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -6376,6 +6726,24 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 + '@radix-ui/react-portal@1.1.3(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.0.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + + '@radix-ui/react-presence@1.1.2(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.3)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + '@radix-ui/react-primitive@2.0.0(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@radix-ui/react-slot': 1.1.0(@types/react@18.3.3)(react@18.2.0) @@ -6384,6 +6752,47 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 + '@radix-ui/react-primitive@2.0.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/react-slot': 1.1.1(@types/react@18.3.3)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + + '@radix-ui/react-radio-group@1.2.2(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-presence': 1.1.2(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-primitive': 2.0.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-roving-focus': 1.1.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.3)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + + '@radix-ui/react-roving-focus@1.1.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-collection': 1.1.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-primitive': 2.0.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + '@radix-ui/react-select@2.1.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@radix-ui/number': 1.1.0 @@ -6427,6 +6836,46 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 + '@radix-ui/react-slot@1.1.1(@types/react@18.3.3)(react@18.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.3)(react@18.2.0) + react: 18.2.0 + optionalDependencies: + '@types/react': 18.3.3 + + '@radix-ui/react-switch@1.1.2(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-primitive': 2.0.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.3)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + + '@radix-ui/react-tooltip@1.1.6(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-dismissable-layer': 1.1.3(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-popper': 1.2.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-portal': 1.1.3(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-presence': 1.1.2(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-primitive': 2.0.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@radix-ui/react-slot': 1.1.1(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.3)(react@18.2.0) + '@radix-ui/react-visually-hidden': 1.1.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + '@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.3)(react@18.2.0)': dependencies: react: 18.2.0 @@ -6481,6 +6930,14 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 + '@radix-ui/react-visually-hidden@1.1.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.0.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + optionalDependencies: + '@types/react': 18.3.3 + '@radix-ui/rect@1.1.0': {} '@rushstack/eslint-patch@1.10.3': {} @@ -8033,8 +8490,34 @@ snapshots: csstype@3.1.3: {} + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-color@3.1.0: {} + d3-format@3.1.0: {} + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.0 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + damerau-levenshtein@1.0.8: {} data-view-buffer@1.0.1: @@ -8995,6 +9478,8 @@ snapshots: hasown: 2.0.2 side-channel: 1.0.6 + internmap@2.0.3: {} + invariant@2.2.4: dependencies: loose-envify: 1.4.0 diff --git a/public/images/sizing-tool/kafka.svg b/public/images/sizing-tool/kafka.svg new file mode 100644 index 000000000..38ecef9cd --- /dev/null +++ b/public/images/sizing-tool/kafka.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/images/sizing-tool/pulsar.svg b/public/images/sizing-tool/pulsar.svg new file mode 100644 index 000000000..911b51527 --- /dev/null +++ b/public/images/sizing-tool/pulsar.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/archive/sizing.txt b/src/archive/sizing.txt new file mode 100644 index 000000000..f7e85aea8 --- /dev/null +++ b/src/archive/sizing.txt @@ -0,0 +1,903 @@ +import React, { useState, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import Layout from '@/components/layout/commonLayout'; +import classes from '@/styles/sizingTool.module.less'; +import pageClasses from '@/styles/responsive.module.less'; +import { InfoFilled, DownloadIcon } from '@/components/icons'; +import SizingToolCard from '@/components/card/sizingToolCard'; +import SizingConfigCard from '@/components/card/sizingToolCard/sizingConfigCard'; +import clsx from 'clsx'; +import Slider from '@mui/material/Slider'; +import TextField from '@mui/material/TextField'; +import InputLabel from '@mui/material/InputLabel'; +import MenuItem from '@mui/material/MenuItem'; +import FormControl from '@mui/material/FormControl'; +import Select from '@mui/material/Select'; +import Radio from '@mui/material/Radio'; +import RadioGroup from '@mui/material/RadioGroup'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Head from 'next/head'; +import CustomButton from '@/components/customButton'; + +import { + memorySizeCalculator, + rawFileSizeCalculator, + commonCoordCalculator, + unitBYTE2Any, + indexNodeCalculator, + queryNodeCalculator, + isBetween, + rootCoordCalculator, + dataNodeCalculator, + proxyCalculator, + helmYmlGenerator, + operatorYmlGenerator, + etcdCalculator, + minioCalculator, + pulsarCalculator, + kafkaCalculator, + mixCoordCalculator, + unitAny2BYTE, +} from '@/utils/sizingTool'; +import { CustomizedContentDialogs } from '@/components/dialog/Dialog'; +import HighlightBlock from '@/components/card/sizingToolCard/codeBlock'; +import { + HELM_CONFIG_FILE_NAME, + OPERATOR_CONFIG_FILE_NAME, + REQUIRE_MORE, + INDEX_TYPE_OPTIONS, + SEGMENT_SIZE_OPTIONS, + IndexTypeEnum, +} from '@/components/card/sizingToolCard/constants'; +import { ABSOLUTE_BASE_URL } from '@/consts'; + +// one million +const $1M = Math.pow(10, 6); + +const defaultSizeContent = { + size: REQUIRE_MORE, + cpu: 0, + memory: 0, + amount: 0, +}; + +enum FromKeysEnum { + Nb = 'nb', + D = 'd', + IndexType = 'indexType', + M = 'm', + Nlist = 'nlist', + SegmentSize = 'segmentSize', + aApacheType = 'apacheType', +} + +export default function SizingTool() { + const { t } = useTranslation('sizingTool'); + + const [dialogState, setDialogState] = useState({ + open: false, + title: '', + children: <>, + }); + const [form, setForm] = useState<{ [key in FromKeysEnum]: any }>({ + // number of vectors + nb: { + value: 1, + showError: false, + helpText: '', + placeholder: `[1, 10000]`, + validation: { + validate: isBetween, + params: { min: 1, max: 10000 }, + errorMsg: 'Number of vectors should be an integer between [1, 10000]', + }, + }, + // dimensions + d: { + value: 128, + showError: false, + helpText: '', + placeholder: '[1, 10000]', + validation: { + validate: isBetween, + params: { min: 1, max: 10000 }, + errorMsg: 'Dimensions should be an integer between [1, 10000]', + }, + }, + // index type + indexType: { + value: INDEX_TYPE_OPTIONS[0].value, + showError: false, + }, + // index parameters + m: { + value: 8, + showError: false, + }, + // ivf parameters + nlist: { + value: 1024, + showError: false, + helpText: '', + placeholder: '[1, 10000]', + validation: { + validate: isBetween, + params: { min: 1, max: 10000 }, + errorMsg: 'nList should be an integer between [1, 10000]', + }, + }, + // segment size + segmentSize: { + value: SEGMENT_SIZE_OPTIONS[0].value, + showError: false, + }, + apacheType: 'pulsar', + }); + + const handleFormValueChange = (val: any, key: FromKeysEnum) => { + const { validation } = form[key]; + + if (!validation) { + setForm(v => ({ + ...v, + [key]: { + ...v[key], + value: val, + }, + })); + return; + } + + const isValidated = validation.validate(val, validation.params); + if (isValidated) { + setForm(v => ({ + ...v, + [key]: { + ...v[key], + value: val, + showError: false, + helpText: '', + }, + })); + } else { + setForm(v => ({ + ...v, + [key]: { + ...v[key], + value: val, + showError: true, + helpText: validation.errorMsg, + }, + })); + } + }; + + const hanldeRemovePromot = key => { + if (!form[key].showError) { + return; + } + setForm(v => ({ + ...v, + [key]: { + ...v[key], + value: '', + showError: false, + helpText: '', + }, + })); + }; + + const handleApacheChange = (e, value) => { + setForm(v => ({ + ...v, + apacheType: value, + })); + }; + + const calcResult = useMemo(() => { + const { nb, d, indexType, nlist, m, segmentSize } = form; + + const nbVal = Number(nb.value) * $1M || 0; + const dVal = Number(d.value) || 0; + const nlistVal = Number(nlist.value) || 0; + const mVal = Number(m.value) || 0; + const sVal = Number(segmentSize.value) || 0; + + const isErrorParameters = Object.values(form).some( + v => v.showError || v.value === '' + ); + + if (isErrorParameters) { + const etcdData = etcdCalculator(); + const minioData = minioCalculator(); + const pulsarData = pulsarCalculator(); + const kafkaData = kafkaCalculator(); + return { + memorySize: { size: 0, unit: 'B' }, + rawFileSize: { size: 0, unit: 'B' }, + dataNode: defaultSizeContent, + queryNode: defaultSizeContent, + indexNode: defaultSizeContent, + proxy: defaultSizeContent, + mixCoord: defaultSizeContent, + commonCoord: defaultSizeContent, + etcdData, + minioData, + pulsarData, + kafkaData, + }; + } + + const { memorySize, theorySize } = memorySizeCalculator({ + nb: nbVal, + d: dVal, + nlist: nlistVal, + M: mVal, + indexType: indexType.value, + }); + + const rawFileSize = rawFileSizeCalculator({ d: dVal, nb: nbVal }); + const dataNode = dataNodeCalculator(nbVal); + const indexNode = indexNodeCalculator(theorySize, sVal); + const proxy = proxyCalculator(memorySize); + const queryNode = queryNodeCalculator(memorySize); + const commonCoord = commonCoordCalculator(memorySize); + const mixCoord = mixCoordCalculator(nbVal); + const etcdData = etcdCalculator(rawFileSize); + const minioData = minioCalculator(rawFileSize, theorySize); + const pulsarData = pulsarCalculator(rawFileSize); + const kafkaData = kafkaCalculator(rawFileSize); + return { + memorySize: unitBYTE2Any(memorySize), + rawFileSizeByte: rawFileSize, + rawFileSize: unitBYTE2Any(rawFileSize), + mixCoord, + indexNode, + dataNode, + queryNode, + proxy, + commonCoord, + etcdData, + minioData, + pulsarData, + kafkaData, + }; + }, [form]); + const { milvusData, dependencyData, totalData, queryNodeDiskData } = + useMemo(() => { + const calculateList = [ + calcResult.proxy, + calcResult.mixCoord, + calcResult.indexNode, + calcResult.dataNode, + calcResult.queryNode, + ]; + + const milvusData = { + core: calculateList.reduce((acc, cur) => { + acc += cur.cpu * cur.amount; + return acc; + }, 0), + memory: calculateList.reduce((acc, cur) => { + acc += cur.memory * cur.amount; + return acc; + }, 0), + }; + + const secondPartData = { + core: + calcResult.etcdData.cpu * calcResult.etcdData.podNumber + + calcResult.minioData.cpu * calcResult.minioData.podNumber, + memory: + calcResult.etcdData.memory * calcResult.etcdData.podNumber + + calcResult.minioData.memory * calcResult.minioData.podNumber, + ssd: + calcResult.etcdData.pvcPerPodUnit === 'G' + ? calcResult.etcdData.pvcPerPodSize * calcResult.etcdData.podNumber + : (calcResult.etcdData.pvcPerPodSize * + calcResult.etcdData.podNumber) / + 1024, + disk: + calcResult.minioData.pvcPerPodUnit === 'G' + ? calcResult.minioData.pvcPerPodSize * + calcResult.minioData.podNumber + : (calcResult.minioData.pvcPerPodSize * + calcResult.minioData.podNumber) / + 1024, + }; + + const pulsarBookieData = + calcResult.pulsarData.bookie.ledgers.unit === 'G' + ? calcResult.pulsarData.bookie.ledgers.size * + calcResult.pulsarData.bookie.podNum.value + : (calcResult.pulsarData.bookie.ledgers.size * + calcResult.pulsarData.bookie.podNum.value) / + 1024; + const pulsarZookeeperData = + calcResult.pulsarData.zookeeper.pvc.unit === 'G' + ? calcResult.pulsarData.zookeeper.pvc.size * + calcResult.pulsarData.zookeeper.podNum.value + : (calcResult.pulsarData.zookeeper.pvc.size * + calcResult.pulsarData.zookeeper.podNum.value) / + 1024; + const pulsarData = { + core: Object.values(calcResult.pulsarData).reduce((acc, cur) => { + acc += cur.cpu.size * cur.podNum.value; + return acc; + }, 0), + memory: Object.values(calcResult.pulsarData).reduce((acc, cur) => { + acc += cur.memory.size * cur.podNum.value; + return acc; + }, 0), + ssd: pulsarBookieData + pulsarZookeeperData, + disk: + calcResult.pulsarData.bookie.journal.unit === 'G' + ? calcResult.pulsarData.bookie.journal.size * + calcResult.pulsarData.bookie.podNum.value + : (calcResult.pulsarData.bookie.journal.size * + calcResult.pulsarData.bookie.podNum.value) / + 1024, + }; + + const kafkaData = { + core: Object.values(calcResult.kafkaData).reduce((acc, cur) => { + acc += cur.cpu.size * cur.podNum.value; + return acc; + }, 0), + memory: Object.values(calcResult.kafkaData).reduce((acc, cur) => { + acc += cur.memory.size * cur.podNum.value; + return acc; + }, 0), + ssd: Object.values(calcResult.kafkaData).reduce((acc, cur) => { + if (cur.pvc?.isSSD) { + if (cur.pvc.unit === 'G') { + acc += cur.pvc.size * cur.podNum.value; + } else { + acc += (cur.pvc.size * cur.podNum.value) / 1024; + } + } + return acc; + }, 0), + disk: Object.values(calcResult.kafkaData).reduce((acc, cur) => { + if (!cur.pvc?.isSSD) { + if (cur.pvc.unit === 'G') { + acc += cur.pvc.size * cur.podNum.value; + } else { + acc += (cur.pvc.size * cur.podNum.value) / 1024; + } + } + return acc; + }, 0), + }; + + const thirdPartData = + form.apacheType === 'pulsar' ? pulsarData : kafkaData; + + let queryNodeDiskData = undefined; + + if (form.indexType.value === IndexTypeEnum.DISKANN) { + const rawFileSizeValue = unitBYTE2Any(calcResult.rawFileSizeByte, 'GB'); + queryNodeDiskData = { + totalQueryNodeDisk: Math.ceil(rawFileSizeValue.size * 10) / 10, + key: 'SSD', + value: `${ + Math.ceil( + (rawFileSizeValue.size / calcResult.queryNode.amount) * 10 + ) / 10 + } GB`, + }; + } + + const totalData = { + core: milvusData.core + secondPartData.core + thirdPartData.core, + memory: + milvusData.memory + secondPartData.memory + thirdPartData.memory, + ssd: queryNodeDiskData + ? Math.ceil(secondPartData.ssd + thirdPartData.ssd) + + queryNodeDiskData.totalQueryNodeDisk + : Math.ceil(secondPartData.ssd + thirdPartData.ssd), + disk: Math.ceil(secondPartData.disk + thirdPartData.disk), + }; + + const dependencyData = { + core: secondPartData.core + thirdPartData.core, + memory: secondPartData.memory + thirdPartData.memory, + ssd: Math.ceil(secondPartData.ssd + thirdPartData.ssd), + disk: Math.ceil(secondPartData.disk + thirdPartData.disk), + }; + + return { + milvusData, + dependencyData, + totalData, + queryNodeDiskData, + }; + }, [calcResult, form.apacheType]); + + const handleDownloadYmlFile = (content, fileName) => { + if (typeof window !== 'undefined') { + const blob = new Blob([content], { + type: 'text/plain', + }); + + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = `${fileName}.yml`; + a.click(); + } + }; + + const handleDownloadHelm = () => { + const content = helmYmlGenerator(calcResult, form.apacheType); + handleDownloadYmlFile(content, HELM_CONFIG_FILE_NAME); + }; + const handleDownloadOperator = () => { + const content = operatorYmlGenerator(calcResult, form.apacheType); + handleDownloadYmlFile(content, OPERATOR_CONFIG_FILE_NAME); + }; + + const handleCloseDialog = () => { + setDialogState(v => ({ + ...v, + open: false, + })); + }; + + const handleOpenInstallGuide = type => { + const title = type === 'helm' ? t('buttons.helm') : t('buttons.operator'); + + setDialogState({ + open: true, + title, + children: , + }); + }; + + return ( +
+ + + + Milvus Sizing Tool ยท Vector Database built for scalable similarity + search + + + + +
+

{t('title')}

+
+ + + +

{t('subTitle')}

+
+
+
+
+

{t('labels.dataSize')}

+ +
+

{t('labels.vector')}

+ hanldeRemovePromot('nb')} + onChange={e => { + handleFormValueChange(e.target.value, FromKeysEnum.Nb); + }} + /> +
+ +
+

{t('labels.dimension')}

+ hanldeRemovePromot('d')} + onChange={e => { + handleFormValueChange(e.target.value, FromKeysEnum.D); + }} + /> +
+
+ +
+

{t('labels.indexType')}

+ +
+ + {t('labels.index')} + + +
+ +
+ {form.indexType.value === 'FLAT' ? null : form.indexType + .value === 'HNSW' ? ( + <> +

+ {t('labels.indexParam')} +

+

+ {t('labels.m')} +

+
+ { + handleFormValueChange(value, FromKeysEnum.M); + }} + marks={[ + { label: '4', value: 4 }, + { label: '64', value: 64 }, + ]} + /> +
+ + ) : ( + <> +

{t('labels.m')}

+ hanldeRemovePromot('nlist')} + onChange={e => { + handleFormValueChange( + e.target.value, + FromKeysEnum.Nlist + ); + }} + /> + + )} +
+ +
+

{t('labels.segmentSize')}

+ + {t('labels.segment')} + + + + + + } + label="Pulsar" + /> + } + label="Kafka" + /> + + +
+
+
+
+
+

{t('capacity')}

+ +
+ + +
+
+ +
+

{t('setups.title')}

+ +
+
+
+

{t('total')}

+

+ {t('coreInfo', { + core: totalData.core, + memory: totalData.memory, + })} +

+
+
+
+
+

{t('ssd')}

+

+ {t('sizeInfo', { size: totalData.ssd })} +

+
+
+

{t('disk')}

+

+ {t('sizeInfo', { size: totalData.disk })} +

+
+
+
+
+
+
+

{t('milvus')}

+
+
+

{t('total')}: 

+

+ {t('coreInfo', { + core: milvusData.core, + memory: milvusData.memory, + })} +

+
+ {queryNodeDiskData && ( +
+

SSD: 

+

+ {queryNodeDiskData.totalQueryNodeDisk} GB +

+
+ )} +
+
+ +
+
+ + +
+ +
+ + + +
+
+
+
+
+

{t('dependency')}

+
+
+

{t('total')}: 

+

+ {t('coreInfo', { + core: dependencyData.core, + memory: dependencyData.memory, + })} +

+
+
+

{t('ssd')}: 

+

+ {t('sizeInfo', { size: dependencyData.ssd })} +

+
+
+

{t('disk')}: 

+

+ {t('sizeInfo', { size: dependencyData.disk })} +

+
+
+
+ +
+ + + +

{t('dependencyNote')}

+
+ +
+ + {/* Minio */} + +
+ + {/* pulsar or kafka */} +
+ {form.apacheType === 'pulsar' ? ( + + ) : ( + + )} +
+
+ +
+
+ } + > + {t('buttons.helm')} + + handleOpenInstallGuide('helm')} + variant="outlined" + > + {t('buttons.guide')} + +
+
+ } + > + {t('buttons.operator')} + + handleOpenInstallGuide('operator')} + variant="outlined" + > + {t('buttons.guide')} + +
+
+
+
+
+
+ +
+ ); +} diff --git a/src/blogs b/src/blogs index b72db0343..7606b0382 160000 --- a/src/blogs +++ b/src/blogs @@ -1 +1 @@ -Subproject commit b72db0343377b55e360a3ef49d2c65a5af305254 +Subproject commit 7606b038255ed1bc7b6081dc2ec0b6d5db319778 diff --git a/src/components/customButton/index.tsx b/src/components/customButton/index.tsx index c88afbec8..4ae584bc1 100644 --- a/src/components/customButton/index.tsx +++ b/src/components/customButton/index.tsx @@ -8,6 +8,7 @@ export interface CustomButtonProps { variant?: 'contained' | 'outlined' | 'text'; color?: 'primary' | 'secondary'; endIcon?: React.ReactNode; + startIcon?: React.ReactNode; disabled?: boolean; size?: 'small' | 'medium' | 'large'; classes?: { @@ -25,6 +26,7 @@ function CustomLink(props: CustomButtonProps & { computedClasses: any }) { variant = 'contained', color = 'primary', endIcon, + startIcon, disabled = false, classes: customClasses = {}, computedClasses, @@ -46,6 +48,9 @@ function CustomLink(props: CustomButtonProps & { computedClasses: any }) { href={href} className={clsx(classes.linkButton, className, root, computedClasses)} > + {startIcon && ( + {startIcon} + )} {children} {endIcon && ( {endIcon} @@ -76,6 +81,7 @@ const CustomButton = (props: CustomButtonProps) => { variant = 'contained', color = 'primary', endIcon, + startIcon, disabled = false, classes: customClasses = {}, size = 'medium', @@ -106,6 +112,9 @@ const CustomButton = (props: CustomButtonProps) => { {...rest} className={clsx(classes.linkButton, className, root, computedClasses)} > + {startIcon && ( + {startIcon} + )} {children} {endIcon && ( {endIcon} diff --git a/src/components/icons/ArrowRightAltIcon.tsx b/src/components/icons/ArrowRightAltIcon.tsx index bfbeefcd7..1c77dc409 100644 --- a/src/components/icons/ArrowRightAltIcon.tsx +++ b/src/components/icons/ArrowRightAltIcon.tsx @@ -13,7 +13,7 @@ export const ArrowRightAltIcon = (props: { size?: number; color?: string }) => { stroke="#00131A" strokeWidth="1.3" strokeLinecap="round" - stroke-linejoin="round" + strokeLinejoin="round" /> ); diff --git a/src/components/icons/index.tsx b/src/components/icons/index.tsx index b1bcf4547..46f1736a5 100644 --- a/src/components/icons/index.tsx +++ b/src/components/icons/index.tsx @@ -71,11 +71,11 @@ export const DownloadIcon = () => ( > ); @@ -351,6 +351,41 @@ export const ThumbDownIcon: FC<{ color?: string }> = ({ ); +export const ExternalLinkIcon: FC<{ color?: string }> = ({ color }) => ( + + + + +); + +export const ArrowTop = () => ( + + + +); export const TrendingIcon: FC> = props => { return ( { + unit?: string; + label?: React.ReactNode; + customSize?: 'small' | 'medium'; + fullWidth?: boolean; + classes?: { + root?: string; + input?: string; + unit?: string; + }; +} + +const SizingInput = React.forwardRef( + ( + { + className, + unit, + label = '', + customSize = 'medium', + classes: customClasses, + fullWidth = false, + type, + placeholder, + ...props + }, + ref + ) => { + const { + root = '', + input: inputClass = '', + unit: unitClass = '', + } = customClasses || {}; + return ( +
+ {label &&
{label}
} +
+ + {unit && {unit}} +
+
+ ); + } +); +SizingInput.displayName = 'SizingInput'; + +export { SizingInput }; diff --git a/src/components/sizing/sizingRange/index.module.less b/src/components/sizing/sizingRange/index.module.less new file mode 100644 index 000000000..3eaed554d --- /dev/null +++ b/src/components/sizing/sizingRange/index.module.less @@ -0,0 +1,43 @@ +@import url('@/styles/global.module.less'); + +.rangeContainer { + width: 100%; +} + +.label { + .paragraph4(); +} + +.customMarkLabel { + .paragraph6(); + top: 20px; + color: #667176; +} + +.customMark { + background-color: #00b3ff; +} + +.customRail { + height: 4px; + background-color: #e0f2fc; +} + +.customTrack { + background-color: #00b3ff; + border: none; +} + +.customThumb { + width: 14px; + height: 14px; + border-radius: 50%; + background-color: #00b3ff; + border: 2px solid #fff; +} + +.rangeWrapper { + display: flex; + align-items: center; + gap: 32px; +} diff --git a/src/components/sizing/sizingRange/index.tsx b/src/components/sizing/sizingRange/index.tsx new file mode 100644 index 000000000..dac319b8b --- /dev/null +++ b/src/components/sizing/sizingRange/index.tsx @@ -0,0 +1,186 @@ +import { useState, useEffect } from 'react'; +import { styled } from '@mui/material/styles'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; +import Slider from '@mui/material/Slider'; +import classes from './index.module.less'; +import clsx from 'clsx'; +import { scaleLinear, scalePow } from 'd3-scale'; +import { SizingInput } from '../sizingInput'; +import { init } from 'next/dist/compiled/webpack/webpack'; + +type RangeConfigType = { + min: number; + max: number; + defaultValue: number; + range: number[]; + domain: number[]; +}; + +interface SizingRangePropsType { + rangeConfig: RangeConfigType; + label?: React.ReactNode; + classes?: { + root?: string; + label?: string; + }; + value: number; + onRangeChange: (value: number) => void; + unit?: string; + placeholder?: string; +} + +export const SizingRange = (props: SizingRangePropsType) => { + const { + rangeConfig, + label, + classes: customClasses = {}, + onRangeChange, + unit, + value, + placeholder, + } = props; + const { root, label: labelCLass } = customClasses; + + // domain: 0 - 100 under progress + // range: real value + const getRangeValue = (value: number) => { + const rangeScale = scaleLinear() + .domain(rangeConfig.domain) + .range(rangeConfig.range); + + const rangeValue = Math.floor(rangeScale(value)); + + if (rangeValue <= rangeConfig.min) { + return rangeConfig.min; + } + if (rangeValue >= rangeConfig.max) { + return rangeConfig.max; + } + return rangeValue; + }; + + const getDomainValue = (value: number) => { + const domainScale = scaleLinear() + .domain(rangeConfig.range) + .range(rangeConfig.domain); + + const domainValue = domainScale(value); + if (domainValue <= 0) { + return 0; + } + if (domainValue >= 100) { + return 100; + } + return domainValue; + }; + + const [initValue, setInitValue] = useState({ + rangeValue: value, + domainValue: getDomainValue(value), + inputValue: `${value}`, + }); + + const marks = rangeConfig.domain.map((item, index) => { + return { + value: item, + label: rangeConfig.range[index], + }; + }); + + function valuetext(value: number) { + return `${value}`; + } + + // props is domain value; [0,100] + const handleRangeChange = (newValue: number | number[]) => { + const rangeValue = getRangeValue(newValue as number); + setInitValue({ + rangeValue: rangeValue, + domainValue: newValue, + inputValue: `${rangeValue}`, + }); + onRangeChange(rangeValue); + }; + + const handleInputChange = (e: React.ChangeEvent) => { + const inputValue = e.target.value; + let value = Number(inputValue); // range value + if (isNaN(value)) { + return; + } + + value = + value <= rangeConfig.min + ? rangeConfig.min + : value >= rangeConfig.max + ? rangeConfig.max + : value; + + const domainValue = getDomainValue(value); + + setInitValue({ + rangeValue: value, + domainValue, + inputValue, + }); + onRangeChange(value); + }; + + const handleFormatInput = (e: React.ChangeEvent) => { + const inputValue = e.target.value; + let value = Number(inputValue); // range value + + value = + value <= rangeConfig.min + ? rangeConfig.min + : value >= rangeConfig.max + ? rangeConfig.max + : value; + + const domainValue = getDomainValue(value); + console.log(value); + + setInitValue({ + rangeValue: value, + domainValue, + inputValue: `${value}`, + }); + onRangeChange(value); + }; + + return ( +
+ {label &&

{label}

} +
+ { + handleRangeChange(newVal); + }} + classes={{ + markLabel: classes.customMarkLabel, + markLabelActive: classes.customMarkLabel, + mark: classes.customMark, + markActive: classes.customMark, + rail: classes.customRail, + track: classes.customTrack, + thumb: classes.customThumb, + }} + /> + +
+
+ ); +}; diff --git a/src/components/sizing/sizingSwitch/index.module.less b/src/components/sizing/sizingSwitch/index.module.less new file mode 100644 index 000000000..fec4fcdb6 --- /dev/null +++ b/src/components/sizing/sizingSwitch/index.module.less @@ -0,0 +1,10 @@ +.visible { + visibility: visible; + opacity: 1; + width: 12px; + height: 12px; +} +.invisible { + visibility: hidden; + opacity: 0; +} diff --git a/src/components/sizing/sizingSwitch/index.tsx b/src/components/sizing/sizingSwitch/index.tsx new file mode 100644 index 000000000..548656fab --- /dev/null +++ b/src/components/sizing/sizingSwitch/index.tsx @@ -0,0 +1,52 @@ +import classes from './index.module.less'; +import * as React from 'react'; +import * as SwitchPrimitives from '@radix-ui/react-switch'; + +import { cn } from '@/utils/merge'; + +const SizingSwitch = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, checked, onChange, ...props }, ref) => { + return ( + + + + + + + + + + ); +}); +SizingSwitch.displayName = 'SizingSwitch'; + +export { SizingSwitch }; diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx new file mode 100644 index 000000000..c9b095a2d --- /dev/null +++ b/src/components/ui/checkbox.tsx @@ -0,0 +1,28 @@ +import * as React from 'react'; +import * as CheckboxPrimitive from '@radix-ui/react-checkbox'; +import { Check } from 'lucide-react'; + +import { cn } from '@/utils/merge'; + +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)); +Checkbox.displayName = CheckboxPrimitive.Root.displayName; + +export { Checkbox }; diff --git a/src/components/ui/collapsible.tsx b/src/components/ui/collapsible.tsx new file mode 100644 index 000000000..a23e7a281 --- /dev/null +++ b/src/components/ui/collapsible.tsx @@ -0,0 +1,9 @@ +import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" + +const Collapsible = CollapsiblePrimitive.Root + +const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger + +const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent + +export { Collapsible, CollapsibleTrigger, CollapsibleContent } diff --git a/src/components/ui/index.ts b/src/components/ui/index.ts new file mode 100644 index 000000000..a3a2caaa4 --- /dev/null +++ b/src/components/ui/index.ts @@ -0,0 +1,6 @@ +export * from './checkbox'; +export * from './collapsible'; +export * from './radio-group'; +export * from './checkbox'; +export * from './select'; +export * from './tooltip'; diff --git a/src/components/ui/radio-group.tsx b/src/components/ui/radio-group.tsx new file mode 100644 index 000000000..b875ee9c5 --- /dev/null +++ b/src/components/ui/radio-group.tsx @@ -0,0 +1,42 @@ +import * as React from 'react'; +import * as RadioGroupPrimitive from '@radix-ui/react-radio-group'; +import { Circle } from 'lucide-react'; + +import { cn } from '@/utils/merge'; + +const RadioGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + return ( + + ); +}); +RadioGroup.displayName = RadioGroupPrimitive.Root.displayName; + +const RadioGroupItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + return ( + + + + + + ); +}); +RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName; + +export { RadioGroup, RadioGroupItem }; diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx index 608ccbbc3..ed2c5a744 100644 --- a/src/components/ui/select.tsx +++ b/src/components/ui/select.tsx @@ -35,7 +35,7 @@ const SelectTrigger = React.forwardRef< )); -SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; +SelectTrigger.displayName = 'SelectTrigger'; const SelectScrollUpButton = React.forwardRef< React.ElementRef, diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx new file mode 100644 index 000000000..fa23a5d3e --- /dev/null +++ b/src/components/ui/tooltip.tsx @@ -0,0 +1,28 @@ +import * as React from 'react'; +import * as TooltipPrimitive from '@radix-ui/react-tooltip'; + +import { cn } from '@/utils/merge'; + +const TooltipProvider = TooltipPrimitive.Provider; + +const Tooltip = TooltipPrimitive.Root; + +const TooltipTrigger = TooltipPrimitive.Trigger; + +const TooltipContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + +)); +TooltipContent.displayName = TooltipPrimitive.Content.displayName; + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; diff --git a/src/consts/sizing.ts b/src/consts/sizing.ts new file mode 100644 index 000000000..56c7a6638 --- /dev/null +++ b/src/consts/sizing.ts @@ -0,0 +1,321 @@ +import { + SegmentSizeEnum, + IndexTypeEnum, + DependencyComponentEnum, + ModeEnum, +} from '@/types/sizing'; + +export const ONE_MILLION = Math.pow(10, 6); +export const ONE_BILLION = Math.pow(10, 9); + +export const VECTOR_RANGE_CONFIG = { + min: 1, + max: 10000, + defaultValue: 1, + domain: [0, 25, 50, 75, 100], + range: [1, 10, 100, 1000, 10000], +}; + +export const DIMENSION_RANGE_CONFIG = { + min: 2, + max: 32768, + defaultValue: 128, + domain: [0, 25, 50, 75, 100], + range: [2, 128, 768, 1536, 32768], +}; + +export const MAX_NODE_DEGREE_RANGE_CONFIG = { + min: 1, + max: 2048, + defaultValue: 56, + domain: [0, 20, 40, 60, 80, 100], + range: [1, 8, 32, 128, 512, 2048], +}; + +export const M_RANGE_CONFIG = { + min: 2, + max: 2048, + defaultValue: 30, + domain: [0, 20, 40, 60, 80, 100], + range: [2, 8, 32, 128, 512, 2048], +}; + +export const N_LIST_RANGE_CONFIG = { + min: 1, + max: 65536, + defaultValue: 128, + domain: [0, 10, 20, 40, 70, 100], + range: [1, 16, 128, 4096, 16384, 65536], +}; + +export const SEGMENT_SIZE_OPTIONS = [ + { + label: '512MB', + value: SegmentSizeEnum._512MB, + }, + { + label: '1024MB', + value: SegmentSizeEnum._1024MB, + }, + { + label: '2048MB', + value: SegmentSizeEnum._2048MB, + }, +]; + +export const INDEX_TYPE_OPTIONS = [ + { + label: 'FLAT', + value: IndexTypeEnum.FLAT, + }, + { + label: 'SCANN', + value: IndexTypeEnum.SCANN, + }, + { + label: 'HNSW', + value: IndexTypeEnum.HNSW, + }, + { + label: 'IVF_FLAT', + value: IndexTypeEnum.IVF_FLAT, + }, + { + label: 'IVFSQ8', + value: IndexTypeEnum.IVFSQ8, + }, + { + label: 'DISKANN', + value: IndexTypeEnum.DISKANN, + }, +]; + +export const DEPENDENCY_COMPONENTS = [ + { + label: 'Pulsar', + value: DependencyComponentEnum.Pulsar, + }, + { + label: 'Kafka', + value: DependencyComponentEnum.Kafka, + }, +]; + +export const MODE_OPTIONS = [ + { + label: 'Standalone', + value: ModeEnum.Standalone, + }, + { + label: 'Cluster', + value: ModeEnum.Cluster, + }, +]; + +export const FIXED_QUERY_NODE_CONFIG = [ + { + memory: [0, 8], + queryNode: { + cpu: 2, + memory: 8, + count: 1, + }, + proxy: { + cpu: 1, + memory: 4, + count: 1, + }, + mixCoord: { + cpu: 1, + memory: 4, + count: 1, + }, + dataNode: { + cpu: 2, + memory: 8, + count: 1, + }, + indexNode: { + cpu: 2, + memory: 8, + count: 1, + }, + }, + { + memory: [8, 16], + queryNode: { + cpu: 4, + memory: 16, + count: 1, + }, + proxy: { + cpu: 2, + memory: 8, + count: 1, + }, + mixCoord: { + cpu: 2, + memory: 8, + count: 1, + }, + dataNode: { + cpu: 2, + memory: 8, + count: 1, + }, + indexNode: { + cpu: 4, + memory: 16, + count: 1, + }, + }, + { + memory: [16, 32], + queryNode: { + cpu: 4, + memory: 16, + count: 2, + }, + proxy: { + cpu: 2, + memory: 8, + count: 1, + }, + mixCoord: { + cpu: 2, + memory: 8, + count: 1, + }, + dataNode: { + cpu: 2, + memory: 8, + count: 1, + }, + indexNode: { + cpu: 4, + memory: 16, + count: 2, + }, + }, + { + memory: [32, 48], + queryNode: { + cpu: 4, + memory: 16, + count: 3, + }, + proxy: { + cpu: 2, + memory: 8, + count: 1, + }, + mixCoord: { + cpu: 2, + memory: 8, + count: 1, + }, + dataNode: { + cpu: 2, + memory: 8, + count: 1, + }, + indexNode: { + cpu: 4, + memory: 16, + count: 2, + }, + }, + { + memory: [48, 64], + queryNode: { + cpu: 4, + memory: 16, + count: 4, + }, + proxy: { + cpu: 2, + memory: 8, + count: 1, + }, + mixCoord: { + cpu: 2, + memory: 8, + count: 1, + }, + dataNode: { + cpu: 2, + memory: 8, + count: 1, + }, + indexNode: { + cpu: 4, + memory: 16, + count: 2, + }, + }, + { + memory: [64, 80], + queryNode: { + cpu: 4, + memory: 16, + count: 5, + }, + proxy: { + cpu: 2, + memory: 8, + count: 1, + }, + mixCoord: { + cpu: 2, + memory: 8, + count: 1, + }, + dataNode: { + cpu: 2, + memory: 8, + count: 1, + }, + indexNode: { + cpu: 4, + memory: 16, + count: 4, + }, + }, + { + memory: [80, 96], + queryNode: { + cpu: 4, + memory: 16, + count: 6, + }, + proxy: { + cpu: 2, + memory: 8, + count: 1, + }, + mixCoord: { + cpu: 2, + memory: 8, + count: 1, + }, + dataNode: { + cpu: 2, + memory: 8, + count: 1, + }, + indexNode: { + cpu: 4, + memory: 16, + count: 4, + }, + }, +]; + +export const helmCodeExample = `helm repo add milvus https://zilliztech.github.io/milvus-helm/helm repo update helm install my-release milvus/milvus -f helmConfigYml.yml +`; + +export const operatorCodeExample = `helm repo add milvus-operator https://zilliztech.github.io/milvus-operator/ +helm repo update milvus-operator +helm -n milvus-operator upgrade --install milvus-operator milvus-operator/milvus-operator +kubectl create -f operatorConfigYml.yml +`; diff --git a/src/docs b/src/docs index 324771420..b5b2c7cd1 160000 --- a/src/docs +++ b/src/docs @@ -1 +1 @@ -Subproject commit 32477142052fb3a6dab6bf094ad54da1982a4999 +Subproject commit b5b2c7cd15c378ff69d21a5f7f2510c7576ec892 diff --git a/src/i18n/en/sizingToolV2.json b/src/i18n/en/sizingToolV2.json new file mode 100644 index 000000000..7f5d5e2b6 --- /dev/null +++ b/src/i18n/en/sizingToolV2.json @@ -0,0 +1,83 @@ +{ + "title": "Milvus Sizing Tool", + "content": "A tool for estimate the resources required to set up Milvus based on your dataset.", + "tooltip": "Note: all the recommendations are calculated based on our lab data, you should adjust it with your own testing before deploying to production. If you have any question, please <0>contact us.", + "form": { + "num": "Number of Vector", + "dim": "Vector Dimension", + "withScalar": "With Scalar Fields", + "averageLength": "Average Data Size Per Row", + "offloading": "Offloading Fields to Disk", + "mmp": "Milvus uses <0>Mmap to enable direct memory access to large files on disk without reading the entire files into memory.", + "indexType": "Index Type", + "withRawData": "With_raw_data", + "m": "M(Maximum degree of the node)", + "nlist": "nlist", + "maxDegree": "Max Degree", + "segmentSize": "Segment Size", + "dependencyComp": "Dependency Component", + "mode": "Mode", + "modeTip": "With data growth, you can migrate data from standalone mode to cluster mode. <0>View More", + "modeDisableTip": "Donโ€˜t support standalone mode when number of vector exceed 10 Million or XXXXXXXX.", + "pulsar": "Pulsar", + "kafka": "Kafka", + "standalone": "Standalone", + "standaloneDesc": "Suitable for small data size and poc env.", + "cluster": "Cluster", + "clusterDesc": "Suitable for large datasize and production env. " + }, + "overview": { + "title": "Approximate Capacity", + "raw": "Raw Data Size", + "rawTooltip": "Raw data size - Original data size, depending on the number of Vector, Vector Dimension and Scalar Field data size", + "memory": "Loading Memory", + "memoryTooltip": "Memory consumption for query nodes, depending on raw data size, index type and params" + }, + "setup": { + "title": "Minimum Milvus cluster setup in total", + "share": "Scaling cluster or dependencies", + "milvus": { + "title": "Milvus", + "proxy": "Proxy", + "mixCoord": "Mix Coord", + "dataNode": "Data Node", + "indexNode": "Index Node", + "queryNode": "Query Node" + }, + "dependency": { + "title": "Dependencies", + "etcd": "ETCD", + "minio": "Minio", + "pulsar": "Pulsar", + "kafka": "Kafka", + "bookie": "Bookie <0>x{{count}}", + "broker": "Broker <0>x{{count}}", + "proxy": "Proxy <0>x{{count}}", + "zookeeper": "Zookeeper <0>x{{count}}" + }, + "basic": { + "cpuAndMemory": "CPU and Memory: <0>{{cpu}} Core {{memory}} GB", + "storageWithValue": "Storage: <0>{{storage}} GB", + "diskWithValue": "Local Disk: <0>{{disk}} {{unit}}", + "cpu": "CPU", + "memory": "Memory", + "disk": "Local Disk", + "storage": "Storage", + "podNum": "Pod Number", + "pvc": "Pcv per Pod: <0>{{pvc}} GB", + "pvcLabel": "Pcv per Pod", + "journal": "Journal", + "ledger": "Ledgers", + "config": "{{cpu}} Core {{memory}} GB", + "core": "{{cpu}} Core", + "gb": "{{memory}} GB", + "byte": "Bytes" + } + }, + "install": { + "title": "Install Milvus", + "helm": "Helm Chart configuration.yml", + "operator": "Milvus Operator configuration.yml", + "adv": "Too many resources consumed? Try Zilliz Cloud (managed Milvus) to save resources and cut deployment and operation costs. Check out more details with the Zilliz Cloud calculator. <0>Go to Zilliz Cloud calculator." + } +} diff --git a/src/i18n/index.ts b/src/i18n/index.ts index 2a9555d15..8a9096e66 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -11,6 +11,7 @@ import sizingToolEn from './en/sizingTool.json'; import useCaseEn from './en/useCase.json'; import communityEn from './en/community.json'; import notFoundEn from './en/404.json'; +import sizingToolV2 from './en/sizingToolV2.json'; import blogEn from './en/blog.json'; import docsCn from './cn/docs.json'; @@ -40,6 +41,7 @@ export const resources = { demo: demoEn, community: communityEn, notFound: notFoundEn, + sizingToolV2, blog: blogEn, }, cn: { diff --git a/src/pages/blog/index.tsx b/src/pages/blog/index.tsx index 716eb3222..29fa67936 100644 --- a/src/pages/blog/index.tsx +++ b/src/pages/blog/index.tsx @@ -10,8 +10,6 @@ import { ABSOLUTE_BASE_URL } from '@/consts'; import { AirplaneArrowIcon, ClockIcon, - ListItemTickIcon, - RightTopArrowIcon, RocketIcon, SearchIcon, TrendingIcon, @@ -31,6 +29,7 @@ import Link from 'next/link'; import dayjs from 'dayjs'; import { Trans, useTranslation } from 'react-i18next'; import { InkeepCustomTriggerWrapper } from '@/components/inkeep/inkeepChat'; +import ZillizAdv from '@/parts/blogs/zillizAdv'; const PAGE_SIZE = 9; const TITLE = 'Learn Milvus: Insights and Innovations in VectorDB Technology'; @@ -47,9 +46,6 @@ const SEARCH_QUERY_KEY = 'q'; const ELLIPSIS = 'ellipsis'; const ELLIPSIS_2 = 'ellipsis2'; -const CTA_LINK = - 'https://cloud.zilliz.com/signup?utm_source=partner&utm_medium=referral&utm_campaign=2024-12-19_blog_overview-page_milvusio'; - const generatePaginationNavigators = ( currentPage: number, totalPages: number @@ -528,46 +524,6 @@ const Blog: React.FC = props => { ); }; - const renderZillizAdv = () => { - const features = [ - t('blog:zillizAdv.feature1'), - t('blog:zillizAdv.feature2'), - ]; - - const featureItems = features.map(f => ( -
  • - - {f} -
  • - )); - - return ( -
    -
    -
    - {t('blog:zillizAdv.smallTitle')} -
    -

    - {t('blog:zillizAdv.title')} -

    -
      {featureItems}
    - - {t('blog:zillizAdv.btn')} - - -
    -
    -
    - ); - }; - const renderAuthors = () => { const authors = [ { @@ -641,7 +597,7 @@ const Blog: React.FC = props => { {renderPaging()} {renderAIService()} - {renderZillizAdv()} + {renderAuthors()} diff --git a/src/pages/error.tsx b/src/pages/error.tsx index e0b803276..0f0ba9e47 100644 --- a/src/pages/error.tsx +++ b/src/pages/error.tsx @@ -12,13 +12,13 @@ const RELOAD_LIMIT = 3; export default function Error(props: { error: Error; reset: () => void }) { const count = useRef(0); - useEffect(() => { - if (count.current >= RELOAD_LIMIT) { - redirect('/'); - } - count.current += 1; - window.location.reload(); - }, []); + // useEffect(() => { + // if (count.current >= RELOAD_LIMIT) { + // redirect('/'); + // } + // count.current += 1; + // window.location.reload(); + // }, []); return (
    diff --git a/src/pages/tools/sizing.tsx b/src/pages/tools/sizing.tsx index f7e85aea8..79bdc2b5c 100644 --- a/src/pages/tools/sizing.tsx +++ b/src/pages/tools/sizing.tsx @@ -1,458 +1,126 @@ import React, { useState, useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; import Layout from '@/components/layout/commonLayout'; import classes from '@/styles/sizingTool.module.less'; import pageClasses from '@/styles/responsive.module.less'; -import { InfoFilled, DownloadIcon } from '@/components/icons'; -import SizingToolCard from '@/components/card/sizingToolCard'; -import SizingConfigCard from '@/components/card/sizingToolCard/sizingConfigCard'; import clsx from 'clsx'; -import Slider from '@mui/material/Slider'; -import TextField from '@mui/material/TextField'; -import InputLabel from '@mui/material/InputLabel'; -import MenuItem from '@mui/material/MenuItem'; -import FormControl from '@mui/material/FormControl'; -import Select from '@mui/material/Select'; -import Radio from '@mui/material/Radio'; -import RadioGroup from '@mui/material/RadioGroup'; -import FormControlLabel from '@mui/material/FormControlLabel'; import Head from 'next/head'; -import CustomButton from '@/components/customButton'; - -import { - memorySizeCalculator, - rawFileSizeCalculator, - commonCoordCalculator, - unitBYTE2Any, - indexNodeCalculator, - queryNodeCalculator, - isBetween, - rootCoordCalculator, - dataNodeCalculator, - proxyCalculator, - helmYmlGenerator, - operatorYmlGenerator, - etcdCalculator, - minioCalculator, - pulsarCalculator, - kafkaCalculator, - mixCoordCalculator, - unitAny2BYTE, -} from '@/utils/sizingTool'; -import { CustomizedContentDialogs } from '@/components/dialog/Dialog'; -import HighlightBlock from '@/components/card/sizingToolCard/codeBlock'; -import { - HELM_CONFIG_FILE_NAME, - OPERATOR_CONFIG_FILE_NAME, - REQUIRE_MORE, - INDEX_TYPE_OPTIONS, - SEGMENT_SIZE_OPTIONS, - IndexTypeEnum, -} from '@/components/card/sizingToolCard/constants'; import { ABSOLUTE_BASE_URL } from '@/consts'; - -// one million -const $1M = Math.pow(10, 6); - -const defaultSizeContent = { - size: REQUIRE_MORE, +import FormSection from '@/parts/sizing/formSection'; +import ResultSection from '@/parts/sizing/resultSection'; +import { + DependencyComponentEnum, + ICalculateResult, + ModeEnum, +} from '@/types/sizing'; +import { InfoFilled } from '@/components/icons'; +import ZillizAdv from '@/parts/blogs/zillizAdv'; + +const etcdBaseValue = { cpu: 0, memory: 0, - amount: 0, + pvc: 0, + count: 0, +}; +const minioBaseValue = { + cpu: 0, + memory: 0, + pvc: 0, + count: 0, +}; +const pulsarBaseValue = { + bookie: { + cpu: 0, + memory: 0, + count: 0, + journal: 0, + ledgers: 0, + }, + broker: { + cpu: 0, + memory: 0, + count: 0, + }, + proxy: { + cpu: 0, + memory: 0, + count: 0, + }, + zookeeper: { + cpu: 0, + memory: 0, + count: 0, + pvc: 0, + }, +}; +const kafkaBaseValue = { + broker: { + cpu: 0, + memory: 0, + count: 0, + pvc: 0, + }, + zookeeper: { + cpu: 0, + memory: 0, + count: 0, + pvc: 0, + }, }; - -enum FromKeysEnum { - Nb = 'nb', - D = 'd', - IndexType = 'indexType', - M = 'm', - Nlist = 'nlist', - SegmentSize = 'segmentSize', - aApacheType = 'apacheType', -} export default function SizingTool() { - const { t } = useTranslation('sizingTool'); - - const [dialogState, setDialogState] = useState({ - open: false, - title: '', - children: <>, - }); - const [form, setForm] = useState<{ [key in FromKeysEnum]: any }>({ - // number of vectors - nb: { - value: 1, - showError: false, - helpText: '', - placeholder: `[1, 10000]`, - validation: { - validate: isBetween, - params: { min: 1, max: 10000 }, - errorMsg: 'Number of vectors should be an integer between [1, 10000]', + const { t } = useTranslation('sizingToolV2'); + const [calculatedResult, setCalculatedResult] = useState({ + rawDataSize: 0, + memorySize: 0, + localDiskSize: 0, + nodeConfig: { + queryNode: { + cpu: 0, + memory: 0, + count: 0, }, - }, - // dimensions - d: { - value: 128, - showError: false, - helpText: '', - placeholder: '[1, 10000]', - validation: { - validate: isBetween, - params: { min: 1, max: 10000 }, - errorMsg: 'Dimensions should be an integer between [1, 10000]', + proxy: { + cpu: 0, + memory: 0, + count: 0, }, - }, - // index type - indexType: { - value: INDEX_TYPE_OPTIONS[0].value, - showError: false, - }, - // index parameters - m: { - value: 8, - showError: false, - }, - // ivf parameters - nlist: { - value: 1024, - showError: false, - helpText: '', - placeholder: '[1, 10000]', - validation: { - validate: isBetween, - params: { min: 1, max: 10000 }, - errorMsg: 'nList should be an integer between [1, 10000]', + mixCoord: { + cpu: 0, + memory: 0, + count: 0, + }, + dataNode: { + cpu: 0, + memory: 0, + count: 0, + }, + indexNode: { + cpu: 0, + memory: 0, + count: 0, }, }, - // segment size - segmentSize: { - value: SEGMENT_SIZE_OPTIONS[0].value, - showError: false, + dependencyConfig: { + etcd: { + ...etcdBaseValue, + }, + minio: { + ...minioBaseValue, + }, + pulsar: { + ...pulsarBaseValue, + }, + kafka: { + ...kafkaBaseValue, + }, }, - apacheType: 'pulsar', + mode: ModeEnum.Standalone, + dependency: DependencyComponentEnum.Pulsar, }); - const handleFormValueChange = (val: any, key: FromKeysEnum) => { - const { validation } = form[key]; - - if (!validation) { - setForm(v => ({ - ...v, - [key]: { - ...v[key], - value: val, - }, - })); - return; - } - - const isValidated = validation.validate(val, validation.params); - if (isValidated) { - setForm(v => ({ - ...v, - [key]: { - ...v[key], - value: val, - showError: false, - helpText: '', - }, - })); - } else { - setForm(v => ({ - ...v, - [key]: { - ...v[key], - value: val, - showError: true, - helpText: validation.errorMsg, - }, - })); - } - }; - - const hanldeRemovePromot = key => { - if (!form[key].showError) { - return; - } - setForm(v => ({ - ...v, - [key]: { - ...v[key], - value: '', - showError: false, - helpText: '', - }, - })); - }; - - const handleApacheChange = (e, value) => { - setForm(v => ({ - ...v, - apacheType: value, - })); - }; - - const calcResult = useMemo(() => { - const { nb, d, indexType, nlist, m, segmentSize } = form; - - const nbVal = Number(nb.value) * $1M || 0; - const dVal = Number(d.value) || 0; - const nlistVal = Number(nlist.value) || 0; - const mVal = Number(m.value) || 0; - const sVal = Number(segmentSize.value) || 0; - - const isErrorParameters = Object.values(form).some( - v => v.showError || v.value === '' - ); - - if (isErrorParameters) { - const etcdData = etcdCalculator(); - const minioData = minioCalculator(); - const pulsarData = pulsarCalculator(); - const kafkaData = kafkaCalculator(); - return { - memorySize: { size: 0, unit: 'B' }, - rawFileSize: { size: 0, unit: 'B' }, - dataNode: defaultSizeContent, - queryNode: defaultSizeContent, - indexNode: defaultSizeContent, - proxy: defaultSizeContent, - mixCoord: defaultSizeContent, - commonCoord: defaultSizeContent, - etcdData, - minioData, - pulsarData, - kafkaData, - }; - } - - const { memorySize, theorySize } = memorySizeCalculator({ - nb: nbVal, - d: dVal, - nlist: nlistVal, - M: mVal, - indexType: indexType.value, - }); - - const rawFileSize = rawFileSizeCalculator({ d: dVal, nb: nbVal }); - const dataNode = dataNodeCalculator(nbVal); - const indexNode = indexNodeCalculator(theorySize, sVal); - const proxy = proxyCalculator(memorySize); - const queryNode = queryNodeCalculator(memorySize); - const commonCoord = commonCoordCalculator(memorySize); - const mixCoord = mixCoordCalculator(nbVal); - const etcdData = etcdCalculator(rawFileSize); - const minioData = minioCalculator(rawFileSize, theorySize); - const pulsarData = pulsarCalculator(rawFileSize); - const kafkaData = kafkaCalculator(rawFileSize); - return { - memorySize: unitBYTE2Any(memorySize), - rawFileSizeByte: rawFileSize, - rawFileSize: unitBYTE2Any(rawFileSize), - mixCoord, - indexNode, - dataNode, - queryNode, - proxy, - commonCoord, - etcdData, - minioData, - pulsarData, - kafkaData, - }; - }, [form]); - const { milvusData, dependencyData, totalData, queryNodeDiskData } = - useMemo(() => { - const calculateList = [ - calcResult.proxy, - calcResult.mixCoord, - calcResult.indexNode, - calcResult.dataNode, - calcResult.queryNode, - ]; - - const milvusData = { - core: calculateList.reduce((acc, cur) => { - acc += cur.cpu * cur.amount; - return acc; - }, 0), - memory: calculateList.reduce((acc, cur) => { - acc += cur.memory * cur.amount; - return acc; - }, 0), - }; - - const secondPartData = { - core: - calcResult.etcdData.cpu * calcResult.etcdData.podNumber + - calcResult.minioData.cpu * calcResult.minioData.podNumber, - memory: - calcResult.etcdData.memory * calcResult.etcdData.podNumber + - calcResult.minioData.memory * calcResult.minioData.podNumber, - ssd: - calcResult.etcdData.pvcPerPodUnit === 'G' - ? calcResult.etcdData.pvcPerPodSize * calcResult.etcdData.podNumber - : (calcResult.etcdData.pvcPerPodSize * - calcResult.etcdData.podNumber) / - 1024, - disk: - calcResult.minioData.pvcPerPodUnit === 'G' - ? calcResult.minioData.pvcPerPodSize * - calcResult.minioData.podNumber - : (calcResult.minioData.pvcPerPodSize * - calcResult.minioData.podNumber) / - 1024, - }; - - const pulsarBookieData = - calcResult.pulsarData.bookie.ledgers.unit === 'G' - ? calcResult.pulsarData.bookie.ledgers.size * - calcResult.pulsarData.bookie.podNum.value - : (calcResult.pulsarData.bookie.ledgers.size * - calcResult.pulsarData.bookie.podNum.value) / - 1024; - const pulsarZookeeperData = - calcResult.pulsarData.zookeeper.pvc.unit === 'G' - ? calcResult.pulsarData.zookeeper.pvc.size * - calcResult.pulsarData.zookeeper.podNum.value - : (calcResult.pulsarData.zookeeper.pvc.size * - calcResult.pulsarData.zookeeper.podNum.value) / - 1024; - const pulsarData = { - core: Object.values(calcResult.pulsarData).reduce((acc, cur) => { - acc += cur.cpu.size * cur.podNum.value; - return acc; - }, 0), - memory: Object.values(calcResult.pulsarData).reduce((acc, cur) => { - acc += cur.memory.size * cur.podNum.value; - return acc; - }, 0), - ssd: pulsarBookieData + pulsarZookeeperData, - disk: - calcResult.pulsarData.bookie.journal.unit === 'G' - ? calcResult.pulsarData.bookie.journal.size * - calcResult.pulsarData.bookie.podNum.value - : (calcResult.pulsarData.bookie.journal.size * - calcResult.pulsarData.bookie.podNum.value) / - 1024, - }; - - const kafkaData = { - core: Object.values(calcResult.kafkaData).reduce((acc, cur) => { - acc += cur.cpu.size * cur.podNum.value; - return acc; - }, 0), - memory: Object.values(calcResult.kafkaData).reduce((acc, cur) => { - acc += cur.memory.size * cur.podNum.value; - return acc; - }, 0), - ssd: Object.values(calcResult.kafkaData).reduce((acc, cur) => { - if (cur.pvc?.isSSD) { - if (cur.pvc.unit === 'G') { - acc += cur.pvc.size * cur.podNum.value; - } else { - acc += (cur.pvc.size * cur.podNum.value) / 1024; - } - } - return acc; - }, 0), - disk: Object.values(calcResult.kafkaData).reduce((acc, cur) => { - if (!cur.pvc?.isSSD) { - if (cur.pvc.unit === 'G') { - acc += cur.pvc.size * cur.podNum.value; - } else { - acc += (cur.pvc.size * cur.podNum.value) / 1024; - } - } - return acc; - }, 0), - }; - - const thirdPartData = - form.apacheType === 'pulsar' ? pulsarData : kafkaData; - - let queryNodeDiskData = undefined; - - if (form.indexType.value === IndexTypeEnum.DISKANN) { - const rawFileSizeValue = unitBYTE2Any(calcResult.rawFileSizeByte, 'GB'); - queryNodeDiskData = { - totalQueryNodeDisk: Math.ceil(rawFileSizeValue.size * 10) / 10, - key: 'SSD', - value: `${ - Math.ceil( - (rawFileSizeValue.size / calcResult.queryNode.amount) * 10 - ) / 10 - } GB`, - }; - } - - const totalData = { - core: milvusData.core + secondPartData.core + thirdPartData.core, - memory: - milvusData.memory + secondPartData.memory + thirdPartData.memory, - ssd: queryNodeDiskData - ? Math.ceil(secondPartData.ssd + thirdPartData.ssd) + - queryNodeDiskData.totalQueryNodeDisk - : Math.ceil(secondPartData.ssd + thirdPartData.ssd), - disk: Math.ceil(secondPartData.disk + thirdPartData.disk), - }; - - const dependencyData = { - core: secondPartData.core + thirdPartData.core, - memory: secondPartData.memory + thirdPartData.memory, - ssd: Math.ceil(secondPartData.ssd + thirdPartData.ssd), - disk: Math.ceil(secondPartData.disk + thirdPartData.disk), - }; - - return { - milvusData, - dependencyData, - totalData, - queryNodeDiskData, - }; - }, [calcResult, form.apacheType]); - - const handleDownloadYmlFile = (content, fileName) => { - if (typeof window !== 'undefined') { - const blob = new Blob([content], { - type: 'text/plain', - }); - - const url = URL.createObjectURL(blob); - - const a = document.createElement('a'); - a.href = url; - a.download = `${fileName}.yml`; - a.click(); - } - }; - - const handleDownloadHelm = () => { - const content = helmYmlGenerator(calcResult, form.apacheType); - handleDownloadYmlFile(content, HELM_CONFIG_FILE_NAME); - }; - const handleDownloadOperator = () => { - const content = operatorYmlGenerator(calcResult, form.apacheType); - handleDownloadYmlFile(content, OPERATOR_CONFIG_FILE_NAME); - }; - - const handleCloseDialog = () => { - setDialogState(v => ({ - ...v, - open: false, - })); - }; - - const handleOpenInstallGuide = type => { - const title = type === 'helm' ? t('buttons.helm') : t('buttons.operator'); - - setDialogState({ - open: true, - title, - children: , - }); + const updateCalculatedResult = (result: ICalculateResult) => { + setCalculatedResult(result); }; return ( @@ -470,434 +138,46 @@ export default function SizingTool() { hrefLang="en" /> -
    -

    {t('title')}

    -
    + +
    +

    {t('title')}

    +

    {t('content')}

    +
    -

    {t('subTitle')}

    -
    -
    -
    -
    -

    {t('labels.dataSize')}

    - -
    -

    {t('labels.vector')}

    - hanldeRemovePromot('nb')} - onChange={e => { - handleFormValueChange(e.target.value, FromKeysEnum.Nb); - }} - /> -
    - -
    -

    {t('labels.dimension')}

    - hanldeRemovePromot('d')} - onChange={e => { - handleFormValueChange(e.target.value, FromKeysEnum.D); - }} - /> -
    -
    - -
    -

    {t('labels.indexType')}

    - -
    - - {t('labels.index')} - - -
    - -
    - {form.indexType.value === 'FLAT' ? null : form.indexType - .value === 'HNSW' ? ( - <> -

    - {t('labels.indexParam')} -

    -

    - {t('labels.m')} -

    -
    - { - handleFormValueChange(value, FromKeysEnum.M); - }} - marks={[ - { label: '4', value: 4 }, - { label: '64', value: 64 }, - ]} - /> -
    - - ) : ( - <> -

    {t('labels.m')}

    - hanldeRemovePromot('nlist')} - onChange={e => { - handleFormValueChange( - e.target.value, - FromKeysEnum.Nlist - ); - }} - /> - - )} -
    - -
    -

    {t('labels.segmentSize')}

    - - {t('labels.segment')} - - - - - - } - label="Pulsar" - /> - } - label="Kafka" - /> - - -
    -
    -
    -
    -
    -

    {t('capacity')}

    - -
    - - -
    -
    - -
    -

    {t('setups.title')}

    - -
    -
    -
    -

    {t('total')}

    -

    - {t('coreInfo', { - core: totalData.core, - memory: totalData.memory, - })} -

    -
    -
    -
    -
    -

    {t('ssd')}

    -

    - {t('sizeInfo', { size: totalData.ssd })} -

    -
    -
    -

    {t('disk')}

    -

    - {t('sizeInfo', { size: totalData.disk })} -

    -
    -
    -
    -
    -
    -
    -

    {t('milvus')}

    -
    -
    -

    {t('total')}: 

    -

    - {t('coreInfo', { - core: milvusData.core, - memory: milvusData.memory, - })} -

    -
    - {queryNodeDiskData && ( -
    -

    SSD: 

    -

    - {queryNodeDiskData.totalQueryNodeDisk} GB -

    -
    - )} -
    -
    - -
    -
    - - -
    - -
    - - - -
    -
    -
    -
    -
    -

    {t('dependency')}

    -
    -
    -

    {t('total')}: 

    -

    - {t('coreInfo', { - core: dependencyData.core, - memory: dependencyData.memory, - })} -

    -
    -
    -

    {t('ssd')}: 

    -

    - {t('sizeInfo', { size: dependencyData.ssd })} -

    -
    -
    -

    {t('disk')}: 

    -

    - {t('sizeInfo', { size: dependencyData.disk })} -

    -
    -
    -
    - -
    - - - -

    {t('dependencyNote')}

    -
    - -
    - - {/* Minio */} - -
    - - {/* pulsar or kafka */} -
    - {form.apacheType === 'pulsar' ? ( - - ) : ( - - )} -
    -
    - -
    -
    - } - > - {t('buttons.helm')} - - handleOpenInstallGuide('helm')} - variant="outlined" - > - {t('buttons.guide')} - -
    -
    - } - > - {t('buttons.operator')} - - handleOpenInstallGuide('operator')} - variant="outlined" - > - {t('buttons.guide')} - -
    -
    -
    +

    + , + ]} + /> +

    +
    + + +
    + +
    - ); } diff --git a/src/parts/blogs/zillizAdv/index.module.less b/src/parts/blogs/zillizAdv/index.module.less new file mode 100644 index 000000000..16aeccdb9 --- /dev/null +++ b/src/parts/blogs/zillizAdv/index.module.less @@ -0,0 +1,96 @@ +@import url('@/styles/global.module.less'); + +.zilliz-adv { + border-radius: 16px; + border: 1px solid @color-black4; + background: @color-white; + padding: 30px 60px; + display: flex; + align-items: center; + gap: 50px; + min-height: 392px; + + @media @phone, @tablet { + flex-wrap: wrap; + padding: 30px; + gap: 20px; + } + + &-main { + flex: 1 1 50%; + } + + &-small-title { + font-size: 16px; + font-weight: 500; + line-height: 1.5; + text-transform: uppercase; + font-family: 'Geist Mono'; + } + + &-title { + font-size: 32px; + font-weight: 600; + line-height: 1.4; + } + + &-features { + margin-top: 12px; + + &-item { + color: @color-black2; + font-size: 16px; + font-weight: 400; + line-height: 1.5; + list-style: none; + display: flex; + align-items: center; + font-family: 'Geist Mono'; + + &:not(:first-of-type) { + margin-top: 8px; + } + + &-icon { + color: @color-blue1; + margin-right: 2px; + flex: 0 0 auto; + } + } + } + + &-btn { + padding: 9.5px 28px; + font-size: 14px; + font-weight: 500; + line-height: 1.5; + border-radius: 6px; + background-color: @color-black1; + color: @color-white; + display: inline-flex; + align-items: center; + gap: 8px; + margin-top: 36px; + + &:hover { + opacity: 0.7; + } + + @media @phone, @tablet { + width: 100%; + justify-content: center; + } + } + + &-logo { + min-height: 230px; + flex: 1 1 50%; + background-size: contain; + background-repeat: no-repeat; + background-position: center; + + @media @tablet { + margin-top: 30px; + } + } +} diff --git a/src/parts/blogs/zillizAdv/index.tsx b/src/parts/blogs/zillizAdv/index.tsx new file mode 100644 index 000000000..da2518436 --- /dev/null +++ b/src/parts/blogs/zillizAdv/index.tsx @@ -0,0 +1,47 @@ +import { ListItemTickIcon, RightTopArrowIcon } from '@/components/icons'; +import styles from './index.module.less'; +import { useTranslation } from 'react-i18next'; +import Link from 'next/link'; +import clsx from 'clsx'; + +const CTA_LINK = + 'https://cloud.zilliz.com/signup?utm_source=partner&utm_medium=referral&utm_campaign=2024-12-19_blog_overview-page_milvusio'; + +export default function ZillizAdv(props: { className?: string }) { + const { t } = useTranslation('blog'); + const { className = '' } = props; + const features = [t('blog:zillizAdv.feature1'), t('blog:zillizAdv.feature2')]; + + const featureItems = features.map(f => ( +
  • + + {f} +
  • + )); + + return ( +
    +
    +
    + {t('blog:zillizAdv.smallTitle')} +
    +

    + {t('blog:zillizAdv.title')} +

    +
      {featureItems}
    + + {t('blog:zillizAdv.btn')} + + +
    +
    +
    + ); +} diff --git a/src/parts/sizing/dependencyComponent.tsx b/src/parts/sizing/dependencyComponent.tsx new file mode 100644 index 000000000..16b2d82d1 --- /dev/null +++ b/src/parts/sizing/dependencyComponent.tsx @@ -0,0 +1,348 @@ +import { + ModeEnum, + DependencyComponentEnum, + DependencyConfigType, +} from '@/types/sizing'; +import classes from './index.module.less'; +import { Trans, useTranslation } from 'react-i18next'; +import clsx from 'clsx'; + +export const DependencyComponent = (props: { + data: DependencyConfigType; + mode: ModeEnum; + dependency: DependencyComponentEnum; +}) => { + const { t } = useTranslation('sizingToolV2'); + const { data, mode, dependency } = props; + + const { etcd, minio, pulsar, kafka } = data; + + const PulsarInfo = (props: Pick) => { + const { pulsar } = props; + + return ( +
    +
    {t('setup.dependency.pulsar')}
    +
      +
    • +

      + , + ]} + /> +

      +
        +
      1. + + {t('setup.basic.cpu')}: + + + {t('setup.basic.core', { cpu: pulsar.bookie.cpu })} + +
      2. +
      3. + + {t('setup.basic.memory')}: + + + {t('setup.basic.gb', { memory: pulsar.bookie.memory })} + +
      4. +
      5. + + {t('setup.basic.journal')}: + + + {t('setup.basic.gb', { memory: pulsar.bookie.journal })} + +
      6. +
      7. + + {t('setup.basic.ledger')}: + + + {t('setup.basic.gb', { memory: pulsar.bookie.ledgers })} + +
      8. +
      +
    • +
    • +

      + , + ]} + /> +

      +
        +
      1. + + {t('setup.basic.cpu')}: + + + {t('setup.basic.core', { cpu: pulsar.broker.cpu })} + +
      2. +
      3. + + {t('setup.basic.memory')}: + + + {t('setup.basic.gb', { memory: pulsar.broker.memory })} + +
      4. +
      +
    • +
    • +

      + , + ]} + /> +

      +
        +
      1. + + {t('setup.basic.cpu')}: + + + {t('setup.basic.core', { cpu: pulsar.proxy.cpu })} + +
      2. +
      3. + + {t('setup.basic.memory')}: + + + {t('setup.basic.gb', { memory: pulsar.proxy.memory })} + +
      4. +
      +
    • +
    • +

      + , + ]} + /> +

      +
        +
      1. + + {t('setup.basic.cpu')}: + + + {t('setup.basic.core', { cpu: pulsar.zookeeper.cpu })} + +
      2. +
      3. + + {t('setup.basic.memory')}: + + + {t('setup.basic.gb', { memory: pulsar.zookeeper.memory })} + +
      4. +
      5. + + {t('setup.basic.pvcLabel')}: + + + {t('setup.basic.gb', { memory: pulsar.zookeeper.pvc })} + +
      6. +
      +
    • +
    +
    + ); + }; + + const KafkaInfo = (props: Pick) => { + const { kafka } = props; + + return ( +
    +
    {t('setup.dependency.pulsar')}
    +
      +
    • +

      + , + ]} + /> +

      +
        +
      1. + + {t('setup.basic.cpu')}: + + + {t('setup.basic.core', { cpu: kafka.broker.cpu })} + +
      2. +
      3. + + {t('setup.basic.memory')}: + + + {t('setup.basic.gb', { memory: kafka.broker.memory })} + +
      4. +
      5. + + {t('setup.basic.pvcLabel')}: + + + {t('setup.basic.gb', { memory: kafka.broker.pvc })} + +
      6. +
      +
    • + +
    • +

      + , + ]} + /> +

      +
        +
      1. + + {t('setup.basic.cpu')}: + + + {t('setup.basic.core', { cpu: kafka.zookeeper.cpu })} + +
      2. +
      3. + + {t('setup.basic.memory')}: + + + {t('setup.basic.gb', { memory: kafka.zookeeper.memory })} + +
      4. +
      5. + + {t('setup.basic.pvcLabel')}: + + + {t('setup.basic.gb', { memory: kafka.zookeeper.pvc })} + +
      6. +
      +
    • +
    +
    + ); + }; + + return ( +
    +
    + + ]} + /> +

    + } + classname={classes.card} + /> + + ]} + /> +

    + } + classname={classes.card} + /> +
    + {mode === ModeEnum.Cluster && ( +
    + <> + {dependency === DependencyComponentEnum.Pulsar ? ( + + ) : ( + + )} + +
    + )} +
    + ); +}; + +export const DataCard = (props: { + name: React.ReactNode; + data: string; + desc?: React.ReactNode; + count?: number; + classname?: string; + size?: string; +}) => { + const { name, data, count, desc, classname = '', size = 'medium' } = props; + return ( +
    +
    + {name} +
    +

    + {data} +

    + {desc &&
    {desc}
    } + {count &&

    x{count}

    } +
    + ); +}; diff --git a/src/parts/sizing/formSection.tsx b/src/parts/sizing/formSection.tsx new file mode 100644 index 000000000..f8a4e36a6 --- /dev/null +++ b/src/parts/sizing/formSection.tsx @@ -0,0 +1,397 @@ +import { use, useEffect, useMemo, useRef, useState } from 'react'; +import classes from './index.module.less'; +import { SizingInput, SizingRange, SizingSwitch } from '@/components/sizing'; +import { + Collapsible, + Checkbox, + Tooltip, + TooltipTrigger, + TooltipContent, + TooltipProvider, + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, +} from '@/components/ui'; +import { + VECTOR_RANGE_CONFIG, + DIMENSION_RANGE_CONFIG, + MAX_NODE_DEGREE_RANGE_CONFIG, + SEGMENT_SIZE_OPTIONS, + INDEX_TYPE_OPTIONS, + DEPENDENCY_COMPONENTS, + MODE_OPTIONS, + N_LIST_RANGE_CONFIG, + M_RANGE_CONFIG, +} from '@/consts/sizing'; +import clsx from 'clsx'; +import { ICalculateResult, IIndexType, ModeEnum } from '@/types/sizing'; +import { IndexTypeComponent } from './indexTypeComponent'; +import { + memoryAndDiskCalculator, + nodesConfigCalculator, + rawDataSizeCalculator, + $10M768D, + dependencyCalculator, +} from '@/utils/sizingToolV2'; +import { Trans, useTranslation } from 'react-i18next'; + +export default function FormSection(props: { + className: string; + updateCalculatedResult: (params: ICalculateResult) => void; +}) { + const { t } = useTranslation('sizingToolV2'); + const { className, updateCalculatedResult } = props; + + const [collapseHeight, setCollapseHeight] = useState(0); + const collapseEle = useRef(null); + + const [form, setForm] = useState({ + vector: VECTOR_RANGE_CONFIG.defaultValue, + dimension: DIMENSION_RANGE_CONFIG.defaultValue, + widthScalar: false, + scalarData: { + average: 0, + offLoading: false, + }, + segmentSize: SEGMENT_SIZE_OPTIONS[0].value, + dependency: DEPENDENCY_COMPONENTS[0].value, + mode: MODE_OPTIONS[0].value, + }); + + const [indexTypeParams, setIndexTypeParams] = useState({ + indexType: INDEX_TYPE_OPTIONS[0].value, + widthRawData: false, + maxDegree: MAX_NODE_DEGREE_RANGE_CONFIG.defaultValue, + flatNList: N_LIST_RANGE_CONFIG.defaultValue, + sq8NList: N_LIST_RANGE_CONFIG.defaultValue, + m: M_RANGE_CONFIG.defaultValue, + }); + + const [disableStandalone, setDisableStandalone] = useState(false); + + const handleFormChange = (key: string, value: any) => { + setForm({ + ...form, + [key]: value, + }); + }; + + const handleIndexTypeParamsChange = (key: string, value: any) => { + setIndexTypeParams({ + ...indexTypeParams, + [key]: value, + }); + }; + + const handleAverageLengthChange = (e: any) => { + const length = Number(e.target.value) || 0; + setForm({ + ...form, + scalarData: { + ...form.scalarData, + average: length, + }, + }); + }; + + const handleOffLoadingChange = (value: boolean) => { + setForm({ + ...form, + scalarData: { + ...form.scalarData, + offLoading: value, + }, + }); + }; + + useEffect(() => { + const targetEle = collapseEle?.current; + const height = targetEle.offsetHeight; + setCollapseHeight(height); + }, []); + + const selectedSegmentSize = useMemo(() => { + return SEGMENT_SIZE_OPTIONS.find(v => v.value === form.segmentSize); + }, [form.segmentSize]); + + const dependencyOptions = [ + { + ...DEPENDENCY_COMPONENTS[0], + icon: '/images/sizing-tool/pulsar.svg', + }, + { + ...DEPENDENCY_COMPONENTS[1], + icon: '/images/sizing-tool/kafka.svg', + }, + ]; + + const modeOptions = [ + { + ...MODE_OPTIONS[0], + desc: t('form.standaloneDesc'), + }, + { + ...MODE_OPTIONS[1], + desc: t('form.clusterDesc'), + }, + ]; + + useEffect(() => { + let currentMode = form.mode; + const rawDataSize = rawDataSizeCalculator({ + num: form.vector, + d: form.dimension, + withScalar: form.widthScalar, + scalarAvg: form.scalarData.average, + }); + + if (rawDataSize >= $10M768D) { + currentMode = ModeEnum.Cluster; + + setDisableStandalone(true); + } else if (rawDataSize < $10M768D) { + setDisableStandalone(false); + } + const { memory, disk: localDisk } = memoryAndDiskCalculator({ + rawDataSize, + indexTypeParams, + d: form.dimension, + num: form.vector, + withScalar: form.widthScalar, + offLoading: form.scalarData.offLoading, + scalarAvg: form.scalarData.average, + segSize: Number(form.segmentSize), + }); + + const nodeConfig = nodesConfigCalculator({ + memory: memory, + }); + + const dependencyConfig = dependencyCalculator({ + num: form.vector, + d: form.dimension, + withScalar: form.widthScalar, + scalarAvg: form.scalarData.average, + mode: currentMode, + }); + + updateCalculatedResult({ + rawDataSize, + memorySize: memory, + localDiskSize: localDisk, + nodeConfig: nodeConfig, + dependencyConfig: dependencyConfig, + mode: currentMode, + dependency: form.dependency, + }); + }, [form, indexTypeParams]); + + useEffect(() => { + if (disableStandalone && form.mode === ModeEnum.Standalone) { + setForm({ + ...form, + mode: ModeEnum.Cluster, + }); + } + }, [disableStandalone, form.mode]); + + return ( +
    +
    +
    + { + handleFormChange('vector', val); + }} + value={form.vector} + unit="Million" + /> +
    +
    + { + handleFormChange('dimension', val); + }} + value={form.dimension} + /> +
    +
    +
    +

    {t('form.withScalar')}

    + { + handleFormChange('widthScalar', value); + }} + /> +
    + +
    + + + + some test tooltip sad dsdsadasd dadsadsa dssad d dasd + + + {t('form.averageLength')} + + + + } + unit={t('setup.basic.byte')} + value={form.scalarData.average} + onChange={handleAverageLengthChange} + fullWidth + classes={{ + root: classes.marginBtm20, + }} + /> +
    +
    + +

    {t('form.offloading')}

    +
    +

    + ]} + /> +

    +
    +
    +
    +
    +
    +
    +
    +

    {t('form.indexType')}

    + + + +
    +
    +
    +
    +

    {t('form.segmentSize')}

    + +
    +
    +

    {t('dependencyComp')}

    +
    + {dependencyOptions.map(v => ( + + ))} +
    +
    +
    +

    + + + + {t('form.mode')} + + + ]} + /> + + + +

    +
    + {modeOptions.map(v => ( + + ))} +
    +
    +
    +
    + ); +} diff --git a/src/parts/sizing/index.module.less b/src/parts/sizing/index.module.less new file mode 100644 index 000000000..41a63f11c --- /dev/null +++ b/src/parts/sizing/index.module.less @@ -0,0 +1,451 @@ +@import url('@/styles/global.module.less'); + +.tooltipTrigger { + text-decoration: underline dashed #d0d7dc; +} + +.flexRow { + display: flex; + gap: 8px; + align-items: center; +} + +.flexColumn { + display: flex; + flex-direction: column; + gap: 8px; +} + +.marginBtm20 { + margin-bottom: 20px; +} + +.marginBtm0 { + margin-bottom: 0; +} + +.paddingBtm20 { + padding-bottom: 20px; +} + +.formSection { + padding: 30px 24px; + border-right: 1px solid #ececee; + + @media @tablet, @phone { + border-right: none; + border-bottom: 1px solid #ececee; + } + + .singlePart { + border-bottom: 1px solid #ececee; + margin-bottom: 24px; + + &:last-of-type { + border-bottom: none; + margin-bottom: 0; + padding-bottom: 0; + } + } + + .commonLabel { + .paragraph4(); + margin-bottom: 8px; + } + + .smallerLabel { + .paragraph6(); + } + + .innerRow { + margin-top: 20px; + } + + .offLoadingLabel { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 8px; + } + + .offLoadingDesc { + .paragraph6-regular; + color: #2e373b; + + a { + color: #2e373b; + text-decoration: underline; + } + } + + .switchRow { + margin-bottom: 20px; + } + + .collapsible { + overflow: hidden; + height: fit-content; + padding-top: 20px; + } + + .visibleCollapse { + } + .invisibleCollapse { + height: 0; + padding-top: 0; + } + + .selectTrigger { + .paragraph6-regular(); + padding: 10px 12px; + height: 36px; + border-radius: 8px; + border: 1px solid #e8eaee; + background: #fff; + } + + .indexParamLabel { + .paragraph4-regular(); + margin-bottom: 8px; + } + + .radioGroup { + display: flex; + gap: 20px; + align-items: center; + } + + .cardsWrapper { + display: flex; + gap: 12px; + + .card { + flex: 1; + border-radius: 10px; + border: 1px solid #e4eaf1; + background: #fff; + cursor: pointer; + } + + .dependencyCard { + .flexRow(); + padding: 3px 12px; + justify-content: center; + align-items: center; + + img { + width: 28px; + height: 28px; + } + + .depName { + .paragraph4-regular(); + } + } + .modeCard { + padding: 12px; + .flexColumn(); + + .modeName { + .paragraph4(); + } + + .modeDesc { + .paragraph6-regular(); + text-align: left; + } + } + + .activeCard { + border-color: @color-blue1; + } + + .switchLabel { + .paragraph4(); + } + } +} + +.resultContainer { + padding: 40px 20px; + + .dataSection { + margin-bottom: 40px; + } + + .sectionLabel { + .paragraph3-bold(); + margin-bottom: 16px; + } + + .dataCardName { + .paragraph4-bold(); + margin-bottom: 4px; + } + + .sectionLabelBar { + .flexRow(); + align-items: center; + justify-content: space-between; + margin-bottom: 16px; + + .title { + .paragraph3-bold(); + } + + .buttonWrapper { + display: flex; + align-items: center; + gap: 4px; + + &:hover { + text-decoration: underline solid #000; + } + + span { + .paragraph5(); + color: #000; + } + } + } + + .totalDataContent { + background-color: @color-white1; + border-radius: 12px; + padding: 20px 0; + margin-bottom: 40px; + } + .dataSummary { + display: flex; + border-bottom: 1px solid @color-black4; + padding: 0 20px 20px 20px; + + .summaryCard { + flex: 1; + } + } + + .dataDetail { + padding: 0 20px; + border-bottom: 1px solid @color-black4; + + &:last-of-type { + border-bottom: none; + } + + .commonCollapseTitle { + width: 100%; + display: flex; + gap: 12px; + align-items: center; + padding: 24px 0; + + .collapseTitle { + flex: 1; + display: flex; + justify-content: space-between; + align-items: center; + + @media @phone { + flex-direction: column; + gap: 8px; + align-items: flex-start; + justify-content: center; + } + + .titleName { + .paragraph3-bold(); + } + .titleOverview { + .paragraph3(); + + .valueInfo { + .paragraph3-bold(); + color: @color-blue2; + letter-spacing: 150%; + } + } + } + .collapseIcon { + flex: 0 0 16px; + } + + .activeIcon { + transform: rotate(180deg); + } + } + + .milvusDataDetail { + display: flex; + flex-wrap: wrap; + + .detailCard { + flex: 0 1 33.33%; + } + } + + .depDataItem { + flex: 1; + } + } + + .installationSection { + .sectionTitle { + .paragraph3-bold(); + margin-bottom: 16px; + } + .installButton { + .paragraph4(); + padding: 9px 0; + width: 100%; + justify-content: center; + margin-bottom: 16px; + } + .installCodeWrapper { + padding: 32px 24px 20px 24px; + background-color: @color-white1; + border-radius: 16px; + position: relative; + + code { + .paragraph4-regular(); + display: block; + width: 100%; + overflow-x: auto; + padding-bottom: 10px; + } + + .copyBtn { + display: flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + padding: 0; + margin: 0; + outline: none; + position: absolute; + top: 12px; + right: 12px; + cursor: pointer; + + &:hover { + opacity: 0.7; + } + } + } + + .advCard { + .paragraph4-regular(); + padding: 12px 24px; + border-radius: 12px; + background: linear-gradient( + 91deg, + rgba(208, 131, 255, 0.2) 0.09%, + rgba(142, 221, 255, 0.2) 100.63% + ); + + a { + color: #000; + text-decoration: underline; + } + } + } +} + +.dataCard { + width: 100%; + padding: 8px 20px; + border-radius: 12px; + background: @color-white1; + + .dataName { + .paragraph4-bold(); + margin-bottom: 6px; + } + + .dataInfo { + .paragraph3-bold(); + color: #008dc8; + } + + .dataDesc { + } + + .dataCount { + .paragraph3-regular(); + margin-top: 6px; + } + + .largeName { + } + .largeData { + .heading4(); + } +} + +.dependencyDetailContainer { + .firstRow { + display: flex; + gap: 20px; + margin-bottom: 20px; + + .card { + flex: 1; + padding: 0 16px; + + .dataDesc { + .paragraph3-regular(); + + span { + font-weight: 500; + color: @color-blue2; + } + } + } + } +} + +.configContainer { + padding: 0 16px; + + .configName { + .paragraph4-bold(); + margin-bottom: 8px; + } + + .configList { + display: flex; + gap: 16px; + list-style: none; + + .configItem { + flex: 1; + display: flex; + flex-direction: column; + + .columnName { + .paragraph4(); + margin-bottom: 4px; + } + + .columnValue { + .paragraph4(); + color: @color-blue2; + } + + .columnList { + list-style: none; + + .columnItem { + margin-right: 4px; + + .columnLabel { + .paragraph4-regular(); + margin-right: 6px; + } + } + } + } + } +} diff --git a/src/parts/sizing/indexTypeComponent.tsx b/src/parts/sizing/indexTypeComponent.tsx new file mode 100644 index 000000000..ad34af168 --- /dev/null +++ b/src/parts/sizing/indexTypeComponent.tsx @@ -0,0 +1,139 @@ +import { IIndexType, IndexTypeEnum } from '@/types/sizing'; +import { RadioGroupItem, RadioGroup } from '@/components/ui'; +import classes from './index.module.less'; +import clsx from 'clsx'; +import { SizingRange } from '@/components/sizing'; +import { + MAX_NODE_DEGREE_RANGE_CONFIG, + N_LIST_RANGE_CONFIG, + M_RANGE_CONFIG, +} from '@/consts/sizing'; + +type IndexTypeComponentProps = { + data: IIndexType; + onChange: (key: string, value: any) => void; +}; + +const SCANNComponent = (props: IndexTypeComponentProps) => { + const { data, onChange } = props; + + return ( +
    +

    Index Parameters

    +

    With_raw_data

    + { + onChange('widthRawData', val === 'true'); + }} + className={classes.radioGroup} + > +
    + +

    True

    +
    +
    + False +

    False

    +
    +
    +
    + ); +}; + +const HNSWComponent = (props: IndexTypeComponentProps) => { + const { data, onChange } = props; + return ( +
    +

    Index Parameters

    +

    + M(Maximum degree of the node) +

    + { + onChange('m', value); + }} + placeholder={`[${M_RANGE_CONFIG.min}, ${M_RANGE_CONFIG.max}]`} + /> +
    + ); +}; + +const DISKANNComponent = (props: IndexTypeComponentProps) => { + const { data, onChange } = props; + return ( +
    +

    Index Parameters

    +

    nlist

    + { + onChange('maxDegree', value); + }} + placeholder={`[${MAX_NODE_DEGREE_RANGE_CONFIG.min}, ${MAX_NODE_DEGREE_RANGE_CONFIG.max}]`} + /> +
    + ); +}; + +const IVFFlatComponent = (props: IndexTypeComponentProps) => { + const { data, onChange } = props; + return ( +
    +

    Index Parameters

    +

    nlist

    + { + onChange('flatNList', value); + }} + placeholder={`[${N_LIST_RANGE_CONFIG.min}, ${N_LIST_RANGE_CONFIG.max}]`} + /> +
    + ); +}; + +const IVFSQ8Component = (props: IndexTypeComponentProps) => { + const { data, onChange } = props; + return ( +
    +

    Index Parameters

    +

    nlist

    + { + onChange('sq8NList', value); + }} + placeholder={`[${N_LIST_RANGE_CONFIG.min}, ${N_LIST_RANGE_CONFIG.max}]`} + /> +
    + ); +}; + +export const IndexTypeComponent = (props: { + data: IIndexType; + onChange: (key: string, value: any) => void; +}) => { + const { data } = props; + switch (data.indexType) { + case IndexTypeEnum.FLAT: + return null; + case IndexTypeEnum.SCANN: + return ; + case IndexTypeEnum.HNSW: + return ; + case IndexTypeEnum.DISKANN: + return ; + case IndexTypeEnum.IVF_FLAT: + return ; + case IndexTypeEnum.IVFSQ8: + return ; + default: + return null; + } +}; diff --git a/src/parts/sizing/resultSection.tsx b/src/parts/sizing/resultSection.tsx new file mode 100644 index 000000000..4040c6d20 --- /dev/null +++ b/src/parts/sizing/resultSection.tsx @@ -0,0 +1,375 @@ +import clsx from 'clsx'; +import classes from './index.module.less'; +import { + TooltipProvider, + Tooltip, + TooltipTrigger, + TooltipContent, + Collapsible, + CollapsibleTrigger, + CollapsibleContent, +} from '@/components/ui'; +import { + ExternalLinkIcon, + ArrowTop, + DownloadIcon, + copyIconTpl, +} from '@/components/icons'; +import { ICalculateResult } from '@/types/sizing'; +import { unitBYTE2Any } from '@/utils/sizingTool'; +import { + milvusOverviewDataCalculator, + dependencyOverviewDataCalculator, +} from '@/utils/sizingToolV2'; +import { DataCard, DependencyComponent } from './dependencyComponent'; +import { Trans, useTranslation } from 'react-i18next'; +import { useState } from 'react'; +import CustomButton from '@/components/customButton'; +import { helmCodeExample, operatorCodeExample } from '@/consts/sizing'; +import { useCopyCode } from '@/hooks/enhanceCodeBlock'; + +export default function ResultSection(props: { + className?: string; + calculatedResult: ICalculateResult; +}) { + const { t } = useTranslation('sizingToolV2'); + const { className, calculatedResult } = props; + + const { + rawDataSize, + memorySize, + localDiskSize, + nodeConfig, + dependencyConfig, + mode, + dependency, + } = calculatedResult; + const { queryNode, proxy, mixCoord, dataNode, indexNode } = nodeConfig; + + console.log('mode--', mode); + + const { size: totalRawDataSize, unit: rawDataUnit } = + unitBYTE2Any(rawDataSize); + const { size: totalMemorySize, unit: memoryUnit } = unitBYTE2Any(memorySize); + const { size: diskSize, unit: diskUnit } = unitBYTE2Any(localDiskSize); + + const { milvusCpu, milvusMemory } = milvusOverviewDataCalculator(nodeConfig); + const { dependencyCpu, dependencyMemory, dependencyStorage } = + dependencyOverviewDataCalculator({ mode, dependency, dependencyConfig }); + + const totalCpu = milvusCpu + dependencyCpu; + const totalMemory = milvusMemory + dependencyMemory; + + const [isMilvusOpen, setIsMilvusOpen] = useState(false); + const [isDependencyOpen, setIsDependencyOpen] = useState(false); + + useCopyCode([helmCodeExample, operatorCodeExample]); + + return ( +
    +
    +

    {t('overview.title')}

    +
    + + + + {t('overview.raw')} + + + {t('overview.rawTooltip')} + + + + } + data={`${totalRawDataSize} ${rawDataUnit}`} + /> + + + + {t('overview.memory')} + + + {t('overview.memoryTooltip')} + + + + } + data={`${totalMemorySize} ${memoryUnit}`} + /> +
    +
    +
    +
    +

    {t('setup.title')}

    + + {t('setup.share')} + + +
    + +
    +
    + + + + +
    + +
    + { + setIsMilvusOpen(isOpen); + }} + > + +
    +

    {t('setup.milvus.title')}

    +
    +

    + , + ]} + /> +

    +

    + , + ]} + /> +

    +
    +
    + + + +
    + + +
    + + + + + +
    +
    +
    +
    + +
    + { + setIsDependencyOpen(isOpen); + }} + > + +
    +

    + {t('setup.dependency.title')} +

    +
    +

    + , + ]} + /> +

    +

    + , + ]} + /> +

    +
    +
    + + + +
    + + + + +
    +
    +
    + +
    +

    {t('install.title')}

    +
    + } + classes={{ + root: classes.installButton, + }} + > + {t('install.helm')} + +
    +              {helmCodeExample}
    +              
    +            
    +
    +
    + } + classes={{ + root: classes.installButton, + }} + > + {t('install.operator')} + +
    +              {operatorCodeExample}
    +              
    +            
    +
    + +
    + , + ]} + /> +
    +
    +
    +
    + ); +} diff --git a/src/styles/reset.css b/src/styles/reset.css index 13024fedb..b63bd1c4b 100644 --- a/src/styles/reset.css +++ b/src/styles/reset.css @@ -144,6 +144,7 @@ a { } button { + font-family: Inter; background-color: transparent; padding: 0; margin: 0; diff --git a/src/styles/sizingTool.module.less b/src/styles/sizingTool.module.less index 2481837cf..554ea00c8 100644 --- a/src/styles/sizingTool.module.less +++ b/src/styles/sizingTool.module.less @@ -3,317 +3,76 @@ .pageContainer { background-color: #fff; } -.container { + +.sizingToolContainer { padding-top: 40px; - padding-bottom: 128px; - font-family: Geist; + padding-bottom: 40px; - h1 { - font-weight: 600; - font-size: 42px; - line-height: 1.5em; - margin-bottom: 40px; + .title { + .heading2(); + margin-bottom: 12px; text-align: center; } - h2 { - font-size: 14px; - line-height: 1.5em; - font-weight: 400; - } - - h3 { - font-weight: 600; - font-size: 18px; - line-height: 1.5em; - margin-bottom: 12px; - color: @color-black1; + .desc { + .paragraph6-regular(); + margin-bottom: 40px; + text-align: center; } - .note { + .tipWrapper { display: flex; align-items: flex-start; - background-color: #e0f2fc; - padding: 16px; - border-radius: 12px; - margin-bottom: 20px; gap: 8px; + padding: 20px 0; .iconWrapper { - width: 21px; - height: 21px; + flex: 0 0 20px; + height: 20px; + line-height: 0; + svg { width: 100%; height: 100%; } } - } - .contentWrapper { - display: flex; - gap: 64px; + .tip { + .paragraph6-regular(); - @media (max-width: 1280px) { - gap: 32px; + a { + color: #000; + text-decoration: underline; + } } + } - @media @tablet { - gap: 40px; - flex-direction: column; - } + .contentContainer { + display: flex; + border-radius: 24px; + border: 2px solid #ececee; + margin-bottom: 80px; - @media @phone { - gap: 40px; + @media @tablet, @phone { flex-direction: column; } - .leftPart { - flex: 0 1 412px; - padding: 32px 49px 32px 40px; - display: flex; - flex-direction: column; - gap: 0; - border: 1px solid @color-black3; - border-radius: 12px; - - @media @tablet { - gap: 64px; - } - - @media @phone { - gap: 32px; - } - - .dataSize, - .indexType { - width: 100%; - } - - .dataItem { - margin-bottom: 18px; - - .label { - font-weight: 400; - font-size: 12px; - line-height: 1.5em; - margin-bottom: 16px; - } - - .interpretation { - font-weight: 400; - font-size: 12px; - line-height: 1.5em; - margin-bottom: 24px; - } - - .sliderWrapper { - padding: 0 8px; - } - - .shortMargin { - margin-bottom: 8px; - } - - .largeMargin { - margin-bottom: 32px; - } - } - - .arrowIcon { - display: inline-block; - width: 11px; - height: 9px; - background-color: #000; - clip-path: polygon(0 0, 100% 0, 50% 100%, 0 0); - position: absolute; - right: 16px; - top: 50%; - transform: translateY(-50%); - cursor: pointer; - } - - .dropdownController { - border-radius: 2em; - padding: 19px 52px 19px 34px; - font-weight: 400; - font-size: 14px; - line-height: 20px; - color: #000; - } - - .dropdownMenu { - margin-top: 8px; - background: #ffffff; - box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.08); - border-radius: 29px; - padding: 8px 0; - max-height: 300px; - - & > div { - padding: 14px 32px; - - &:hover { - background-color: #eff9fe; - color: #1493cc; - } - } - } - - .radioGroup { - display: flex; - margin-top: 16px; - } + .leftSection { + flex: 0 1 446px; } - .rightPart { + .rightSection { flex: 1; + min-width: 650px; - .contentClassName { - color: #1493cc; - font-weight: 600; - font-size: 24px; - line-height: 34px; - } - - .capacity { - margin-bottom: 40px; - - .cardsWrapper { - display: flex; - gap: 20px; - } - } - - .cluster { - margin-bottom: 40px; - - .singleRowCard { - display: flex; - padding: 20px; - background-color: @color-white1; - border-radius: 4px; - gap: 68px; - margin-bottom: 20px; - - .totalWrapper { - flex: 1; - - display: flex; - - .singlePart { - flex: 1; - - .label { - font-size: 18px; - line-height: 26px; - font-weight: 600; - margin-bottom: 4px; - } - - .value { - font-size: 24px; - font-weight: 600; - line-height: 34px; - color: @color-text-primary; - } - } - } - } - - .titleRow { - display: flex; - justify-content: space-between; - gap: 24px; - margin-bottom: 12px; - - .title { - margin-bottom: 0px; - } - - .detailWrapper { - display: flex; - gap: 20px; - - .detailInfo { - display: flex; - - .label { - font-size: 14px; - font-weight: 600; - line-height: 1.5em; - } - - .value { - font-size: 14px; - font-weight: 600; - line-height: 1.5em; - color: @color-primary; - } - } - } - } - - .cardsWrapper { - display: grid; - grid-template-columns: repeat(4, 1fr); - column-gap: 20px; - row-gap: 20px; - - @media @tablet { - grid-template-columns: repeat(3, 1fr); - } - - @media @phone { - grid-template-columns: repeat(2, 1fr); - } - } - - .cardLayoutWrapper { - .cardsRow { - display: flex; - gap: 20px; - - div { - flex: 1; - } - } - - .cardsRow + .cardsRow { - margin-top: 12px; - } - } - } - - .btnContainer { - display: flex; - gap: 20px; - - @media (max-width: 800px) { - flex-direction: column; - gap: 24px; - } - - .btnsWrapper { - flex: 1; - display: flex; - flex-direction: column; - align-items: stretch; - gap: 12px; - - .ctaButton { - .paragraph4(); - justify-content: center; - } - } - } - - .line { - display: flex; - gap: 20px; - justify-content: space-evenly; - align-items: stretch; - margin-bottom: 40px; + @media @tablet, @phone { + min-width: 0; + width: 100%; } } } + + .zillizAdv { + margin-bottom: 50px; + } } diff --git a/src/types/sizing.ts b/src/types/sizing.ts new file mode 100644 index 000000000..2cc7230ff --- /dev/null +++ b/src/types/sizing.ts @@ -0,0 +1,92 @@ +export enum SegmentSizeEnum { + _512MB = '512', + _1024MB = '1024', + _2048MB = '2048', +} + +export enum IndexTypeEnum { + FLAT = 'FLAT', + SCANN = 'SCANN', + HNSW = 'HNSW', + IVF_FLAT = 'IVF_FLAT', + IVFSQ8 = 'IVFSQ8', + DISKANN = 'DISKANN', +} + +export interface IIndexType { + indexType: IndexTypeEnum; + widthRawData: boolean; + maxDegree: number; + flatNList: number; + sq8NList: number; + m: number; +} + +export enum DependencyComponentEnum { + Pulsar, + Kafka, +} + +export enum ModeEnum { + Standalone, + Cluster, +} + +const NodesType = { + queryNode: 'Query Node', + proxy: 'Proxy', + mixCoord: 'Mix Coord', + dataNode: 'Data Node', + indexNode: 'Index Node', +} as const; + +export type NodesKeyType = keyof typeof NodesType; +export type NodesValueType = { + cpu: number; + memory: number; + count: number; +}; + +type DependencyBaseConfigType = { + cpu: number; + memory: number; + count: number; +}; + +export type DependencyConfigType = { + etcd: DependencyBaseConfigType & { + pvc: number; + }; + minio: DependencyBaseConfigType & { + pvc: number; + }; + pulsar?: { + bookie: DependencyBaseConfigType & { + journal: number; + ledgers: number; + }; + broker: DependencyBaseConfigType; + proxy: DependencyBaseConfigType; + zookeeper: DependencyBaseConfigType & { + pvc: number; + }; + }; + kafka?: { + broker: DependencyBaseConfigType & { + pvc: number; + }; + zookeeper: DependencyBaseConfigType & { + pvc: number; + }; + }; +}; + +export interface ICalculateResult { + rawDataSize: number; + memorySize: number; + localDiskSize: number; + nodeConfig: Record; + dependencyConfig: DependencyConfigType; + mode: ModeEnum; + dependency: DependencyComponentEnum; +} diff --git a/src/utils/sizing.ts b/src/utils/sizing.ts new file mode 100644 index 000000000..0e2612585 --- /dev/null +++ b/src/utils/sizing.ts @@ -0,0 +1,29 @@ +// Converts a number to a string with commas separating groups of three digits. +export const convertNumToString = (params: { + amount: number; + scale?: number; + decimal?: number; + showZeroDecimal?: boolean; +}): string => { + const { amount, scale, decimal = 0, showZeroDecimal = false } = params; + const decimalCoefficient = Math.pow(10, decimal); + const scaleNum = scale || 1; + const scaledNum = + Math.round((amount / scaleNum) * decimalCoefficient) / decimalCoefficient; + const formattedNumber = new Intl.NumberFormat( + 'en-US', + showZeroDecimal + ? { + minimumFractionDigits: decimal, + maximumFractionDigits: decimal, + } + : {} + ).format(scaledNum); + return formattedNumber; +}; + +// Converts a string containing digits to a number. +export const convertStringToNum = (value: string, scale?: number): number => { + const scaleNum = scale || 1; + return Number(value.replace(/[^0-9.-]+/g, '')) * scaleNum; +}; diff --git a/src/utils/sizingTool.ts b/src/utils/sizingTool.ts index 1394f036c..dcf07c17a 100644 --- a/src/utils/sizingTool.ts +++ b/src/utils/sizingTool.ts @@ -55,14 +55,14 @@ export const unitBYTE2Any = (size: number, unit?: DataSizeUnit) => { sizeStatus === 1 ? (baseUnit = 'B') : sizeStatus === 2 - ? (baseUnit = 'K') + ? (baseUnit = 'KB') : sizeStatus === 3 - ? (baseUnit = 'M') + ? (baseUnit = 'MB') : sizeStatus === 4 - ? (baseUnit = 'G') + ? (baseUnit = 'GB') : sizeStatus === 5 - ? (baseUnit = 'T') - : (baseUnit = 'K'); + ? (baseUnit = 'TB') + : (baseUnit = 'KB'); size = Math.ceil(size * 10) / 10; return { diff --git a/src/utils/sizingToolV2.ts b/src/utils/sizingToolV2.ts new file mode 100644 index 000000000..0469f4538 --- /dev/null +++ b/src/utils/sizingToolV2.ts @@ -0,0 +1,596 @@ +import { ONE_MILLION, FIXED_QUERY_NODE_CONFIG } from '@/consts/sizing'; +import { + DependencyComponentEnum, + DependencyConfigType, + IIndexType, + IndexTypeEnum, + ModeEnum, +} from '@/types/sizing'; +import { unitAny2BYTE } from './sizingTool'; +import { NodesKeyType, NodesValueType } from '@/types/sizing'; +/** + * params list: + * num: number: number of vectors + * d: number: dimension of vectors + * withScalar: boolean: whether with scalar data + * scalarAvg: number: average length of scalar data + * maxDegree: number: maximum degree of the node + * segSize: number: segment size + * + */ + +// raw data size calculator +export const rawDataSizeCalculator = (params: { + num: number; + d: number; + withScalar: boolean; + scalarAvg: number; +}) => { + const { num, d, withScalar, scalarAvg } = params; + + const vectorRaw = (num * d * ONE_MILLION * 32) / 8; // bytes + const scalarRaw = withScalar ? num * scalarAvg * ONE_MILLION : 0; // bytes + + return vectorRaw + scalarRaw; +}; + +const $1M768D = rawDataSizeCalculator({ + num: 1, + d: 768, + withScalar: false, + scalarAvg: 0, +}); +export const $10M768D = rawDataSizeCalculator({ + num: 10, + d: 768, + withScalar: false, + scalarAvg: 0, +}); +const $100M768D = rawDataSizeCalculator({ + num: 100, + d: 768, + withScalar: false, + scalarAvg: 0, +}); +const $1B768D = rawDataSizeCalculator({ + num: 1000, + d: 768, + withScalar: false, + scalarAvg: 0, +}); + +// loading memory and disk calculator +export const memoryAndDiskCalculator = (params: { + rawDataSize: number; + indexTypeParams: IIndexType; + d: number; + num: number; + withScalar: boolean; + offLoading: boolean; + scalarAvg: number; + segSize: number; +}) => { + const { + rawDataSize, + num, + d, + withScalar, + scalarAvg, + offLoading, + indexTypeParams, + segSize, + } = params; + const { indexType, widthRawData, maxDegree, m, flatNList, sq8NList } = + indexTypeParams; + const segmentSizeByte = segSize * 1024; + + let result = { + memory: 0, + disk: 0, + }; + const rowSize = (d * 32) / 8; + + switch (indexType) { + case IndexTypeEnum.FLAT: + result = { + memory: rawDataSize, + disk: 0, + }; + break; + case IndexTypeEnum.IVF_FLAT: + result = { + memory: rawDataSize + flatNList * rowSize, + disk: 0, + }; + break; + case IndexTypeEnum.IVFSQ8: + result = { + memory: rawDataSize / 4 + sq8NList * rowSize, + disk: 0, + }; + break; + case IndexTypeEnum.SCANN: + result = { + memory: widthRawData + ? (1 + 1 / 8) * rawDataSize + : (1 / 8) * rawDataSize, + disk: 0, + }; + break; + case IndexTypeEnum.HNSW: + result = { + memory: (1 + (2 * m) / d) * rawDataSize, + disk: 0, + }; + + break; + case IndexTypeEnum.DISKANN: + result = { + memory: rawDataSize / 4, + disk: (1 + maxDegree / d) * rawDataSize, + }; + break; + default: + break; + } + + const scalarLoadingMemory = withScalar + ? offLoading + ? (num * scalarAvg * ONE_MILLION) / 10 + : num * scalarAvg * ONE_MILLION + : 0; + + const scalarLocalDisk = offLoading ? num * scalarAvg * ONE_MILLION : 0; + + if (indexType === IndexTypeEnum.DISKANN) { + const vectorLoadingMemory = result.memory * 1.15; + return { + memory: vectorLoadingMemory + scalarLoadingMemory, // bytes + disk: scalarLocalDisk + result.disk, // bytes + }; + } + + const vectorLoadingMemory = (result.memory + segmentSizeByte * 2) * 1.15; + return { + memory: vectorLoadingMemory + scalarLoadingMemory, // bytes + disk: scalarLocalDisk + result.disk, // bytes + }; +}; + +// query nodes calculator +export const nodesConfigCalculator = (params: { memory: number }) => { + const { memory } = params; + const memoryGB = Math.ceil((memory * 10) / 1024 / 1024 / 1024) / 10; + + const queryNodeCount = + memoryGB >= 2048 + ? Math.ceil(memoryGB / 128) + : memoryGB >= 512 + ? Math.ceil(memoryGB / 64) + : memoryGB >= 96 + ? Math.ceil(memoryGB / 32) + : 0; + + const basicLargeNodeConfig = { + proxy: { + cpu: 8, + memory: 32, + count: 1, + }, + mixCoord: { + cpu: 8, + memory: 32, + count: 1, + }, + dataNode: { + cpu: 8, + memory: 32, + count: 1, + }, + indexNode: { + cpu: 8, + memory: 16, + count: 8, + }, + }; + const queryNodesConfig = [ + ...FIXED_QUERY_NODE_CONFIG, + { + memory: [96, 512], + queryNode: { + cpu: 8, + memory: 32, + count: queryNodeCount, + }, + ...basicLargeNodeConfig, + }, + { + memory: [512, 2048], + queryNode: { + cpu: 16, + memory: 64, + count: queryNodeCount, + }, + ...basicLargeNodeConfig, + }, + { + memory: [2048, Infinity], + queryNode: { + cpu: 32, + memory: 128, + count: queryNodeCount, + }, + ...basicLargeNodeConfig, + }, + ]; + + const properMemorySection = queryNodesConfig.find( + v => v.memory[0] <= memoryGB && v.memory[1] > memoryGB + ); + return { + queryNode: properMemorySection?.queryNode, + proxy: properMemorySection?.proxy, + mixCoord: properMemorySection?.mixCoord, + dataNode: properMemorySection?.dataNode, + indexNode: properMemorySection?.indexNode, + }; +}; + +export const dependencyCalculator = (params: { + num: number; + d: number; + mode: ModeEnum; + withScalar: boolean; + scalarAvg: number; +}): DependencyConfigType => { + const { num, d, mode, scalarAvg, withScalar } = params; + + const rawDataSize = rawDataSizeCalculator({ + num, + d, + withScalar, + scalarAvg, + }); + + const thresholds = [$1M768D, $10M768D, $100M768D, $1B768D]; + + let etcdBaseConfig = { + cpu: 0, + memory: 0, + pvc: 0, + count: 0, + }; + let minioBaseConfig = { + cpu: 0, + memory: 0, + pvc: 0, + count: 0, + }; + let pulsarBaseConfig = { + bookie: { + cpu: 0, + memory: 0, + count: 0, + journal: 0, + ledgers: 0, + }, + broker: { + cpu: 0, + memory: 0, + count: 0, + }, + proxy: { + cpu: 0, + memory: 0, + count: 0, + }, + zookeeper: { + cpu: 0, + memory: 0, + count: 0, + pvc: 0, + }, + }; + let kafkaBaseConfig = { + broker: { + cpu: 0, + memory: 0, + count: 0, + pvc: 0, + }, + zookeeper: { + cpu: 0, + memory: 0, + count: 0, + pvc: 0, + }, + }; + + switch (mode) { + case ModeEnum.Standalone: + etcdBaseConfig = { + cpu: 0.5, + memory: 1, + pvc: 10, + count: 1, + }; + minioBaseConfig = { + cpu: 1, + memory: 4, + pvc: 20, + count: 1, + }; + + if (rawDataSize >= thresholds[0]) { + etcdBaseConfig.memory = 2; + minioBaseConfig.memory = 8; + minioBaseConfig.pvc = 120; + } + + return { + etcd: etcdBaseConfig, + minio: minioBaseConfig, + pulsar: undefined, + kafka: undefined, + }; + case ModeEnum.Cluster: + etcdBaseConfig = { + cpu: 1, + memory: 4, + pvc: 20, + count: 3, + }; + minioBaseConfig = { + cpu: 1, + memory: 8, + pvc: 30, + count: 4, + }; + + pulsarBaseConfig = { + bookie: { + cpu: 1, + memory: 8, + count: 3, + journal: 30, + ledgers: 40, + }, + broker: { + cpu: 1, + memory: 4, + count: 2, + }, + proxy: { + cpu: 0.5, + memory: 4, + count: 2, + }, + zookeeper: { + cpu: 0.5, + memory: 2, + count: 3, + pvc: 30, + }, + }; + + kafkaBaseConfig = { + broker: { + cpu: 2, + memory: 8, + count: 3, + pvc: 100, + }, + zookeeper: { + cpu: 0.5, + memory: 2, + count: 3, + pvc: 30, + }, + }; + + if (rawDataSize >= thresholds[1] && rawDataSize < thresholds[2]) { + minioBaseConfig = { + cpu: 1, + memory: 8, + pvc: 300, + count: 4, + }; + pulsarBaseConfig = { + bookie: { + cpu: 1, + memory: 8, + count: 3, + journal: 50, + ledgers: 400, + }, + broker: { + cpu: 1, + memory: 4, + count: 2, + }, + proxy: { + cpu: 0.5, + memory: 4, + count: 2, + }, + zookeeper: { + cpu: 0.5, + memory: 2, + count: 3, + pvc: 30, + }, + }; + + kafkaBaseConfig = { + broker: { + cpu: 2, + memory: 8, + count: 3, + pvc: 400, + }, + zookeeper: { + cpu: 0.5, + memory: 2, + count: 3, + pvc: 30, + }, + }; + } + + if (rawDataSize >= thresholds[2] && rawDataSize < thresholds[3]) { + etcdBaseConfig = { + cpu: 1, + memory: 8, + pvc: 50, + count: 3, + }; + minioBaseConfig = { + cpu: 1, + memory: 8, + pvc: 3000, + count: 4, + }; + + pulsarBaseConfig = { + bookie: { + cpu: 2, + memory: 16, + count: 3, + journal: 100, + ledgers: 4000, + }, + broker: { + cpu: 2, + memory: 8, + count: 2, + }, + proxy: { + cpu: 1, + memory: 4, + count: 2, + }, + zookeeper: { + cpu: 0.5, + memory: 4, + count: 3, + pvc: 100, + }, + }; + + kafkaBaseConfig = { + broker: { + cpu: 4, + memory: 16, + count: 3, + pvc: 4000, + }, + zookeeper: { + cpu: 0.5, + memory: 4, + count: 3, + pvc: 100, + }, + }; + } + + return { + etcd: etcdBaseConfig, + minio: minioBaseConfig, + pulsar: pulsarBaseConfig, + kafka: kafkaBaseConfig, + }; + } +}; + +export const milvusOverviewDataCalculator = ( + params: Record +) => { + const { queryNode, proxy, mixCoord, dataNode, indexNode } = params; + const milvusCpu = + queryNode.cpu * queryNode.count + + proxy.cpu * proxy.count + + mixCoord.cpu * mixCoord.count + + dataNode.cpu * dataNode.count + + indexNode.cpu * indexNode.count; + + const milvusMemory = + queryNode.memory * queryNode.count + + proxy.memory * proxy.count + + mixCoord.memory * mixCoord.count + + dataNode.memory * dataNode.count + + indexNode.memory * indexNode.count; + + return { + milvusCpu, + milvusMemory, + }; +}; + +export const dependencyOverviewDataCalculator = (params: { + mode: ModeEnum; + dependency: DependencyComponentEnum; + dependencyConfig: DependencyConfigType; +}) => { + const { mode, dependency, dependencyConfig } = params; + const { etcd, minio, pulsar, kafka } = dependencyConfig; + + const pulsarCpu = + mode === ModeEnum.Standalone + ? 0 + : pulsar.bookie.cpu * pulsar.bookie.count + + pulsar.broker.cpu * pulsar.broker.count + + pulsar.zookeeper.cpu * pulsar.zookeeper.count + + pulsar.proxy.cpu * pulsar.proxy.count; + const kafkaCpu = + mode === ModeEnum.Standalone + ? 0 + : kafka.broker.cpu * kafka.broker.count + + kafka.zookeeper.cpu * kafka.zookeeper.count; + const streamPlatformCpu = + dependency === DependencyComponentEnum.Pulsar ? pulsarCpu : kafkaCpu; + const dependencyCpu = + etcd.cpu * etcd.count + minio.cpu * etcd.count + streamPlatformCpu; + + const pulsarMemory = + mode === ModeEnum.Standalone + ? 0 + : pulsar.bookie.memory * pulsar.bookie.count + + pulsar.broker.memory * pulsar.broker.count + + pulsar.zookeeper.memory * pulsar.zookeeper.count + + pulsar.proxy.memory * pulsar.proxy.count; + const kafkaMemory = + mode === ModeEnum.Standalone + ? 0 + : kafka.broker.memory * kafka.broker.count + + kafka.zookeeper.memory * kafka.zookeeper.count; + + const streamPlatformMemory = + dependency === DependencyComponentEnum.Pulsar ? pulsarMemory : kafkaMemory; + + const dependencyMemory = + etcd.memory * etcd.count + minio.memory * etcd.count + streamPlatformMemory; + + const pulsarStorage = + mode === ModeEnum.Standalone + ? 0 + : (pulsar.bookie.journal + pulsar.bookie.ledgers) * pulsar.bookie.count + + pulsar.zookeeper.pvc * pulsar.zookeeper.count; + const kafkaStorage = + mode === ModeEnum.Standalone + ? 0 + : kafka.broker.pvc * kafka.broker.count + + kafka.zookeeper.pvc * kafka.zookeeper.count; + const streamPlatformStorage = + dependency === DependencyComponentEnum.Pulsar + ? pulsarStorage + : kafkaStorage; + + const dependencyStorage = + etcd.pvc * etcd.count + minio.pvc * etcd.count + streamPlatformStorage; + + return { + dependencyCpu, + dependencyMemory, + dependencyStorage, + }; +}; diff --git a/tailwind.config.js b/tailwind.config.js index 805d28c3d..0e7cbbc90 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -19,6 +19,7 @@ module.exports = { black3: '#D0D7DC', black4: '#ececee', blue1: '#00B3FF', + gray1: '#CDD8E8', textPrimary: 'var(--color-text-primary)', }, boxShadow: {