├── .dockerignore ├── .github ├── FUNDING.yml └── workflows │ ├── aws-e2e.yaml │ ├── cli-test.yaml │ ├── dashboard-run-test.yaml │ ├── dashboard-start-test.yaml │ ├── publish.yaml │ └── release.yaml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yaml ├── .releaserc.json ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── cmd ├── add.go ├── build.go ├── debug.go ├── new.go ├── root.go ├── run.go ├── stack.go ├── start.go └── version.go ├── docs └── assets │ └── nitric-logo.svg ├── go.mod ├── go.sum ├── hack ├── github_release │ └── main.go ├── modversion │ └── main.go └── readmegen │ └── main.go ├── main.go ├── mocks └── mock_utils │ └── mock_getter.go ├── pkg ├── browser │ └── browser.go ├── cloud │ ├── apis │ │ └── apis.go │ ├── batch │ │ └── batch.go │ ├── cloud.go │ ├── env │ │ └── env.go │ ├── errorsx │ │ └── logger.go │ ├── gateway │ │ └── gateway.go │ ├── http │ │ └── httpproxy.go │ ├── keyvalue │ │ └── keyvalue.go │ ├── queues │ │ └── queues.go │ ├── resources │ │ ├── registrar.go │ │ ├── resources.go │ │ └── services.go │ ├── schedules │ │ └── schedules.go │ ├── secrets │ │ └── secret.go │ ├── sql │ │ ├── sql.go │ │ ├── sqlsplit.go │ │ └── sqlsplit_test.go │ ├── storage │ │ └── storage.go │ ├── topics │ │ └── topics.go │ ├── websites │ │ └── websites.go │ └── websockets │ │ └── websockets.go ├── collector │ ├── api.go │ ├── batch.go │ ├── default-migrations.dockerfile │ ├── openid.go │ ├── runtime.go │ ├── service.go │ └── spec.go ├── dashboard │ ├── dashboard.go │ ├── frontend │ │ ├── .eslintrc.cjs │ │ ├── .gitignore │ │ ├── README.md │ │ ├── astro.config.mjs │ │ ├── components.json │ │ ├── cypress.config.ts │ │ ├── cypress │ │ │ ├── e2e │ │ │ │ ├── a11y.cy.ts │ │ │ │ ├── api-explorer.cy.ts │ │ │ │ ├── architecture.cy.ts │ │ │ │ ├── databases.cy.ts │ │ │ │ ├── fathom.cy.ts │ │ │ │ ├── logs.cy.ts │ │ │ │ ├── schedules.cy.ts │ │ │ │ ├── secrets.cy.ts │ │ │ │ ├── storage-explorer.cy.ts │ │ │ │ ├── topics.cy.ts │ │ │ │ ├── websites.cy.ts │ │ │ │ └── websockets.cy.ts │ │ │ ├── fixtures │ │ │ │ └── photo.jpg │ │ │ ├── support │ │ │ │ ├── commands.ts │ │ │ │ └── e2e.ts │ │ │ └── tsconfig.json │ │ ├── package.json │ │ ├── prettier.config.js │ │ ├── public │ │ │ ├── favicon.ico │ │ │ └── nitric-no-text.svg │ │ ├── src │ │ │ ├── components │ │ │ │ ├── apis │ │ │ │ │ ├── APIExplorer.tsx │ │ │ │ │ ├── APIHistory.tsx │ │ │ │ │ ├── APIMenu.tsx │ │ │ │ │ ├── APIMethodBadge.tsx │ │ │ │ │ ├── APIResponseContent.tsx │ │ │ │ │ ├── APIRoutesList.tsx │ │ │ │ │ ├── APITreeView.tsx │ │ │ │ │ ├── CodeEditor.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── architecture │ │ │ │ │ ├── Architecture.tsx │ │ │ │ │ ├── DetailsDrawer.tsx │ │ │ │ │ ├── ExportButton.tsx │ │ │ │ │ ├── NitricEdge.tsx │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── nodes │ │ │ │ │ │ ├── APINode.tsx │ │ │ │ │ │ ├── BatchNode.tsx │ │ │ │ │ │ ├── BucketNode.tsx │ │ │ │ │ │ ├── HttpProxyNode.tsx │ │ │ │ │ │ ├── JobNode.tsx │ │ │ │ │ │ ├── KeyValueNode.tsx │ │ │ │ │ │ ├── NodeBase.tsx │ │ │ │ │ │ ├── QueueNode.tsx │ │ │ │ │ │ ├── SQLNode.tsx │ │ │ │ │ │ ├── ScheduleNode.tsx │ │ │ │ │ │ ├── SecretNode.tsx │ │ │ │ │ │ ├── ServiceNode.tsx │ │ │ │ │ │ ├── TopicNode.tsx │ │ │ │ │ │ ├── WebsitesNode.tsx │ │ │ │ │ │ └── WebsocketNode.tsx │ │ │ │ │ └── styles.css │ │ │ │ ├── databases │ │ │ │ │ ├── DatabasesExplorer.tsx │ │ │ │ │ ├── DatabasesMenu.tsx │ │ │ │ │ ├── DatabasesTreeView.tsx │ │ │ │ │ └── QueryResults.tsx │ │ │ │ ├── events │ │ │ │ │ ├── EventsExplorer.tsx │ │ │ │ │ ├── EventsHistory.tsx │ │ │ │ │ ├── EventsMenu.tsx │ │ │ │ │ └── EventsTreeView.tsx │ │ │ │ ├── layout │ │ │ │ │ ├── AppLayout │ │ │ │ │ │ ├── AppLayout.tsx │ │ │ │ │ │ ├── NavigationBar.tsx │ │ │ │ │ │ ├── NavigationItem.tsx │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── BreadCrumbs.tsx │ │ │ │ ├── logs │ │ │ │ │ ├── FilterSidebar.tsx │ │ │ │ │ ├── FilterTrigger.tsx │ │ │ │ │ └── LogsExplorer.tsx │ │ │ │ ├── secrets │ │ │ │ │ ├── SecretVersionsTable │ │ │ │ │ │ ├── SecretVersionsTable.tsx │ │ │ │ │ │ ├── columns.tsx │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── SecretsContext.tsx │ │ │ │ │ ├── SecretsExplorer.tsx │ │ │ │ │ ├── SecretsTreeView.tsx │ │ │ │ │ └── VersionActionDialog.tsx │ │ │ │ ├── shared │ │ │ │ │ ├── Badge.tsx │ │ │ │ │ ├── DataTable.tsx │ │ │ │ │ ├── FieldRows.tsx │ │ │ │ │ ├── HistoryAccordion.tsx │ │ │ │ │ ├── Loading │ │ │ │ │ │ ├── Loading.tsx │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ └── loading.animation.json │ │ │ │ │ ├── MultiSelect.tsx │ │ │ │ │ ├── NotFoundAlert.tsx │ │ │ │ │ ├── ResourceDropdownMenu.tsx │ │ │ │ │ ├── SectionCard.tsx │ │ │ │ │ ├── Select.tsx │ │ │ │ │ ├── Spinner.tsx │ │ │ │ │ ├── TableGroup.tsx │ │ │ │ │ ├── Tabs.tsx │ │ │ │ │ ├── TextField.tsx │ │ │ │ │ ├── TreeView.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── storage │ │ │ │ │ ├── FileBrowser.tsx │ │ │ │ │ ├── FileUpload.tsx │ │ │ │ │ ├── StorageExplorer.tsx │ │ │ │ │ ├── StorageTreeView.tsx │ │ │ │ │ ├── download-files.ts │ │ │ │ │ ├── file-browser-styles.css │ │ │ │ │ └── index.ts │ │ │ │ ├── ui │ │ │ │ │ ├── accordion.tsx │ │ │ │ │ ├── alert.tsx │ │ │ │ │ ├── badge.tsx │ │ │ │ │ ├── button.tsx │ │ │ │ │ ├── card.tsx │ │ │ │ │ ├── checkbox.tsx │ │ │ │ │ ├── collapsible.tsx │ │ │ │ │ ├── command.tsx │ │ │ │ │ ├── context-menu.tsx │ │ │ │ │ ├── dialog.tsx │ │ │ │ │ ├── drawer.tsx │ │ │ │ │ ├── dropdown-menu.tsx │ │ │ │ │ ├── input.tsx │ │ │ │ │ ├── label.tsx │ │ │ │ │ ├── popover.tsx │ │ │ │ │ ├── scroll-area.tsx │ │ │ │ │ ├── select.tsx │ │ │ │ │ ├── separator.tsx │ │ │ │ │ ├── sheet.tsx │ │ │ │ │ ├── sidebar.tsx │ │ │ │ │ ├── skeleton.tsx │ │ │ │ │ ├── switch.tsx │ │ │ │ │ ├── table.tsx │ │ │ │ │ ├── tabs.tsx │ │ │ │ │ ├── textarea.tsx │ │ │ │ │ └── tooltip.tsx │ │ │ │ ├── websites │ │ │ │ │ ├── SiteExplorer.tsx │ │ │ │ │ ├── SiteTreeView.tsx │ │ │ │ │ └── SitesList.tsx │ │ │ │ └── websockets │ │ │ │ │ ├── WSExplorer.tsx │ │ │ │ │ └── WSTreeView.tsx │ │ │ ├── env.d.ts │ │ │ ├── hooks │ │ │ │ ├── use-mobile.tsx │ │ │ │ └── use-params.tsx │ │ │ ├── layouts │ │ │ │ └── Layout.astro │ │ │ ├── lib │ │ │ │ ├── constants.ts │ │ │ │ ├── hooks │ │ │ │ │ ├── fetcher.ts │ │ │ │ │ ├── use-bucket.ts │ │ │ │ │ ├── use-history.ts │ │ │ │ │ ├── use-logs.ts │ │ │ │ │ ├── use-secret.ts │ │ │ │ │ ├── use-sql-meta.ts │ │ │ │ │ └── use-web-socket.ts │ │ │ │ └── utils │ │ │ │ │ ├── cn.ts │ │ │ │ │ ├── copy-to-clipboard.ts │ │ │ │ │ ├── flatten-paths.ts │ │ │ │ │ ├── format-file-size.ts │ │ │ │ │ ├── format-response-time.ts │ │ │ │ │ ├── generate-architecture-data.ts │ │ │ │ │ ├── generate-path-params.ts │ │ │ │ │ ├── generate-path.ts │ │ │ │ │ ├── generate-response.ts │ │ │ │ │ ├── get-bucket-notifications.ts │ │ │ │ │ ├── get-date-string.ts │ │ │ │ │ ├── get-file-extension.ts │ │ │ │ │ ├── get-host.ts │ │ │ │ │ ├── get-topic-subscriptions.ts │ │ │ │ │ ├── headers.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── is-valid-url.ts │ │ │ │ │ ├── strings.ts │ │ │ │ │ └── uniq.ts │ │ │ ├── pages │ │ │ │ ├── architecture.astro │ │ │ │ ├── databases.astro │ │ │ │ ├── index.astro │ │ │ │ ├── jobs.astro │ │ │ │ ├── logs.astro │ │ │ │ ├── not-found.astro │ │ │ │ ├── schedules.astro │ │ │ │ ├── secrets.astro │ │ │ │ ├── storage.astro │ │ │ │ ├── topics.astro │ │ │ │ ├── websites.astro │ │ │ │ └── websockets.astro │ │ │ ├── styles │ │ │ │ ├── globals.css │ │ │ │ └── grid.css │ │ │ └── types.ts │ │ ├── tailwind.config.cjs │ │ ├── test-app │ │ │ ├── .gitignore │ │ │ ├── README.md │ │ │ ├── docs-website │ │ │ │ ├── .gitignore │ │ │ │ ├── index.html │ │ │ │ ├── package.json │ │ │ │ ├── public │ │ │ │ │ └── vite.svg │ │ │ │ ├── src │ │ │ │ │ ├── counter.js │ │ │ │ │ ├── javascript.svg │ │ │ │ │ ├── main.js │ │ │ │ │ └── style.css │ │ │ │ └── yarn.lock │ │ │ ├── local.nitric.yaml │ │ │ ├── migrations │ │ │ │ ├── my-db │ │ │ │ │ └── 1_create_table.up.sql │ │ │ │ └── my-second-db │ │ │ │ │ └── 1_create_table.up.sql │ │ │ ├── nitric.yaml │ │ │ ├── package.json │ │ │ ├── services │ │ │ │ ├── my-test-db.ts │ │ │ │ ├── my-test-secret.ts │ │ │ │ └── my-test-service.ts │ │ │ ├── tsconfig.json │ │ │ ├── vite-website │ │ │ │ ├── .gitignore │ │ │ │ ├── index.html │ │ │ │ ├── package.json │ │ │ │ ├── public │ │ │ │ │ └── vite.svg │ │ │ │ ├── src │ │ │ │ │ ├── counter.js │ │ │ │ │ ├── javascript.svg │ │ │ │ │ ├── main.js │ │ │ │ │ └── style.css │ │ │ │ └── yarn.lock │ │ │ └── yarn.lock │ │ ├── tsconfig.json │ │ └── yarn.lock │ ├── handlers.go │ ├── history.go │ └── servicelogs.go ├── docker │ └── docker.go ├── env │ └── env.go ├── eventbus │ ├── eventbus.go │ └── resourceevts │ │ └── resourceevts.go ├── grpcx │ ├── metadata.go │ └── streams.go ├── iox │ └── channelwriter.go ├── netx │ ├── freeport.go │ └── net.go ├── paths │ └── paths.go ├── pflagx │ └── string_enum.go ├── ports │ └── ports.go ├── preview │ └── feature.go ├── project │ ├── batch.go │ ├── config.go │ ├── dockerhost │ │ └── dockerhost.go │ ├── localconfig │ │ └── localconfig.go │ ├── migrations.go │ ├── project.go │ ├── runtime │ │ ├── csharp.dockerfile │ │ ├── dart.dockerfile │ │ ├── generate_test.go │ │ ├── javascript.dockerfile │ │ ├── jvm.dockerfile │ │ ├── python.dockerfile │ │ ├── runtime.go │ │ └── typescript.dockerfile │ ├── service.go │ ├── stack │ │ ├── aws.config.yaml │ │ ├── awstf.config.yaml │ │ ├── azure.config.yaml │ │ ├── azuretf.config.yaml │ │ ├── gcp.config.yaml │ │ ├── gcptf.config.yaml │ │ └── stack.go │ ├── templates │ │ ├── contents.go │ │ ├── contents_test.go │ │ └── getter.go │ └── website.go ├── provider │ ├── client.go │ ├── image.go │ ├── provider.go │ ├── pulumi │ │ └── pulumi.go │ └── standard.go ├── system │ ├── logs.go │ └── servicelogs.go ├── update │ └── update.go ├── validation │ ├── name.go │ └── rule.go ├── version │ └── version.go └── view │ └── tui │ ├── colors.go │ ├── commands │ ├── build │ │ └── build.go │ ├── local │ │ ├── run.go │ │ └── startup.go │ ├── project │ │ ├── new.go │ │ └── validators.go │ ├── services │ │ └── run.go │ ├── stack │ │ ├── down │ │ │ └── stack_down.go │ │ ├── new │ │ │ └── stack_new.go │ │ ├── resource.go │ │ ├── select │ │ │ └── stack_select.go │ │ ├── up │ │ │ └── stack_up.go │ │ └── validators.go │ └── website │ │ ├── new.go │ │ ├── toolsetup.go │ │ └── validators.go │ ├── components │ ├── list │ │ ├── inline.go │ │ ├── list.go │ │ ├── sliceview.go │ │ └── sliceview_test.go │ ├── listprompt │ │ └── listprompt.go │ ├── textprompt │ │ └── textprompt.go │ ├── validation │ │ └── validation.go │ └── view │ │ ├── fragment.go │ │ └── view.go │ ├── dependency.go │ ├── errors.go │ ├── fragments │ ├── errorlist.go │ ├── hotkey.go │ ├── statustree.go │ └── tag.go │ ├── keys.go │ ├── printer.go │ ├── reactive │ └── reactive.go │ ├── teax │ ├── program.go │ ├── quit.go │ └── teax.go │ └── tui.go ├── tools └── tools.go └── vhs ├── .gitignore ├── new_project.tape └── new_project_non-interactive.tape /.dockerignore: -------------------------------------------------------------------------------- 1 | .github/ 2 | .git/ 3 | bin/ -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: nitrictech -------------------------------------------------------------------------------- /.github/workflows/cli-test.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Tests 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | - develop 9 | pull_request: 10 | 11 | concurrency: 12 | group: ci-cli-tests-${{ github.ref_name }} 13 | cancel-in-progress: true 14 | 15 | env: 16 | GOPROXY: https://proxy.golang.org 17 | FATHOM_SITE: ${{ vars.FATHOM_SITE }} 18 | 19 | jobs: 20 | full-test: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v3 25 | - name: yaml-lint 26 | uses: ibiqlik/action-yamllint@v3 27 | with: 28 | file_or_dir: .github/workflows/ .golangci.yml .goreleaser.yaml 29 | config_data: | 30 | extends: default 31 | rules: 32 | new-line-at-end-of-file: 33 | level: warning 34 | trailing-spaces: 35 | level: warning 36 | line-length: 37 | level: warning 38 | - name: Setup Go 39 | uses: actions/setup-go@v3 40 | with: 41 | go-version: 1.21 42 | - name: Download go modules 43 | run: go mod download 44 | - name: Run Lint 45 | run: make lint 46 | - name: Build 47 | run: make build 48 | - name: Run Tests 49 | run: make test-coverage 50 | other-os-build: 51 | strategy: 52 | matrix: 53 | os: [windows-latest, macos-latest] 54 | fail-fast: true 55 | 56 | runs-on: ${{ matrix.os }} 57 | steps: 58 | - name: Checkout 59 | uses: actions/checkout@v3 60 | - name: Setup Go 61 | uses: actions/setup-go@v3 62 | with: 63 | go-version: 1.21 64 | - name: Build 65 | run: make build 66 | -------------------------------------------------------------------------------- /.github/workflows/dashboard-run-test.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Dashboard Tests with nitric run 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | - develop 9 | pull_request: 10 | 11 | concurrency: 12 | group: ci-dash-run-tests-${{ github.ref_name }} 13 | cancel-in-progress: true 14 | 15 | env: 16 | GOPROXY: https://proxy.golang.org 17 | FATHOM_SITE: FAKE1234 18 | 19 | jobs: 20 | nitric-dashboard: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v3 25 | with: 26 | path: cli 27 | 28 | - uses: actions/setup-node@v4 29 | with: 30 | node-version: 22 31 | 32 | - name: Setup Go 33 | uses: actions/setup-go@v3 34 | with: 35 | go-version: 1.22 36 | 37 | - name: Build Nitric 38 | run: | 39 | cd ${{ github.workspace }}/cli 40 | make build 41 | mv bin/nitric $(go env GOPATH)/bin/nitric 42 | 43 | - name: Run nitric run with test-app in the background 44 | run: | 45 | cd ${{ github.workspace }}/cli/pkg/dashboard/frontend/test-app 46 | yarn install 47 | yarn install:websites 48 | nitric run --ci & 49 | sleep 25 50 | 51 | - name: Run Tests 52 | uses: cypress-io/github-action@v5 53 | env: 54 | CYPRESS_NITRIC_TEST_TYPE: "run" 55 | with: 56 | install: false 57 | wait-on: "http://localhost:49152" 58 | # wait for 3 minutes for the server to respond 59 | wait-on-timeout: 180 60 | working-directory: cli/pkg/dashboard/frontend 61 | browser: chrome 62 | 63 | - uses: actions/upload-artifact@v4 64 | if: failure() 65 | with: 66 | name: cypress-screenshots 67 | path: cli/pkg/dashboard/frontend/cypress/screenshots 68 | 69 | - uses: actions/upload-artifact@v4 70 | if: failure() 71 | with: 72 | name: cypress-videos 73 | path: cli/pkg/dashboard/frontend/cypress/videos 74 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Publish 3 | 4 | on: 5 | push: 6 | # run only against tags 7 | tags: 8 | - "*" 9 | 10 | env: 11 | FATHOM_SITE: ${{ vars.FATHOM_SITE }} 12 | 13 | jobs: 14 | publish: 15 | name: GoReleaser 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | 22 | - uses: actions/setup-go@v3 23 | with: 24 | go-version: 1.22 25 | 26 | - uses: goreleaser/goreleaser-action@v3 27 | with: 28 | distribution: goreleaser 29 | version: 1.18 30 | args: release --rm-dist 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.NITRIC_BOT_TOKEN }} 33 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | workflow_dispatch: 9 | 10 | env: 11 | FATHOM_SITE: ${{ vars.FATHOM_SITE }} 12 | 13 | jobs: 14 | release: 15 | name: semantic-release 16 | runs-on: ubuntu-latest 17 | outputs: 18 | new-release-published: ${{ steps.semantic-release.outputs.new_release_published }} 19 | new-release-version: ${{ steps.semantic-release.outputs.new_release_version }} 20 | steps: 21 | - uses: actions/checkout@v4 22 | with: 23 | fetch-depth: 0 24 | persist-credentials: false 25 | - id: semantic-release 26 | uses: cycjimmy/semantic-release-action@v4 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.NITRIC_BOT_TOKEN }} 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | bin/* 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | all.coverprofile 15 | 16 | # Go workspace file 17 | go.work 18 | .vscode/ 19 | 20 | dist/ 21 | 22 | # Ignore nitric temp files 23 | .nitric/ -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | run: 3 | timeout: "5m" 4 | 5 | linters: 6 | disable-all: true 7 | enable: 8 | - goimports 9 | - goheader 10 | - gofmt 11 | - gofumpt 12 | - whitespace 13 | - staticcheck 14 | - ineffassign 15 | - unused 16 | - misspell 17 | - unconvert 18 | - errcheck 19 | - errorlint 20 | - wsl 21 | 22 | issues: 23 | exclude-files: 24 | - tools/tools.go 25 | max-issues-per-linter: 0 26 | max-same-issues: 0 27 | 28 | linters-settings: 29 | goimports: 30 | local-prefixes: github.com/nitrictech 31 | errorlint: 32 | asserts: false 33 | govet: 34 | check-shadowing: false 35 | goheader: 36 | template: | 37 | Copyright Nitric Pty Ltd. 38 | 39 | SPDX-License-Identifier: Apache-2.0 40 | 41 | Licensed under the Apache License, Version 2.0 (the "License"); 42 | you may not use this file except in compliance with the License. 43 | You may obtain a copy of the License at: 44 | 45 | http://www.apache.org/licenses/LICENSE-2.0 46 | 47 | Unless required by applicable law or agreed to in writing, software 48 | distributed under the License is distributed on an "AS IS" BASIS, 49 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 50 | See the License for the specific language governing permissions and 51 | limitations under the License. -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # This is an example .goreleaser.yml file with some sensible defaults. 3 | # Make sure to check the documentation at https://goreleaser.com 4 | project_name: nitric 5 | release: 6 | prerelease: auto 7 | before: 8 | hooks: 9 | - go mod tidy 10 | - make generate 11 | - make build-dashboard 12 | builds: 13 | - env: 14 | - CGO_ENABLED=0 15 | id: nitric 16 | binary: nitric 17 | ldflags: 18 | - -s -w -X github.com/nitrictech/cli/pkg/version.Version={{.Version}} -X github.com/nitrictech/cli/pkg/version.Commit={{.Commit}} -X github.com/nitrictech/cli/pkg/version.BuildTime={{.Date}} 19 | goos: 20 | - linux 21 | - windows 22 | - darwin 23 | goarch: 24 | - amd64 25 | - arm64 26 | archives: 27 | - replacements: 28 | darwin: macOS 29 | linux: Linux 30 | windows: Windows 31 | amd64: x86_64 32 | archives: 33 | format_overrides: 34 | - goos: windows 35 | format: zip 36 | checksum: 37 | name_template: "checksums.txt" 38 | snapshot: 39 | name_template: "{{ incpatch .Version }}-next" 40 | changelog: 41 | skip: true 42 | scoop: 43 | bucket: 44 | owner: nitrictech 45 | name: scoop-bucket 46 | homepage: "https://nitric.io" 47 | description: "Nitric CLI" 48 | license: Apache 2.0 49 | 50 | brews: 51 | - name: nitric 52 | tap: 53 | owner: nitrictech 54 | name: homebrew-tap 55 | commit_author: 56 | name: nitric-bot 57 | email: maintainers@nitric.io 58 | folder: Formula 59 | homepage: "https://nitric.io/" 60 | description: "Nitric CLI" 61 | license: "Apache 2.0" 62 | dependencies: 63 | - name: git 64 | - name: pulumi # needed for certain provider deployments 65 | type: optional 66 | -------------------------------------------------------------------------------- /.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "branches": ["main"], 3 | "plugins": [ 4 | "@semantic-release/commit-analyzer", 5 | "@semantic-release/release-notes-generator", 6 | "@semantic-release/github" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /cmd/build.go: -------------------------------------------------------------------------------- 1 | // Copyright Nitric Pty Ltd. 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at: 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package cmd 18 | 19 | import ( 20 | "github.com/spf13/afero" 21 | "github.com/spf13/cobra" 22 | 23 | "github.com/nitrictech/cli/pkg/project" 24 | "github.com/nitrictech/cli/pkg/view/tui" 25 | "github.com/nitrictech/cli/pkg/view/tui/commands/build" 26 | "github.com/nitrictech/cli/pkg/view/tui/teax" 27 | ) 28 | 29 | var buildCmd = &cobra.Command{ 30 | Use: "build", 31 | Short: "Build a Nitric project", 32 | Long: `Build all services in a nitric project as docker container images`, 33 | Run: func(cmd *cobra.Command, args []string) { 34 | // info.Run(cmd.Context()) 35 | fs := afero.NewOsFs() 36 | 37 | proj, err := project.FromFile(fs, "") 38 | tui.CheckErr(err) 39 | 40 | updates, err := proj.BuildServices(fs, !noBuilder) 41 | tui.CheckErr(err) 42 | 43 | prog := teax.NewProgram(build.NewModel(updates, "Building Services")) 44 | // blocks but quits once the above updates channel is closed by the build process 45 | _, err = prog.Run() 46 | tui.CheckErr(err) 47 | }, 48 | } 49 | 50 | func init() { 51 | buildCmd.Flags().BoolVar(&noBuilder, "no-builder", false, "don't create a buildx container") 52 | rootCmd.AddCommand(tui.AddDependencyCheck(buildCmd, tui.RequireContainerBuilder)) 53 | } 54 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | // Copyright Nitric Pty Ltd. 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at: 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package cmd 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/spf13/cobra" 23 | 24 | "github.com/nitrictech/cli/pkg/version" 25 | ) 26 | 27 | var versionCmd = &cobra.Command{ 28 | Use: "version", 29 | Short: "Print the version number of this CLI", 30 | Long: `All software has versions. This is Nitric's`, 31 | Run: func(cmd *cobra.Command, args []string) { 32 | fmt.Println(version.Version) 33 | }, 34 | } 35 | 36 | func init() { 37 | rootCmd.AddCommand(versionCmd) 38 | } 39 | -------------------------------------------------------------------------------- /docs/assets/nitric-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /hack/github_release/main.go: -------------------------------------------------------------------------------- 1 | // Copyright Nitric Pty Ltd. 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at: 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package main 18 | 19 | import ( 20 | "encoding/json" 21 | "fmt" 22 | "io" 23 | "net/http" 24 | "os" 25 | ) 26 | 27 | func main() { 28 | if len(os.Args) != 3 { 29 | fmt.Println("usage ") 30 | os.Exit(1) 31 | } 32 | 33 | owner := os.Args[1] 34 | repo := os.Args[2] 35 | 36 | resp, err := http.Get(fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", owner, repo)) 37 | if err != nil { 38 | fmt.Println(err) 39 | os.Exit(1) 40 | } 41 | 42 | result := map[string]any{} 43 | 44 | defer resp.Body.Close() 45 | 46 | body, err := io.ReadAll(resp.Body) 47 | if err != nil { 48 | fmt.Println(err) 49 | os.Exit(1) 50 | } 51 | 52 | err = json.Unmarshal(body, &result) 53 | if err != nil { 54 | fmt.Println(err) 55 | os.Exit(1) 56 | } 57 | 58 | fmt.Print(result["tag_name"]) 59 | } 60 | -------------------------------------------------------------------------------- /hack/modversion/main.go: -------------------------------------------------------------------------------- 1 | // Copyright Nitric Pty Ltd. 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at: 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package main 18 | 19 | import ( 20 | "bufio" 21 | "flag" 22 | "fmt" 23 | "io" 24 | "os" 25 | "strings" 26 | 27 | "github.com/hashicorp/go-version" 28 | ) 29 | 30 | func main() { 31 | inpath := flag.Bool("inpath", false, "") 32 | 33 | flag.Parse() 34 | 35 | prefix := flag.Arg(0) 36 | 37 | file, err := os.Open("go.sum") 38 | if err != nil { 39 | panic(err) 40 | } 41 | defer file.Close() 42 | 43 | reader := bufio.NewReader(file) 44 | 45 | for { 46 | line, _, err := reader.ReadLine() 47 | if err != nil { 48 | if err == io.EOF { 49 | break 50 | } 51 | 52 | panic(err) 53 | } 54 | 55 | words := strings.Split(string(line), " ") 56 | 57 | if !strings.HasPrefix(words[0], prefix) { 58 | continue 59 | } 60 | 61 | if *inpath { 62 | for _, seg := range strings.Split(strings.TrimPrefix(words[0], prefix), "/") { 63 | v, err := version.NewSemver(seg) 64 | if err != nil { 65 | continue 66 | } 67 | 68 | fmt.Println(v.Original()) 69 | 70 | return 71 | } 72 | } else if len(words) == 3 { 73 | fmt.Print(words[1]) 74 | 75 | return 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright Nitric Pty Ltd. 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at: 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package main 18 | 19 | import "github.com/nitrictech/cli/cmd" 20 | 21 | func main() { 22 | cmd.Execute() 23 | } 24 | -------------------------------------------------------------------------------- /mocks/mock_utils/mock_getter.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/nitrictech/cli/pkg/project/templates (interfaces: GetterClient) 3 | 4 | // Package mock_templates is a generated GoMock package. 5 | package mock_templates 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | gomock "github.com/golang/mock/gomock" 11 | ) 12 | 13 | // MockGetterClient is a mock of GetterClient interface. 14 | type MockGetterClient struct { 15 | ctrl *gomock.Controller 16 | recorder *MockGetterClientMockRecorder 17 | } 18 | 19 | // MockGetterClientMockRecorder is the mock recorder for MockGetterClient. 20 | type MockGetterClientMockRecorder struct { 21 | mock *MockGetterClient 22 | } 23 | 24 | // NewMockGetterClient creates a new mock instance. 25 | func NewMockGetterClient(ctrl *gomock.Controller) *MockGetterClient { 26 | mock := &MockGetterClient{ctrl: ctrl} 27 | mock.recorder = &MockGetterClientMockRecorder{mock} 28 | return mock 29 | } 30 | 31 | // EXPECT returns an object that allows the caller to indicate expected use. 32 | func (m *MockGetterClient) EXPECT() *MockGetterClientMockRecorder { 33 | return m.recorder 34 | } 35 | 36 | // Get mocks base method. 37 | func (m *MockGetterClient) Get() error { 38 | m.ctrl.T.Helper() 39 | ret := m.ctrl.Call(m, "Get") 40 | ret0, _ := ret[0].(error) 41 | return ret0 42 | } 43 | 44 | // Get indicates an expected call of Get. 45 | func (mr *MockGetterClientMockRecorder) Get() *gomock.Call { 46 | mr.mock.ctrl.T.Helper() 47 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockGetterClient)(nil).Get)) 48 | } 49 | -------------------------------------------------------------------------------- /pkg/cloud/env/env.go: -------------------------------------------------------------------------------- 1 | // Copyright Nitric Pty Ltd. 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at: 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package env 18 | 19 | import ( 20 | "path/filepath" 21 | 22 | "github.com/nitrictech/nitric/core/pkg/env" 23 | ) 24 | 25 | // Base directory used for all temporary files, such as logs, etc. 26 | var NITRIC_TMP = "./.nitric" 27 | 28 | // Base directory for temporary files used for local development, e.g. files in buckets, key/value stores, etc. 29 | var NITRIC_LOCAL_RUN_DIR = env.GetEnv("NITRIC_LOCAL_RUN_DIR", filepath.Join(NITRIC_TMP, "./run/")) 30 | 31 | // Local run temporary files sub-directories 32 | var ( 33 | LOCAL_DB_DIR = env.GetEnv("LOCAL_DB_DIR", filepath.Join(NITRIC_LOCAL_RUN_DIR.String(), "./kv/")) 34 | LOCAL_BUCKETS_DIR = env.GetEnv("LOCAL_BUCKETS_DIR", filepath.Join(NITRIC_LOCAL_RUN_DIR.String(), "./buckets/")) 35 | LOCAL_SEAWEED_LOGS_DIR = env.GetEnv("LOCAL_SEAWEED_LOGS_DIR", filepath.Join(NITRIC_LOCAL_RUN_DIR.String(), "./logs/")) 36 | LOCAL_SECRETS_DIR = env.GetEnv("LOCAL_SECRETS_DIR", filepath.Join(NITRIC_LOCAL_RUN_DIR.String(), "./secrets/")) 37 | ) 38 | 39 | var MAX_WORKERS = env.GetEnv("MAX_WORKERS", "300") 40 | -------------------------------------------------------------------------------- /pkg/cloud/errorsx/logger.go: -------------------------------------------------------------------------------- 1 | // Copyright Nitric Pty Ltd. 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at: 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | package errorsx 18 | 19 | type ServiceErrorLogger func(serviceName string, err error) 20 | -------------------------------------------------------------------------------- /pkg/collector/default-migrations.dockerfile: -------------------------------------------------------------------------------- 1 | # Default migrations dockerfile 2 | FROM migrate/migrate 3 | 4 | ENV DB_URL="" 5 | ENV NITRIC_DB_NAME="" 6 | 7 | ARG MIGRATIONS_PATH 8 | 9 | COPY ${MIGRATIONS_PATH} /migrations 10 | 11 | ENTRYPOINT ["sh", "-c", "migrate -path=/migrations -database $DB_URL up"] 12 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'eslint:recommended', 4 | 'plugin:@typescript-eslint/recommended', 5 | 'plugin:react/recommended', 6 | 'plugin:jsx-a11y/recommended', 7 | ], 8 | parser: '@typescript-eslint/parser', 9 | plugins: ['@typescript-eslint'], 10 | root: true, 11 | ignorePatterns: ['test-app', 'cypress'], 12 | settings: { 13 | react: { 14 | version: 'detect', 15 | }, 16 | }, 17 | overrides: [ 18 | { 19 | files: ['*.ts', '*.tsx'], 20 | rules: { 21 | '@typescript-eslint/no-explicit-any': 'warn', 22 | '@typescript-eslint/triple-slash-reference': 'off', 23 | '@typescript-eslint/no-empty-function': 'off', 24 | 'react/react-in-jsx-scope': 'off', 25 | 'react/prop-types': 'off', 26 | 'jsx-a11y/media-has-caption': 'off', 27 | 'jsx-a11y/heading-has-content': 'off', 28 | }, 29 | }, 30 | ], 31 | } 32 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | 4 | # generated types 5 | .astro/ 6 | 7 | # dependencies 8 | node_modules/ 9 | 10 | # logs 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | 23 | cypress/downloads 24 | cypress/screenshots 25 | cypress/videos -------------------------------------------------------------------------------- /pkg/dashboard/frontend/README.md: -------------------------------------------------------------------------------- 1 | # Nitric Local Dashboard 2 | 3 |

