├── .env ├── .env.production ├── .github ├── .README_images │ ├── 2859cc17.png │ ├── 35e39bdd.png │ ├── 67668ee6.png │ ├── Dashboard.png │ ├── flow-demo.gif │ ├── flowCompose.gif │ ├── hubView.gif │ ├── jinaD_run_cli.png │ ├── jinaD_run_docker.png │ ├── logging.gif │ ├── logs-demo.gif │ ├── logserver.png │ ├── overview.gif │ └── refresh.PNG ├── ISSUE_TEMPLATE │ ├── ---found-a-bug-and-i-solved-it.md │ ├── ---new-feature---discussion-needed.md │ ├── ---question---may-be-a-bug-.md │ └── config.yml ├── pull_request_template.md └── workflows │ ├── ci.yml │ ├── commit-lint.yml │ ├── deploy-hub.yml │ ├── deploy-swaggerui.yml │ ├── deploy.yml │ ├── issue-ref.yml │ ├── label-pr.yml │ ├── stale.yml │ └── sync-version-with-jina.yml ├── .gitignore ├── .nvmrc ├── .prettierrc ├── .stylelintrc.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── codecov.yml ├── craco.config.js ├── cypress.json ├── cypress ├── fixtures │ ├── hubImages.json │ └── sample-output.json ├── integration │ ├── global │ │ └── login.ts │ ├── views │ │ ├── flowview.ts │ │ ├── helpView.ts │ │ ├── homeview.ts │ │ ├── hubView.ts │ │ ├── logsView.ts │ │ └── podView.ts │ └── visual-tests │ │ ├── flowDesign.ts │ │ └── helpPage.ts ├── plugins │ └── index.js ├── support │ ├── commands.ts │ ├── index.d.ts │ └── index.ts └── tsconfig.json ├── dashboard.js ├── deployment.md ├── dev-resources ├── README.md ├── config.js ├── examples │ ├── exampleFlow1.yml │ ├── exampleFlow2.yml │ └── exampleImages.json ├── probe.js ├── sample-data │ ├── args-flow.json │ ├── args-pea.json │ ├── args-pod.json │ ├── daemon-output.json │ ├── daemon-status.json │ └── logs.txt ├── testServer.js └── utils │ └── convertToGradual.js ├── hub-resources ├── config-template.js ├── db.js └── hubAPI.js ├── netlify.toml ├── package-lock.json ├── package.json ├── public ├── banner.png ├── favicon.ico ├── icon.png ├── index.html ├── jina-j.png ├── jina-light.svg ├── manifest.json └── robots.txt ├── src ├── App.css ├── apps │ ├── Dashboard.tsx │ ├── Hub.tsx │ ├── Styleguide.tsx │ ├── Swagger.tsx │ └── utils.ts ├── assets │ └── icons │ │ ├── Copy.svg │ │ ├── Play.svg │ │ ├── Save.svg │ │ ├── Stop.svg │ │ ├── Upload.svg │ │ ├── Yaml.svg │ │ └── hub-icon.svg ├── components │ ├── Common │ │ ├── Button.tsx │ │ ├── Card.tsx │ │ ├── ConnectionToast.tsx │ │ ├── CookiesBanner.tsx │ │ ├── ExpandingSearchbar.test.tsx │ │ ├── ExpandingSearchbar.tsx │ │ ├── HelpCard.tsx │ │ ├── InfoBanner.test.tsx │ │ ├── InfoBanner.tsx │ │ ├── InfoToast.test.tsx │ │ ├── InfoToast.tsx │ │ ├── MultiFilterSelect.tsx │ │ ├── NavigateButton.tsx │ │ ├── PageTitle.test.tsx │ │ ├── PageTitle.tsx │ │ ├── SpinningLoader.tsx │ │ └── Typography.tsx │ ├── DebuggingTool │ │ ├── CustomLiveResponse.tsx │ │ ├── CustomRequestBodyEditor.tsx │ │ ├── DocumentTabs.tsx │ │ ├── FlowChartNodes.tsx │ │ ├── Matches.tsx │ │ ├── Request.tsx │ │ ├── Response.tsx │ │ ├── RouteTable.tsx │ │ ├── Scores.tsx │ │ └── tests │ │ │ ├── Matches.test.tsx │ │ │ └── Matches.testData.ts │ ├── FlowChart │ │ ├── ChartNode.tsx │ │ ├── CommandBar.tsx │ │ ├── FlowChart.tsx │ │ ├── FlowSelection.tsx │ │ ├── Sidebar.tsx │ │ ├── SidebarItem.tsx │ │ ├── Tooltip.tsx │ │ └── WorkspaceSelection.tsx │ ├── Hub │ │ ├── CodeSnippetWithCopy.tsx │ │ ├── FilterButton.tsx │ │ ├── HubFilters.test.tsx │ │ ├── HubFilters.tsx │ │ ├── HubImagesList.tsx │ │ ├── HubImagesListPreview.test.tsx │ │ ├── HubImagesListPreview.tsx │ │ ├── HubNavigationBar.tsx │ │ ├── HubOverviewActionsContainer.test.tsx │ │ ├── HubOverviewActionsContainer.tsx │ │ ├── HubRecommendedCategories.tsx │ │ ├── HubSortDropdown.tsx │ │ ├── ImageCard.test.tsx │ │ ├── ImageCard.tsx │ │ ├── ImageDetails.test.tsx │ │ ├── ImageDetails.tsx │ │ ├── Readme.test.tsx │ │ ├── Readme.tsx │ │ └── __snapshots__ │ │ │ ├── HubImagesListPreview.test.tsx.snap │ │ │ ├── HubOverviewActionsContainer.test.tsx.snap │ │ │ ├── ImageCard.test.tsx.snap │ │ │ ├── ImageDetails.test.tsx.snap │ │ │ └── Readme.test.tsx.snap │ ├── Layout │ │ ├── MainFooter.tsx │ │ ├── SideNavBar │ │ │ ├── NavLogo.tsx │ │ │ ├── SideNavBar.tsx │ │ │ └── SidebarNavItem.tsx │ │ └── TopNavBar │ │ │ ├── TopNavBar.tsx │ │ │ └── UserActions.tsx │ ├── LogStream │ │ ├── BarChartBase.tsx │ │ ├── LogGroup.tsx │ │ ├── LogItem.tsx │ │ ├── LogLevelPieChart.tsx │ │ ├── LogLevelSummaryChart.tsx │ │ ├── LogsTable.tsx │ │ ├── LogsTableHeader.tsx │ │ ├── PieChartBase.tsx │ │ ├── levels.ts │ │ └── types.ts │ ├── Settings │ │ ├── FormItem.tsx │ │ ├── Settings.test.tsx │ │ ├── Settings.tsx │ │ ├── ToggleButton.tsx │ │ └── options.ts │ ├── Styleguide │ │ ├── ColorsPage.test.tsx │ │ ├── ColorsPage.tsx │ │ ├── MiscPage.test.tsx │ │ ├── MiscPage.tsx │ │ ├── TypographyPage.test.tsx │ │ ├── TypographyPage.tsx │ │ └── __snapshots__ │ │ │ ├── ColorsPage.test.tsx.snap │ │ │ ├── MiscPage.test.tsx.snap │ │ │ └── TypographyPage.test.tsx.snap │ └── Task │ │ ├── BarChartCard.tsx │ │ ├── ElapsedCard.tsx │ │ ├── ProgressCard.tsx │ │ ├── QueriesPerSecond.tsx │ │ └── SpeedCard.tsx ├── createPalette.d.ts ├── data │ ├── defaultPods.ts │ ├── exampleData.ts │ ├── flowTestData.ts │ ├── globalArguments.ts │ ├── multiModalExample.ts │ ├── sampleHubImages.json │ └── sidebar-nav-items.ts ├── emotion.d.ts ├── helpers │ ├── featureSwitch.ts │ ├── flow-chart.ts │ ├── format.ts │ ├── hubHelpers.ts │ ├── index.ts │ ├── serialise.ts │ ├── tests │ │ ├── featureSwitch.test.ts │ │ ├── flow-chart.test.ts │ │ ├── flow-chart.testData.ts │ │ ├── format.test.ts │ │ ├── format.testData.ts │ │ ├── hubHelpers.test.ts │ │ └── utils.test.ts │ ├── transform-log.ts │ ├── typeCheckers.ts │ └── utils.ts ├── index.css ├── index.tsx ├── layouts │ ├── HubLayout.tsx │ ├── IconSidebar.tsx │ ├── SwaggerLayout.tsx │ └── index.ts ├── logger.ts ├── modals │ ├── ConfigFileUpload.tsx │ ├── FlowSettings.tsx │ ├── LogDetails.tsx │ ├── MultiModalExample.tsx │ ├── NewFlow.tsx │ ├── PasteYAML.tsx │ ├── PodEdit.tsx │ ├── QuerySearchModal.tsx │ └── WriteReview.tsx ├── react-app-env.d.ts ├── redux │ ├── flows │ │ ├── flows.actions.test.ts │ │ ├── flows.actions.ts │ │ ├── flows.constants.ts │ │ ├── flows.reducer.ts │ │ ├── flows.selectors.ts │ │ ├── flows.test.ts │ │ ├── flows.testData.ts │ │ └── flows.types.ts │ ├── global │ │ ├── global.actions.ts │ │ ├── global.constants.ts │ │ ├── global.reducer.ts │ │ ├── global.selectors.ts │ │ ├── global.test.ts │ │ ├── global.testData.ts │ │ └── global.types.ts │ ├── hub │ │ ├── hub.actions.ts │ │ ├── hub.constants.ts │ │ ├── hub.reducer.ts │ │ ├── hub.selectors.ts │ │ ├── hub.test.ts │ │ └── hub.types.ts │ ├── index.ts │ ├── logStream │ │ ├── logStream.actions.ts │ │ ├── logStream.constants.ts │ │ ├── logStream.reducer.ts │ │ ├── logStream.selectors.ts │ │ ├── logStream.test.ts │ │ ├── logStream.testData.ts │ │ └── logStream.types.ts │ ├── settings │ │ ├── settings.actions.ts │ │ ├── settings.constants.ts │ │ ├── settings.reducer.ts │ │ ├── settings.selectors.ts │ │ ├── settings.test.ts │ │ ├── settings.testData.ts │ │ └── settings.types.ts │ └── task │ │ ├── task.actions.ts │ │ ├── task.constants.ts │ │ ├── task.reducer.ts │ │ ├── task.selectors.ts │ │ ├── task.test.ts │ │ ├── task.testData.ts │ │ └── task.types.ts ├── routes │ ├── Dashboard.tsx │ ├── Hub.tsx │ ├── Styleguide.tsx │ ├── Swagger.tsx │ └── index.ts ├── services │ ├── config.ts │ ├── gatewayClient.ts │ ├── github.ts │ ├── hubApi.ts │ ├── jinaApi.ts │ ├── jinad.ts │ ├── multiModalScript.ts │ ├── services.types.ts │ └── tests │ │ ├── gatewayClient.test.ts │ │ ├── gatewayClient.testData.ts │ │ ├── gatewayClient.ts │ │ ├── github.test.ts │ │ ├── hubApi.test.ts │ │ ├── jinaApi.test.ts │ │ ├── jinaApi.testData.ts │ │ ├── jinad.test.ts │ │ └── jinad.testData.ts ├── settings.ts ├── setupTests.js ├── styles │ ├── fonts │ │ ├── Monaco │ │ │ └── Monaco.ttf │ │ ├── Montserrat │ │ │ └── Montserrat-Regular.ttf │ │ └── Roboto │ │ │ ├── Roboto-Black.ttf │ │ │ ├── Roboto-BlackItalic.ttf │ │ │ ├── Roboto-Bold.ttf │ │ │ ├── Roboto-BoldItalic.ttf │ │ │ ├── Roboto-Italic.ttf │ │ │ ├── Roboto-Light.ttf │ │ │ ├── Roboto-LightItalic.ttf │ │ │ ├── Roboto-Medium.ttf │ │ │ ├── Roboto-MediumItalic.ttf │ │ │ ├── Roboto-Regular.ttf │ │ │ ├── Roboto-Thin.ttf │ │ │ └── Roboto-ThinItalic.ttf │ ├── mediaQuery.test.ts │ └── mediaQuery.ts ├── test-utils.tsx ├── theme.ts ├── types.d.ts ├── views │ ├── DebuggingTool.tsx │ ├── FallbackPage.tsx │ ├── FlowView.tsx │ ├── HomeView.tsx │ ├── HubView.tsx │ ├── LogIn.tsx │ ├── LogsView.tsx │ ├── NotFound.tsx │ ├── PackageView.tsx │ ├── Settings.tsx │ ├── SwaggerView.tsx │ └── TaskView.tsx └── withTracker.tsx ├── tailwind.config.js └── tsconfig.json /.env: -------------------------------------------------------------------------------- 1 | REACT_APP_HUB_API=https://hubapi.jina.ai 2 | REACT_APP_JINA_API=https://api.jina.ai 3 | REACT_APP_GITHUB_LAMBDA=https://1f06hf594m.execute-api.us-west-1.amazonaws.com/prod/webauth 4 | REACT_APP_CLIENT_ID=aefd9df23a647df8eb2c 5 | REACT_APP_GITHUB_API=https://api.github.com/user 6 | 7 | REACT_APP_FEATURE_HOMEPAGE=enabled 8 | REACT_APP_FEATURE_FILES=enabled 9 | REACT_APP_FEATURE_QUERY_SEARCH=enabled 10 | REACT_APP_FEATURE_DEBUGGING_TOOL=enabled -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | REACT_APP_FEATURE_HOMEPAGE=disabled 2 | REACT_APP_FEATURE_FILES=disabled 3 | REACT_APP_FEATURE_QUERY_SEARCH=disabled 4 | REACT_APP_FEATURE_DEBUGGING_TOOL=disabled -------------------------------------------------------------------------------- /.github/.README_images/2859cc17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jina-ai/dashboard/761245cc0ef7f568872fb15b21386f12bb587bbb/.github/.README_images/2859cc17.png -------------------------------------------------------------------------------- /.github/.README_images/35e39bdd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jina-ai/dashboard/761245cc0ef7f568872fb15b21386f12bb587bbb/.github/.README_images/35e39bdd.png -------------------------------------------------------------------------------- /.github/.README_images/67668ee6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jina-ai/dashboard/761245cc0ef7f568872fb15b21386f12bb587bbb/.github/.README_images/67668ee6.png -------------------------------------------------------------------------------- /.github/.README_images/Dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jina-ai/dashboard/761245cc0ef7f568872fb15b21386f12bb587bbb/.github/.README_images/Dashboard.png -------------------------------------------------------------------------------- /.github/.README_images/flow-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jina-ai/dashboard/761245cc0ef7f568872fb15b21386f12bb587bbb/.github/.README_images/flow-demo.gif -------------------------------------------------------------------------------- /.github/.README_images/flowCompose.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jina-ai/dashboard/761245cc0ef7f568872fb15b21386f12bb587bbb/.github/.README_images/flowCompose.gif -------------------------------------------------------------------------------- /.github/.README_images/hubView.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jina-ai/dashboard/761245cc0ef7f568872fb15b21386f12bb587bbb/.github/.README_images/hubView.gif -------------------------------------------------------------------------------- /.github/.README_images/jinaD_run_cli.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jina-ai/dashboard/761245cc0ef7f568872fb15b21386f12bb587bbb/.github/.README_images/jinaD_run_cli.png -------------------------------------------------------------------------------- /.github/.README_images/jinaD_run_docker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jina-ai/dashboard/761245cc0ef7f568872fb15b21386f12bb587bbb/.github/.README_images/jinaD_run_docker.png -------------------------------------------------------------------------------- /.github/.README_images/logging.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jina-ai/dashboard/761245cc0ef7f568872fb15b21386f12bb587bbb/.github/.README_images/logging.gif -------------------------------------------------------------------------------- /.github/.README_images/logs-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jina-ai/dashboard/761245cc0ef7f568872fb15b21386f12bb587bbb/.github/.README_images/logs-demo.gif -------------------------------------------------------------------------------- /.github/.README_images/logserver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jina-ai/dashboard/761245cc0ef7f568872fb15b21386f12bb587bbb/.github/.README_images/logserver.png -------------------------------------------------------------------------------- /.github/.README_images/overview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jina-ai/dashboard/761245cc0ef7f568872fb15b21386f12bb587bbb/.github/.README_images/overview.gif -------------------------------------------------------------------------------- /.github/.README_images/refresh.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jina-ai/dashboard/761245cc0ef7f568872fb15b21386f12bb587bbb/.github/.README_images/refresh.PNG -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---found-a-bug-and-i-solved-it.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41E Found a Bug!" 3 | about: I found a problem! It is Jina’s bug and I’d like to bring it to others' attention. 4 | title: '' 5 | labels: kind/bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | 12 | 13 | **Have you solved it? If so, let us know how!** 14 | 15 | 16 | --- 17 | 18 | 19 | 20 | **Environment** 21 | 22 | 23 | **Screenshots and logs** 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---new-feature---discussion-needed.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F31F Feature Request?" 3 | about: I’d like add/request a new feature to Jina, before that I need some discussion. 4 | title: '' 5 | labels: kind/feature 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the feature** 11 | 12 | 13 | **Your proposal** 14 | 15 | 16 | --- 17 | 18 | 19 | **Environment** 20 | 21 | 22 | **Screenshots** 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---question---may-be-a-bug-.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F914 Question?" 3 | about: Question? 4 | title: '' 5 | labels: kind/question 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe your proposal/problem** 11 | 12 | 13 | --- 14 | 15 | 16 | 17 | **Environment** 18 | 19 | 20 | **Screenshots** 21 | 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: "📚 Read docs" 4 | url: https://docs.jina.ai/ 5 | about: Find your solution in our documentation 6 | - name: "🐣 Learn examples" 7 | url: https://github.com/jina-ai/examples 8 | about: Learn from our examples 9 | - name: "😊 Join us" 10 | url: https://career.jina.ai 11 | about: Shape the open-source neural search ecosystem! 12 | - name: "💬 Talk to us" 13 | url: https://slack.jina.ai 14 | about: Join our Slack community and ask questions 15 | # - name: "🔎 Search on Existing Issues" 16 | # url: https://github.com/jina-ai/jina/search?q=&type=Issues 17 | # about: Someone else may already post this issue and got solved 18 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 |

