From 872c73d574dd834ea925ca9aeeb02879d20f5e64 Mon Sep 17 00:00:00 2001 From: waynelwz <100347187+waynelwz@users.noreply.github.com> Date: Wed, 31 Aug 2022 15:58:49 +0800 Subject: [PATCH] feat: refector dataset/eva by datastore api (#1055) * wip: datastore summary * feat: project card with stats * feat: eva table with store summary * fix: dag lab of reaflow erros & new eva overview * update: move project fetching to api header * fix: lint erros * fix: eslint error * feat: new link ui * optimize: text ellis of links * optimize: overview & accordin ui 1. fix lazylog error when empty * feat: user avator * wip: binarylable heatmap * feat: eva result based on datastore * fix: eslint error --- console/.eslintrc.js | 1 + console/package.json | 3 +- console/pnpm-lock.yaml | 257 +++++++++++- console/src/api/ApiHeader.tsx | 24 +- .../src/components/Accordion/Accordion.tsx | 3 + console/src/components/Avatar/index.tsx | 31 ++ console/src/components/BaseSidebar.tsx | 50 ++- .../BusyLoaderWrapper/BusyPlaceholder.tsx | 4 +- console/src/components/Header/index.tsx | 143 +++++-- console/src/components/IconFont/index.tsx | 2 +- console/src/components/Indicator/utils.ts | 51 ++- console/src/components/Link/ButtonLink.tsx | 37 ++ console/src/components/Link/IconLink.tsx | 34 ++ console/src/components/Link/Link.tsx | 39 ++ console/src/components/Link/TextLink.tsx | 37 ++ console/src/components/Link/index.ts | 4 + console/src/components/Table/TableNormal.tsx | 10 +- console/src/components/Table/TableTyped.tsx | 26 +- console/src/components/Table/index.tsx | 8 +- .../data-table/data-custom-table.tsx | 4 +- .../src/components/data-table/header-cell.tsx | 6 +- .../data-table/measure-column-widths.tsx | 1 - .../components/data-table/storeContext.tsx | 42 ++ .../datastore/hooks/useParseDatastore.ts | 41 ++ console/src/domain/datastore/utils.ts | 33 +- .../domain/project/hooks/useFetchProject.ts | 3 +- .../src/domain/project/schemas/project.tsx | 6 + console/src/domain/user/schemas/user.tsx | 1 + console/src/i18n/locales.ts | 9 +- console/src/main.tsx | 7 +- console/src/pages/Dataset/DatasetListCard.tsx | 26 +- .../pages/Dataset/DatasetOverviewLayout.tsx | 2 + .../pages/Dataset/DatasetVersionLayout.tsx | 12 +- .../pages/Dataset/DatasetVersionListCard.tsx | 25 +- .../src/pages/Evaluation/EvaluationLayout.tsx | 9 +- .../pages/Evaluation/EvaluationListCard.tsx | 89 ++--- .../Evaluation/EvaluationListCompare.tsx | 39 +- .../Evaluation/EvaluationOverviewLayout.tsx | 172 ++++---- .../pages/Evaluation/EvaluationResults.tsx | 62 ++- console/src/pages/Job/JobDAG.tsx | 3 - console/src/pages/Job/JobLayout.tsx | 10 +- console/src/pages/Job/JobListCard.tsx | 11 +- console/src/pages/Job/JobTasks.tsx | 12 +- console/src/pages/Model/ModelLayout.tsx | 79 +++- console/src/pages/Model/ModelListCard.tsx | 13 +- .../src/pages/Model/ModelVersionLayout.tsx | 11 +- console/src/pages/Model/Overview.tsx | 60 +-- console/src/pages/Project/Overview.tsx | 10 +- console/src/pages/Project/ProjectListCard.tsx | 268 ++++++++----- console/src/pages/Runtime/RuntimeLayout.tsx | 10 +- console/src/pages/Runtime/RuntimeListCard.tsx | 11 +- .../pages/Runtime/RuntimeVersionLayout.tsx | 11 +- console/src/routes.tsx | 374 +++++++++--------- console/vite.config.ts | 2 +- 54 files changed, 1476 insertions(+), 762 deletions(-) create mode 100644 console/src/components/Avatar/index.tsx create mode 100644 console/src/components/Link/ButtonLink.tsx create mode 100644 console/src/components/Link/IconLink.tsx create mode 100644 console/src/components/Link/Link.tsx create mode 100644 console/src/components/Link/TextLink.tsx create mode 100644 console/src/components/Link/index.ts create mode 100644 console/src/components/data-table/storeContext.tsx create mode 100644 console/src/domain/datastore/hooks/useParseDatastore.ts diff --git a/console/.eslintrc.js b/console/.eslintrc.js index ee7fcfeff3..9faa40002b 100644 --- a/console/.eslintrc.js +++ b/console/.eslintrc.js @@ -76,6 +76,7 @@ module.exports = { 'react/jsx-one-expression-per-line': 'off', 'react/jsx-wrap-multilines': 'off', 'react/no-array-index-key': 'off', + 'react/jsx-props-no-spreading': 'off', 'react/require-default-props': [ 'error', { diff --git a/console/package.json b/console/package.json index e002fc16a2..c898e40170 100644 --- a/console/package.json +++ b/console/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@aksel/structjs": "^1.0.0", "@faker-js/faker": "^6.3.1", "@monaco-editor/react": "^4.3.1", "@papercups-io/chat-widget": "^1.3.1", @@ -105,7 +106,7 @@ "xterm": "^4.14.1", "xterm-addon-fit": "^0.5.0", "xterm-addon-web-links": "^0.4.0", - "zustand": "^4.0.0-rc.1" + "zustand": "v4.1.1" }, "scripts": { "typecheck": "tsc --noEmit -p ./tsconfig.json", diff --git a/console/pnpm-lock.yaml b/console/pnpm-lock.yaml index d14fef9c56..0d2921fcb0 100644 --- a/console/pnpm-lock.yaml +++ b/console/pnpm-lock.yaml @@ -17,6 +17,7 @@ overrides: react-virtualized: git+https://git@github.com/remorses/react-virtualized-fixed-import.git#9.22.3 specifiers: + '@aksel/structjs': ^1.0.0 '@babel/core': '>=7.13.0 <8.0.0' '@faker-js/faker': ^6.3.1 '@mdx-js/react': ^2.1.3 @@ -171,9 +172,10 @@ specifiers: xterm: ^4.14.1 xterm-addon-fit: ^0.5.0 xterm-addon-web-links: ^0.4.0 - zustand: ^4.0.0-rc.1 + zustand: v4.1.1 dependencies: + '@aksel/structjs': 1.0.0 '@faker-js/faker': 6.3.1 '@monaco-editor/react': 4.4.5_gnpkoj5uzrqs5wqxh42z7ffyam '@papercups-io/chat-widget': 1.3.1_sfoxds7t5ydpegc3knd667wn6m @@ -276,7 +278,7 @@ dependencies: xterm: 4.19.0 xterm-addon-fit: 0.5.0_xterm@4.19.0 xterm-addon-web-links: 0.4.0_xterm@4.19.0 - zustand: 4.0.0_immer@9.0.15+react@17.0.2 + zustand: 4.1.1_immer@9.0.15+react@17.0.2 devDependencies: '@babel/core': 7.18.10 @@ -338,6 +340,12 @@ packages: resolution: {integrity: sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g==} dev: false + /@aksel/structjs/1.0.0: + resolution: {integrity: sha512-7BuOlCj7bS09Gr/uJFrKJVWaZqTSOoK2eUuqUebAZq5cALZP6eNz5K5cbprYNX6KBFGMnR+CeSmZk/DjVo3ecg==} + dependencies: + npm-name: 5.5.0 + dev: false + /@ampproject/remapping/2.2.0: resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==} engines: {node: '>=6.0.0'} @@ -3205,6 +3213,11 @@ packages: resolution: {integrity: sha512-K7C7IlQ3zLePEZleUN21ceBA2aLcMnLHTLph8QWk1JK37L90obdpY+QGY8bXMKxf1ht1Z0MNewvXxWv0oGDYFg==} dev: true + /@sindresorhus/is/0.14.0: + resolution: {integrity: sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==} + engines: {node: '>=6'} + dev: false + /@sinonjs/commons/1.8.3: resolution: {integrity: sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==} dependencies: @@ -4946,6 +4959,13 @@ packages: - supports-color dev: true + /@szmarczak/http-timer/1.1.2: + resolution: {integrity: sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==} + engines: {node: '>=6'} + dependencies: + defer-to-connect: 1.1.3 + dev: false + /@testing-library/dom/7.31.2: resolution: {integrity: sha512-3UqjCpey6HiTZT92vODYLPxTBWlM8ZOOjr3LX5F37/VRipW2M1kX6I/Cm4VXzteZqfGfagg8yXywpcOgQBlNsQ==} engines: {node: '>=10'} @@ -5350,6 +5370,12 @@ packages: /@types/json5/0.0.29: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + /@types/keyv/3.1.4: + resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} + dependencies: + '@types/node': 18.6.5 + dev: false + /@types/lodash/4.14.182: resolution: {integrity: sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==} @@ -5519,6 +5545,12 @@ packages: '@types/node': 18.6.5 dev: true + /@types/responselike/1.0.0: + resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} + dependencies: + '@types/node': 18.6.5 + dev: false + /@types/retry/0.12.0: resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} dev: true @@ -7565,6 +7597,10 @@ packages: resolution: {integrity: sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==} dev: true + /builtins/1.0.3: + resolution: {integrity: sha512-uYBjakWipfaO/bXI7E8rq6kpwHRZK5cNYrUv2OzZSI/FvmdMyXJ2tG9dKcjEC5YHmHpUAwsargWIZNWdxb/bnQ==} + dev: false + /bytes/3.0.0: resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==} engines: {node: '>= 0.8'} @@ -7655,6 +7691,19 @@ packages: unset-value: 1.0.0 dev: true + /cacheable-request/6.1.0: + resolution: {integrity: sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==} + engines: {node: '>=8'} + dependencies: + clone-response: 1.0.3 + get-stream: 5.2.0 + http-cache-semantics: 4.1.0 + keyv: 3.1.0 + lowercase-keys: 2.0.0 + normalize-url: 4.5.1 + responselike: 1.0.2 + dev: false + /cachedir/2.3.0: resolution: {integrity: sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==} engines: {node: '>=6'} @@ -7979,6 +8028,12 @@ packages: shallow-clone: 3.0.1 dev: true + /clone-response/1.0.3: + resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} + dependencies: + mimic-response: 1.0.1 + dev: false + /clsx/1.1.0: resolution: {integrity: sha512-3avwM37fSK5oP6M5rQ9CNe99lwxhXDOeSWVPAOYF6OazUTgZCMb0yWlJpmdD74REy1gkEaFiub2ULv4fq9GUhA==} engines: {node: '>=6'} @@ -9531,6 +9586,13 @@ packages: resolution: {integrity: sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==} engines: {node: '>=0.10'} + /decompress-response/3.3.0: + resolution: {integrity: sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==} + engines: {node: '>=4'} + dependencies: + mimic-response: 1.0.1 + dev: false + /dedent/0.7.0: resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} dev: true @@ -9551,6 +9613,11 @@ packages: regexp.prototype.flags: 1.4.3 dev: false + /deep-extend/0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + dev: false + /deep-is/0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -9583,6 +9650,10 @@ packages: deep-copy: 1.4.2 dev: false + /defer-to-connect/1.1.3: + resolution: {integrity: sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==} + dev: false + /define-lazy-prop/2.0.0: resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} engines: {node: '>=8'} @@ -9940,6 +10011,10 @@ packages: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} dev: true + /duplexer3/0.1.5: + resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==} + dev: false + /duplexify/3.7.1: resolution: {integrity: sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==} dependencies: @@ -12104,14 +12179,12 @@ packages: engines: {node: '>=6'} dependencies: pump: 3.0.0 - dev: true /get-stream/5.2.0: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} dependencies: pump: 3.0.0 - dev: true /get-stream/6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} @@ -12418,6 +12491,25 @@ packages: xtend: 4.0.2 dev: false + /got/9.6.0: + resolution: {integrity: sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==} + engines: {node: '>=8.6'} + dependencies: + '@sindresorhus/is': 0.14.0 + '@szmarczak/http-timer': 1.1.2 + '@types/keyv': 3.1.4 + '@types/responselike': 1.0.0 + cacheable-request: 6.1.0 + decompress-response: 3.3.0 + duplexer3: 0.1.5 + get-stream: 4.1.0 + lowercase-keys: 1.0.1 + mimic-response: 1.0.1 + p-cancelable: 1.1.0 + to-readable-stream: 1.0.0 + url-parse-lax: 3.0.0 + dev: false + /graceful-fs/4.2.10: resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} @@ -12951,6 +13043,10 @@ packages: entities: 2.2.0 dev: true + /http-cache-semantics/4.1.0: + resolution: {integrity: sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==} + dev: false + /http-deceiver/1.2.7: resolution: {integrity: sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==} dev: true @@ -13227,7 +13323,6 @@ packages: /ini/1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - dev: true /ini/2.0.0: resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} @@ -13277,6 +13372,11 @@ packages: loose-envify: 1.4.0 dev: false + /ip-regex/4.3.0: + resolution: {integrity: sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==} + engines: {node: '>=8'} + dev: false + /ip/2.0.0: resolution: {integrity: sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==} dev: true @@ -13650,6 +13750,13 @@ packages: engines: {node: '>=6'} dev: true + /is-scoped/2.1.0: + resolution: {integrity: sha512-Cv4OpPTHAK9kHYzkzCrof3VJh7H/PrG2MBUMvvJebaaUMbqhm0YAtXnvh0I3Hnj2tMZWwrRROWLSgfJrKqWmlQ==} + engines: {node: '>=8'} + dependencies: + scoped-regex: 2.1.0 + dev: false + /is-set/2.0.2: resolution: {integrity: sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==} dev: true @@ -13697,6 +13804,13 @@ packages: engines: {node: '>=10'} dev: true + /is-url-superb/3.0.0: + resolution: {integrity: sha512-3faQP+wHCGDQT1qReM5zCPx2mxoal6DzbzquFlCYJLWyy4WPTved33ea2xFbX37z4NoriEwZGIYhFtx8RUB5wQ==} + engines: {node: '>=8'} + dependencies: + url-regex: 5.0.0 + dev: false + /is-utf8/0.2.1: resolution: {integrity: sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==} dev: true @@ -14551,6 +14665,10 @@ packages: engines: {node: '>=4'} hasBin: true + /json-buffer/3.0.0: + resolution: {integrity: sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==} + dev: false + /json-parse-better-errors/1.0.2: resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} dev: true @@ -14757,6 +14875,12 @@ packages: resolution: {integrity: sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==} dev: false + /keyv/3.1.0: + resolution: {integrity: sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==} + dependencies: + json-buffer: 3.0.0 + dev: false + /kind-of/3.2.2: resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==} engines: {node: '>=0.10.0'} @@ -15014,6 +15138,10 @@ packages: resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} dev: true + /lodash.zip/4.2.0: + resolution: {integrity: sha512-C7IOaBBK/0gMORRBd8OETNx3kmOkgIWIPvyDpZSCTwUrpYmgZwJkjZeOD8ww4xbOUOs4/attY+pciKvadNfFbg==} + dev: false + /lodash/4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -15069,6 +15197,16 @@ packages: tslib: 2.4.0 dev: true + /lowercase-keys/1.0.1: + resolution: {integrity: sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==} + engines: {node: '>=0.10.0'} + dev: false + + /lowercase-keys/2.0.0: + resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} + engines: {node: '>=8'} + dev: false + /lowlight/1.12.1: resolution: {integrity: sha512-OqaVxMGIESnawn+TU/QMV5BJLbUghUfjDWPAtFqDYDmDtr4FnB+op8xM+pR7nKlauHNUHXGt0VgWatFB8voS5w==} dependencies: @@ -15887,6 +16025,11 @@ packages: engines: {node: '>=6'} dev: true + /mimic-response/1.0.1: + resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} + engines: {node: '>=4'} + dev: false + /min-document/2.19.0: resolution: {integrity: sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==} dependencies: @@ -16336,6 +16479,11 @@ packages: svg-arc-to-cubic-bezier: 3.2.0 dev: false + /normalize-url/4.5.1: + resolution: {integrity: sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==} + engines: {node: '>=8'} + dev: false + /normalize-url/6.1.0: resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} engines: {node: '>=10'} @@ -16349,6 +16497,19 @@ packages: resolution: {integrity: sha512-5PDmaAsVfnWUgTUbJ3ERwn7u79Z0dYxN9ErxCpVJJqe2RK0PJ3z+iFUxuqjwtlDDegXvtWoxD/3Fzxox7tFGWA==} dev: false + /npm-name/5.5.0: + resolution: {integrity: sha512-l7/uyVfEi2e3ho+ovaJZC0xlbwzXNUz3RxkxpfcnLuoGKAuYoo9YoJ/uy18PsTD8IziugGHks4t/mGmBJEZ4Qg==} + engines: {node: '>=8'} + dependencies: + got: 9.6.0 + is-scoped: 2.1.0 + is-url-superb: 3.0.0 + lodash.zip: 4.2.0 + registry-auth-token: 4.2.2 + registry-url: 5.1.0 + validate-npm-package-name: 3.0.0 + dev: false + /npm-run-path/2.0.2: resolution: {integrity: sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==} engines: {node: '>=4'} @@ -16643,6 +16804,11 @@ packages: p-map: 2.1.0 dev: true + /p-cancelable/1.1.0: + resolution: {integrity: sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==} + engines: {node: '>=6'} + dev: false + /p-cancelable/3.0.0: resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==} engines: {node: '>=12.20'} @@ -18042,6 +18208,11 @@ packages: engines: {node: '>= 0.8.0'} dev: true + /prepend-http/2.0.0: + resolution: {integrity: sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==} + engines: {node: '>=4'} + dev: false + /prettier-linter-helpers/1.0.0: resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} engines: {node: '>=6.0.0'} @@ -18255,7 +18426,6 @@ packages: dependencies: end-of-stream: 1.4.4 once: 1.4.0 - dev: true /pumpify/1.5.1: resolution: {integrity: sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==} @@ -18477,6 +18647,16 @@ packages: shallowequal: 1.1.0 dev: false + /rc/1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.6 + strip-json-comments: 2.0.1 + dev: false + /rdk/6.0.2_646vzgqebfewgbtcnhdqyrdafi: resolution: {integrity: sha512-Q3nlhqboFdoigCCHIUZFDzAlRY6dTCfOi4zPuELLcbqUZ8cqFE6HK7KREBRqIo2WPmGrCmf+FW299j/0LAvJIA==} peerDependencies: @@ -19618,6 +19798,20 @@ packages: unicode-match-property-value-ecmascript: 2.0.0 dev: true + /registry-auth-token/4.2.2: + resolution: {integrity: sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg==} + engines: {node: '>=6.0.0'} + dependencies: + rc: 1.2.8 + dev: false + + /registry-url/5.1.0: + resolution: {integrity: sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==} + engines: {node: '>=8'} + dependencies: + rc: 1.2.8 + dev: false + /regjsgen/0.6.0: resolution: {integrity: sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==} dev: true @@ -20051,6 +20245,12 @@ packages: supports-preserve-symlinks-flag: 1.0.0 dev: true + /responselike/1.0.2: + resolution: {integrity: sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==} + dependencies: + lowercase-keys: 1.0.1 + dev: false + /restore-cursor/3.1.0: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} @@ -20331,6 +20531,11 @@ packages: ajv-keywords: 5.1.0_ajv@8.11.0 dev: true + /scoped-regex/2.1.0: + resolution: {integrity: sha512-g3WxHrqSWCZHGHlSrF51VXFdjImhwvH8ZO/pryFH56Qi0cDsZfylQa/t0jCzVQFNbNvM00HfHjkDPEuarKDSWQ==} + engines: {node: '>=8'} + dev: false + /screenfull/5.2.0: resolution: {integrity: sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==} engines: {node: '>=0.10.0'} @@ -21157,6 +21362,11 @@ packages: dependencies: min-indent: 1.0.1 + /strip-json-comments/2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + dev: false + /strip-json-comments/3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -21769,6 +21979,11 @@ packages: resolution: {integrity: sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==} dev: false + /tlds/1.231.0: + resolution: {integrity: sha512-L7UQwueHSkGxZHQBXHVmXW64oi+uqNtzFt2x6Ssk7NVnpIbw16CRs4eb/jmKOZ9t2JnqZ/b3Cfvo97lnXqKrhw==} + hasBin: true + dev: false + /tmp/0.2.1: resolution: {integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==} engines: {node: '>=8.17.0'} @@ -21813,6 +22028,11 @@ packages: parse-unit: 1.0.1 dev: false + /to-readable-stream/1.0.0: + resolution: {integrity: sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==} + engines: {node: '>=6'} + dev: false + /to-regex-range/2.1.1: resolution: {integrity: sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==} engines: {node: '>=0.10.0'} @@ -22397,10 +22617,25 @@ packages: webpack: 4.46.0 dev: true + /url-parse-lax/3.0.0: + resolution: {integrity: sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==} + engines: {node: '>=4'} + dependencies: + prepend-http: 2.0.0 + dev: false + /url-polyfill/1.1.12: resolution: {integrity: sha512-mYFmBHCapZjtcNHW0MDq9967t+z4Dmg5CJ0KqysK3+ZbyoNOWQHksGCTWwDhxGXllkWlOc10Xfko6v4a3ucM6A==} dev: false + /url-regex/5.0.0: + resolution: {integrity: sha512-O08GjTiAFNsSlrUWfqF1jH0H1W3m35ZyadHrGv5krdnmPPoxP27oDTqux/579PtaroiSGm5yma6KT1mHFH6Y/g==} + engines: {node: '>=8'} + dependencies: + ip-regex: 4.3.0 + tlds: 1.231.0 + dev: false + /url/0.11.0: resolution: {integrity: sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ==} dependencies: @@ -22571,6 +22806,12 @@ packages: spdx-expression-parse: 3.0.1 dev: true + /validate-npm-package-name/3.0.0: + resolution: {integrity: sha512-M6w37eVCMMouJ9V/sdPGnC5H4uDr73/+xdq0FBLO3TFFX1+7wiUY6Es328NN+y43tmY+doUdN9g9J21vqB7iLw==} + dependencies: + builtins: 1.0.3 + dev: false + /validate.io-array-like/1.0.2: resolution: {integrity: sha512-rGLiN0cvY9OWzQcWP+RtqZR/MK9RUz3gKDTCcRLtEQ/BvlanMF5PyqtVIN+CgrIBCv/ypfme9v7r4yMJPYpbNA==} dependencies: @@ -23565,8 +23806,8 @@ packages: engines: {node: '>=10'} dev: true - /zustand/4.0.0_immer@9.0.15+react@17.0.2: - resolution: {integrity: sha512-OrsfQTnRXF1LZ9/vR/IqN9ws5EXUhb149xmPjErZnUrkgxS/gAHGy2dPNIVkVvoxrVe1sIydn4JjF0dYHmGeeQ==} + /zustand/4.1.1_immer@9.0.15+react@17.0.2: + resolution: {integrity: sha512-h4F3WMqsZgvvaE0n3lThx4MM81Ls9xebjvrABNzf5+jb3/03YjNTSgZXeyrvXDArMeV9untvWXRw1tY+ntPYbA==} engines: {node: '>=12.7.0'} peerDependencies: immer: '>=9.0' diff --git a/console/src/api/ApiHeader.tsx b/console/src/api/ApiHeader.tsx index 94055623c9..5f09ef8057 100644 --- a/console/src/api/ApiHeader.tsx +++ b/console/src/api/ApiHeader.tsx @@ -8,7 +8,9 @@ import { getErrMsg } from '@/api' import { useLocation } from 'react-router-dom' import useTranslation from '@/hooks/useTranslation' import { useCurrentUserRoles } from '@/hooks/useCurrentUserRoles' -import { useFirstRender } from '../hooks/useFirstRender' +import { useFirstRender } from '@/hooks/useFirstRender' +import { useProject } from '@project/hooks/useProject' +import { useFetchProject } from '@/domain/project/hooks/useFetchProject' export default function ApiHeader() { const location = useLocation() @@ -21,6 +23,11 @@ export default function ApiHeader() { const [, setCurrentUserRoles] = useCurrentUserRoles() const userRoles = useQuery('currentUserRoles', () => fetchCurrentUserRoles(), { enabled: false }) const [t] = useTranslation() + const projectId = React.useMemo(() => location?.pathname.match(/^\/projects\/(\d*)\/?/)?.[1], [location]) + const projectInfo = useFetchProject(projectId) + const { setProject } = useProject() + + // console.log(projectId, location, location?.pathname.match(/^\/projects\/(\d*)\/?/)) useFirstRender(() => { axios.interceptors.response.use( @@ -58,9 +65,10 @@ export default function ApiHeader() { useEffect(() => { if (currentUser) { - // userRoles.refetch() + userRoles.refetch() } - }, [currentUser, userRoles]) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentUser]) useEffect(() => { if (lastLocationPathRef.current !== location.pathname) { @@ -70,8 +78,16 @@ export default function ApiHeader() { }, [location.pathname]) useEffect(() => { - setCurrentUserRoles(userRoles.data) + if (userRoles.data) { + setCurrentUserRoles(userRoles.data) + } }, [userRoles.data, setCurrentUserRoles]) + useEffect(() => { + if (projectInfo.data) { + setProject(projectInfo.data) + } + }, [projectInfo.data, setProject, projectId]) + return <> } diff --git a/console/src/components/Accordion/Accordion.tsx b/console/src/components/Accordion/Accordion.tsx index 45d1aa88cf..ea6b1f55fc 100644 --- a/console/src/components/Accordion/Accordion.tsx +++ b/console/src/components/Accordion/Accordion.tsx @@ -22,6 +22,9 @@ export default function Accordion({ children, ...props }: IAccordionProps) { Header: { style: { backgroundColor: '#F7F8FA', + paddingTop: '9px', + paddingBottom: '9px', + fontSize: '14px', }, }, Content: { diff --git a/console/src/components/Avatar/index.tsx b/console/src/components/Avatar/index.tsx new file mode 100644 index 0000000000..3ada61006a --- /dev/null +++ b/console/src/components/Avatar/index.tsx @@ -0,0 +1,31 @@ +import React from 'react' +import { createUseStyles } from 'react-jss' +import { StatefulTooltip } from 'baseui/tooltip' + +const useStyles = createUseStyles({ + member: { + display: 'grid', + placeItems: 'center', + backgroundColor: '#8B9FC7', + borderRadius: '50%', + color: '#fff', + }, +}) + +export default function Avatar({ name = '', size = 34, isTooltip = true }) { + const styles = useStyles() + + return ( + +
+ {name?.substr(0, 2)} +
+
+ ) +} diff --git a/console/src/components/BaseSidebar.tsx b/console/src/components/BaseSidebar.tsx index 3b80f10a1b..884a03fc27 100644 --- a/console/src/components/BaseSidebar.tsx +++ b/console/src/components/BaseSidebar.tsx @@ -3,7 +3,7 @@ import { Navigation } from 'baseui/side-navigation' import _ from 'lodash' import React, { useCallback, useContext, useMemo } from 'react' -import { useLocation, useHistory, Link } from 'react-router-dom' +import { useLocation, useHistory } from 'react-router-dom' import useSidebarWidth from '@/hooks/useSidebarWidth' import { useStyletron } from 'baseui' import type { IconBaseProps } from 'react-icons/lib' @@ -11,6 +11,8 @@ import { SidebarContext } from '@/contexts/SidebarContext' import Text from '@/components/Text' import { createUseStyles } from 'react-jss' import IconFont from '@/components/IconFont' +import { StatefulTooltip } from 'baseui/tooltip' +import TextLink from './Link/TextLink' const useBaseSideBarStyles = createUseStyles({ sidebarWrapper: { @@ -29,7 +31,7 @@ const useBaseSideBarStyles = createUseStyles({ backgroundColor: '#F7F8FA', color: '#02102B', display: 'flex', - gap: 14, + gap: 8, fontSize: '14px', placeItems: 'center', padding: '8px 26px 8px 26px', @@ -98,8 +100,13 @@ export default function BaseSidebar({ navItems, style, title, icon, titleLink }: justifyContent: ctx.expanded ? 'flex-start' : 'center', }} > - {Icon} + {ctx.expanded && Icon} {ctx.expanded && {item.title}} + {!ctx.expanded && ( + +
{Icon}
+
+ )} ), itemId: item.path, @@ -146,26 +153,33 @@ export default function BaseSidebar({ navItems, style, title, icon, titleLink }: }} > {title && icon && ( - - {icon} - {ctx.expanded && ( - - {title} - - )} - + +
{icon}
+
+ + {ctx.expanded && ( + + {title} + + )} + +

)}
- +
) @@ -28,7 +28,7 @@ export default function BusyPlaceholder({ type }: IBusyPlaceholderProps) { children = ( <>
- +
) diff --git a/console/src/components/Header/index.tsx b/console/src/components/Header/index.tsx index 62e07c1419..2d64f665c9 100644 --- a/console/src/components/Header/index.tsx +++ b/console/src/components/Header/index.tsx @@ -7,17 +7,19 @@ import useTranslation from '@/hooks/useTranslation' import { createUseStyles } from 'react-jss' import { IThemedStyleProps } from '@/theme' import { useCurrentThemeType } from '@/hooks/useCurrentThemeType' -import { BsChevronDown } from 'react-icons/bs' -import { Link, useHistory } from 'react-router-dom' +import { useHistory } from 'react-router-dom' import PasswordForm from '@user/components/PasswordForm' import { IChangePasswordSchema } from '@user/schemas/user' import { changePassword } from '@user/services/user' import { SidebarContext } from '@/contexts/SidebarContext' import { toaster } from 'baseui/toast' import { useCurrentUserRoles } from '@/hooks/useCurrentUserRoles' +import { TextLink } from '@/components/Link' +import classNames from 'classnames' import { useAuth } from '@/api/Auth' import IconFont from '../IconFont' import Logo from './Logo' +import Avatar from '../Avatar' const useHeaderStyles = createUseStyles({ headerWrapper: { @@ -114,11 +116,11 @@ const useStyles = createUseStyles({ 'cursor': 'pointer', 'display': 'flex', 'align-items': 'center', - 'min-width': '140px', 'height': '100%', 'margin-left': '12px', 'padding': '10px 0 10px 0', 'justifyContent': 'flex-end', + 'width': '220px', '&:hover': { '& $userMenu': { @@ -138,11 +140,11 @@ const useStyles = createUseStyles({ 'backgroundColor': '#264480', }, userMenu: (props: IThemedStyleProps) => ({ + 'padding': '16px 0px 0px', 'position': 'absolute', 'top': '100%', 'display': 'none', 'margin': 0, - 'padding': '8px 0', 'line-height': 1.6, 'flex-direction': 'column', 'alignItems': 'center', @@ -164,18 +166,69 @@ const useStyles = createUseStyles({ 'text-decoration': 'none', }, }, + 'backgroundColor': '#FFF', }), + userMenuItems: { + width: '100%', + display: 'flex', + flexShrink: 0, + flexDirection: 'column', + overflow: 'hidden', + overflowY: 'auto', + background: '#FFFFFF', + transition: 'all 200ms cubic-bezier(0.7, 0.1, 0.33, 1) 0ms', + color: 'rgba(2,16,43,0.60)', + borderRight: '1px solid #E2E7F0', + padding: '8px 12px 8px', + }, userMenuItem: (props: IThemedStyleProps) => ({ + 'display': 'flex', + 'alignItems': 'center', + 'justifyContent': 'left', + 'alignSelf': 'normal', + 'gap': '10px', + 'height': '32px', + 'paddingLeft': '10px', + 'color': props.theme.colors.contentPrimary, + 'borderRadius': '4px', + '&:hover': { + backgroundColor: 'var(--color-brandMenuItemBackground)', + }, + }), + userSignedIn: { + flex: 1, + color: 'rgba(2,16,43,0.40)', + textAlign: 'left', + width: '100%', + marginBottom: '13px', + padding: '0 12px', + }, + userAvatar: { + flex: 1, + width: '100%', + paddingBottom: '17px', + gap: '13px', + display: 'grid', + gridTemplateColumns: '34px 1fr', + overflow: 'hidden', + padding: '0 12px', + }, + userAvatarInfo: { + flex: 1, display: 'flex', + flexDirection: 'row', alignItems: 'center', - justifyContent: 'left', - alignSelf: 'normal', - gap: '10px', - height: '32px', - paddingLeft: '10px', - color: props.theme.colors.contentPrimary, - backgroundColor: 'var(--color-brandMenuItemBackground)', - }), + }, + userAvatarName: { + fontSize: '14px', + lineHeight: '14px', + color: '#02102B', + }, + userAvatarEmail: { + fontSize: '12px', + lineHeight: '12px', + color: 'rgba(2,16,43,0.60)', + }, roundWrapper: { borderRadius: '50%', backgroundColor: 'var(--color-brandWhite)', @@ -185,6 +238,11 @@ const useStyles = createUseStyles({ alignItems: 'center', justifyContent: 'center', }, + divider: { + height: '1px', + width: '100%', + backgroundColor: '#EEF1F6', + }, }) export default function Header() { @@ -231,7 +289,9 @@ export default function Header() { {currentUser && (
- {t('Project')} + + {t('Project')} +
)}
@@ -239,40 +299,51 @@ export default function Header() { {currentUser && (
-
- -
- - + +
- {sysRole === 'OWNER' && ( +

{t('Signed in as')}

+

+ +

+ {currentUser.name} +

{currentUser.email ?? ''}

+
+

+
+
+ {sysRole === 'OWNER' && ( +
{ + history.push('/admin') + }} + > + + {t('Admin Settings')} +
+ )}
{ - history.push('/admin') + setIsChangePasswordOpen(true) }} > - - {t('Admin Settings')} + + {t('Change Password')}
- )} -
{ - setIsChangePasswordOpen(true) - }} - > - - {t('Change Password')}
-
- - {t('Logout')} +
+
+
+ + {t('Logout')} +
diff --git a/console/src/components/IconFont/index.tsx b/console/src/components/IconFont/index.tsx index 8a0ec1646f..7a210cb42f 100644 --- a/console/src/components/IconFont/index.tsx +++ b/console/src/components/IconFont/index.tsx @@ -97,7 +97,7 @@ export default function IconFont({ size = 14, type = 'user', kind = 'inherit', s }} > {type in hijacked ? ( - {type} + {type} ) : ( )} diff --git a/console/src/components/Indicator/utils.ts b/console/src/components/Indicator/utils.ts index 4ff7c17873..6dd4f7b15b 100644 --- a/console/src/components/Indicator/utils.ts +++ b/console/src/components/Indicator/utils.ts @@ -1,8 +1,8 @@ // @ts-nocheck +/* eslint-disable */ +import struct from '@aksel/structjs' -import _ from 'lodash' - -interface IRocAuc { +export interface IRocAuc { fpr: number[] tpr: number[] thresholds: number[] @@ -70,7 +70,18 @@ const Layout = { }, } -export function getRocAucConfig(title = '', labels: string[], data: Record) { +var unhexlify = function (str) { + const f = new Uint8Array(8) + let j = 0 + for (var i = 0, l = str.length; i < l; i += 2) { + f[j] = parseInt(str.substr(i, 2), 16) + j++ + } + let s = struct('>d') + return s.unpack(f.buffer)[0] +} + +export function getRocAucConfig(title = '', labels: string[], data: IRocAuc[]) { const layout = { ...Layout.init, title, @@ -86,20 +97,29 @@ export function getRocAucConfig(title = '', labels: string[], data: Record { + return parseInt(a.id) - parseInt(b.id) + }) + data?.forEach((item, i) => { + if (i % 6 != 0) return + fpr.push(Number(unhexlify(item.fpr).toFixed('4'))) + tpr.push(Number(unhexlify(item.tpr).toFixed('4'))) + }) + const rocAucData = { data: [ - ..._.map(data, (roc_auc, label) => { - return { - x: roc_auc.fpr, - y: roc_auc.tpr, - mode: 'lines+markers', - name: `label ${label}`, - type: 'scatter', - } - }), { - x: [0, 1], - y: [0, 1], + x: fpr, + y: tpr, + mode: 'lines+markers', + name: `label ${0}`, + type: 'scatter', + }, + { + x: [0.0, 1], + y: [0.0, 1], mode: 'lines', name: 'baseline', line: { @@ -112,6 +132,7 @@ export function getRocAucConfig(title = '', labels: string[], data: Record void +} + +export default function ButtonLink({ children, className, onClick, ...rest }: IButtonLinkProps) { + const styles = useLinkStyles() + + return ( + + {children} + + ) +} +// 'row-center--inline', 'gap4', diff --git a/console/src/components/Link/IconLink.tsx b/console/src/components/Link/IconLink.tsx new file mode 100644 index 0000000000..865331d2b5 --- /dev/null +++ b/console/src/components/Link/IconLink.tsx @@ -0,0 +1,34 @@ +import React from 'react' +import { createUseStyles } from 'react-jss' +import cn from 'classnames' +import BaseLink, { ILinkProps } from './Link' + +const useLinkStyles = createUseStyles({ + link: { + 'display': 'flex', + 'fontSize': '12px', + 'backgroundColor': '#F4F5F7', + 'borderRadius': '2px', + 'width': '20px', + 'height': '20px', + 'textDecoration': 'none', + 'color': 'gray', + '&:hover span': { + color: ' #5181E0', + }, + '&:hover': { + color: ' #5181E0', + backgroundColor: '#F0F4FF', + }, + }, +}) + +export default function IconLink({ children, className, ...rest }: ILinkProps) { + const styles = useLinkStyles() + + return ( + + {children} + + ) +} diff --git a/console/src/components/Link/Link.tsx b/console/src/components/Link/Link.tsx new file mode 100644 index 0000000000..01748beb62 --- /dev/null +++ b/console/src/components/Link/Link.tsx @@ -0,0 +1,39 @@ +import React from 'react' +import { StatefulTooltip, StatefulTooltipProps } from 'baseui/tooltip' +import { createUseStyles } from 'react-jss' +import cn from 'classnames' +import { Link as BaseLink, LinkProps } from 'react-router-dom' + +const useLinkStyles = createUseStyles({ + link: { + 'display': 'flex', + 'fontSize': '12px', + 'textDecoration': 'none', + 'color': 'gray', + '&:hover': { + color: ' #5181E0', + }, + }, +}) + +export type ILinkProps = { + to: string + tooltip?: StatefulTooltipProps + children: React.ReactNode + style?: React.CSSProperties + className?: string +} & LinkProps + +export default function Link({ to, tooltip, className, style = {}, children, ...rest }: ILinkProps) { + const styles = useLinkStyles() + + const { content, placement = 'top', ...tooltipRest } = tooltip || {} + + return ( + + + {children} + + + ) +} diff --git a/console/src/components/Link/TextLink.tsx b/console/src/components/Link/TextLink.tsx new file mode 100644 index 0000000000..c41c1b6324 --- /dev/null +++ b/console/src/components/Link/TextLink.tsx @@ -0,0 +1,37 @@ +import React from 'react' +import { createUseStyles } from 'react-jss' +import cn from 'classnames' +import BaseLink, { ILinkProps } from './Link' + +const useLinkStyles = createUseStyles({ + link: { + // display: 'inline-block', + textDecoration: 'none', + flex: 1, + width: '100%', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + overflow: 'hidden', + }, + text: { + 'display': 'initial', + 'fontSize': '14px', + 'color': 'rgb(2, 16, 43)', + '&:hover': { + textDecoration: 'underline', + color: ' #5181E0 ', + }, + }, +}) + +export default function TextLink({ children, className, style, ...rest }: ILinkProps) { + const styles = useLinkStyles() + + return ( + +

+ {children} +

+
+ ) +} diff --git a/console/src/components/Link/index.ts b/console/src/components/Link/index.ts new file mode 100644 index 0000000000..38b8017c7c --- /dev/null +++ b/console/src/components/Link/index.ts @@ -0,0 +1,4 @@ +export * from './Link' +export { default as IconLink } from './IconLink' +export { default as TextLink } from './TextLink' +export { default as ButtonLink } from './ButtonLink' diff --git a/console/src/components/Table/TableNormal.tsx b/console/src/components/Table/TableNormal.tsx index f1689db878..773516559d 100644 --- a/console/src/components/Table/TableNormal.tsx +++ b/console/src/components/Table/TableNormal.tsx @@ -1,14 +1,11 @@ /* eslint-disable */ -import React, { useRef, useState, useMemo } from 'react' -import { Table as TableSemantic, TableProps as BaseTableProps } from 'baseui/table-semantic' +import React, { useRef, useMemo } from 'react' +import { TableProps as BaseTableProps } from 'baseui/table-semantic' import { Pagination, SIZE as PaginationSize } from 'baseui/pagination' import { Skeleton } from 'baseui/skeleton' -import { FiInbox } from 'react-icons/fi' import useTranslation from '@/hooks/useTranslation' -import Text from '@/components/Text' import { usePage } from '@/hooks/usePage' import { IPaginationProps } from '@/components/Table/IPaginationProps' -import { StatefulTooltip } from 'baseui/tooltip' import { StatefulDataTable, CategoricalColumn, @@ -27,6 +24,7 @@ import type { ColumnT, ConfigT, RowT } from '../data-table/types' import { useUID, useUIDSeed } from 'react-uid' import useStore from '../data-table/store' import { useEffect } from 'react' +import BusyPlaceholder from '../BusyLoaderWrapper/BusyPlaceholder' export interface ITableProps extends BaseTableProps { batchActions?: Types.BatchActionT[] @@ -254,7 +252,7 @@ export function TableTyped({ gap: 8, }} > - +
)} /> diff --git a/console/src/components/Table/TableTyped.tsx b/console/src/components/Table/TableTyped.tsx index b7c164adae..1f7d2a26b6 100644 --- a/console/src/components/Table/TableTyped.tsx +++ b/console/src/components/Table/TableTyped.tsx @@ -1,14 +1,11 @@ /* eslint-disable */ -import React, { useRef, useState, useMemo } from 'react' -import { Table as TableSemantic, TableProps as BaseTableProps } from 'baseui/table-semantic' +import React, { useRef, useMemo } from 'react' +import { TableProps as BaseTableProps } from 'baseui/table-semantic' import { Pagination, SIZE as PaginationSize } from 'baseui/pagination' import { Skeleton } from 'baseui/skeleton' -import { FiInbox } from 'react-icons/fi' import useTranslation from '@/hooks/useTranslation' -import Text from '@/components/Text' import { usePage } from '@/hooks/usePage' import { IPaginationProps } from '@/components/Table/IPaginationProps' -import { StatefulTooltip } from 'baseui/tooltip' import { StatefulDataTable, CategoricalColumn, @@ -29,6 +26,7 @@ import { useEffect } from 'react' import { useStyletron } from 'baseui' import { createUseStyles } from 'react-jss' import cn from 'classnames' +import BusyPlaceholder from '../BusyLoaderWrapper/BusyPlaceholder' const useStyles = createUseStyles({ table: { @@ -286,23 +284,7 @@ export function TableTyped({ /> )} // @ts-ignore - emptyMessage={() => ( -
- -
- )} + emptyMessage={() => } />
{paginationProps && ( diff --git a/console/src/components/Table/index.tsx b/console/src/components/Table/index.tsx index e24524eb00..421508f6f1 100644 --- a/console/src/components/Table/index.tsx +++ b/console/src/components/Table/index.tsx @@ -2,18 +2,15 @@ import React from 'react' import { Table as TableSemantic, TableProps as BaseTableProps } from 'baseui/table-semantic' import { Pagination, SIZE as PaginationSize } from 'baseui/pagination' import { Skeleton } from 'baseui/skeleton' -import { FiInbox } from 'react-icons/fi' -import useTranslation from '@/hooks/useTranslation' -import Text from '@/components/Text' import { usePage } from '@/hooks/usePage' import { IPaginationProps } from '@/components/Table/IPaginationProps' +import BusyPlaceholder from '../BusyLoaderWrapper/BusyPlaceholder' export interface ITableProps extends BaseTableProps { paginationProps?: IPaginationProps } export default function Table({ isLoading, columns, data, overrides, paginationProps }: ITableProps) { - const [t] = useTranslation() const [page, setPage] = usePage() return ( @@ -77,8 +74,7 @@ export default function Table({ isLoading, columns, data, overrides, paginationP height: 100, }} > - - {t('no data')} +
} /> diff --git a/console/src/components/data-table/data-custom-table.tsx b/console/src/components/data-table/data-custom-table.tsx index 9fe703c362..bf82ed4fe5 100644 --- a/console/src/components/data-table/data-custom-table.tsx +++ b/console/src/components/data-table/data-custom-table.tsx @@ -228,7 +228,7 @@ const RowPlacementMemo: React.ReactComponentElement = React.memo (c ? c.fillWidth : true)).length const padding = filledColumnsLen === 0 ? 0 : Math.floor(remainder / filledColumnsLen) - // console.log(resizedWidths, remainder, padding) if (padding > 0) { const result = [] // -1 so that we loop over all but the last item @@ -1046,6 +1045,7 @@ export function DataTable({ return result } } + return resizedWidths }, [ gridRef, diff --git a/console/src/components/data-table/header-cell.tsx b/console/src/components/data-table/header-cell.tsx index 3b60ccf4c2..3fa6939e9c 100644 --- a/console/src/components/data-table/header-cell.tsx +++ b/console/src/components/data-table/header-cell.tsx @@ -171,7 +171,8 @@ const HeaderCell = React.forwardRef((props, re position: 'relative', width: '100%', display: 'flex', - alignItems: 'center', + alignItems: 'flex-end', + flex: 1, })} > {(props.isHovered || props.sortDirection) && props.sortable && ( @@ -256,9 +257,8 @@ const HeaderCell = React.forwardRef((props, re {/* usesd for popover postion ref */}
+ children: React.ReactNode +} + +// const StoreProvider = ({ key, initState, children }: IStoreProviderProps) => ( +// const storeRef = useRef>(); +// if (!storeRef.current) { +// storeRef.current = createMyStore(); +// } + +// createCustomStore(key ?? 'store', initState)}>{children} +// ) + +// export { StoreProvider, useStore } + +const MyContext = createContext(null) + +export function StoreProvider({ key = 'store', initState, children }: IStoreProviderProps) { + const storeRef = React.useRef() + if (!storeRef.current) { + storeRef.current = createCustomStore(key, initState) + } + console.log(storeRef.current) + return {children} +} + +export function useContextStore(selector: (state: ITableState) => T) { + const store = useContext(MyContext) + if (store === null) { + throw new Error('Missing Wrapper in the tree') + } + const value = useStore(store, selector) + return value +} diff --git a/console/src/domain/datastore/hooks/useParseDatastore.ts b/console/src/domain/datastore/hooks/useParseDatastore.ts new file mode 100644 index 0000000000..60bdd10fb6 --- /dev/null +++ b/console/src/domain/datastore/hooks/useParseDatastore.ts @@ -0,0 +1,41 @@ +// @ts-nocheck +/* eslint-disable */ +import omit from 'lodash/omit' +import keyBy from 'lodash/keyBy' +import React from 'react' +import { RecordListVO } from '../schemas/datastore' +import struct from '@aksel/structjs' + +const unhexlify = function (str) { + const f = new Uint8Array(8) + let j = 0 + for (var i = 0, l = str.length; i < l; i += 2) { + f[j] = parseInt(str.substr(i, 2), 16) + j++ + } + let s = struct('>d') + + return s.unpack(f.buffer)[0] +} +export function useParseConfusionMatrix(data: RecordListVO = {}) { + const labels = React.useMemo(() => { + const { columnTypes } = data + return Object.keys(omit(columnTypes, 'id')).sort() + }, [data]) + + const binarylabel = React.useMemo(() => { + const { records = [] } = data + const recordMap = keyBy(records, 'id') + const rtn: any[][] = [] + labels.forEach((labeli, i) => { + labels.forEach((labelj, j) => { + if (!rtn[i]) rtn[i] = [] + rtn[i][j] = unhexlify(recordMap?.[labeli.split('_')[1]]?.[labelj]) ?? '' + }) + }) + return rtn + }, [data, labels]) + + // console.log(labels, binarylabel) + return { labels, binarylabel } +} diff --git a/console/src/domain/datastore/utils.ts b/console/src/domain/datastore/utils.ts index 9d6730be1c..9a06c40054 100644 --- a/console/src/domain/datastore/utils.ts +++ b/console/src/domain/datastore/utils.ts @@ -4,8 +4,8 @@ const VERSION_PREFIX_CNT = 2 export function tableDataLink( projectId: string, - datasetId: string, - datasetVersion: string, + datasetName: string, + datasetVersionName: string, query: { uri: string authName: string @@ -14,20 +14,33 @@ export function tableDataLink( Authorization?: string } ) { - return `/api/v1/project/${projectId}/dataset/${datasetId}/version/${datasetVersion}/link?${qs.stringify(query)}` + return `/api/v1/project/${projectId}/dataset/${datasetName}/version/${datasetVersionName}/link?${qs.stringify( + query + )}` } -export function tableNameOfDataset(projectId: string, datasetId: string, datasetVersion: string) { - return `project/${projectId}/dataset/${datasetId}/${datasetVersion.substring( +export function tableNameOfDataset(projectName: string, datasetName: string, datasetVersionName: string) { + return `project/${projectName}/dataset/${datasetName}/${datasetVersionName.substring( 0, VERSION_PREFIX_CNT - )}/${datasetVersion}/meta` + )}/${datasetVersionName}/meta` } -export function tableNameOfResult(projectId: string, evaluationId: string) { - return `project/${projectId}/eval/${evaluationId}/results` +export function tableNameOfResult(projectName: string, evaluationUuid: string) { + return `project/${projectName}/eval/${evaluationUuid.substring(0, VERSION_PREFIX_CNT)}/${evaluationUuid}/results` } -export function tableNameOfSummary(projectId: string) { - return `/project/${projectId}/eval/summary` +export function tableNameOfConfusionMatrix(projectName: string, evaluationUuid: string) { + return `project/${projectName}/eval/${evaluationUuid.substring( + 0, + VERSION_PREFIX_CNT + )}/${evaluationUuid}/confusion_matrix/binarylabel` +} + +export function tableNameOfRocAuc(projectName: string, evaluationUuid: string) { + return `project/${projectName}/eval/${evaluationUuid.substring(0, VERSION_PREFIX_CNT)}/${evaluationUuid}/roc_auc/0` +} + +export function tableNameOfSummary(projectName: string) { + return `project/${projectName}/eval/summary` } diff --git a/console/src/domain/project/hooks/useFetchProject.ts b/console/src/domain/project/hooks/useFetchProject.ts index 29ffef4aa2..66aa1f26aa 100644 --- a/console/src/domain/project/hooks/useFetchProject.ts +++ b/console/src/domain/project/hooks/useFetchProject.ts @@ -3,8 +3,7 @@ import { fetchProject } from '../services/project' export function useFetchProject(projectId?: string) { const projectInfo = useQuery(`fetchProject:${projectId}`, () => { - // eslint-disable-next-line prefer-promise-reject-errors - if (!projectId) return Promise.reject('fetchProject: no projectId stop fetching') + if (!projectId) return Promise.reject(new Error('fetchProject: no projectId stop fetching')) return fetchProject(projectId) }) diff --git a/console/src/domain/project/schemas/project.tsx b/console/src/domain/project/schemas/project.tsx index 3e123572f5..f4767e8d38 100644 --- a/console/src/domain/project/schemas/project.tsx +++ b/console/src/domain/project/schemas/project.tsx @@ -6,6 +6,12 @@ export interface IProjectSchema extends IResourceSchema { owner?: IUserSchema privacy?: string description?: string + statistics: { + modelCounts: number + datasetCounts: number + evaluationCounts: number + memberCounts: number + } } export interface IUpdateProjectSchema { diff --git a/console/src/domain/user/schemas/user.tsx b/console/src/domain/user/schemas/user.tsx index d743690b89..486487d31b 100644 --- a/console/src/domain/user/schemas/user.tsx +++ b/console/src/domain/user/schemas/user.tsx @@ -10,6 +10,7 @@ export interface IUserRoleSchema { export interface IUserSchema extends IResourceSchema { id: string name: string + email: string isEnabled: string role: IUserRoleSchema } diff --git a/console/src/i18n/locales.ts b/console/src/i18n/locales.ts index 6c5cf5936e..cf89139112 100644 --- a/console/src/i18n/locales.ts +++ b/console/src/i18n/locales.ts @@ -348,9 +348,6 @@ const locales0 = { 'Password Not Equal': { en: 'The passwords you entered do not match', }, - 'no data': { - en: 'no data', - }, 'submit': { en: 'submit', }, @@ -616,6 +613,12 @@ const locales0 = { 'or': { en: 'or', }, + 'Members': { + en: 'Members', + }, + 'Signed in as': { + en: 'Signed in as', + }, } export const locales: { [key in keyof typeof locales0]: ILocaleItem } = locales0 diff --git a/console/src/main.tsx b/console/src/main.tsx index 57db783996..435de725e0 100644 --- a/console/src/main.tsx +++ b/console/src/main.tsx @@ -4,7 +4,12 @@ import '@/styles/_global.scss' import '@/i18n' import reportWebVitals from '@/reportWebVitals' import App from './App' - +// eslint-disable-next-line +// @ts-ignore +window.g = null +// eslint-disable-next-line +// @ts-ignore +window.i = null ReactDOM.render( diff --git a/console/src/pages/Dataset/DatasetListCard.tsx b/console/src/pages/Dataset/DatasetListCard.tsx index ab1563539b..84662d4374 100644 --- a/console/src/pages/Dataset/DatasetListCard.tsx +++ b/console/src/pages/Dataset/DatasetListCard.tsx @@ -6,13 +6,12 @@ import { ICreateDatasetSchema } from '@dataset/schemas/dataset' import DatasetForm from '@dataset/components/DatasetForm' import { formatTimestampDateTime } from '@/utils/datetime' import useTranslation from '@/hooks/useTranslation' -// import { Button, SIZE as ButtonSize } from 'baseui/button' import User from '@/domain/user/components/User' import { Modal, ModalHeader, ModalBody } from 'baseui/modal' import Table from '@/components/Table' -import { Link, useParams } from 'react-router-dom' +import { useParams } from 'react-router-dom' import { useFetchDatasets } from '@dataset/hooks/useFetchDatasets' -// import IconFont from '@/components/IconFont' +import { TextLink } from '@/components/Link' export default function DatasetListCard() { const [page] = usePage() @@ -31,32 +30,21 @@ export default function DatasetListCard() { const [t] = useTranslation() return ( - } - // size={ButtonSize.compact} - // onClick={() => setIsCreateDatasetOpen(true)} - // > - // {t('create')} - // - // } - > + { return [ - + {dataset.name} - , + , dataset.owner && , dataset.createdTime && formatTimestampDateTime(dataset.createdTime), - + {t('Version History')} - , + , ] }) ?? [] } diff --git a/console/src/pages/Dataset/DatasetOverviewLayout.tsx b/console/src/pages/Dataset/DatasetOverviewLayout.tsx index bccd2a9e44..001f0fc85f 100644 --- a/console/src/pages/Dataset/DatasetOverviewLayout.tsx +++ b/console/src/pages/Dataset/DatasetOverviewLayout.tsx @@ -167,9 +167,11 @@ export default function DatasetOverviewLayout({ children }: IDatasetLayoutProps) style: { flex: 1, display: 'flex', + fontSize: '14px', }, }, }} + accordion={false} >
diff --git a/console/src/pages/Dataset/DatasetVersionLayout.tsx b/console/src/pages/Dataset/DatasetVersionLayout.tsx index 47dc877597..ad4332f82a 100644 --- a/console/src/pages/Dataset/DatasetVersionLayout.tsx +++ b/console/src/pages/Dataset/DatasetVersionLayout.tsx @@ -6,7 +6,6 @@ import { useParams } from 'react-router-dom' import { INavItem } from '@/components/BaseSidebar' import { fetchDataset } from '@dataset/services/dataset' import BaseSubLayout from '@/pages/BaseSubLayout' -import { useFetchProject } from '@/domain/project/hooks/useFetchProject' export interface IDatasetLayoutProps { children: React.ReactNode @@ -16,7 +15,6 @@ export default function DatasetVersionLayout({ children }: IDatasetLayoutProps) const { projectId, datasetId } = useParams<{ datasetId: string; projectId: string }>() const datasetInfo = useQuery(`fetchDataset:${projectId}:${datasetId}`, () => fetchDataset(projectId, datasetId)) const { dataset, setDataset } = useDataset() - const projectInfo = useFetchProject(projectId) const { setDatasetLoading } = useDatasetLoading() useEffect(() => { setDatasetLoading(datasetInfo.isLoading) @@ -38,25 +36,23 @@ export default function DatasetVersionLayout({ children }: IDatasetLayoutProps) const [t] = useTranslation() const datasetName = dataset?.versionName ?? '-' - const project = projectInfo.data ?? {} - const breadcrumbItems: INavItem[] = useMemo(() => { const items = [ { title: t('Datasets'), - path: `/projects/${project?.id}/datasets`, + path: `/projects/${projectId}/datasets`, }, { title: datasetName, - path: `/projects/${project?.id}/datasets/${datasetId}`, + path: `/projects/${projectId}/datasets/${datasetId}`, }, { title: t('dataset versions'), - path: `/projects/${project?.id}/datasets/${datasetId}/versions`, + path: `/projects/${projectId}/datasets/${datasetId}/versions`, }, ] return items - }, [project?.id, datasetId, datasetName, t]) + }, [projectId, datasetId, datasetName, t]) return {children} } diff --git a/console/src/pages/Dataset/DatasetVersionListCard.tsx b/console/src/pages/Dataset/DatasetVersionListCard.tsx index 3ab5ef5903..e9e3e77ccb 100644 --- a/console/src/pages/Dataset/DatasetVersionListCard.tsx +++ b/console/src/pages/Dataset/DatasetVersionListCard.tsx @@ -9,10 +9,10 @@ import useTranslation from '@/hooks/useTranslation' import User from '@/domain/user/components/User' import { Modal, ModalHeader, ModalBody } from 'baseui/modal' import Table from '@/components/Table' -import { Link, useParams } from 'react-router-dom' +import { useParams } from 'react-router-dom' import { useFetchDatasetVersions } from '@dataset/hooks/useFetchDatasetVersions' -import { StyledLink } from 'baseui/link' import { toaster } from 'baseui/toast' +import { ButtonLink, TextLink } from '@/components/Link' export default function DatasetVersionListCard() { const [page] = usePage() @@ -48,35 +48,22 @@ export default function DatasetVersionListCard() { data={ datasetVersionsInfo.data?.list.map((datasetVersion) => { return [ - {datasetVersion.name} - , - // , + , datasetVersion.createdTime && formatTimestampDateTime(datasetVersion.createdTime), datasetVersion.owner && , - { handleAction(datasetVersion.id) }} > {t('Revert')} - , + , ] }) ?? [] } diff --git a/console/src/pages/Evaluation/EvaluationLayout.tsx b/console/src/pages/Evaluation/EvaluationLayout.tsx index 47ec29dbc0..907da6e4aa 100644 --- a/console/src/pages/Evaluation/EvaluationLayout.tsx +++ b/console/src/pages/Evaluation/EvaluationLayout.tsx @@ -6,7 +6,6 @@ import { useParams } from 'react-router-dom' import { INavItem } from '@/components/BaseSidebar' import { fetchJob } from '@job/services/job' import BaseSubLayout from '@/pages/BaseSubLayout' -import { useFetchProject } from '@/domain/project/hooks/useFetchProject' export interface IJobLayoutProps { children: React.ReactNode @@ -16,7 +15,6 @@ export default function EvaluationLayout({ children }: IJobLayoutProps) { const { projectId, jobId } = useParams<{ jobId: string; projectId: string }>() const jobInfo = useQuery(`fetchJob:${projectId}:${jobId}`, () => fetchJob(projectId, jobId)) const { job, setJob } = useJob() - const projectInfo = useFetchProject(projectId) const { setJobLoading } = useJobLoading() useEffect(() => { setJobLoading(jobInfo.isLoading) @@ -31,21 +29,20 @@ export default function EvaluationLayout({ children }: IJobLayoutProps) { const [t] = useTranslation() const uuid = job?.uuid ?? '-' - const project = projectInfo.data ?? {} const breadcrumbItems: INavItem[] = useMemo(() => { const items = [ { title: t('Jobs'), - path: `/projects/${project?.id}/evaluations`, + path: `/projects/${projectId}/evaluations`, }, { title: uuid ?? '-', - path: `/projects/${project?.id}/evaluations/${jobId}`, + path: `/projects/${projectId}/evaluations/${jobId}`, }, ] return items - }, [project?.id, jobId, uuid, t]) + }, [projectId, jobId, uuid, t]) return {children} } diff --git a/console/src/pages/Evaluation/EvaluationListCard.tsx b/console/src/pages/Evaluation/EvaluationListCard.tsx index 31876a6fc5..a996cc10e4 100644 --- a/console/src/pages/Evaluation/EvaluationListCard.tsx +++ b/console/src/pages/Evaluation/EvaluationListCard.tsx @@ -8,22 +8,22 @@ import useTranslation from '@/hooks/useTranslation' import { Button, SIZE as ButtonSize } from 'baseui/button' import { Modal, ModalHeader, ModalBody } from 'baseui/modal' import Table from '@/components/Table/TableTyped' -import { Link, useHistory, useParams } from 'react-router-dom' +import { useHistory, useParams } from 'react-router-dom' import IconFont from '@/components/IconFont' import { CustomColumn, StringColumn } from '@/components/data-table' import { useDrawer } from '@/hooks/useDrawer' import { useFetchEvaluations } from '@/domain/evaluation/hooks/useFetchEvaluations' -import { useFetchEvaluationAttrs } from '@/domain/evaluation/hooks/useFetchEvaluationAttrs' -import { usePage } from '@/hooks/usePage' import { ColumnT } from '@/components/data-table/types' -import { IEvaluationAttributeValue } from '@/domain/evaluation/schemas/evaluation' import _ from 'lodash' import { useStyletron } from 'baseui' import { ITableState, useEvaluationCompareStore, useEvaluationStore } from '@/components/data-table/store' +import { StoreProvider } from '@/components/data-table/storeContext' import { useFetchViewConfig } from '@/domain/evaluation/hooks/useFetchViewConfig' import { setEvaluationViewConfig } from '@/domain/evaluation/services/evaluation' import { useQueryDatasetList } from '@/domain/datastore/hooks/useFetchDatastore' import { tableNameOfSummary } from '@/domain/datastore/utils' +import { useProject } from '@/domain/project/hooks/useProject' +import { TextLink } from '@/components/Link' import EvaluationListCompare from './EvaluationListCompare' const gridLayout = [ @@ -40,11 +40,10 @@ export default function EvaluationListCard() { const { expandedWidth, expanded } = useDrawer() const [t] = useTranslation() const history = useHistory() - const [page] = usePage() const { projectId } = useParams<{ projectId: string }>() const evaluationsInfo = useFetchEvaluations(projectId, { pageNum: 1, pageSize: 1000 }) - const evaluationAttrsInfo = useFetchEvaluationAttrs(projectId, page) const evaluationViewConfig = useFetchViewConfig(projectId, 'evaluation') + const { project } = useProject() const [isCreateJobOpen, setIsCreateJobOpen] = useState(false) const handleCreateJob = useCallback( @@ -58,11 +57,13 @@ export default function EvaluationListCard() { const store = useEvaluationStore() + // console.log(useContextStore('eva')) + const summaryTableName = React.useMemo(() => { - return tableNameOfSummary(projectId) - }, [projectId]) - const summaryTable = useQueryDatasetList(summaryTableName, { pageNum: 1, pageSize: 1000 }) - console.log(summaryTable.data) + if (!project?.name) return '' + return tableNameOfSummary(project?.name as string) + }, [project]) + const summaryTable = useQueryDatasetList(summaryTableName, { pageNum: 0, pageSize: 1000 }) // TODO // 1. column key should be equal with eva attr field @@ -72,17 +73,15 @@ export default function EvaluationListCard() { CustomColumn({ key: 'uuid', title: t('Evaluation ID'), - // filterable: true, - // renderFilter: () =>
1
, mapDataToValue: (item: any) => item, // @ts-ignore renderCell: (props: any) => { const item = props.value return ( - + {`${item.modelName}-${item.id}`} - + ) }, }), @@ -138,30 +137,27 @@ export default function EvaluationListCard() { const $columnsWithAttrs = useMemo(() => { const columnsWithAttrs = [...columns] - evaluationAttrsInfo?.data?.forEach((attr) => { - if (!attr.name.startsWith('summary/')) { - return - } - - const name = attr.name.split('/').slice(1).join('/') + if (!summaryTable?.data) return columnsWithAttrs - switch (attr.type) { - default: - case 'string': + Object.entries(summaryTable?.data?.columnTypes ?? {}).forEach(([name, type]) => { + switch (type) { + case 'UNKNOWN': + case 'BYTES': + break + case 'STRING': columnsWithAttrs.push( StringColumn({ - key: attr.name, + key: name, title: name, filterType: 'string', - mapDataToValue: (data: any) => data.attributes?.[attr.name], + mapDataToValue: (data: any) => data[name], }) ) break - case 'float': - case 'int': + default: columnsWithAttrs.push( CustomColumn({ - key: attr.name, + key: name, title: name, sortable: true, filterType: 'number', @@ -176,12 +172,9 @@ export default function EvaluationListCard() { }, // @ts-ignore renderCell: (props: any) => { - // .slice(0, 6) return

{props?.value}

}, - mapDataToValue: (data: any): string => - data.attributes?.find((v: IEvaluationAttributeValue) => v.name === attr.name)?.value ?? - '-', + mapDataToValue: (data: any): string => data.attributes?.[name] ?? '-', }) ) break @@ -189,7 +182,7 @@ export default function EvaluationListCard() { }) return columnsWithAttrs - }, [evaluationAttrsInfo, columns]) + }, [summaryTable.data, columns]) const [compareRows, setCompareRows] = useState([]) @@ -209,13 +202,13 @@ export default function EvaluationListCard() { const $data = useMemo( () => evaluationsInfo.data?.list?.map((raw) => { - const $attributes = raw.attributes?.filter((item: any) => _.startsWith(item.name, 'summary')) + const $attributes = summaryTable.data?.records?.find((item: any) => item.id === raw.id) return { ...raw, attributes: $attributes, } }) ?? [], - [evaluationsInfo.data] + [evaluationsInfo.data, summaryTable.data] ) const [gridMode, setGridMode] = useState(1) @@ -367,17 +360,19 @@ export default function EvaluationListCard() { } > -
+ +
+ setIsCreateJobOpen(false)} closeable animate autoFocus> {t('create sth', [t('Job')])} @@ -444,7 +439,7 @@ export default function EvaluationListCard() { title={t('Compare Evaluations')} style={{ marginRight: expanded ? expandedWidth : '0', marginBottom: 0 }} > - + )} diff --git a/console/src/pages/Evaluation/EvaluationListCompare.tsx b/console/src/pages/Evaluation/EvaluationListCompare.tsx index 2bb95eb778..d591c45fc5 100644 --- a/console/src/pages/Evaluation/EvaluationListCompare.tsx +++ b/console/src/pages/Evaluation/EvaluationListCompare.tsx @@ -6,12 +6,12 @@ import Table from '@/components/Table/TableTyped' import { useParams } from 'react-router-dom' import { useFetchJobs } from '@job/hooks/useFetchJobs' import { CustomColumn, StringColumn } from '@/components/data-table' -import { IEvaluationAttributeValue } from '@/domain/evaluation/schemas/evaluation' import _ from 'lodash' import IconFont from '@/components/IconFont' import { useEvaluationCompareStore } from '@/components/data-table/store' import { Checkbox } from 'baseui/checkbox' import { longestCommonSubstring } from '@/utils' +import { RecordListVO } from '../../domain/datastore/schemas/datastore' type RowT = { key: string @@ -73,24 +73,15 @@ const StringCompareCell = ({ value, comparedValue, renderedValue, data }: CellT< export default function EvaluationListCompare({ rows = [], - attrs = [], + attrs, }: { - attrs: IEvaluationAttributeValue[] rows: any[] + attrs: RecordListVO['columnTypes'] }) { const [t] = useTranslation() const [page] = usePage() const { projectId } = useParams<{ projectId: string }>() const evaluationsInfo = useFetchJobs(projectId, page) - - // const results = useQueries( - // rows.map((row: any) => ({ - // queryKey: `fetchJobResult:${projectId}:${row.id}`, - // queryFn: () => fetchJobResult(projectId, row.id), - // refetchOnWindowFocus: false, - // })) - // ) - const store = useEvaluationCompareStore() const { comparePinnedKey, compareShowCellChanges, compareShowDiffOnly } = store.compare ?? {} @@ -123,7 +114,7 @@ export default function EvaluationListCompare({ const row = rows.find((r) => r.id === comparePinnedKey) ?? {} return { ...row, - ..._.mapValues(_.keyBy(row.attributes, 'name'), (o) => o.value), + ...row.attributes, } }, [rows, comparePinnedKey]) @@ -186,27 +177,11 @@ export default function EvaluationListCompare({ const $rowWithAttrs = useMemo(() => { const rowWithAttrs = [...$rows] - attrs.forEach((attr) => { - if (!attr.name.startsWith('summary/')) { - return - } - - const name = attr.name.split('/').slice(1).join('/') - + Object.entries(attrs ?? {}).forEach(([name]) => { rowWithAttrs.push({ - key: attr.name, + key: name, title: name, - values: rows.map((data: any) => { - const attrIndex = data.attributes?.findIndex( - (row: IEvaluationAttributeValue) => row.name === attr.name - ) - - if (attrIndex >= 0) { - return data.attributes?.[attrIndex]?.value ?? '-' - } - - return '-' - }), + values: rows.map((data: any) => data.attributes?.[name] ?? '-'), renderCompare: NumberCompareCell, }) }) diff --git a/console/src/pages/Evaluation/EvaluationOverviewLayout.tsx b/console/src/pages/Evaluation/EvaluationOverviewLayout.tsx index 1940ed9508..658d96cb2b 100644 --- a/console/src/pages/Evaluation/EvaluationOverviewLayout.tsx +++ b/console/src/pages/Evaluation/EvaluationOverviewLayout.tsx @@ -6,9 +6,10 @@ import { useParams } from 'react-router-dom' import { INavItem } from '@/components/BaseSidebar' import { fetchJob } from '@job/services/job' import BaseSubLayout from '@/pages/BaseSubLayout' -import Card from '@/components/Card' import { durationToStr, formatTimestampDateTime } from '@/utils/datetime' -import IconFont from '../../components/IconFont/index' +import IconFont from '@/components/IconFont/index' +import Accordion from '@/components/Accordion' +import { Panel } from 'baseui/accordion' export interface IJobLayoutProps { children: React.ReactNode @@ -20,14 +21,10 @@ function EvaluationOverviewLayout({ children }: IJobLayoutProps) { const { job, setJob } = useJob() const { setJobLoading } = useJobLoading() - // useEffect(() => { - // setExpanded(false) - // }, [setExpanded]) - useEffect(() => { setJobLoading(jobInfo.isLoading) if (jobInfo.isSuccess) { - if (jobInfo.data.id !== job?.id) { + if (jobInfo.data?.id !== job?.id) { setJob(jobInfo.data) } } else if (jobInfo.isLoading) { @@ -74,93 +71,96 @@ function EvaluationOverviewLayout({ children }: IJobLayoutProps) { return items }, [projectId, jobId, t]) - const items = [ - { - label: t('Evaluation ID'), - value: job?.id ?? '-', - }, - { - label: t('Owner'), - value: job?.owner?.name ?? '-', - }, - { - label: t('Status'), - value: job?.jobStatus ?? '-', - }, - { - label: t('Runtime'), - value: job?.duration && job?.duration > 0 ? durationToStr(job?.duration) : '-', - }, - { - label: t('Created'), - value: job?.createdTime && formatTimestampDateTime(job.createdTime), - }, - { - label: t('End Time'), - value: job?.stopTime && formatTimestampDateTime(job.stopTime), - }, - { - label: t('Device'), - value: `${job?.device ?? '-'}, ${job?.deviceAmount ?? '-'}`, - }, - { - label: t('Model'), - style: { - gridColumnStart: 'span 2', + const info = React.useMemo(() => { + const items = [ + { + label: t('Owner'), + value: job?.owner?.name ?? '-', + }, + { + label: t('Status'), + value: job?.jobStatus ?? '-', + }, + { + label: t('Runtime'), + value: job?.duration && job?.duration > 0 ? durationToStr(job?.duration) : '-', + }, + { + label: t('Created'), + value: job?.createdTime && formatTimestampDateTime(job.createdTime), + }, + { + label: t('End Time'), + value: job?.stopTime && formatTimestampDateTime(job.stopTime), + }, + { + label: t('Device'), + value: `${job?.device ?? '-'}, ${job?.deviceAmount ?? '-'}`, + }, + { + label: t('Model'), + style: { + gridColumnStart: 'span 2', + }, + value: `${job?.modelName ?? '-'}:${job?.modelVersion ?? '-'}`, }, - value: `${job?.modelName ?? '-'}:${job?.modelVersion ?? '-'}`, - }, - { - label: t('Datasets'), - style: { - gridColumnStart: 'span 2', + { + label: t('Datasets'), + style: { + gridColumnStart: 'span 2', + }, + value: job?.datasets?.join(', '), }, - value: job?.datasets?.join(', '), - }, - { - label: t('Runtime'), - style: { - gridColumnStart: 'span 2', + { + label: t('Runtime'), + style: { + gridColumnStart: 'span 2', + }, + value: [job?.runtime?.name ?? '-', job?.runtime?.version?.name ?? '-'].join(':'), }, - value: [job?.runtime?.name ?? '-', job?.runtime?.version?.name ?? '-'].join(':'), - }, - ] + ] - const header = ( - - {items.map((v) => ( -
-
- {v?.label}: + return ( +
+ {items.map((v) => ( +
+
+ {v?.label}: +
+
{v?.value}
-
{v?.value}
-
- ))} - + ))} +
+ ) + }, [job, t]) + + const header = useMemo( + () => ( +
+ + {info} + +
+ ), + [job, info, t] ) + return ( - {children} +
{children}
) } diff --git a/console/src/pages/Evaluation/EvaluationResults.tsx b/console/src/pages/Evaluation/EvaluationResults.tsx index bb14e25983..33bca2c0c9 100644 --- a/console/src/pages/Evaluation/EvaluationResults.tsx +++ b/console/src/pages/Evaluation/EvaluationResults.tsx @@ -1,26 +1,79 @@ import LabelsIndicator from '@/components/Indicator/LabelsIndicator' -import React, { useMemo } from 'react' +import React, { useMemo, useEffect } from 'react' import { useParams } from 'react-router-dom' import { useQuery } from 'react-query' import { fetchJobResult } from '@/domain/job/services/job' import { ILabels, INDICATORTYPE } from '@/components/Indicator/types.d' import _ from 'lodash' -import { getHeatmapConfig, getRocAucConfig } from '@/components/Indicator/utils' +import { getHeatmapConfig, getRocAucConfig, IRocAuc } from '@/components/Indicator/utils' import { LabelSmall } from 'baseui/typography' import Card from '@/components/Card' import useTranslation from '@/hooks/useTranslation' import SummaryIndicator from '@/components/Indicator/SummaryIndicator' import BusyPlaceholder from '@/components/BusyLoaderWrapper/BusyPlaceholder' +import { tableNameOfConfusionMatrix, tableNameOfRocAuc } from '@/domain/datastore/utils' +import { useJob } from '@/domain/job/hooks/useJob' +import { useQueryDatasetList, useScanDatastore } from '@/domain/datastore/hooks/useFetchDatastore' +import { useProject } from '@/domain/project/hooks/useProject' +import { useParseConfusionMatrix } from '@/domain/datastore/hooks/useParseDatastore' const PlotlyVisualizer = React.lazy( () => import(/* webpackChunkName: "PlotlyVisualizer" */ '../../components/Indicator/PlotlyVisualizer') ) +function Heatmap({ labels, binarylabel }: any) { + const [t] = useTranslation() + const heatmapData = getHeatmapConfig(t('Confusion Matrix'), labels, binarylabel) + return ( + + }> + + + + ) +} + +function RocAuc({ labels, data }: { labels: any[]; data: IRocAuc[] }) { + const [t] = useTranslation() + const title = t('Roc Auc') + const rocaucData = getRocAucConfig(title, labels, data) + + return ( + + }> + + + + ) +} + function EvaluationResults() { const { jobId, projectId } = useParams<{ jobId: string; projectId: string }>() const jobResult = useQuery(`fetchJobResult:${projectId}:${jobId}`, () => fetchJobResult(projectId, jobId), { refetchOnWindowFocus: false, }) + const { project } = useProject() + const { job } = useJob() + const resultTableName = React.useMemo(() => { + if (!project?.name || !job?.uuid) return '' + return tableNameOfConfusionMatrix(project?.name as string, job?.uuid) + }, [project, job]) + + const resultTable = useQueryDatasetList(resultTableName, { pageNum: 0, pageSize: 1000 }) + // console.log(project?.name, resultTableName, resultTable) + const { labels, binarylabel } = useParseConfusionMatrix(resultTable.data) + + const rocAucTable = useScanDatastore({ + tables: [{ tableName: tableNameOfRocAuc(project?.name as string, job?.uuid ?? '') }], + start: 0, + limit: 1000, + }) + + useEffect(() => { + if (job?.uuid && project?.name) { + rocAucTable.refetch() + } + }, [project?.name, job?.uuid]) const [t] = useTranslation() @@ -40,7 +93,7 @@ function EvaluationResults() { break } case INDICATORTYPE.CONFUSION_MATRIX: { - const heatmapData = getHeatmapConfig(k, _.keys(v?.binarylabel), v?.binarylabel) + const heatmapData = getHeatmapConfig(k, labels, v?.binarylabel) outTitle = t('Confusion Matrix') children = ( }> @@ -150,6 +203,9 @@ function EvaluationResults() { }} > {indicators} + + {/* @ts-ignore */} +
) diff --git a/console/src/pages/Job/JobDAG.tsx b/console/src/pages/Job/JobDAG.tsx index 4f23a3dc63..2b617da8a5 100644 --- a/console/src/pages/Job/JobDAG.tsx +++ b/console/src/pages/Job/JobDAG.tsx @@ -3,7 +3,6 @@ import { useParams } from 'react-router-dom' import { useQuery } from 'react-query' import { fetchJobDAG } from '@/domain/job/services/job' import _ from 'lodash' -// import useTranslation from '@/hooks/useTranslation' import BusyPlaceholder from '@/components/BusyLoaderWrapper/BusyPlaceholder' import DAG from '@/components/DAG/DAG' import Card from '../../components/Card/index' @@ -14,8 +13,6 @@ function JobDAG() { refetchOnWindowFocus: false, }) - // const [t] = useTranslation() - const nodes = useMemo(() => { if (!jobDAG.data?.groupingNodes) return [] diff --git a/console/src/pages/Job/JobLayout.tsx b/console/src/pages/Job/JobLayout.tsx index 0b0bc3fcc3..63371e9885 100644 --- a/console/src/pages/Job/JobLayout.tsx +++ b/console/src/pages/Job/JobLayout.tsx @@ -6,7 +6,6 @@ import { useParams } from 'react-router-dom' import { INavItem } from '@/components/BaseSidebar' import { fetchJob } from '@job/services/job' import BaseSubLayout from '@/pages/BaseSubLayout' -import { useFetchProject } from '@/domain/project/hooks/useFetchProject' export interface IJobLayoutProps { children: React.ReactNode @@ -16,7 +15,6 @@ export default function JobLayout({ children }: IJobLayoutProps) { const { projectId, jobId } = useParams<{ jobId: string; projectId: string }>() const jobInfo = useQuery(`fetchJob:${projectId}:${jobId}`, () => fetchJob(projectId, jobId)) const { job, setJob } = useJob() - const projectInfo = useFetchProject(projectId) const { setJobLoading } = useJobLoading() useEffect(() => { setJobLoading(jobInfo.isLoading) @@ -31,21 +29,19 @@ export default function JobLayout({ children }: IJobLayoutProps) { const [t] = useTranslation() const uuid = job?.uuid ?? '-' - const project = projectInfo.data ?? {} - const breadcrumbItems: INavItem[] = useMemo(() => { const items = [ { title: t('Jobs'), - path: `/projects/${project?.id}/jobs`, + path: `/projects/${projectId}/jobs`, }, { title: uuid ?? '-', - path: `/projects/${project?.id}/jobs/${jobId}`, + path: `/projects/${projectId}/jobs/${jobId}`, }, ] return items - }, [project?.id, jobId, uuid, t]) + }, [projectId, jobId, uuid, t]) return {children} } diff --git a/console/src/pages/Job/JobListCard.tsx b/console/src/pages/Job/JobListCard.tsx index 19958d5e1e..971156cf46 100644 --- a/console/src/pages/Job/JobListCard.tsx +++ b/console/src/pages/Job/JobListCard.tsx @@ -10,11 +10,12 @@ import { Button, SIZE as ButtonSize } from 'baseui/button' import User from '@/domain/user/components/User' import { Modal, ModalHeader, ModalBody } from 'baseui/modal' import Table from '@/components/Table/index' -import { Link, useHistory, useParams } from 'react-router-dom' +import { useHistory, useParams } from 'react-router-dom' import { useFetchJobs } from '@job/hooks/useFetchJobs' import { StyledLink } from 'baseui/link' import { toaster } from 'baseui/toast' import IconFont from '@/components/IconFont' +import { TextLink } from '@/components/Link' export default function JobListCard() { const [t] = useTranslation() @@ -108,16 +109,16 @@ export default function JobListCard() { ), [JobStatusType.SUCCESS]: ( - + {t('View Results')} - + ), } return [ - + {job.uuid} - , + , job.modelName, job.modelVersion, job.owner && , diff --git a/console/src/pages/Job/JobTasks.tsx b/console/src/pages/Job/JobTasks.tsx index 07c967deb6..d73fcf6683 100644 --- a/console/src/pages/Job/JobTasks.tsx +++ b/console/src/pages/Job/JobTasks.tsx @@ -4,12 +4,12 @@ import { toaster } from 'baseui/toast' import useTranslation from '@/hooks/useTranslation' import Card from '@/components/Card' import { LazyLog } from 'react-lazylog' -import { Accordion, Panel } from 'baseui/accordion' +import { Panel } from 'baseui/accordion' import { fetchTaskOfflineFileLog, fetchTaskOfflineLogFiles } from '@/domain/job/services/task' import { getToken } from '@/api' -// import useWebSocket from '@/hooks/useWebSocket' +import { ITaskSchema, TaskStatusType } from '@/domain/job/schemas/task' +import Accordion from '@/components/Accordion' import TaskListCard from './TaskListCard' -import { ITaskSchema, TaskStatusType } from '../../domain/job/schemas/task' export interface IScrollProps { scrollTop: number @@ -38,7 +38,7 @@ export default function JobTasks() { const files: Record = {} data.map(async (v: string) => { const content = await fetchTaskOfflineFileLog(task?.id, v) - files[v] = content + files[v] = content ?? '' setCurrentLogFiles({ ...files, }) @@ -176,11 +176,11 @@ export default function JobTasks() { ) : ( () const modelInfo = useQuery(`fetchModel:${projectId}:${modelId}`, () => fetchModel(projectId, modelId)) const { model, setModel } = useModel() - const projectInfo = useFetchProject(projectId) const { setModelLoading } = useModelLoading() useEffect(() => { setModelLoading(modelInfo.isLoading) @@ -31,21 +32,83 @@ export default function ModelLayout({ children }: IModelLayoutProps) { const [t] = useTranslation() const modelName = model?.versionMeta ?? '-' - const project = projectInfo.data ?? {} - const breadcrumbItems: INavItem[] = useMemo(() => { const items = [ { title: t('Models'), - path: `/projects/${project?.id}/models`, + path: `/projects/${projectId}/models`, }, { title: modelName, - path: `/projects/${project?.id}/models/${modelId}`, + path: `/projects/${projectId}/models/${modelId}`, }, ] return items - }, [project?.id, modelName, modelId, t]) + }, [projectId, modelName, modelId, t]) + + const items = [ + { + label: t('Version Name'), + value: model?.versionName ?? '', + }, + { + label: t('Version Meta'), + value: model?.versionMeta ?? '', + }, + { + label: t('Version Tag'), + value: model?.versionTag ?? '', + }, + { + label: t('Model ID'), + value: model?.id ?? '', + }, + { + label: t('Created'), + value: model?.createdTime && formatTimestampDateTime(model.createdTime), + }, + ] + + const info = ( +
+ {items.map((v) => ( +
+
+ {v?.label}: +
+
{v?.value}
+
+ ))} +
+ ) + + const header = React.useMemo( + () => ( +
+ + {info} + +
+ ), + [model, info, t] + ) - return {children} + return ( + + {children} + + ) } diff --git a/console/src/pages/Model/ModelListCard.tsx b/console/src/pages/Model/ModelListCard.tsx index 0f001d0ec0..1229f7ee01 100644 --- a/console/src/pages/Model/ModelListCard.tsx +++ b/console/src/pages/Model/ModelListCard.tsx @@ -6,13 +6,12 @@ import { ICreateModelSchema } from '@model/schemas/model' import ModelForm from '@model/components/ModelForm' import { formatTimestampDateTime } from '@/utils/datetime' import useTranslation from '@/hooks/useTranslation' -// import { Button, SIZE as ButtonSize } from 'baseui/button' import User from '@/domain/user/components/User' import { Modal, ModalHeader, ModalBody } from 'baseui/modal' import Table from '@/components/Table' -import { Link, useParams } from 'react-router-dom' +import { useParams } from 'react-router-dom' import { useFetchModels } from '@model/hooks/useFetchModels' -// import IconFont from '@/components/IconFont' +import { TextLink } from '@/components/Link' export default function ModelListCard() { const [page] = usePage() @@ -49,14 +48,14 @@ export default function ModelListCard() { data={ modelsInfo.data?.list.map((model) => { return [ - + {model.name} - , + , model.owner && , model.createdTime && formatTimestampDateTime(model.createdTime), - + {t('Version History')} - , + , ] }) ?? [] } diff --git a/console/src/pages/Model/ModelVersionLayout.tsx b/console/src/pages/Model/ModelVersionLayout.tsx index 9ba12c76da..32c2c8ad46 100644 --- a/console/src/pages/Model/ModelVersionLayout.tsx +++ b/console/src/pages/Model/ModelVersionLayout.tsx @@ -6,7 +6,6 @@ import { useParams } from 'react-router-dom' import { INavItem } from '@/components/BaseSidebar' import { fetchModel } from '@model/services/model' import BaseSubLayout from '@/pages/BaseSubLayout' -import { useFetchProject } from '@/domain/project/hooks/useFetchProject' export interface IModelLayoutProps { children: React.ReactNode @@ -16,7 +15,6 @@ export default function ModelVersionLayout({ children }: IModelLayoutProps) { const { projectId, modelId } = useParams<{ modelId: string; projectId: string }>() const modelInfo = useQuery(`fetchModel:${projectId}:${modelId}`, () => fetchModel(projectId, modelId)) const { model, setModel } = useModel() - const projectInfo = useFetchProject(projectId) const { setModelLoading } = useModelLoading() useEffect(() => { setModelLoading(modelInfo.isLoading) @@ -31,25 +29,24 @@ export default function ModelVersionLayout({ children }: IModelLayoutProps) { const [t] = useTranslation() const modelName = model?.versionMeta ?? '-' - const project = projectInfo.data ?? {} const breadcrumbItems: INavItem[] = useMemo(() => { const items = [ { title: t('Models'), - path: `/projects/${project?.id}/models`, + path: `/projects/${projectId}/models`, }, { title: modelName, - path: `/projects/${project?.id}/models/${modelId}`, + path: `/projects/${projectId}/models/${modelId}`, }, { title: t('model versions'), - path: `/projects/${project?.id}/models/${modelId}/versions`, + path: `/projects/${projectId}/models/${modelId}/versions`, }, ] return items - }, [modelName, t, project?.id, modelId]) + }, [modelName, t, projectId, modelId]) return {children} } diff --git a/console/src/pages/Model/Overview.tsx b/console/src/pages/Model/Overview.tsx index 1ef010e589..1dcaa4117c 100644 --- a/console/src/pages/Model/Overview.tsx +++ b/console/src/pages/Model/Overview.tsx @@ -4,7 +4,6 @@ import useTranslation from '@/hooks/useTranslation' import { useModel, useModelLoading } from '@model/hooks/useModel' import Card from '@/components/Card' import { IModelFileSchema } from '@model/schemas/model' -import { formatTimestampDateTime } from '@/utils/datetime' export default function ModelOverview() { const { model } = useModel() @@ -12,70 +11,13 @@ export default function ModelOverview() { const [t] = useTranslation() - const items = [ - { - label: t('Version Name'), - value: model?.versionName ?? '', - }, - { - label: t('Version Meta'), - value: model?.versionMeta ?? '', - }, - { - label: t('Version Tag'), - value: model?.versionTag ?? '', - }, - { - label: t('Model ID'), - value: model?.id ?? '', - }, - { - label: t('Created'), - value: model?.createdTime && formatTimestampDateTime(model.createdTime), - }, - ] - - const info = ( - - {items.map((v) => ( -
-
- {v?.label}: -
-
{v?.value}
-
- ))} -
- ) - return ( <> - {info} - diff --git a/console/src/pages/Project/Overview.tsx b/console/src/pages/Project/Overview.tsx index ffa416d59d..a3bc800303 100644 --- a/console/src/pages/Project/Overview.tsx +++ b/console/src/pages/Project/Overview.tsx @@ -14,6 +14,7 @@ import { createUseStyles } from 'react-jss' import { useFetchProjectMembers } from '@/domain/project/hooks/useFetchProjectMembers' import Button from '@/components/Button/Button' import { useQuery } from 'react-query' +import Avatar from '@/components/Avatar' type IProjectCardProps = { project: IProjectSchema @@ -99,9 +100,10 @@ const ProjectCard = ({ project, onEdit }: IProjectCardProps) => { className={styles.tag} style={{ color: project?.privacy === 'PRIVATE' ? '#4848B3' : '#00B368', + backgroundColor: project?.privacy === 'PRIVATE' ? '#EDEDFF' : '#E6FFF4', }} > - {project.privacy} + {project.privacy === 'PRIVATE' ? t('Private') : t('Public')}

@@ -129,10 +131,8 @@ const ProjectCard = ({ project, onEdit }: IProjectCardProps) => {
- {members.data?.map((member) => ( - -
{member.user.name?.substr(0, 2)}
-
+ {members.data?.map((member, i) => ( + ))}
diff --git a/console/src/pages/Project/ProjectListCard.tsx b/console/src/pages/Project/ProjectListCard.tsx index 66d338386f..84f2d79687 100644 --- a/console/src/pages/Project/ProjectListCard.tsx +++ b/console/src/pages/Project/ProjectListCard.tsx @@ -7,7 +7,6 @@ import ProjectForm from '@project/components/ProjectForm' import useTranslation from '@/hooks/useTranslation' import { Button, SIZE as ButtonSize } from 'baseui/button' import { Modal, ModalHeader, ModalBody } from 'baseui/modal' -import { Link } from 'react-router-dom' import { useFetchProjects } from '@project/hooks/useFetchProjects' import IconFont from '@/components/IconFont' import { useCurrentUser } from '@/hooks/useCurrentUser' @@ -16,70 +15,105 @@ import { QueryInput } from '@/components/data-table/stateful-data-table' import cn from 'classnames' import BusyPlaceholder from '@/components/BusyLoaderWrapper/BusyPlaceholder' import { StatefulTooltip } from 'baseui/tooltip' -import { IProjectSchema } from '../../domain/project/schemas/project' +import { createUseStyles } from 'react-jss' +import { IProjectSchema } from '@/domain/project/schemas/project' +import { IconLink, TextLink } from '@/components/Link' type IProjectCardProps = { project: IProjectSchema onEdit?: () => void } +const useCardStyles = createUseStyles({ + card: { + 'display': 'flex', + 'height': '120px', + 'gap': '6px', + 'background': '#FFFFFF', + 'border': '1px solid #E2E7F0', + 'borderRadius': '4px', + 'padding': '20px', + 'flexDirection': 'column', + 'alignItems': 'space-between', + 'justifyContent': 'space-between', + 'textDecoration': 'none', + 'color': ' rgba(2,16,43,0.60)', + ':hover': { + boxShadow: '0 2px 8px 0 rgba(0,0,0,0.20)', + }, + }, + row: { + display: 'flex', + justifyContent: 'space-between', + flexGrow: 0, + lineHeight: '18px', + }, + rowKey: { + color: 'rgba(2,16,43,0.60)', + marginRight: '8px', + }, + rowValue: { + display: 'flex', + alignItems: 'center', + color: '#02102B', + }, + rowEnd: { + marginLeft: 'auto', + }, + name: { + textOverflow: 'ellipsis', + display: '-webkit-box', + WebkitLineClamp: 1, + whiteSpace: 'nowrap', + overflow: 'hidden', + flexBasis: '80%', + }, + description: { + display: 'flex', + justifyContent: 'space-between', + color: ' rgba(2,16,43,0.60)', + }, + descriptionText: { + lineHeight: '12px', + fontSize: '12px', + whiteSpace: 'normal', + display: '-webkit-box', + WebkitLineClamp: 2, + WebkitBoxOrient: 'vertical', + overflow: 'hidden', + }, + statistics: { + display: 'flex', + justifyContent: 'flex-start', + color: ' rgba(2,16,43,0.60)', + gap: '12px', + }, + statisticsItem: { + display: 'flex', + gap: '4px', + }, + tag: { + fontSize: '12px', + color: '#00B368', + backgroundColor: '#E6FFF4', + borderRadius: '9px', + padding: '3px 10px', + }, +}) + const ProjectCard = ({ project, onEdit }: IProjectCardProps) => { const [css] = useStyletron() const [t] = useTranslation() + const styles = useCardStyles() return ( -
-
- -

+

+
+
+ {[project.owner?.name, project.name].join('/')} -

- +
+
{ display: 'flex', fontSize: '12px', color: project?.privacy === 'PRIVATE' ? '#4848B3' : '#00B368', - backgroundColor: '#E6FFF4', + backgroundColor: project?.privacy === 'PRIVATE' ? '#EDEDFF' : '#E6FFF4', borderRadius: '9px', padding: '3px 10px', }) )} > - {project.privacy?.toLowerCase()} + {project.privacy === 'PRIVATE' ? t('Private') : t('Public')}

-
- - {project.description ?? ''} +
+

{project.description ?? ''}

} + placement='bottom' + > +

{project.description ?? ''}

{ justifyContent: 'space-between', })} > -
+
+
+ + + {project?.statistics.evaluationCounts} + +
+
+ + + {project?.statistics.datasetCounts} + +
+
+ + + {project?.statistics.modelCounts} + +
+
+ + + {project?.statistics.memberCounts} + +
+
- - - - - - + + + +
diff --git a/console/src/pages/Runtime/RuntimeLayout.tsx b/console/src/pages/Runtime/RuntimeLayout.tsx index 83bf5b1bc2..e5994eb9a8 100644 --- a/console/src/pages/Runtime/RuntimeLayout.tsx +++ b/console/src/pages/Runtime/RuntimeLayout.tsx @@ -6,7 +6,6 @@ import { useParams } from 'react-router-dom' import { INavItem } from '@/components/BaseSidebar' import { fetchRuntime } from '@/domain/runtime/services/runtime' import BaseSubLayout from '@/pages/BaseSubLayout' -import { useFetchProject } from '@/domain/project/hooks/useFetchProject' export interface IRuntimeLayoutProps { children: React.ReactNode @@ -16,7 +15,6 @@ export default function RuntimeLayout({ children }: IRuntimeLayoutProps) { const { projectId, runtimeId } = useParams<{ runtimeId: string; projectId: string }>() const runtimeInfo = useQuery(`fetchRuntime:${projectId}:${runtimeId}`, () => fetchRuntime(projectId, runtimeId)) const { runtime, setRuntime } = useRuntime() - const projectInfo = useFetchProject(projectId) const { setRuntimeLoading } = useRuntimeLoading() useEffect(() => { setRuntimeLoading(runtimeInfo.isLoading) @@ -38,21 +36,19 @@ export default function RuntimeLayout({ children }: IRuntimeLayoutProps) { const [t] = useTranslation() const runtimeName = runtime?.versionMeta ?? '-' - const project = projectInfo.data ?? {} - const breadcrumbItems: INavItem[] = useMemo(() => { const items = [ { title: t('Runtimes'), - path: `/projects/${project?.id}/runtimes`, + path: `/projects/${projectId}/runtimes`, }, { title: runtimeName, - path: `/projects/${project?.id}/runtimes/${runtimeId}`, + path: `/projects/${projectId}/runtimes/${runtimeId}`, }, ] return items - }, [project?.id, runtimeName, runtimeId, t]) + }, [projectId, runtimeName, runtimeId, t]) return {children} } diff --git a/console/src/pages/Runtime/RuntimeListCard.tsx b/console/src/pages/Runtime/RuntimeListCard.tsx index 579b426466..2d7e417070 100644 --- a/console/src/pages/Runtime/RuntimeListCard.tsx +++ b/console/src/pages/Runtime/RuntimeListCard.tsx @@ -4,9 +4,10 @@ import { usePage } from '@/hooks/usePage' import { formatTimestampDateTime } from '@/utils/datetime' import useTranslation from '@/hooks/useTranslation' import Table from '@/components/Table' -import { Link, useParams } from 'react-router-dom' +import { useParams } from 'react-router-dom' import { useFetchRuntimes } from '@/domain/runtime/hooks/useFetchRuntimes' import User from '@/domain/user/components/User' +import { TextLink } from '@/components/Link' export default function RuntimeListCard() { const [page] = usePage() @@ -24,15 +25,15 @@ export default function RuntimeListCard() { data={ runtimesInfo.data?.list.map((runtime) => { return [ - + {runtime.name} - , + , runtime.version?.meta ?? '-', runtime.owner && , runtime.createdTime && formatTimestampDateTime(runtime.createdTime), - + {t('Version History')} - , + , ] }) ?? [] } diff --git a/console/src/pages/Runtime/RuntimeVersionLayout.tsx b/console/src/pages/Runtime/RuntimeVersionLayout.tsx index a6a9fb3438..4880dfa633 100644 --- a/console/src/pages/Runtime/RuntimeVersionLayout.tsx +++ b/console/src/pages/Runtime/RuntimeVersionLayout.tsx @@ -6,7 +6,6 @@ import { useParams } from 'react-router-dom' import { INavItem } from '@/components/BaseSidebar' import { fetchRuntime } from '@/domain/runtime/services/runtime' import BaseSubLayout from '@/pages/BaseSubLayout' -import { useFetchProject } from '@/domain/project/hooks/useFetchProject' export interface IRuntimeLayoutProps { children: React.ReactNode @@ -16,7 +15,6 @@ export default function RuntimeVersionLayout({ children }: IRuntimeLayoutProps) const { projectId, runtimeId } = useParams<{ runtimeId: string; projectId: string }>() const runtimeInfo = useQuery(`fetchRuntime:${projectId}:${runtimeId}`, () => fetchRuntime(projectId, runtimeId)) const { runtime, setRuntime } = useRuntime() - const projectInfo = useFetchProject(projectId) const { setRuntimeLoading } = useRuntimeLoading() useEffect(() => { setRuntimeLoading(runtimeInfo.isLoading) @@ -38,25 +36,24 @@ export default function RuntimeVersionLayout({ children }: IRuntimeLayoutProps) const [t] = useTranslation() const runtimeName = runtime?.versionMeta ?? '-' - const project = projectInfo.data ?? {} const breadcrumbItems: INavItem[] = useMemo(() => { const items = [ { title: t('Runtimes'), - path: `/projects/${project?.id}/runtimes`, + path: `/projects/${projectId}/runtimes`, }, { title: runtimeName, - path: `/projects/${project?.id}/runtimes/${runtimeId}`, + path: `/projects/${projectId}/runtimes/${runtimeId}`, }, { title: t('runtime versions'), - path: `/projects/${project?.id}/runtimes/${runtimeId}/versions`, + path: `/projects/${projectId}/runtimes/${runtimeId}/versions`, }, ] return items - }, [runtimeName, t, project?.id, runtimeId]) + }, [runtimeName, t, projectId, runtimeId]) return {children} } diff --git a/console/src/routes.tsx b/console/src/routes.tsx index c172680f18..b99a56563b 100644 --- a/console/src/routes.tsx +++ b/console/src/routes.tsx @@ -75,8 +75,8 @@ const Routes = () => { }>
- + @@ -95,189 +95,199 @@ const Routes = () => { }>
- -
- - {/* setting */} - - - - - - - - - {/* project */} - - - - - - - - - - - - - - + + +
+ + {/* setting */} + + + + + + + + + {/* project */} + + - - - - - - {/* evaluation */} - - - - - - - - - - - {/* job & task */} - - - - - - - - - - - {/* datasets */} - - - - - - - + + + + + + + + + + + + + + + + + + {/* evaluation */} + + + + + + + + + + + {/* job & task */} + + + + + + + + + + + {/* datasets */} + + + + + + + - - - - - - {/* runtime */} - - - - - - - - - - - - - - - {/* model */} - - - - - - - - - - - - - - - {/* admin */} - - - - - - - - - {/* default */} - - - - - - - - - + + + + + + {/* runtime */} + + + + + + + + + + + + + + + {/* model */} + + + + + + + + + + + + + + + {/* admin */} + + + + + + + + + {/* default */} + + + + + + + + + +
diff --git a/console/vite.config.ts b/console/vite.config.ts index cca7e3c05a..0706509afc 100644 --- a/console/vite.config.ts +++ b/console/vite.config.ts @@ -40,7 +40,7 @@ export default defineConfig({ }, }, plugins: [ - // eslint(), + eslint(), react({ exclude: /\.stories\.(t|j)sx?$/, }),