4 | 5 | Nitric Logo 6 | 7 |

8 | 9 |

10 | CLI for building and deploying nitric apps 11 |

12 | 13 | ## 🚀 Project Structure 14 | 15 | Inside the dashboard project, you'll see the following folders and files: 16 | 17 | ``` 18 | / 19 | ├── public/ 20 | │ └── favicon.ico 21 | ├── src/ 22 | │ ├── components/ 23 | │ ├── layouts/ 24 | | ├── lib/ 25 | │ └── pages/ 26 | │ └── index.astro 27 | └── package.json 28 | ``` 29 | 30 | Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name. 31 | 32 | There's nothing special about `src/components/`, but that's where we like to put any Astro or React components. 33 | 34 | Any static assets, like images, can be placed in the `public/` directory. 35 | 36 | ## 🧞 Commands 37 | 38 | All commands are run from the root of the project, from a terminal: 39 | 40 | | Command | Action | 41 | | :------------------ | :-------------------------------------------------------- | 42 | | `yarn install` | Installs dependencies | 43 | | `yarn dev` | Starts local dev server at `localhost:3000` | 44 | | `yarn build` | Build the production dashboard to `../pkg/dashboard/dist` | 45 | | `yarn preview` | Preview your build locally, before deploying | 46 | | `yarn cypress:open` | Open Cypress for e2e testing | 47 | | `yarn astro ...` | Run CLI commands like `astro add`, `astro check` | 48 | | `yarn astro --help` | Get help using the Astro CLI | 49 | 50 | ## Need help with Nitric? 51 | 52 | Feel free to check out the [Nitric documentation](https://nitric.io/docs) or jump on our [Discord Server](https://nitric.io/chat). 53 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'astro/config' 2 | import tailwind from '@astrojs/tailwind' 3 | 4 | import react from '@astrojs/react' 5 | 6 | // https://astro.build/config 7 | export default defineConfig({ 8 | integrations: [tailwind({ applyBaseStyles: false, nesting: true }), react()], 9 | outDir: '../dist', 10 | prefetch: { 11 | prefetchAll: true, 12 | }, 13 | }) 14 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.cjs", 8 | "css": "./src/styles/globals.css", 9 | "baseColor": "gray", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils/cn" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'cypress' 2 | 3 | export default defineConfig({ 4 | e2e: { 5 | baseUrl: 'http://localhost:49152', 6 | setupNodeEvents(on, config) { 7 | // implement node event listeners here 8 | }, 9 | chromeWebSecurity: false, 10 | }, 11 | }) 12 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/cypress/e2e/a11y.cy.ts: -------------------------------------------------------------------------------- 1 | describe('a11y test suite', () => { 2 | const pages = [ 3 | '/', 4 | '/architecture', 5 | '/databases', 6 | '/schedules', 7 | '/storage', 8 | '/secrets', 9 | '/topics', 10 | '/jobs', 11 | '/websockets', 12 | '/websites', 13 | '/logs', 14 | '/not-found', 15 | ] 16 | 17 | pages.forEach((page) => { 18 | it(`Should test page ${page} for a11y violations on desktop screen`, () => { 19 | cy.viewport('macbook-16') 20 | cy.visit(page) 21 | cy.wait(1500) 22 | cy.injectAxe() 23 | cy.checkA11y(undefined, { 24 | includedImpacts: ['critical'], 25 | rules: { 26 | 'aria-required-children': { enabled: false }, 27 | }, 28 | }) 29 | }) 30 | 31 | it(`Should test page ${page} for a11y violations on small screen`, () => { 32 | cy.viewport('ipad-mini') 33 | cy.visit(page) 34 | cy.injectAxe() 35 | cy.checkA11y(undefined, { 36 | includedImpacts: ['critical'], 37 | }) 38 | }) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/cypress/e2e/architecture.cy.ts: -------------------------------------------------------------------------------- 1 | const expectedNodes = [ 2 | 'first-api', 3 | 'second-api', 4 | 'socket', 5 | 'socket-2', 6 | 'socket-3', 7 | 'process-tests', 8 | 'process-tests-2', 9 | 'test-collection', 10 | 'connections', 11 | 'test-bucket', 12 | 'subscribe-tests', 13 | 'subscribe-tests-2', 14 | ':', 15 | 'my-db', 16 | 'my-second-db', 17 | 'services/my-test-service.ts', 18 | 'services/my-test-db.ts', 19 | 'services/my-test-secret.ts', 20 | 'my-first-secret', 21 | 'my-second-secret', 22 | 'CDN', 23 | ] 24 | 25 | describe('Architecture Spec', () => { 26 | beforeEach(() => { 27 | cy.viewport('macbook-16') 28 | cy.visit('/architecture') 29 | }) 30 | 31 | it('should retrieve correct arch nodes', () => { 32 | cy.wait(500) 33 | 34 | expectedNodes.forEach((content) => { 35 | cy.log(`Checking that node: ${content} exists`) 36 | expect(cy.contains('.react-flow__node', content)).to.exist 37 | }) 38 | }) 39 | 40 | it('should have correct routes drawer content', () => { 41 | const expected = [ 42 | [ 43 | 'edge-label-e-first-api-services/my-test-service.ts', 44 | 'DELETE/all-methodsGET/all-methodsOPTIONS/all-methodsPATCH/all-methodsPOST/all-methodsPUT/all-methodsGET/header-testPOST/json-testGET/path-test/{name}GET/query-testGET/schedule-countGET/topic-count', 45 | ], 46 | [ 47 | 'edge-label-e-second-api-services/my-test-service.ts', 48 | 'GET/content-type-binaryGET/content-type-cssGET/content-type-htmlGET/content-type-imageGET/content-type-xmlDELETE/image-from-bucketGET/image-from-bucketPUT/image-from-bucketPUT/very-nested-files', 49 | ], 50 | [ 51 | 'edge-label-e-my-secret-api-services/my-test-secret.ts', 52 | 'GET/getPOST/setPOST/set-binary', 53 | ], 54 | ['edge-label-e-my-db-api-services/my-test-db.ts', 'GET/get'], 55 | ] 56 | 57 | expected.forEach(([edge, routes]) => { 58 | cy.getTestEl(edge).click({ 59 | force: true, 60 | }) 61 | 62 | cy.getTestEl('api-routes-list').should('have.text', routes) 63 | }) 64 | }) 65 | }) 66 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/cypress/e2e/fathom.cy.ts: -------------------------------------------------------------------------------- 1 | describe('fathom test suite', () => { 2 | it(`Should include fathom script`, () => { 3 | cy.visit('/') 4 | cy.get('#fathom-script[data-site=FAKE1234]').should('exist') 5 | }) 6 | }) 7 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/cypress/e2e/schedules.cy.ts: -------------------------------------------------------------------------------- 1 | describe('Schedules Spec', () => { 2 | beforeEach(() => { 3 | cy.viewport('macbook-16') 4 | cy.visit('/schedules') 5 | }) 6 | 7 | it('should retrieve correct schedules', () => { 8 | cy.get('h2').should('contain.text', 'process-tests') 9 | 10 | const expectedSchedules = ['process-tests', 'process-tests-2'] 11 | 12 | expectedSchedules.forEach((id) => { 13 | cy.get(`[data-rct-item-id="${id}"]`).should('exist') 14 | }) 15 | }) 16 | ;['process-tests', 'process-tests-2'].forEach((schedule) => { 17 | it(`should trigger schedule ${schedule}`, () => { 18 | cy.get(`[data-rct-item-id="${schedule}"]`).click() 19 | 20 | cy.getTestEl('generated-request-path').should( 21 | 'have.text', 22 | `http://localhost:4002/schedules/${schedule}`, 23 | ) 24 | 25 | cy.getTestEl('trigger-schedules-btn').click() 26 | 27 | cy.getAPIResponseCodeEditor().should( 28 | 'have.text', 29 | 'Successfully triggered schedule', 30 | ) 31 | }) 32 | }) 33 | 34 | it(`should add to doc count after schedule triggers`, () => { 35 | cy.visit('/') 36 | 37 | cy.get('[data-rct-item-id="first-api-/schedule-count-GET"]').click() 38 | 39 | cy.getTestEl('send-api-btn').click() 40 | 41 | cy.getAPIResponseCodeEditor() 42 | .invoke('text') 43 | .then((text) => { 44 | expect(JSON.parse(text)).to.deep.equal({ 45 | firstCount: 1, 46 | secondCount: 1, 47 | }) 48 | }) 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/cypress/e2e/topics.cy.ts: -------------------------------------------------------------------------------- 1 | describe('Topics Spec', () => { 2 | beforeEach(() => { 3 | cy.viewport('macbook-16') 4 | cy.visit('/topics') 5 | }) 6 | 7 | it('should retrieve correct topics', () => { 8 | cy.get('h2').should('contain.text', 'subscribe-tests') 9 | 10 | const expectedTopics = ['subscribe-tests', 'subscribe-tests-2'] 11 | 12 | expectedTopics.forEach((id) => { 13 | cy.get(`[data-rct-item-id="${id}"]`).should('exist') 14 | }) 15 | }) 16 | ;['subscribe-tests', 'subscribe-tests-2'].forEach((topic) => { 17 | it(`should trigger topic ${topic}`, () => { 18 | cy.get(`[data-rct-item-id="${topic}"]`).click() 19 | 20 | cy.getTestEl('generated-request-path').should( 21 | 'have.text', 22 | `http://localhost:4002/topics/${topic}`, 23 | ) 24 | 25 | cy.getTestEl('trigger-topics-btn').click() 26 | 27 | cy.getAPIResponseCodeEditor().should( 28 | 'have.text', 29 | 'Successfully delivered message to topic', 30 | ) 31 | }) 32 | }) 33 | 34 | it(`should add to doc count after topic triggers`, () => { 35 | cy.visit('/') 36 | 37 | cy.get('[data-rct-item-id="first-api-/topic-count-GET"]').click() 38 | 39 | cy.getTestEl('send-api-btn').click() 40 | 41 | cy.getAPIResponseCodeEditor() 42 | .invoke('text') 43 | .then((text) => { 44 | expect(JSON.parse(text)).to.deep.equal({ 45 | firstCount: 1, 46 | secondCount: 1, 47 | }) 48 | }) 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/cypress/e2e/websites.cy.ts: -------------------------------------------------------------------------------- 1 | describe('Websites Spec', () => { 2 | beforeEach(() => { 3 | cy.viewport('macbook-16') 4 | }) 5 | 6 | const expectedWebsites = ['vite-website', 'docs-website'] 7 | 8 | it('should retrieve correct websites in list', () => { 9 | cy.visit('/websites') 10 | cy.get('h2').should('contain.text', 'docs-website') 11 | 12 | expectedWebsites.forEach((id) => { 13 | cy.get(`[data-rct-item-id="${id}"]`).should('exist') 14 | }) 15 | }) 16 | 17 | expectedWebsites.forEach((id) => { 18 | it(`should render website ${id}`, () => { 19 | cy.visit('/websites') 20 | cy.get(`[data-rct-item-id="${id}"]`).click() 21 | cy.get('h2').should('contain.text', id) 22 | 23 | let originMap = {} 24 | 25 | if (Cypress.env('NITRIC_TEST_TYPE') === 'run') { 26 | originMap = { 27 | 'vite-website': 'http://localhost:5000', 28 | 'docs-website': 'http://localhost:5000', 29 | } 30 | } else { 31 | originMap = { 32 | 'vite-website': 'http://localhost:5000', 33 | 'docs-website': 'http://localhost:5001', 34 | } 35 | } 36 | 37 | const pathMap = { 38 | 'vite-website': '/', 39 | 'docs-website': '/docs', 40 | } 41 | 42 | // check iframe url 43 | cy.get('iframe').should('have.attr', 'src', originMap[id] + pathMap[id]) 44 | 45 | cy.visit(originMap[id] + pathMap[id]) 46 | 47 | const titleMap = { 48 | 'vite-website': 'Hello Nitric!', 49 | 'docs-website': 'Hello Nitric Docs Test!', 50 | } 51 | 52 | const title = titleMap[id] 53 | 54 | cy.origin(originMap[id], { args: { title } }, ({ title }) => { 55 | cy.get('h1').should('have.text', title) 56 | }) 57 | }) 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/cypress/fixtures/photo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitrictech/cli/5ac1f33997b15644d18e52a4d361d3d03ee73eb2/pkg/dashboard/frontend/cypress/fixtures/photo.jpg -------------------------------------------------------------------------------- /pkg/dashboard/frontend/cypress/support/e2e.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/e2e.ts 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-axe' 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/cypress/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["esnext", "dom"], 5 | "types": ["cypress", "cypress-axe"], 6 | "resolveJsonModule": true, 7 | "isolatedModules": false, 8 | }, 9 | "include": ["**/*.ts"], 10 | } 11 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/prettier.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | singleQuote: true, 3 | semi: false, 4 | plugins: ['prettier-plugin-tailwindcss'], 5 | } 6 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nitrictech/cli/5ac1f33997b15644d18e52a4d361d3d03ee73eb2/pkg/dashboard/frontend/public/favicon.ico -------------------------------------------------------------------------------- /pkg/dashboard/frontend/public/nitric-no-text.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/apis/APIMethodBadge.tsx: -------------------------------------------------------------------------------- 1 | import type { FC } from 'react' 2 | import { Badge } from '../shared' 3 | import type { Method } from '../../types' 4 | 5 | interface Props { 6 | method: Method 7 | className?: string 8 | } 9 | 10 | export const APIMethodBadge: FC = ({ method, className }) => { 11 | return ( 12 | 26 | {method} 27 | 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/apis/APIRoutesList.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { APIMethodBadge } from './APIMethodBadge' 3 | import type { Endpoint } from '@/types' 4 | 5 | interface APIRoutesListProps { 6 | endpoints: Endpoint[] 7 | apiAddress: string 8 | } 9 | 10 | const APIRoutesList: React.FC = ({ 11 | endpoints, 12 | apiAddress, 13 | }) => { 14 | return ( 15 |
16 | {endpoints.map((endpoint) => ( 17 |
18 |
19 | 20 |
21 | 31 |
32 | ))} 33 |
34 | ) 35 | } 36 | 37 | export default APIRoutesList 38 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/apis/index.ts: -------------------------------------------------------------------------------- 1 | import APIExplorer from './APIExplorer' 2 | 3 | export default APIExplorer 4 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/architecture/index.ts: -------------------------------------------------------------------------------- 1 | import Architecture from './Architecture' 2 | 3 | export default Architecture 4 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/architecture/nodes/APINode.tsx: -------------------------------------------------------------------------------- 1 | import { type ComponentType } from 'react' 2 | 3 | import type { Api, Endpoint } from '@/types' 4 | import type { NodeProps } from 'reactflow' 5 | import NodeBase, { type NodeBaseData } from './NodeBase' 6 | import APIRoutesList from '@/components/apis/APIRoutesList' 7 | 8 | export interface ApiNodeData extends NodeBaseData { 9 | endpoints: Endpoint[] 10 | } 11 | 12 | export const APINode: ComponentType> = (props) => { 13 | const { data } = props 14 | 15 | return ( 16 | 28 | Routes: 29 | 33 | 34 | ) : null, 35 | }} 36 | /> 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/architecture/nodes/BatchNode.tsx: -------------------------------------------------------------------------------- 1 | import { type ComponentType } from 'react' 2 | 3 | import type { Edge, NodeProps } from 'reactflow' 4 | import NodeBase, { type NodeBaseData } from './NodeBase' 5 | import { Button } from '@/components/ui/button' 6 | 7 | type BatchData = { 8 | filePath: string 9 | } 10 | 11 | export interface BatchNodeData extends NodeBaseData { 12 | connectedEdges: Edge[] 13 | } 14 | 15 | export const BatchNode: ComponentType> = (props) => { 16 | const { data } = props 17 | 18 | const Icon = data.icon 19 | 20 | return ( 21 | 30 | 31 | 32 | Open in VSCode 33 | 34 | 35 | ), 36 | }} 37 | /> 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/architecture/nodes/BucketNode.tsx: -------------------------------------------------------------------------------- 1 | import { type ComponentType } from 'react' 2 | 3 | import type { Bucket } from '@/types' 4 | import type { NodeProps } from 'reactflow' 5 | import NodeBase, { type NodeBaseData } from './NodeBase' 6 | 7 | export type BucketNodeData = NodeBaseData 8 | 9 | export const BucketNode: ComponentType> = (props) => { 10 | const { data } = props 11 | 12 | return ( 13 | 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/architecture/nodes/HttpProxyNode.tsx: -------------------------------------------------------------------------------- 1 | import { type ComponentType } from 'react' 2 | 3 | import type { HttpProxy } from '@/types' 4 | import type { NodeProps } from 'reactflow' 5 | import NodeBase, { type NodeBaseData } from './NodeBase' 6 | 7 | export type HttpProxyNodeData = NodeBaseData 8 | 9 | export const HttpProxyNode: ComponentType> = ( 10 | props, 11 | ) => { 12 | const { data } = props 13 | 14 | return ( 15 | 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/architecture/nodes/JobNode.tsx: -------------------------------------------------------------------------------- 1 | import { type ComponentType } from 'react' 2 | 3 | import type { BatchJob } from '@/types' 4 | import type { NodeProps } from 'reactflow' 5 | import NodeBase, { type NodeBaseData } from './NodeBase' 6 | 7 | export type JobNodeData = NodeBaseData 8 | 9 | export const JobNode: ComponentType> = (props) => { 10 | const { data } = props 11 | 12 | return ( 13 | 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/architecture/nodes/KeyValueNode.tsx: -------------------------------------------------------------------------------- 1 | import { type ComponentType } from 'react' 2 | 3 | import type { KeyValue } from '@/types' 4 | import type { NodeProps } from 'reactflow' 5 | import NodeBase, { type NodeBaseData } from './NodeBase' 6 | 7 | export type KeyValueNodeData = NodeBaseData 8 | 9 | export const KeyValueNode: ComponentType> = ( 10 | props, 11 | ) => { 12 | const { data } = props 13 | 14 | return ( 15 | 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/architecture/nodes/QueueNode.tsx: -------------------------------------------------------------------------------- 1 | import { type ComponentType } from 'react' 2 | 3 | import type { Queue } from '@/types' 4 | import type { NodeProps } from 'reactflow' 5 | import NodeBase, { type NodeBaseData } from './NodeBase' 6 | 7 | export type QueueNodeData = NodeBaseData 8 | 9 | export const QueueNode: ComponentType> = (props) => { 10 | const { data } = props 11 | 12 | return ( 13 | 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/architecture/nodes/ScheduleNode.tsx: -------------------------------------------------------------------------------- 1 | import { type ComponentType } from 'react' 2 | import cronstrue from 'cronstrue' 3 | import type { Schedule } from '@/types' 4 | import type { NodeProps } from 'reactflow' 5 | import NodeBase, { type NodeBaseData } from './NodeBase' 6 | 7 | export type ScheduleNodeData = NodeBaseData 8 | 9 | export const ScheduleNode: ComponentType> = ( 10 | props, 11 | ) => { 12 | const { data } = props 13 | 14 | return ( 15 | 27 | {data.resource.expression ? ( 28 | <> 29 |
30 | Cron: 31 | {data.resource.expression} 32 |
33 |
34 | Description: 35 | 36 | {cronstrue.toString(data.resource.expression, { 37 | verbose: true, 38 | })} 39 | 40 |
41 | 42 | ) : ( 43 |
44 | Rate: 45 | Every {data.resource.rate} 46 |
47 | )} 48 | 49 | ), 50 | }} 51 | /> 52 | ) 53 | } 54 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/architecture/nodes/SecretNode.tsx: -------------------------------------------------------------------------------- 1 | import { type ComponentType } from 'react' 2 | 3 | import type { Secret } from '@/types' 4 | import type { NodeProps } from 'reactflow' 5 | import NodeBase, { type NodeBaseData } from './NodeBase' 6 | 7 | export type SecretNodeData = NodeBaseData 8 | 9 | export const SecretNode: ComponentType> = (props) => { 10 | const { data } = props 11 | 12 | return ( 13 | 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/architecture/nodes/ServiceNode.tsx: -------------------------------------------------------------------------------- 1 | import { type ComponentType } from 'react' 2 | 3 | import type { Edge, NodeProps } from 'reactflow' 4 | import NodeBase, { type NodeBaseData } from './NodeBase' 5 | import { Button } from '@/components/ui/button' 6 | 7 | type ServiceData = { 8 | filePath: string 9 | } 10 | 11 | export interface ServiceNodeData extends NodeBaseData { 12 | connectedEdges: Edge[] 13 | } 14 | 15 | export const ServiceNode: ComponentType> = ( 16 | props, 17 | ) => { 18 | const { data } = props 19 | 20 | const Icon = data.icon 21 | 22 | return ( 23 | 32 | 33 | 34 | Open in VSCode 35 | 36 | 37 | ), 38 | }} 39 | /> 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/architecture/nodes/TopicNode.tsx: -------------------------------------------------------------------------------- 1 | import { type ComponentType } from 'react' 2 | 3 | import type { Topic } from '@/types' 4 | import type { NodeProps } from 'reactflow' 5 | import NodeBase, { type NodeBaseData } from './NodeBase' 6 | 7 | export type TopicNodeData = NodeBaseData 8 | 9 | export const TopicNode: ComponentType> = (props) => { 10 | const { data } = props 11 | //http://localhost:4001/topics/updates 12 | return ( 13 | 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/architecture/nodes/WebsitesNode.tsx: -------------------------------------------------------------------------------- 1 | import { type ComponentType } from 'react' 2 | 3 | import type { Website } from '@/types' 4 | import type { NodeProps } from 'reactflow' 5 | import NodeBase, { type NodeBaseData } from './NodeBase' 6 | import SitesList from '@/components/websites/SitesList' 7 | 8 | export type WebsitesNodeData = NodeBaseData 9 | 10 | export const WebsitesNode: ComponentType> = ( 11 | props, 12 | ) => { 13 | const { data } = props 14 | 15 | const websites = data.resource 16 | 17 | const rootWebsite = websites.find((website) => 18 | /localhost:\d+$/.test(website.url.replace(/\/$/, '')), 19 | ) 20 | 21 | const description = `${websites.length === 1 ? 'website' : 'websites'} stored in a bucket and served via CDN.` 22 | 23 | return ( 24 | site !== rootWebsite)} 37 | /> 38 | ) : null, 39 | }} 40 | /> 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/architecture/nodes/WebsocketNode.tsx: -------------------------------------------------------------------------------- 1 | import { type ComponentType } from 'react' 2 | 3 | import type { WebSocket } from '@/types' 4 | import type { NodeProps } from 'reactflow' 5 | import NodeBase, { type NodeBaseData } from './NodeBase' 6 | 7 | export type WebsocketNodeData = NodeBaseData 8 | 9 | export const WebsocketNode: ComponentType> = ( 10 | props, 11 | ) => { 12 | const { data } = props 13 | 14 | return ( 15 | 27 | {data.resource.targets ? ( 28 | <> 29 |
30 | Events: 31 | {Object.keys(data.resource.targets).join(', ')} 32 |
33 | 34 | ) : null} 35 | 36 | ), 37 | }} 38 | /> 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/databases/DatabasesMenu.tsx: -------------------------------------------------------------------------------- 1 | import { TrashIcon } from '@heroicons/react/20/solid' 2 | 3 | import { useHistory } from '../../lib/hooks/use-history' 4 | import ResourceDropdownMenu from '../shared/ResourceDropdownMenu' 5 | import { 6 | DropdownMenuGroup, 7 | DropdownMenuItem, 8 | DropdownMenuLabel, 9 | DropdownMenuSeparator, 10 | } from '../ui/dropdown-menu' 11 | 12 | interface Props { 13 | storageKey: string 14 | selected: string 15 | onAfterClear: () => void 16 | } 17 | 18 | const DatabasesMenu: React.FC = ({ 19 | storageKey, 20 | selected, 21 | onAfterClear, 22 | }) => { 23 | const clearHistory = async () => { 24 | localStorage.removeItem(storageKey) 25 | 26 | onAfterClear() 27 | } 28 | 29 | return ( 30 | 31 | 32 | Database Menu 33 | 34 | 35 | 36 | 37 | 38 | Clear Saved Query 39 | 40 | 41 | 42 | ) 43 | } 44 | 45 | export default DatabasesMenu 46 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/events/EventsHistory.tsx: -------------------------------------------------------------------------------- 1 | import type { 2 | EventHistoryItem, 3 | EventResource, 4 | TopicHistoryItem, 5 | } from '../../types' 6 | import { formatJSON } from '@/lib/utils' 7 | import CodeEditor from '../apis/CodeEditor' 8 | import HistoryAccordion from '../shared/HistoryAccordion' 9 | 10 | interface Props { 11 | history: EventHistoryItem[] 12 | selectedWorker: EventResource 13 | workerType: 'schedules' | 'topics' | 'jobs' 14 | } 15 | 16 | const EventsHistory: React.FC = ({ 17 | selectedWorker, 18 | workerType, 19 | history, 20 | }) => { 21 | const requestHistory = history 22 | .sort((a, b) => b.time - a.time) 23 | .filter((h) => h.event) 24 | .filter((h) => h.event.name === selectedWorker.name) 25 | 26 | if (!requestHistory.length) { 27 | return

There is no history.

28 | } 29 | 30 | return ( 31 |
32 | { 34 | let payload = '' 35 | 36 | if (workerType === 'topics' || workerType === 'jobs') { 37 | payload = (h.event as TopicHistoryItem['event']).payload 38 | } 39 | 40 | const formattedPayload = payload ? formatJSON(payload) : '' 41 | 42 | return { 43 | label: h.event.name, 44 | time: h.time, 45 | success: Boolean(h.event.success), 46 | content: formattedPayload ? ( 47 |
48 |
49 |

Payload

50 | 56 |
57 |
58 | ) : undefined, 59 | } 60 | })} 61 | /> 62 |
63 | ) 64 | } 65 | 66 | export default EventsHistory 67 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/events/EventsMenu.tsx: -------------------------------------------------------------------------------- 1 | import { TrashIcon } from '@heroicons/react/20/solid' 2 | 3 | import { useHistory } from '../../lib/hooks/use-history' 4 | import ResourceDropdownMenu from '../shared/ResourceDropdownMenu' 5 | import { 6 | DropdownMenuGroup, 7 | DropdownMenuItem, 8 | DropdownMenuLabel, 9 | DropdownMenuSeparator, 10 | } from '../ui/dropdown-menu' 11 | 12 | interface Props { 13 | storageKey: string 14 | workerType: string 15 | selected: string 16 | onAfterClear: () => void 17 | } 18 | 19 | const EventsMenu: React.FC = ({ 20 | workerType, 21 | storageKey, 22 | selected, 23 | onAfterClear, 24 | }) => { 25 | const { deleteHistory } = useHistory(workerType) 26 | 27 | const clearHistory = async () => { 28 | const prefix = `${storageKey}-${selected}-` 29 | 30 | for (let i = 0; i < localStorage.length; i++) { 31 | const key = localStorage.key(i) 32 | if (key?.startsWith(prefix)) { 33 | localStorage.removeItem(key) 34 | } 35 | } 36 | 37 | localStorage.removeItem(`${storageKey}-requests`) 38 | 39 | await deleteHistory() 40 | 41 | onAfterClear() 42 | } 43 | 44 | return ( 45 | 46 | 47 | {workerType} Menu 48 | 49 | 50 | 51 | 52 | 53 | Clear History 54 | 55 | 56 | 57 | ) 58 | } 59 | 60 | export default EventsMenu 61 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/layout/AppLayout/NavigationItem.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/lib/utils' 2 | import React from 'react' 3 | 4 | export interface NavigationItemProps { 5 | name: string 6 | href: string 7 | icon: React.ForwardRefExoticComponent< 8 | Omit, 'ref'> & { 9 | title?: string 10 | titleId?: string 11 | } & React.RefAttributes 12 | > 13 | onClick?: () => void 14 | routePath: string 15 | } 16 | 17 | const NavigationItem: React.FC = ({ 18 | name, 19 | href, 20 | icon: Icon, 21 | onClick, 22 | routePath, 23 | }) => { 24 | const isActive = href === routePath 25 | 26 | return ( 27 |
  • 28 | 41 | 53 |
  • 54 | ) 55 | } 56 | 57 | export default NavigationItem 58 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/layout/AppLayout/index.ts: -------------------------------------------------------------------------------- 1 | import AppLayout from './AppLayout' 2 | 3 | export default AppLayout 4 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/layout/BreadCrumbs.tsx: -------------------------------------------------------------------------------- 1 | import { type PropsWithChildren, Children } from 'react' 2 | import { Separator } from '../ui/separator' 3 | import { cn } from '@/lib/utils' 4 | 5 | interface Props extends PropsWithChildren { 6 | className?: string 7 | } 8 | 9 | const BreadCrumbs = ({ children, className }: Props) => { 10 | const childArray = Children.toArray(children) 11 | 12 | return ( 13 | 32 | ) 33 | } 34 | 35 | export default BreadCrumbs 36 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/logs/FilterTrigger.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useSidebar } from '../ui/sidebar' 3 | import { Button } from '../ui/button' 4 | import { FunnelIcon } from '@heroicons/react/24/outline' 5 | 6 | const FilterTrigger: React.FC = () => { 7 | const { toggleSidebar } = useSidebar() 8 | 9 | return ( 10 | 19 | ) 20 | } 21 | 22 | export default FilterTrigger 23 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/secrets/SecretVersionsTable/index.ts: -------------------------------------------------------------------------------- 1 | import { SecretVersionsTable } from './SecretVersionsTable' 2 | 3 | export default SecretVersionsTable 4 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/secrets/SecretsContext.tsx: -------------------------------------------------------------------------------- 1 | import { useSecret } from '@/lib/hooks/use-secret' 2 | import type { Secret, SecretVersion } from '@/types' 3 | import React, { createContext, useState, type PropsWithChildren } from 'react' 4 | import { VersionActionDialog } from './VersionActionDialog' 5 | 6 | interface SecretsContextProps { 7 | selectedVersions: SecretVersion[] 8 | setSelectedVersions: React.Dispatch> 9 | selectedSecret?: Secret 10 | setSelectedSecret: (secret: Secret | undefined) => void 11 | setDialogAction: React.Dispatch> 12 | setDialogOpen: React.Dispatch> 13 | } 14 | 15 | export const SecretsContext = createContext({ 16 | selectedVersions: [], 17 | setSelectedVersions: () => {}, 18 | selectedSecret: undefined, 19 | setSelectedSecret: () => {}, 20 | setDialogAction: () => {}, 21 | setDialogOpen: () => {}, 22 | }) 23 | 24 | export const SecretsProvider: React.FC = ({ children }) => { 25 | const [selectedSecret, setSelectedSecret] = useState() 26 | 27 | const [selectedVersions, setSelectedVersions] = useState([]) 28 | const [dialogOpen, setDialogOpen] = useState(false) 29 | const [dialogAction, setDialogAction] = useState<'add' | 'delete'>('add') 30 | 31 | return ( 32 | 42 | {selectedSecret && ( 43 | 48 | )} 49 | {children} 50 | 51 | ) 52 | } 53 | 54 | export const useSecretsContext = () => { 55 | return React.useContext(SecretsContext) 56 | } 57 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/secrets/SecretsTreeView.tsx: -------------------------------------------------------------------------------- 1 | import { type FC, useMemo } from 'react' 2 | import type { Secret } from '@/types' 3 | import TreeView, { type TreeItemType } from '../shared/TreeView' 4 | import type { TreeItem, TreeItemIndex } from 'react-complex-tree' 5 | 6 | export type SecretsTreeItemType = TreeItemType 7 | 8 | interface Props { 9 | resources: Secret[] 10 | onSelect: (resource: Secret) => void 11 | initialItem: Secret 12 | } 13 | 14 | const SecretsTreeView: FC = ({ resources, onSelect, initialItem }) => { 15 | const treeItems: Record< 16 | TreeItemIndex, 17 | TreeItem 18 | > = useMemo(() => { 19 | const rootItem: TreeItem = { 20 | index: 'root', 21 | isFolder: true, 22 | children: [], 23 | data: null, 24 | } 25 | 26 | const rootItems: Record> = { 27 | root: rootItem, 28 | } 29 | 30 | for (const resource of resources) { 31 | // add api if not added already 32 | if (!rootItems[resource.name]) { 33 | rootItems[resource.name] = { 34 | index: resource.name, 35 | data: { 36 | label: resource.name, 37 | data: resource, 38 | }, 39 | } 40 | 41 | rootItem.children!.push(resource.name) 42 | } 43 | } 44 | 45 | return rootItems 46 | }, [resources]) 47 | 48 | return ( 49 | 50 | label={'Secrets'} 51 | items={treeItems} 52 | initialItem={initialItem.name} 53 | getItemTitle={(item) => item.data.label} 54 | onPrimaryAction={(items) => { 55 | if (items.data.data) { 56 | onSelect(items.data.data) 57 | } 58 | }} 59 | renderItemTitle={({ item }) => { 60 | return {item.data.label} 61 | }} 62 | /> 63 | ) 64 | } 65 | 66 | export default SecretsTreeView 67 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/shared/Badge.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/lib/utils' 2 | import type { PropsWithChildren } from 'react' 3 | 4 | interface Props extends PropsWithChildren { 5 | status: 'red' | 'green' | 'yellow' | 'orange' | 'blue' | 'default' 6 | className?: string 7 | } 8 | 9 | const Badge: React.FC = ({ 10 | status = 'default', 11 | className, 12 | children, 13 | ...rest 14 | }) => { 15 | return ( 16 | 29 | {children} 30 | 31 | ) 32 | } 33 | 34 | export default Badge 35 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/shared/Loading/Loading.tsx: -------------------------------------------------------------------------------- 1 | import Lottie from 'lottie-react' 2 | import loadingAnim from './loading.animation.json' 3 | import { PropsWithChildren, Suspense, useEffect, useRef, useState } from 'react' 4 | import { cn } from '@/lib/utils' 5 | 6 | interface Props extends PropsWithChildren { 7 | delay: number 8 | conditionToShow: boolean 9 | className?: string 10 | } 11 | 12 | const Loading: React.FC = ({ 13 | delay, 14 | conditionToShow, 15 | className, 16 | children, 17 | }) => { 18 | const timeoutRef = useRef() 19 | const [showLoader, setShowLoader] = useState(true) 20 | 21 | useEffect(() => { 22 | if (delay) { 23 | if (timeoutRef.current) { 24 | clearTimeout(timeoutRef.current) 25 | } 26 | 27 | timeoutRef.current = setTimeout(() => { 28 | setShowLoader(false) 29 | }, delay) 30 | } 31 | 32 | return () => clearTimeout(timeoutRef.current) 33 | }, [delay]) 34 | 35 | const Loader = ( 36 |
    40 |
    41 | 47 |
    48 | Loading... 49 |
    50 | ) 51 | 52 | return showLoader || !conditionToShow ? ( 53 | Loader 54 | ) : ( 55 | {children} 56 | ) 57 | } 58 | 59 | export default Loading 60 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/shared/Loading/index.tsx: -------------------------------------------------------------------------------- 1 | import Loading from './Loading' 2 | 3 | export default Loading 4 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/shared/NotFoundAlert.tsx: -------------------------------------------------------------------------------- 1 | import React, { type PropsWithChildren } from 'react' 2 | import { Alert } from '../ui/alert' 3 | import { ExclamationTriangleIcon } from '@heroicons/react/24/outline' 4 | 5 | interface Props extends PropsWithChildren { 6 | className?: string 7 | } 8 | 9 | const NotFoundAlert: React.FC = ({ children, className }) => { 10 | return ( 11 | 12 |
    13 |
    14 |
    16 |
    17 |

    {children}

    18 |
    19 |
    20 |
    21 | ) 22 | } 23 | 24 | export default NotFoundAlert 25 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/shared/ResourceDropdownMenu.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | DropdownMenu, 3 | DropdownMenuContent, 4 | DropdownMenuTrigger, 5 | } from '../ui/dropdown-menu' 6 | import { EllipsisHorizontalIcon } from '@heroicons/react/20/solid' 7 | 8 | import { Button } from '../ui/button' 9 | import type { PropsWithChildren } from 'react' 10 | 11 | const ResourceDropdownMenu = ({ children }: PropsWithChildren) => { 12 | return ( 13 | 14 | 15 | 19 | 20 | {children} 21 | 22 | ) 23 | } 24 | 25 | export default ResourceDropdownMenu 26 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/shared/SectionCard.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/lib/utils' 2 | import { 3 | Card, 4 | CardContent, 5 | CardDescription, 6 | CardFooter, 7 | CardHeader, 8 | CardTitle, 9 | } from '../ui/card' 10 | 11 | interface SectionCardProps { 12 | title?: string 13 | description?: string 14 | children: React.ReactNode 15 | className?: string 16 | innerClassName?: string 17 | headerClassName?: string 18 | headerSiblings?: React.ReactNode 19 | footer?: React.ReactNode 20 | } 21 | 22 | const SectionCard = ({ 23 | title, 24 | description, 25 | children, 26 | className, 27 | innerClassName, 28 | headerClassName, 29 | headerSiblings, 30 | footer, 31 | }: SectionCardProps) => { 32 | return ( 33 | 34 | {title && ( 35 | 36 |
    37 | 38 | {title} 39 | 40 | {headerSiblings} 41 |
    42 | {description && {description}} 43 |
    44 | )} 45 | 46 | {children} 47 | 48 | {footer && {footer}} 49 |
    50 | ) 51 | } 52 | 53 | export default SectionCard 54 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/shared/TextField.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/lib/utils' 2 | import type { 3 | ForwardRefExoticComponent, 4 | InputHTMLAttributes, 5 | SVGProps, 6 | } from 'react' 7 | import { Input } from '../ui/input' 8 | import { Label } from '../ui/label' 9 | 10 | interface Props extends InputHTMLAttributes { 11 | label: string 12 | hideLabel?: boolean 13 | id: string 14 | icon?: ForwardRefExoticComponent> 15 | } 16 | 17 | const TextField = ({ 18 | label, 19 | hideLabel, 20 | id, 21 | icon: Icon, 22 | ...inputProps 23 | }: Props) => { 24 | return ( 25 |
    26 | 29 |
    32 | {Icon && ( 33 |
    34 |
    36 | )} 37 | 42 |
    43 |
    44 | ) 45 | } 46 | 47 | export default TextField 48 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/shared/index.ts: -------------------------------------------------------------------------------- 1 | import Loading from './Loading' 2 | import Badge from './Badge' 3 | import FieldRows, { type FieldRow } from './FieldRows' 4 | import Spinner from './Spinner' 5 | import Tabs from './Tabs' 6 | 7 | export { Loading, Badge, FieldRows, Spinner, Tabs } 8 | export type { FieldRow } 9 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/storage/FileUpload.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/lib/utils' 2 | import { ArrowUpOnSquareIcon } from '@heroicons/react/24/outline' 3 | import { type DropzoneOptions, useDropzone } from 'react-dropzone' 4 | import type { FC } from 'react' 5 | 6 | type Props = DropzoneOptions 7 | 8 | const FileUpload: FC = ({ multiple, ...rest }) => { 9 | const { getRootProps, getInputProps, isDragActive } = useDropzone({ 10 | multiple, 11 | ...rest, 12 | }) 13 | 14 | return ( 15 |
    22 | 23 | 24 |

    25 | {isDragActive 26 | ? `Drop the ${multiple ? 'files' : 'file'} here ...` 27 | : `Drag or click to add ${multiple ? 'files' : 'file'}.`} 28 |

    29 |
    30 | ) 31 | } 32 | 33 | export default FileUpload 34 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/storage/download-files.ts: -------------------------------------------------------------------------------- 1 | export const downloadFiles = async ( 2 | files: Array<{ url: string; name: string }>, 3 | ): Promise => { 4 | const promises = files.map(async (file) => { 5 | const response = await fetch(file.url) 6 | const blob = await response.blob() 7 | const link = document.createElement('a') 8 | link.href = window.URL.createObjectURL(blob) 9 | link.setAttribute('download', file.name) 10 | document.body.appendChild(link) 11 | link.click() 12 | document.body.removeChild(link) 13 | }) 14 | 15 | await Promise.all(promises) 16 | } 17 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/storage/file-browser-styles.css: -------------------------------------------------------------------------------- 1 | .file-explorer .chonky-activeButton, 2 | .MuiPopover-root .chonky-activeButton, 3 | .chonky-selectionSizeText { 4 | @apply !text-primary; 5 | } 6 | 7 | [data-test-id='file-entry'] > *:first-child { 8 | @apply !bg-blue-100; 9 | } 10 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/storage/index.ts: -------------------------------------------------------------------------------- 1 | import StorageExplorer from './StorageExplorer' 2 | 3 | export default StorageExplorer 4 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/ui/alert.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { cva, type VariantProps } from 'class-variance-authority' 3 | 4 | import { cn } from '@/lib/utils/cn' 5 | 6 | const alertVariants = cva( 7 | 'relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground', 8 | { 9 | variants: { 10 | variant: { 11 | default: 'bg-background text-foreground', 12 | destructive: 13 | 'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive', 14 | warning: 15 | 'border-yellow-50 bg-yellow-50 text-yellow-700 [&>svg]:text-yellow-400', 16 | info: 'border-blue-50 bg-blue-50 text-blue-700 [&>svg]:text-blue-400', 17 | }, 18 | }, 19 | defaultVariants: { 20 | variant: 'default', 21 | }, 22 | }, 23 | ) 24 | 25 | const Alert = React.forwardRef< 26 | HTMLDivElement, 27 | React.HTMLAttributes & VariantProps 28 | >(({ className, variant, ...props }, ref) => ( 29 |
    35 | )) 36 | Alert.displayName = 'Alert' 37 | 38 | const AlertTitle = React.forwardRef< 39 | HTMLParagraphElement, 40 | React.HTMLAttributes 41 | >(({ className, ...props }, ref) => ( 42 |
    47 | )) 48 | AlertTitle.displayName = 'AlertTitle' 49 | 50 | const AlertDescription = React.forwardRef< 51 | HTMLParagraphElement, 52 | React.HTMLAttributes 53 | >(({ className, ...props }, ref) => ( 54 |
    59 | )) 60 | AlertDescription.displayName = 'AlertDescription' 61 | 62 | export { Alert, AlertTitle, AlertDescription } 63 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { cva, type VariantProps } from 'class-variance-authority' 3 | 4 | import { cn } from '@/lib/utils/cn' 5 | 6 | const badgeVariants = cva( 7 | 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', 8 | { 9 | variants: { 10 | variant: { 11 | default: 12 | 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80', 13 | secondary: 14 | 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80', 15 | success: 16 | 'border-transparent bg-green-500 text-white hover:bg-green-500/80', 17 | destructive: 18 | 'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80', 19 | outline: 'text-foreground', 20 | }, 21 | }, 22 | defaultVariants: { 23 | variant: 'default', 24 | }, 25 | }, 26 | ) 27 | 28 | export interface BadgeProps 29 | extends React.HTMLAttributes, 30 | VariantProps {} 31 | 32 | function Badge({ className, variant, ...props }: BadgeProps) { 33 | return ( 34 |
    35 | ) 36 | } 37 | 38 | export { Badge, badgeVariants } 39 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { Slot } from '@radix-ui/react-slot' 3 | import { cva, type VariantProps } from 'class-variance-authority' 4 | 5 | import { cn } from '@/lib/utils/cn' 6 | 7 | const buttonVariants = cva( 8 | 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', 9 | { 10 | variants: { 11 | variant: { 12 | default: 'bg-primary text-primary-foreground hover:bg-primary/90', 13 | destructive: 14 | 'bg-destructive text-destructive-foreground hover:bg-destructive/90', 15 | outline: 16 | 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', 17 | secondary: 18 | 'bg-secondary text-secondary-foreground hover:bg-secondary/80', 19 | ghost: 'hover:bg-accent hover:text-accent-foreground', 20 | link: 'text-primary underline-offset-4 hover:underline', 21 | }, 22 | size: { 23 | default: 'h-10 px-4 py-2', 24 | sm: 'h-9 rounded-md px-3', 25 | lg: 'h-11 rounded-md px-8 text-lg', 26 | icon: 'h-10 w-10', 27 | }, 28 | }, 29 | defaultVariants: { 30 | variant: 'default', 31 | size: 'default', 32 | }, 33 | }, 34 | ) 35 | 36 | export interface ButtonProps 37 | extends React.ButtonHTMLAttributes, 38 | VariantProps { 39 | asChild?: boolean 40 | } 41 | 42 | const Button = React.forwardRef( 43 | ({ className, variant, size, asChild = false, ...props }, ref) => { 44 | const Comp = asChild ? Slot : 'button' 45 | return ( 46 | 51 | ) 52 | }, 53 | ) 54 | Button.displayName = 'Button' 55 | 56 | export { Button, buttonVariants } 57 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/ui/checkbox.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as CheckboxPrimitive from '@radix-ui/react-checkbox' 3 | import { Check } from 'lucide-react' 4 | 5 | import { cn } from '@/lib/utils/cn' 6 | 7 | const Checkbox = React.forwardRef< 8 | React.ElementRef, 9 | React.ComponentPropsWithoutRef 10 | >(({ className, ...props }, ref) => ( 11 | 19 | 22 | 23 | 24 | 25 | )) 26 | Checkbox.displayName = CheckboxPrimitive.Root.displayName 27 | 28 | export { Checkbox } 29 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/ui/collapsible.tsx: -------------------------------------------------------------------------------- 1 | import * as CollapsiblePrimitive from '@radix-ui/react-collapsible' 2 | 3 | const Collapsible = CollapsiblePrimitive.Root 4 | 5 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger 6 | 7 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent 8 | 9 | export { Collapsible, CollapsibleTrigger, CollapsibleContent } 10 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | import { cn } from '@/lib/utils/cn' 4 | 5 | const Input = React.forwardRef>( 6 | ({ className, type, ...props }, ref) => { 7 | return ( 8 | 17 | ) 18 | }, 19 | ) 20 | Input.displayName = 'Input' 21 | 22 | export { Input } 23 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as LabelPrimitive from '@radix-ui/react-label' 3 | import { cva, type VariantProps } from 'class-variance-authority' 4 | 5 | import { cn } from '@/lib/utils/cn' 6 | 7 | const labelVariants = cva( 8 | 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70', 9 | ) 10 | 11 | const Label = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef & 14 | VariantProps 15 | >(({ className, ...props }, ref) => ( 16 | 21 | )) 22 | Label.displayName = LabelPrimitive.Root.displayName 23 | 24 | export { Label } 25 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/ui/popover.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as PopoverPrimitive from '@radix-ui/react-popover' 3 | 4 | import { cn } from '@/lib/utils/cn' 5 | 6 | const Popover = PopoverPrimitive.Root 7 | 8 | const PopoverTrigger = PopoverPrimitive.Trigger 9 | 10 | const PopoverContent = React.forwardRef< 11 | React.ElementRef, 12 | React.ComponentPropsWithoutRef 13 | >(({ className, align = 'center', sideOffset = 4, ...props }, ref) => ( 14 | 15 | 25 | 26 | )) 27 | PopoverContent.displayName = PopoverPrimitive.Content.displayName 28 | 29 | export { Popover, PopoverTrigger, PopoverContent } 30 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/ui/scroll-area.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area' 3 | 4 | import { cn } from '@/lib/utils/cn' 5 | 6 | const ScrollArea = React.forwardRef< 7 | React.ElementRef, 8 | React.ComponentPropsWithoutRef 9 | >(({ className, children, ...props }, ref) => ( 10 | 14 | 18 | {children} 19 | 20 | 21 | 22 | 23 | )) 24 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName 25 | 26 | const ScrollBar = React.forwardRef< 27 | React.ElementRef, 28 | React.ComponentPropsWithoutRef 29 | >(({ className, orientation = 'vertical', ...props }, ref) => ( 30 | 43 | 44 | 45 | )) 46 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName 47 | 48 | export { ScrollArea, ScrollBar } 49 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/ui/separator.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as SeparatorPrimitive from '@radix-ui/react-separator' 3 | 4 | import { cn } from '@/lib/utils/cn' 5 | 6 | const Separator = React.forwardRef< 7 | React.ElementRef, 8 | React.ComponentPropsWithoutRef 9 | >( 10 | ( 11 | { className, orientation = 'horizontal', decorative = true, ...props }, 12 | ref, 13 | ) => ( 14 | 25 | ), 26 | ) 27 | Separator.displayName = SeparatorPrimitive.Root.displayName 28 | 29 | export { Separator } 30 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/lib/utils/cn' 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
    12 | ) 13 | } 14 | 15 | export { Skeleton } 16 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/ui/switch.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as SwitchPrimitives from '@radix-ui/react-switch' 3 | 4 | import { cn } from '@/lib/utils/cn' 5 | 6 | const Switch = React.forwardRef< 7 | React.ElementRef, 8 | React.ComponentPropsWithoutRef 9 | >(({ className, ...props }, ref) => ( 10 | 18 | 23 | 24 | )) 25 | Switch.displayName = SwitchPrimitives.Root.displayName 26 | 27 | export { Switch } 28 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/ui/tabs.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as TabsPrimitive from '@radix-ui/react-tabs' 3 | 4 | import { cn } from '@/lib/utils/cn' 5 | 6 | const Tabs = TabsPrimitive.Root 7 | 8 | const TabsList = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | )) 21 | TabsList.displayName = TabsPrimitive.List.displayName 22 | 23 | const TabsTrigger = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, ...props }, ref) => ( 27 | 35 | )) 36 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName 37 | 38 | const TabsContent = React.forwardRef< 39 | React.ElementRef, 40 | React.ComponentPropsWithoutRef 41 | >(({ className, ...props }, ref) => ( 42 | 50 | )) 51 | TabsContent.displayName = TabsPrimitive.Content.displayName 52 | 53 | export { Tabs, TabsList, TabsTrigger, TabsContent } 54 | -------------------------------------------------------------------------------- /pkg/dashboard/frontend/src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | import { cn } from '@/lib/utils/cn' 4 | 5 | export type TextareaProps = React.TextareaHTMLAttributes 6 | 7 | const Textarea = React.forwardRef( 8 | ({ className, ...props }, ref) => { 9 | return ( 10 |