PR checklist

2 | Please tick all of the below (if a certain item is not required for this PR please tick the box anyway) 3 | 4 | **Docs** 5 | - [ ] If you added new code; is it self documenting? (all the variable names clear, comments added where appropriate) 6 | - [ ] If you touched existing code; did you refactor existing naming, added comments where necessary? 7 | 8 | **Tests** 9 | - [ ] If your ticket adds new functionality; have you added unit tests to verify behaviour? 10 | - [ ] If your ticket alters user interaction with the UI; have you added integration tests? 11 | - [ ] Do your tests check for every _convievable_ user behabviour? (all behaviours the user _can_ perform) 12 | 13 | **Typing** 14 | - [ ] If you added new code; have you added typing? 15 | - [ ] If you touched existing code; have you improved its typing? 16 | 17 | **Styling** 18 | - [ ] If new styles were created; Did you refactor/add more styles in css-in-js style? 19 | - [ ] Are all fonts, sizes, colours, margins, paddings in line with designs? 20 | - [ ] Did you perform a visual comparison of the implementation with the designs? 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: "Unit, integration tests" 2 | on: 3 | push: 4 | jobs: 5 | unit-test: 6 | runs-on: ubuntu-latest 7 | strategy: 8 | matrix: 9 | node-version: [12.x] 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Use Node.js ${{ matrix.node-version }} 13 | uses: actions/setup-node@v1 14 | with: 15 | node-version: ${{ matrix.node-version }} 16 | - name: Install Packages 17 | run: npm ci 18 | - name: Run unit tests 🧪 19 | run: set CI=true&&npm test 20 | - name: Test coverage 21 | run: npm test -- --coverage --watchAll=false 22 | - name: Upload coverage 23 | run: bash <(curl -s https://codecov.io/bash) 24 | 25 | integration-tests: 26 | runs-on: ubuntu-latest 27 | strategy: 28 | matrix: 29 | node-version: [12.x] 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v2 33 | - name: Cypress run ⚗️ 34 | uses: cypress-io/github-action@v2 35 | with: 36 | start: npm run dev 37 | wait-on: 'http://localhost:3000' 38 | command-prefix: 'npm run cy:run' 39 | # - name: Integration test Coverage 40 | # run: | 41 | # npx nyc report --reporter=text-summary 42 | # npx nyc report --reporter=text 43 | env: 44 | PERCY_TOKEN: ${{ secrets.percy_token }} 45 | -------------------------------------------------------------------------------- /.github/workflows/deploy-hub.yml: -------------------------------------------------------------------------------- 1 | name: "Deploy Hub" 2 | on: 3 | push: 4 | branches: 5 | - release 6 | jobs: 7 | build: 8 | if: "!startsWith(github.event.head_commit.message, 'chore')" 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | node-version: [12.x] 13 | steps: 14 | - uses: actions/checkout@v1 15 | - name: Use Node.js ${{ matrix.node-version }} 16 | uses: actions/setup-node@v1 17 | with: 18 | node-version: ${{ matrix.node-version }} 19 | - name: Install Packages 20 | run: npm ci 21 | - name: Create Build 22 | run: npm run hub build 23 | - name: Deploy Build 24 | uses: JamesIves/github-pages-deploy-action@releases/v3 25 | with: 26 | ACCESS_TOKEN: ${{ secrets.JINA_DEV_BOT }} 27 | BRANCH: gh-pages 28 | FOLDER: build 29 | REPOSITORY_NAME: jina-ai/jina-hub 30 | -------------------------------------------------------------------------------- /.github/workflows/deploy-swaggerui.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Swagger Build 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | 7 | jobs: 8 | push-to-api: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | node-version: [12.x] 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions/checkout@v2 16 | with: 17 | repository: jina-ai/api 18 | path: api 19 | token: ${{ secrets.JINA_DEV_BOT }} 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v1 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | - name: Install Packages 25 | run: npm ci 26 | - name: Create Build 27 | run: | 28 | npx browserslist@latest --update-db 29 | npm run swagger build-cdn 30 | - name: Push to API 31 | run: | 32 | rm -rf api/swagger 33 | cp -R build api/swagger 34 | cd api 35 | git config --local user.email "dev-bot@jina.ai" 36 | git config --local user.name "Jina Dev Bot" 37 | git add . && git commit -m "update swagger build" && git push 38 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: "Deploy Dashboard" 2 | on: 3 | push: 4 | branches: 5 | - release 6 | jobs: 7 | build: 8 | if: "!startsWith(github.event.head_commit.message, 'chore')" 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | node-version: [12.x] 13 | steps: 14 | - uses: actions/checkout@v1 15 | - name: Use Node.js ${{ matrix.node-version }} 16 | uses: actions/setup-node@v1 17 | with: 18 | node-version: ${{ matrix.node-version }} 19 | - name: Install Packages 20 | run: npm ci 21 | - name: Create Build 22 | run: npm run dashboard build 23 | - name: Deploy Build 24 | uses: JamesIves/github-pages-deploy-action@releases/v3 25 | with: 26 | GITHUB_TOKEN: ${{ secrets.JINA_DEV_BOT }} 27 | BRANCH: gh-pages 28 | FOLDER: build 29 | - uses: technote-space/toc-generator@v2 30 | with: 31 | MAX_HEADER_LEVEL: 3 32 | FOLDING: false 33 | GITHUB_TOKEN: ${{ secrets.JINA_DEV_BOT }} 34 | TOC_TITLE: "" 35 | TARGET_PATHS: "README.md" 36 | COMMIT_MESSAGE: "chore(docs): update TOC" 37 | COMMIT_NAME: Jina Dev Bot 38 | COMMIT_EMAIL: dev-bot@jina.ai 39 | -------------------------------------------------------------------------------- /.github/workflows/issue-ref.yml: -------------------------------------------------------------------------------- 1 | name: PR 2 | 3 | on: 4 | pull_request: 5 | types: [opened] 6 | 7 | jobs: 8 | ref-issue-to-pr: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: estrada9166/issue-ref@v1 12 | with: 13 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/label-pr.yml: -------------------------------------------------------------------------------- 1 | name: PR 2 | 3 | on: 4 | pull_request_target: 5 | 6 | jobs: 7 | assign-label-to-pr: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: codelytv/pr-size-labeler@v1 11 | with: 12 | GITHUB_TOKEN: ${{ secrets.JINA_DEV_BOT }} 13 | xs_max_size: '10' 14 | s_max_size: '100' 15 | m_max_size: '500' 16 | l_max_size: '1000' 17 | xl_max_size: '2000' 18 | xxl_max_size: '3000' 19 | fail_if_xl: 'false' 20 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Close stale issues 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * *" 6 | 7 | jobs: 8 | stale: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/stale@v1 12 | with: 13 | repo-token: ${{ secrets.JINA_DEV_BOT }} 14 | stale-issue-message: 'This issue is stale because it has been open 20 days with no activity. Remove stale label or comment or this will be closed in 4 days' 15 | exempt-issue-labels: 'good first issue' 16 | days-before-stale: 28 17 | days-before-close: 7 -------------------------------------------------------------------------------- /.github/workflows/sync-version-with-jina.yml: -------------------------------------------------------------------------------- 1 | name: Sync version with Jina core 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | version: 6 | description: 'version' 7 | required: true 8 | default: '0.0.0' 9 | 10 | jobs: 11 | update-version: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: setup node.js 12.x 16 | uses: actions/setup-node@v1 17 | with: 18 | node-version: 12.x 19 | - name: update version 20 | run: | 21 | echo "Updating version to ${{ github.event.inputs.version }}!" 22 | git config --local user.email "dev-bot@jina.ai" 23 | git config --local user.name "Jina Dev Bot" 24 | npm version --allow-same-version ${{ github.event.inputs.version }} 25 | - name: tag and push 26 | run: | 27 | git push --follow-tags 28 | git push --tags 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | hub-resources/config.js 15 | 16 | dev-resources/recordings/ 17 | 18 | # misc 19 | .DS_Store 20 | .env.local 21 | .env.development.local 22 | .env.test.local 23 | .env.production.local 24 | 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | 29 | # for jetbrains IDE's 30 | .idea/ 31 | 32 | # Cypress' recorded videos 33 | cypress/videos/ 34 | # With percy, we're not handling snapshots ourselves but letting percy take care of it 35 | cypress/screenshots/ 36 | # Cypress' default downloadsFolder 37 | cypress/downloads/ 38 | 39 | .nyc_output/out.json 40 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 12.18.4 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSpacing": true, 4 | "htmlWhitespaceSensitivity": "css", 5 | "insertPragma": false, 6 | "jsxBracketSameLine": false, 7 | "jsxSingleQuote": false, 8 | "printWidth": 80, 9 | "proseWrap": "preserve", 10 | "quoteProps": "as-needed", 11 | "requirePragma": false, 12 | "semi": false, 13 | "singleQuote": false, 14 | "tabWidth": 2, 15 | "trailingComma": "es5", 16 | "useTabs": false, 17 | } 18 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "stylelint-config-standard", 4 | "stylelint-config-prettier" 5 | ], 6 | "rules": { 7 | "at-rule-no-unknown": [ true, { 8 | "ignoreAtRules": [ 9 | "extends", 10 | "tailwind" 11 | ] 12 | }], 13 | "block-no-empty": null, 14 | "unit-allowed-list": ["em", "rem", "s"] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "src/components" 3 | - "src/assets" 4 | - "src/data" 5 | - "src/views" 6 | - "src/layouts" 7 | - "src/styles" 8 | - "src/modals" -------------------------------------------------------------------------------- /craco.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | style: { 3 | postcss: { 4 | plugins: [ 5 | require('tailwindcss'), 6 | require('autoprefixer'), 7 | ], 8 | }, 9 | }, 10 | } -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:3000", 3 | "defaultCommandTimeout": 200000 4 | } 5 | -------------------------------------------------------------------------------- /cypress/fixtures/sample-output.json: -------------------------------------------------------------------------------- 1 | { 2 | "logs": [ 3 | { 4 | "host": "f655e6fd731a", 5 | "process": "43", 6 | "type": "INFO", 7 | "name": "encode1", 8 | "uptime": "2021-01-28T22:37:46.377569", 9 | "context": "encode1/ZEDRuntime", 10 | "workspace_path": "/tmp/jinad/43e8a386-a2a1-48e7-8f70-545c651dddbb", 11 | "log_id": "f0269f70-fe77-4d50-888b-c2c365aaea3c", 12 | "message": "recv IndexRequest from gateway\u001b[32m\u25b8\u001b[0mencode1/ZEDRuntime\u001b[32m\u25b8\u001b[0m\u2690" 13 | }, 14 | { 15 | "host": "f655e6fd731a", 16 | "process": "43", 17 | "type": "INFO", 18 | "name": "encode1", 19 | "uptime": "2021-01-28T22:37:46.377569", 20 | "context": "encode1/ZEDRuntime", 21 | "workspace_path": "/tmp/jinad/43e8a386-a2a1-48e7-8f70-545c651dddbb", 22 | "log_id": "f0269f70-fe77-4d50-888b-c2c365aaea3c", 23 | "message": "#sent: 0 #recv: 1 sent_size: 0 Bytes recv_size: 359 Bytes" 24 | }, 25 | { 26 | "host": "f655e6fd731a", 27 | "process": "50", 28 | "type": "INFO", 29 | "name": "encode2", 30 | "uptime": "2021-01-28T22:37:46.377569", 31 | "context": "encode2/ZEDRuntime", 32 | "workspace_path": "/tmp/jinad/43e8a386-a2a1-48e7-8f70-545c651dddbb", 33 | "log_id": "f0269f70-fe77-4d50-888b-c2c365aaea3c", 34 | "message": "recv IndexRequest from gateway\u001b[32m\u25b8\u001b[0mencode1/ZEDRuntime\u001b[32m\u25b8\u001b[0mencode2/ZEDRuntime\u001b[32m\u25b8\u001b[0m\u2690" 35 | }, 36 | { 37 | "host": "f655e6fd731a", 38 | "process": "50", 39 | "type": "INFO", 40 | "name": "encode2", 41 | "uptime": "2021-01-28T22:37:46.377569", 42 | "context": "encode2/ZEDRuntime", 43 | "workspace_path": "/tmp/jinad/43e8a386-a2a1-48e7-8f70-545c651dddbb", 44 | "log_id": "f0269f70-fe77-4d50-888b-c2c365aaea3c", 45 | "message": "#sent: 0 #recv: 1 sent_size: 0 Bytes recv_size: 425 Bytes" 46 | } 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /cypress/integration/global/login.ts: -------------------------------------------------------------------------------- 1 | describe("Login", () => { 2 | beforeEach(() => { 3 | cy.visit("/") 4 | }) 5 | it("should log you in and out", () => { 6 | const user = { 7 | login: "dummyUser", 8 | name: "dummyUserDisplayName", 9 | email: "dummyUser@jina.ai", 10 | id: "dummyId", 11 | avatar_url: "https://avatars.githubusercontent.com/u/25417797?v=4", 12 | } 13 | 14 | cy.intercept(Cypress.env("githubLambda") + "?ghcode=abcd1234", { 15 | statusCode: 200, 16 | body: { 17 | data: { 18 | access_token: "gho_abcd1234", 19 | token_type: "bearer", 20 | scope: "user", 21 | }, 22 | }, 23 | }) 24 | 25 | cy.intercept(Cypress.env("githubApi"), { 26 | statusCode: 200, 27 | body: user, 28 | }) 29 | 30 | cy.dataName("menuButton").click() 31 | cy.dataName("loginLogout").click() 32 | cy.url().should("include", "#/login") 33 | cy.visit("/?code=abcd1234#/login") 34 | cy.url().should("include", "#/logs") 35 | cy.get(".MuiAvatar-img") 36 | .should("have.attr", "src") 37 | .should("eq", user.avatar_url) 38 | 39 | cy.dataName("menuButton").click() 40 | cy.dataName("loginLogout").click() 41 | 42 | cy.get(".MuiAvatar-img").should("not.exist") 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /cypress/integration/views/homeview.ts: -------------------------------------------------------------------------------- 1 | describe.skip("Hub page", () => { 2 | before(() => { 3 | cy.intercept("images", { fixture: "hubImages" }) 4 | cy.visit("/#/home") 5 | }) 6 | 7 | it("should display the resources", () => { 8 | cy.contains("Resources") 9 | cy.contains("Jina 101") 10 | cy.contains("Jina Docs") 11 | cy.contains('Jina "Hello World"') 12 | cy.contains("Visit us on Github!") 13 | }) 14 | 15 | it("should have Jina 101 link", () => { 16 | cy.dataName("jina-101-card").should( 17 | "have.attr", 18 | "href", 19 | "https://101.jina.ai" 20 | ) 21 | }) 22 | 23 | it("should have Jina docs link", () => { 24 | cy.dataName("jina-docs-card").should( 25 | "have.attr", 26 | "href", 27 | "https://docs.jina.ai" 28 | ) 29 | }) 30 | 31 | it("should have Jina hello world link", () => { 32 | cy.dataName("jina-hello-world-card").should( 33 | "have.attr", 34 | "href", 35 | "https://github.com/jina-ai/jina#jina-hello-world-" 36 | ) 37 | }) 38 | 39 | it("should have Jina github url link", () => { 40 | cy.dataName("jina-github-card").should( 41 | "have.attr", 42 | "href", 43 | "https://opensource.jina.ai" 44 | ) 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /cypress/integration/views/podView.ts: -------------------------------------------------------------------------------- 1 | import { flowArguments } from "../../../src/helpers/tests/flow-chart.testData" 2 | 3 | describe("The Pod View", () => { 4 | beforeEach(() => { 5 | cy.viewport(2000, 2000) 6 | cy.visit("/#/flow") 7 | }) 8 | 9 | it("should have a default 'gateway' pod exist", () => { 10 | cy.dataName("chart-node-gateway").should("exist") 11 | }) 12 | 13 | it.skip("should show 'podEditContainer' when clicking 'gateway' pod label, and hide it when pressing {esc}", () => { 14 | cy.dataName("podEditContainer").should("not.exist") 15 | cy.dataName("chart-node-gateway").dblclick() 16 | cy.dataName("podEditContainer") 17 | .should("exist") 18 | .type("{esc}") 19 | .should("not.exist") 20 | }) 21 | 22 | it.skip("should set default pod's property input as checkbox whichever its type is boolean, and make it checkable", () => { 23 | const booleanProperties = flowArguments.pod.filter( 24 | (arg) => arg.type === "boolean" 25 | ) 26 | cy.dataName("chart-node-gateway").dblclick() 27 | booleanProperties.forEach((prop) => { 28 | cy.get(`input[data-name=pod_${prop.name}_property][type=checkbox]`) 29 | .as(prop.name) 30 | .should("not.be.checked") 31 | cy.get(`@${prop.name}`).check({ force: true }).should("be.checked") 32 | cy.get(`@${prop.name}`).uncheck({ force: true }).should("not.be.checked") 33 | }) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /cypress/integration/visual-tests/helpPage.ts: -------------------------------------------------------------------------------- 1 | const viewPortSizesToTest = [[1024, 768], 'macbook-11', 'macbook-13', 'macbook-15', 'macbook-16'] 2 | 3 | describe('Help page', () => { 4 | viewPortSizesToTest.forEach((size) => { 5 | it(`checks snapshot of help page for size ${size}`, () => { 6 | if (Cypress._.isArray(size)) { 7 | cy.viewport(size[0], size[1]) 8 | } else { 9 | cy.viewport(size) 10 | } 11 | 12 | cy.visit('/#/help') 13 | cy.percySnapshot(`helpPage${size}`) 14 | }) 15 | }) 16 | }) -------------------------------------------------------------------------------- /cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************************** 3 | // This example plugins/index.js can be used to load plugins 4 | // 5 | // You can change the location of this file or turn off loading 6 | // the plugins file with the 'pluginsFile' configuration option. 7 | // 8 | // You can read more here: 9 | // https://on.cypress.io/plugins-guide 10 | // *********************************************************** 11 | 12 | // This function is called when a project is opened or re-opened (e.g. due to 13 | // the project's config changing) 14 | require("dotenv").config() 15 | let percyHealthCheck = require("@percy/cypress/task") 16 | /** 17 | * @type {Cypress.PluginConfig} 18 | */ 19 | module.exports = (on, config) => { 20 | // `on` is used to hook into various events Cypress emits 21 | // `config` is the resolved Cypress config 22 | // require("@cypress/code-coverage/task")(on, config) 23 | on("task", percyHealthCheck) 24 | 25 | config.env.githubLambda = process.env.REACT_APP_GITHUB_LAMBDA 26 | config.env.githubApi = process.env.REACT_APP_GITHUB_API 27 | 28 | return config 29 | } 30 | -------------------------------------------------------------------------------- /cypress/support/commands.ts: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | import '@percy/cypress'; 27 | 28 | Cypress.Commands.add('dataName', (value) => { 29 | return cy.get(`[data-name=${value}]`) 30 | }) 31 | -------------------------------------------------------------------------------- /cypress/support/index.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace Cypress { 2 | interface Chainable { 3 | /** 4 | * Custom command to select DOM element by data-cy attribute. 5 | * @example cy.dataName('navBar') 6 | * @example cy.dataName('title').should('have.text', 'Log Stream') 7 | */ 8 | dataName(value: string): Chainable 9 | } 10 | } -------------------------------------------------------------------------------- /cypress/support/index.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | // import '@cypress/code-coverage/support' 19 | -------------------------------------------------------------------------------- /cypress/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "es5", 6 | "dom" 7 | ], 8 | "types": [ 9 | "cypress", 10 | "@percy/cypress" 11 | ] 12 | }, 13 | "include": [ 14 | "**/*.ts" 15 | ] 16 | } -------------------------------------------------------------------------------- /dashboard.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const path = require('path'); 3 | const app = express(); 4 | const http = require('http').Server(app); 5 | 6 | const PORT = 3030; 7 | 8 | app.use(express.static(path.join(__dirname, 'build'))); 9 | 10 | app.get('/*', function (req, res) { 11 | res.sendFile(path.resolve(__dirname, 'build', 'index.html'), function (err) { 12 | if (err) { 13 | res.status(500).send(err) 14 | } 15 | }) 16 | }); 17 | 18 | http.listen(PORT, () => console.log('Dashboard is served on port', PORT)); -------------------------------------------------------------------------------- /deployment.md: -------------------------------------------------------------------------------- 1 | ## Release process 2 | 3 | Release to production is done by merging master to release branch. 4 | 5 | ## Deployment Environments 6 | 7 | ### Production 8 | 9 | Production version of dashboard is deployed in https://dashboard.jina.ai and https://console.jina.ai 10 | 11 | This deployment is done from release branch 12 | 13 | ### Staging 14 | 15 | Staging environment for dashboard is https://console-dev.jina.ai 16 | 17 | This is deployed from master branch 18 | 19 | ## Feature Switches 20 | 21 | We use feature switches to disable some features in some environments. 22 | We can use .env, .env.local, .env.testing, .env.production to set different values of variables. 23 | In .env* files, prefix feature flag variable with `REACT_APP_FEATURE` 24 | 25 | When you add a feature flag, remember to add a `Todo` to remove it in the future. 26 | Feature flags are a type of technical debt. It should be removed after releasing it to all instances. -------------------------------------------------------------------------------- /dev-resources/README.md: -------------------------------------------------------------------------------- 1 | # Development Resources 2 | This directory includes a variety of development resources to make it easier to build dashboard features in a controlled environment without having to run any Jina instances. 3 | 4 | ## testserver 5 | The test server is designed to mock a jinad instance. 6 | 7 | ### Simulating Flows 8 | 9 | To start a flow simulation, run: 10 | ```bash 11 | node testserver 12 | ``` 13 | You can override config and specify a source by using: 14 | ```bash 15 | node testserver --source= 16 | ``` 17 | To specify a port: 18 | ```bash 19 | node testserver --port= 20 | ``` -------------------------------------------------------------------------------- /dev-resources/config.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | updateStatusInterval: 1000, 4 | jina_version: "0.9.18", 5 | emitter: { 6 | port: 5000, 7 | endpoints: { 8 | home: { 9 | path: "/", 10 | method: "GET", 11 | }, 12 | status: { 13 | path: "/status", 14 | method: "GET", 15 | }, 16 | getFlows: { 17 | path: "/flows", 18 | method: "GET" 19 | } 20 | }, 21 | messageInterval: 100, 22 | messageLoops:100, 23 | source: "../cypress/fixtures/sample-output.json" 24 | }, 25 | recorder: { 26 | url: "http://localhost:5000", 27 | saveDir: "recordings", 28 | endpoints: { 29 | log: { 30 | path: "/stream/log", 31 | type: "stream", 32 | }, 33 | profile: { 34 | path: "/stream/profile", 35 | type: "stream", 36 | }, 37 | yaml: { 38 | path: "/data/yaml", 39 | type: "static", 40 | }, 41 | }, 42 | }, 43 | probe: { 44 | url: "http://localhost:5555", 45 | request: { 46 | interval: 1000, 47 | route: "api/index", 48 | method: "post", 49 | data: { "data": ["hello"] } 50 | } 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /dev-resources/examples/exampleFlow1.yml: -------------------------------------------------------------------------------- 1 | !Flow 2 | pods: 3 | gateway: 4 | answer_extractor: 5 | yaml_path: images/answer_extractor/answer_extractor.yml 6 | needs: gateway 7 | 8 | answer_encoder: 9 | yaml_path: images/encoder/encoder.yml 10 | needs: answer_extractor 11 | timeout_ready: 60000 12 | 13 | answer_compound_chunk_indexer: 14 | yaml_path: images/answer_compound_chunk_indexer/answer_compound_chunk_indexer.yml 15 | needs: answer_encoder 16 | 17 | answer_meta_doc_indexer: 18 | yaml_path: images/answer_meta_doc_indexer/answer_meta_doc_indexer.yml 19 | needs: answer_extractor 20 | 21 | title_extractor: 22 | yaml_path: images/title_extractor/title_extractor.yml 23 | needs: gateway 24 | 25 | title_encoder: 26 | yaml_path: images/encoder/encoder.yml 27 | needs: title_extractor 28 | timeout_ready: 60000 29 | 30 | title_compound_chunk_indexer: 31 | yaml_path: images/title_compound_chunk_indexer/title_compound_chunk_indexer.yml 32 | needs: answer_encoder 33 | 34 | merger: 35 | yaml_path: images/merger/merger.yml 36 | needs: [title_compound_chunk_indexer, answer_meta_doc_indexer, answer_compound_chunk_indexer] -------------------------------------------------------------------------------- /dev-resources/examples/exampleFlow2.yml: -------------------------------------------------------------------------------- 1 | !Flow 2 | with: 3 | board: 4 | canvas: 5 | chunk_seg: 6 | x: 862 7 | y: 138 8 | encode1: 9 | x: 198 10 | y: 311 11 | encode2: 12 | x: 428 13 | y: 309 14 | pod_3: 15 | x: 652 16 | y: 308 17 | pod_4: 18 | x: 863 19 | y: 307 20 | pod_5: 21 | x: 1084 22 | y: 309 23 | pod_6: 24 | x: 1305 25 | y: 311 26 | connector: 27 | x: 861 28 | y: 477 29 | midleft_1: 30 | x: 530 31 | y: 601 32 | midright_1: 33 | x: 1210 34 | y: 598 35 | midleft_2: 36 | x: 331 37 | y: 739 38 | midright_2: 39 | x: 1399 40 | y: 733 41 | pod_left: 42 | x: 508 43 | y: 862 44 | pod_right: 45 | x: 1226 46 | y: 865 47 | end_pod: 48 | x: 870 49 | y: 1083 50 | pod_7: 51 | x: 1526 52 | y: 311 53 | pods: 54 | chunk_seg: 55 | replicas: 3 56 | encode1: 57 | replicas: 2 58 | needs: chunk_seg 59 | encode2: 60 | replicas: 2 61 | needs: chunk_seg 62 | pod_3: 63 | needs: chunk_seg 64 | pod_4: 65 | needs: chunk_seg 66 | pod_5: 67 | needs: chunk_seg 68 | pod_6: 69 | needs: chunk_seg 70 | connector: 71 | needs: 72 | - encode1 73 | - encode2 74 | - pod_3 75 | - pod_4 76 | - pod_5 77 | - pod_6 78 | - pod_7 79 | midleft_1: 80 | needs: connector 81 | midright_1: 82 | needs: connector 83 | midleft_2: 84 | needs: midleft_1 85 | midright_2: 86 | needs: midright_1 87 | pod_left: 88 | needs: 89 | - midleft_2 90 | - midleft_1 91 | - connector 92 | pod_right: 93 | needs: 94 | - midright_2 95 | - midright_1 96 | - connector 97 | end_pod: 98 | pull_latest: false 99 | port_in: 22 100 | volumes: "22" 101 | entrypoint: arst 102 | needs: 103 | - pod_left 104 | - pod_right 105 | pod_7: 106 | needs: chunk_seg 107 | -------------------------------------------------------------------------------- /dev-resources/probe.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | const config = require("./config").probe; 3 | const { argv } = require("yargs"); 4 | 5 | const baseURL = argv.url || config.url; 6 | 7 | console.log("probing daemon at",baseURL); 8 | 9 | const flow = axios.create({ 10 | baseURL 11 | }); 12 | 13 | function startProbe() { 14 | const { interval, method, route, data } = config.request; 15 | setInterval(() => flow[method](route, data ? JSON.stringify(data) : undefined), interval) 16 | } 17 | 18 | startProbe(); -------------------------------------------------------------------------------- /dev-resources/sample-data/args-flow.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": { 3 | "title": "Name", 4 | "description": "\nThe name of this object.\n\nThis will be used in the following places:\n- how you refer to this object in Python/YAML/CLI\n- log message\n- ...\n\nWhen not given, then the default naming strategy will apply.\n ", 5 | "type": "string" 6 | }, 7 | "log_config": { 8 | "title": "Log Config", 9 | "description": "The YAML config of the logger used in this object.", 10 | "default": "/usr/local/lib/python3.7/site-packages/jina/resources/logging.default.yml", 11 | "type": "string" 12 | }, 13 | "hide_exc_info": { 14 | "title": "Hide Exc Info", 15 | "description": "If set, then exception stack information to be added to the logging message, useful in debugging", 16 | "default": false, 17 | "type": "boolean" 18 | }, 19 | "uses": { 20 | "title": "Uses", 21 | "description": "The YAML file represents a flow", 22 | "type": "string" 23 | }, 24 | "inspect": { 25 | "title": "Inspect", 26 | "description": "\nThe strategy on those inspect pods in the flow.\n\nIf `REMOVE` is given then all inspect pods are removed when building the flow.\n", 27 | "default": "COLLECT", 28 | "type": "string" 29 | } 30 | } -------------------------------------------------------------------------------- /dev-resources/utils/convertToGradual.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs-extra"); 2 | const { argv } = require("yargs"); 3 | 4 | const DEFAULT_DELAY = 250; 5 | const DEFAULT_FILE = "examples/hello-world.json" 6 | const DEFAULT_FILETYPE = "JSON" 7 | 8 | async function runConverter() { 9 | let newData = { 10 | logs: [], 11 | profile: [], 12 | yaml: "", 13 | }; 14 | 15 | const delay = parseInt(argv.delay)|| DEFAULT_DELAY 16 | const input = argv.input || DEFAULT_FILE; 17 | const output = `${argv.output||input.split('.').slice(0, -1).join('.')}-gradual.${argv.outputType||DEFAULT_FILETYPE}` 18 | const initial = JSON.parse(await fs.readFile(input)); 19 | 20 | let start = +new Date() 21 | newData.logs = initial.logs.map((item) => { 22 | item.received = start += delay; 23 | return item; 24 | }); 25 | 26 | start = +new Date() 27 | newData.profile = initial.profile.map((item) => { 28 | item.received = start += delay; 29 | return item; 30 | }); 31 | 32 | newData.yaml = initial.yaml; 33 | 34 | fs.writeFile(output,JSON.stringify(newData)) 35 | } 36 | 37 | runConverter(); 38 | -------------------------------------------------------------------------------- /hub-resources/config-template.js: -------------------------------------------------------------------------------- 1 | //Before hubAPI can be started, a config.js file is required in the same directory 2 | //Below is the format: 3 | module.exports = { 4 | PORT: '', 5 | IMAGES_URL: '', 6 | PRIVATE_TOKEN: '', 7 | PRIVATE_MODE: '', 8 | MONGO_URL: 'mongodb://localhost:27017' 9 | } -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | ignore = "git diff --quiet HEAD^ HEAD src/" 3 | -------------------------------------------------------------------------------- /public/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jina-ai/dashboard/761245cc0ef7f568872fb15b21386f12bb587bbb/public/banner.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jina-ai/dashboard/761245cc0ef7f568872fb15b21386f12bb587bbb/public/favicon.ico -------------------------------------------------------------------------------- /public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jina-ai/dashboard/761245cc0ef7f568872fb15b21386f12bb587bbb/public/icon.png -------------------------------------------------------------------------------- /public/jina-j.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jina-ai/dashboard/761245cc0ef7f568872fb15b21386f12bb587bbb/public/jina-j.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Jina Dashboard", 3 | "name": "Jina Dashboard", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "icon.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "icon.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/apps/Dashboard.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { HashRouter as Router, Route } from "react-router-dom" 3 | import { dashboardRoutes as routes } from "../routes" 4 | import withTracker from "../withTracker" 5 | import { Provider } from "react-redux" 6 | import { ErrorBoundary } from "react-error-boundary" 7 | import { FallbackPage } from "../views/FallbackPage" 8 | 9 | import "../App.css" 10 | import "swagger-ui-react/swagger-ui.css" 11 | import store from "../redux" 12 | import { initSentry } from "./utils" 13 | 14 | initSentry() 15 | 16 | if (window.Cypress) { 17 | window.store = store 18 | } 19 | 20 | const Dashboard = () => { 21 | document.title = "Jina Dashboard" 22 | return ( 23 | 24 | 25 |
26 | {routes.map((route, idx: number) => { 27 | return ( 28 | { 33 | return ( 34 | 35 | 36 | 37 | 38 | 39 | ) 40 | }, {})} 41 | /> 42 | ) 43 | })} 44 |
45 |
46 |
47 | ) 48 | } 49 | 50 | export default Dashboard 51 | -------------------------------------------------------------------------------- /src/apps/Hub.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { HashRouter as Router, Route } from "react-router-dom" 3 | 4 | import { hubRoutes as routes } from "../routes" 5 | import withTracker from "../withTracker" 6 | import { Provider } from "react-redux" 7 | import { ErrorBoundary } from "react-error-boundary" 8 | import { FallbackPage } from "../views/FallbackPage" 9 | import { HubRoute } from "../routes/Hub" 10 | 11 | import "../App.css" 12 | import store from "../redux" 13 | import { initSentry } from "./utils" 14 | 15 | initSentry() 16 | 17 | if (window.Cypress) { 18 | window.store = store 19 | } 20 | 21 | const Hub = () => { 22 | document.title = "Jina Hub" 23 | return ( 24 | 25 | 26 |
27 | {routes.map((route: HubRoute, index: number) => { 28 | return ( 29 | { 34 | return ( 35 | 36 | 37 | 38 | 39 | 40 | ) 41 | })} 42 | /> 43 | ) 44 | })} 45 |
46 |
47 |
48 | ) 49 | } 50 | 51 | export default Hub 52 | -------------------------------------------------------------------------------- /src/apps/Styleguide.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { 3 | BrowserRouter as Router, 4 | Switch, 5 | Route, 6 | Redirect, 7 | } from "react-router-dom" 8 | import styled from "@emotion/styled" 9 | import SideNavBar from "../components/Layout/SideNavBar/SideNavBar" 10 | import { TNavItem } from "../redux/global/global.types" 11 | import { styleGuideRoutes } from "../routes/Styleguide" 12 | import { mediaQuery } from "../styles/mediaQuery" 13 | import { Provider } from "react-redux" 14 | import "../App.css" 15 | import store from "../redux" 16 | import { initSentry } from "./utils" 17 | 18 | initSentry() 19 | 20 | if (window.Cypress) { 21 | window.store = store 22 | } 23 | 24 | const Container = styled.div` 25 | display: flex; 26 | ` 27 | const Aside = styled.aside` 28 | width: 25%; 29 | ${mediaQuery("lg")} { 30 | width: 16.67%; 31 | } 32 | ` 33 | const Content = styled.article` 34 | flex-grow: 1; 35 | ` 36 | 37 | const Styleguide = () => { 38 | return ( 39 | 40 | 41 | 42 | 49 | 50 | 51 | 52 | {styleGuideRoutes.map((route, index) => ( 53 | } 58 | /> 59 | ))} 60 | 61 | 62 | 63 | 64 | 65 | 66 | ) 67 | } 68 | 69 | export default Styleguide 70 | -------------------------------------------------------------------------------- /src/apps/Swagger.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import { SwaggerLayout } from "../layouts/SwaggerLayout" 4 | import SwaggerView from "../views/SwaggerView" 5 | 6 | import "swagger-ui-react/swagger-ui.css" 7 | 8 | const Swagger = () => { 9 | document.title = "Jina Debug" 10 | return ( 11 | 12 | 13 | 14 | ) 15 | } 16 | 17 | export default Swagger 18 | -------------------------------------------------------------------------------- /src/apps/utils.ts: -------------------------------------------------------------------------------- 1 | import * as Sentry from "@sentry/react" 2 | import { Integrations } from "@sentry/tracing" 3 | 4 | export function initSentry() { 5 | if (process.env.NODE_ENV === "production") { 6 | Sentry.init({ 7 | dsn: 8 | "https://085edd94ec3e479cb20f2c65f7b8cb82@o525420.ingest.sentry.io/5639443", 9 | integrations: [new Integrations.BrowserTracing()], 10 | tracesSampleRate: 1.0, 11 | }) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/assets/icons/Copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/Play.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/Save.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/Stop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/Upload.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/Yaml.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /src/components/Common/Button.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | export type ButtonElementProps = React.HTMLAttributes 3 | interface ButtonProps extends ButtonElementProps { 4 | label?: string 5 | variant?: "primary" | "secondary" | "outlined" 6 | onClick?: React.MouseEventHandler 7 | } 8 | 9 | const getVariantStyles = (variant: string | undefined) => { 10 | switch (variant) { 11 | case "primary": 12 | return "bg-primary-500 hover:bg-primary-700 " 13 | case "secondary": 14 | return "bg-blue-500 hover:bg-blue-700 " 15 | default: 16 | return "bg-gray-500 hover:bg-gray-700" 17 | } 18 | } 19 | 20 | export default function Button({ 21 | label, 22 | onClick, 23 | children, 24 | variant, 25 | }: ButtonProps) { 26 | return ( 27 | 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /src/components/Common/Card.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | export type DivProps = React.HTMLAttributes 4 | 5 | export default function Card({ children, className }: DivProps) { 6 | return ( 7 |
10 | {children} 11 |
12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /src/components/Common/ConnectionToast.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import Button from "./Button" 3 | 4 | type Props = { 5 | reconnect: () => void 6 | } 7 | 8 | function ConnectionToast({ reconnect }: Props) { 9 | return ( 10 |
14 |
15 |
16 | 17 | warningCould not connect to 18 | Jina instance 19 | 20 |
21 |
22 |
23 | 35 |
36 | 39 |
40 |
41 |
42 |
43 |
44 | ) 45 | } 46 | 47 | export { ConnectionToast } 48 | -------------------------------------------------------------------------------- /src/components/Common/CookiesBanner.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import Button from "./Button" 3 | 4 | type Props = { 5 | acceptCookies: () => void 6 | } 7 | 8 | function CookiesBanner({ acceptCookies }: Props) { 9 | return ( 10 |
11 |

12 | We and third parties use cookies or similar technologies ("Cookies") as 13 | described below to collect and process personal data, such as your IP 14 | address or browser information. You can learn more about how this site 15 | uses Cookies by reading our privacy policy linked below. By clicking "I 16 | consent to cookies", you accept the placement and use of these Cookies 17 | for these purposes. You can change your mind and revisit your 18 | preferences at any time by accessing the “Cookie Preferences” link in 19 | the footer of this site. 20 |

21 | 24 |
25 | ) 26 | } 27 | 28 | export { CookiesBanner } 29 | -------------------------------------------------------------------------------- /src/components/Common/ExpandingSearchbar.test.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { render, fireEvent } from "@testing-library/react" 3 | import { ExpandingSearchbar } from "./ExpandingSearchbar" 4 | import { theme } from "../../theme" 5 | import { ThemeProvider as MuiThemeProvider } from "@material-ui/core/styles" 6 | 7 | let searchQuery = "" 8 | const mockChangeCallback = jest.fn((newValue) => (searchQuery = newValue)) 9 | const mockSearchCallback = jest.fn() 10 | 11 | const expandingSearchbar = render( 12 | 13 | 18 | 19 | ) 20 | const input = expandingSearchbar.getByRole("textbox") 21 | 22 | describe("ExpandingSearchbar", () => { 23 | it("renders the value in input box", () => { 24 | fireEvent.change(input, { target: { value: "con" } }) 25 | expect(mockChangeCallback.mock.calls.length).toBe(1) 26 | fireEvent.change(input, { target: { value: "way" } }) 27 | expect(mockChangeCallback.mock.calls[1][0]).toEqual("way") 28 | fireEvent.keyPress(input, { key: "Enter", code: 13, charCode: 13 }) 29 | expect(mockSearchCallback.mock.calls.length).toBe(1) 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /src/components/Common/ExpandingSearchbar.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import styled from "@emotion/styled" 3 | import { IconButton, InputBase } from "@material-ui/core" 4 | import { Search as SearchIcon, Cancel as CancelIcon } from "@material-ui/icons" 5 | 6 | type Props = { 7 | value: string | number 8 | onChange: (newValue: string) => void 9 | onSearch?: (value: string | number) => void 10 | } 11 | 12 | function ExpandingSearchbar({ onSearch, value, onChange }: Props) { 13 | const handleKeyPress = (event: React.KeyboardEvent) => { 14 | if (event.key === "Enter") { 15 | onSearch && onSearch(value) 16 | } 17 | } 18 | const clearSearch = () => { 19 | onSearch && onSearch("") 20 | } 21 | 22 | const SearchBar = styled.div` 23 | display: flex; 24 | background-color: ${(props) => props.theme.palette.grey[100]}; 25 | border-radius: 2px; 26 | ` 27 | return ( 28 | 29 | onSearch && onSearch(value)}> 30 | 31 | 32 | onChange(e.target.value)} 35 | defaultValue={value} 36 | placeholder="Search" 37 | onKeyPress={handleKeyPress} 38 | /> 39 | {value && ( 40 | 41 | 42 | 43 | )} 44 | 45 | ) 46 | } 47 | 48 | export { ExpandingSearchbar } 49 | -------------------------------------------------------------------------------- /src/components/Common/HelpCard.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from "react" 2 | import Card from "./Card" 3 | 4 | type Props = { 5 | title: ReactNode 6 | content: string 7 | icon: string 8 | theme: string 9 | link: string 10 | dataName: string 11 | } 12 | 13 | function HelpCard({ title, content, icon, theme, link, dataName }: Props) { 14 | return ( 15 | 22 | 23 |
24 |
25 |
26 |

{title}

27 |
28 |
29 |
30 |
31 |
{content}
32 |
33 |
34 | 35 |
36 | ) 37 | } 38 | 39 | export { HelpCard } 40 | -------------------------------------------------------------------------------- /src/components/Common/InfoBanner.test.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { render } from "@testing-library/react" 3 | import { theme } from "../../theme" 4 | import { ThemeProvider } from "@emotion/react" 5 | import { InfoBanner } from "./InfoBanner" 6 | 7 | describe("InfoBanner unit tests", () => { 8 | it("should render", () => { 9 | const { getByText } = render( 10 | 11 | 14 | 15 | ) 16 | 17 | expect(getByText("Some information banner")).not.toBeNull() 18 | }) 19 | 20 | it("should not render when data is not present", () => { 21 | const props = {} 22 | const { container } = render( 23 | 24 | 25 | 26 | ) 27 | 28 | expect(container.innerHTML).toBe("") 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /src/components/Common/InfoBanner.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | type Props = { 4 | data: { 5 | theme: string; 6 | message: string; 7 | }; 8 | }; 9 | 10 | function InfoBanner({ data }: Props) { 11 | if (!data) return null; 12 | return ( 13 |
14 |
15 | {data.message} 16 |
17 |
18 | ); 19 | } 20 | 21 | export { InfoBanner }; 22 | -------------------------------------------------------------------------------- /src/components/Common/InfoToast.test.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { render } from "@testing-library/react" 3 | import { theme } from "../../theme" 4 | import { ThemeProvider } from "@emotion/react" 5 | import { InfoToast } from "./InfoToast" 6 | 7 | describe("InfoToast unit tests", () => { 8 | it("should render", () => { 9 | const { getByText } = render( 10 | 11 | 14 | 15 | ) 16 | 17 | expect(getByText("Some information banner")).not.toBeNull() 18 | }) 19 | 20 | it("should not render when data is not available", () => { 21 | const props = {} 22 | const { container } = render( 23 | 24 | 25 | 26 | ) 27 | 28 | expect(container.innerHTML).toBe("") 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /src/components/Common/InfoToast.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Banner } from "../../redux/global/global.types" 3 | 4 | type Props = { 5 | data: Banner 6 | index: number 7 | } 8 | 9 | const INITIAL_BANNER_MARGIN = 1 10 | const BANNER_MARGIN_STEP = 7 11 | 12 | function InfoToast({ data, index }: Props) { 13 | if (!data) return null 14 | const cssProperties = { 15 | bottom: String(INITIAL_BANNER_MARGIN + BANNER_MARGIN_STEP * index) + "em", 16 | } 17 | return ( 18 |
23 |
24 |
25 | 30 | 31 | 32 |
33 | 34 |
35 |
36 | 37 | {data.theme} 38 | 39 |

40 | {data.message} 41 |

42 |
43 |
44 |
45 |
46 | ) 47 | } 48 | 49 | export { InfoToast } 50 | -------------------------------------------------------------------------------- /src/components/Common/MultiFilterSelect.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import Select, { Props as SelectProps } from "react-select" 3 | import { StylesConfig } from "react-select/src/styles" 4 | import { FilterSelection } from "../LogStream/LogsTable" 5 | 6 | const customStyles: StylesConfig = { 7 | dropdownIndicator: (styles) => ({ 8 | ...styles, 9 | color: "black", 10 | }), 11 | indicatorSeparator: (styles) => ({ 12 | ...styles, 13 | opacity: "0", 14 | }), 15 | container: (provided, { selectProps: { width } }) => ({ 16 | ...provided, 17 | width: width, 18 | }), 19 | option: (styles) => ({ 20 | ...styles, 21 | cursor: "pointer", 22 | }), 23 | control: (styles) => ({ 24 | ...styles, 25 | cursor: "pointer", 26 | border: "none", 27 | background: "#F6F8FA", 28 | }), 29 | } 30 | 31 | export type Props = { 32 | onFilterChange: (something: FilterSelection[]) => void 33 | options: { value: string; label: string }[] 34 | isMulti?: boolean 35 | isSearchable?: boolean 36 | clearAfter?: boolean 37 | } & Omit 38 | 39 | function MultiFilterSelect({ 40 | isMulti, 41 | isSearchable, 42 | clearAfter, 43 | onFilterChange, 44 | options, 45 | ...rest 46 | }: Props) { 47 | let value = clearAfter ? null : undefined 48 | return ( 49 |