├── .env.example ├── .eslintignore ├── .eslintrc.cjs ├── .github └── workflows │ ├── check-links.yml │ ├── playwright.yml │ ├── release.yml │ └── unit-tests.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierignore ├── .prettierrc ├── README.md ├── e2e-tests └── example.spec.ts ├── index.html ├── package.json ├── playwright.config.ts ├── pnpm-lock.yaml ├── postcss.config.js ├── public └── favicon.ico ├── scripts ├── check-links.js ├── check-links.sh └── types.js ├── src ├── App.tsx ├── app │ ├── 404.tsx │ ├── activate-server │ │ ├── PasswordStep.tsx │ │ ├── ServerActivationContext.tsx │ │ ├── ServerNameStep.tsx │ │ ├── Wizard.tsx │ │ └── page.tsx │ ├── activate-user │ │ ├── AccountDetailsStep.tsx │ │ ├── ActivationContext.tsx │ │ ├── InfrastructureStep.tsx │ │ ├── PasswordStep.tsx │ │ ├── PrimaryUseStep.tsx │ │ ├── UsageReasonStep.tsx │ │ ├── Wizard.tsx │ │ └── page.tsx │ ├── artifacts │ │ ├── Fragments.tsx │ │ └── page.tsx │ ├── components │ │ ├── StackComponentList.tsx │ │ ├── [componentId] │ │ │ ├── edit │ │ │ │ ├── form-step.tsx │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── button-group.tsx │ │ ├── columns.tsx │ │ ├── component-dropdown.tsx │ │ ├── create │ │ │ ├── breadcrumb.ts │ │ │ ├── config-step.tsx │ │ │ ├── flavor-step.tsx │ │ │ ├── header.tsx │ │ │ ├── page.tsx │ │ │ ├── type-step.tsx │ │ │ └── wizard.tsx │ │ ├── page.tsx │ │ ├── selector-context.tsx │ │ └── service.ts │ ├── devices │ │ └── verify │ │ │ ├── ConfirmationForm.tsx │ │ │ ├── DeviceInfo.tsx │ │ │ ├── Success.tsx │ │ │ ├── page.tsx │ │ │ └── service.ts │ ├── login │ │ ├── LoginForm.tsx │ │ └── page.tsx │ ├── models │ │ ├── Fragments.tsx │ │ └── page.tsx │ ├── onboarding │ │ ├── Header.tsx │ │ ├── Setup │ │ │ ├── Items.tsx │ │ │ ├── constant.ts │ │ │ ├── index.tsx │ │ │ ├── pipeline-snippet.tsx │ │ │ ├── provider-step.tsx │ │ │ ├── run-script.tsx │ │ │ └── set-project.tsx │ │ ├── page.tsx │ │ └── progress-indicator.tsx │ ├── pipelines │ │ ├── PipelinesTab │ │ │ ├── ButtonGroup.tsx │ │ │ ├── DeletePipelineAlert.tsx │ │ │ ├── PipelineDropdown.tsx │ │ │ ├── PipelineSelectorContext.tsx │ │ │ ├── PipelinesBody.tsx │ │ │ ├── columns.tsx │ │ │ └── service.ts │ │ ├── [namespace] │ │ │ ├── Header.tsx │ │ │ ├── RunsTable.tsx │ │ │ ├── breadcrumb.ts │ │ │ ├── columns.tsx │ │ │ ├── page.tsx │ │ │ └── service.ts │ │ └── page.tsx │ ├── projects │ │ ├── page.tsx │ │ ├── project-item.tsx │ │ ├── project-list.tsx │ │ ├── project-menu.tsx │ │ ├── searchbar.tsx │ │ └── set-default-project.tsx │ ├── run-templates │ │ ├── TemplateBody.tsx │ │ └── page.tsx │ ├── runs │ │ ├── ButtonGroup.tsx │ │ ├── DeleteRunAlert.tsx │ │ ├── RunDropdown.tsx │ │ ├── RunsBody.tsx │ │ ├── RunsSelectorContext.tsx │ │ ├── [id] │ │ │ ├── Dag.tsx │ │ │ ├── DeleteRunAlert.tsx │ │ │ ├── Header.tsx │ │ │ ├── RunActionMenu.tsx │ │ │ ├── _Tabs │ │ │ │ ├── Configuration │ │ │ │ │ ├── CodeCollapsible.tsx │ │ │ │ │ ├── DockerImageCollapsible.tsx │ │ │ │ │ ├── EnvironmentCollapsible.tsx │ │ │ │ │ ├── ParameterCollapsible.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── Metadata │ │ │ │ │ └── index.tsx │ │ │ │ ├── Overview │ │ │ │ │ ├── AlertPanels.tsx │ │ │ │ │ ├── Details.tsx │ │ │ │ │ ├── Orchestrator.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── Stack │ │ │ │ │ └── index.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── service.ts │ │ │ ├── breadcrumbs.ts │ │ │ ├── not-found.tsx │ │ │ ├── page.tsx │ │ │ └── useDag.tsx │ │ ├── columns.tsx │ │ ├── page.tsx │ │ └── service.ts │ ├── settings │ │ ├── api-tokens │ │ │ ├── APITokenModal.tsx │ │ │ ├── CreateTokenButton.tsx │ │ │ └── page.tsx │ │ ├── connectors │ │ │ ├── [id] │ │ │ │ ├── components │ │ │ │ │ └── page.tsx │ │ │ │ ├── configuration │ │ │ │ │ ├── configuration.tsx │ │ │ │ │ ├── page.tsx │ │ │ │ │ └── params.tsx │ │ │ │ └── resources │ │ │ │ │ ├── loading-dialog.tsx │ │ │ │ │ ├── page.tsx │ │ │ │ │ ├── resources-list.tsx │ │ │ │ │ ├── verify-box.tsx │ │ │ │ │ └── verify-button.tsx │ │ │ ├── button-group.tsx │ │ │ ├── columns.tsx │ │ │ ├── connector-dropdown.tsx │ │ │ ├── connector-list.tsx │ │ │ ├── create │ │ │ │ ├── breadcrumbs.ts │ │ │ │ ├── common │ │ │ │ │ └── footer-buttons.tsx │ │ │ │ ├── configuration │ │ │ │ │ ├── auth-method-section.tsx │ │ │ │ │ ├── config-section.tsx │ │ │ │ │ ├── debounced-check.ts │ │ │ │ │ ├── helper.ts │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── name-schema.ts │ │ │ │ │ ├── name-section.tsx │ │ │ │ │ ├── resource-type-section.tsx │ │ │ │ │ ├── schema.ts │ │ │ │ │ ├── skip-verify.tsx │ │ │ │ │ ├── use-verify-button.ts │ │ │ │ │ └── verify-connector-button.tsx │ │ │ │ ├── connector-type │ │ │ │ │ ├── description.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── list.tsx │ │ │ │ │ └── schema.ts │ │ │ │ ├── create-context.tsx │ │ │ │ ├── header.tsx │ │ │ │ ├── page.tsx │ │ │ │ ├── success │ │ │ │ │ ├── columns.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── success-box.tsx │ │ │ │ │ └── table.tsx │ │ │ │ └── wizard.tsx │ │ │ ├── header.tsx │ │ │ ├── list-content.tsx │ │ │ ├── new-connector-button.tsx │ │ │ ├── page.tsx │ │ │ ├── searchbar.tsx │ │ │ ├── selector-context.ts │ │ │ └── use-connector-params.ts │ │ ├── general │ │ │ ├── GeneralForm.tsx │ │ │ ├── GeneralFormSchema.ts │ │ │ └── page.tsx │ │ ├── members │ │ │ ├── AddMemberDialog.tsx │ │ │ ├── DeleteMemberAlert.tsx │ │ │ ├── MemberDropdown.tsx │ │ │ ├── MembersTable.tsx │ │ │ ├── columns.tsx │ │ │ ├── page.tsx │ │ │ └── service.ts │ │ ├── notifications │ │ │ ├── NotificationsForm.tsx │ │ │ └── page.tsx │ │ ├── profile │ │ │ ├── UpdatePasswordDialog.tsx │ │ │ ├── UpdateProfileForm.tsx │ │ │ ├── UpdateProfileSchema.ts │ │ │ └── page.tsx │ │ ├── repositories │ │ │ ├── Fragments.tsx │ │ │ └── page.tsx │ │ ├── secrets │ │ │ ├── AddSecretDialog.tsx │ │ │ ├── DeleteSecretAlert.tsx │ │ │ ├── EditSecretDialog.tsx │ │ │ ├── SecretTooltip.tsx │ │ │ ├── SecretsDropdown.tsx │ │ │ ├── SecretsTable.tsx │ │ │ ├── [id] │ │ │ │ ├── DeleteKeyAlert.tsx │ │ │ │ ├── SecretDetailTable.tsx │ │ │ │ ├── SecretTableDropDown.tsx │ │ │ │ ├── breadcrumbs.ts │ │ │ │ ├── columns.tsx │ │ │ │ └── page.tsx │ │ │ ├── columns.tsx │ │ │ ├── form-schema.ts │ │ │ ├── page.tsx │ │ │ └── service.ts │ │ └── service-accounts │ │ │ ├── AddServiceAccount.tsx │ │ │ ├── ButtonGroup.tsx │ │ │ ├── DeleteAlert.tsx │ │ │ ├── Dropdown.tsx │ │ │ ├── Fallback.tsx │ │ │ ├── SelectorContext.tsx │ │ │ ├── Table.tsx │ │ │ ├── ToggleServiceAccount.tsx │ │ │ ├── [service-account-id] │ │ │ ├── AddApiKeyDialog.tsx │ │ │ ├── ButtonGroup.tsx │ │ │ ├── DeleteApiKeyDialog.tsx │ │ │ ├── Dropdown.tsx │ │ │ ├── Fallback.tsx │ │ │ ├── RotateKeyDialog.tsx │ │ │ ├── SelectorContext.tsx │ │ │ ├── Success.tsx │ │ │ ├── Table.tsx │ │ │ ├── ToggleApiKey.tsx │ │ │ ├── breadcrumb.ts │ │ │ ├── columns.tsx │ │ │ └── page.tsx │ │ │ ├── columns.tsx │ │ │ ├── form-schema.ts │ │ │ ├── page.tsx │ │ │ └── service.ts │ ├── stacks │ │ ├── ActionsDropdown.tsx │ │ ├── DeleteStackModal.tsx │ │ ├── DialogItems.tsx │ │ ├── ResumeBanner.tsx │ │ ├── ResumeStackBanner.tsx │ │ ├── ResumeTerraformBanner.tsx │ │ ├── StackList.tsx │ │ ├── columns.tsx │ │ ├── create │ │ │ ├── ExistingInfra.tsx │ │ │ ├── LocalOverlay.tsx │ │ │ ├── NewInfra.tsx │ │ │ ├── OptionCard.tsx │ │ │ ├── breadcrumb.ts │ │ │ ├── components │ │ │ │ ├── CancelButton.tsx │ │ │ │ ├── ProviderRadio.tsx │ │ │ │ ├── RadioItem.tsx │ │ │ │ ├── StackName.tsx │ │ │ │ ├── WizardFooter.tsx │ │ │ │ └── sharedSchema.ts │ │ │ ├── existing-infrastructure │ │ │ │ ├── ExistingInfraContext.tsx │ │ │ │ ├── FlavorIcon.tsx │ │ │ │ ├── Wizard.tsx │ │ │ │ ├── page.tsx │ │ │ │ └── steps │ │ │ │ │ ├── artifact_store │ │ │ │ │ ├── Form.tsx │ │ │ │ │ ├── Header.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── schema.ts │ │ │ │ │ └── useArtifactStore.ts │ │ │ │ │ ├── connect │ │ │ │ │ ├── AuthMethodSelect.tsx │ │ │ │ │ ├── AuthenticationMethod.tsx │ │ │ │ │ ├── Configuration.tsx │ │ │ │ │ ├── LoadingModal.tsx │ │ │ │ │ ├── NewConnectorForm.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── schema.ts │ │ │ │ │ └── useNewConnector.ts │ │ │ │ │ ├── container_registry │ │ │ │ │ ├── Form.tsx │ │ │ │ │ ├── Header.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── schema.ts │ │ │ │ │ └── useContainerRegistry.tsx │ │ │ │ │ ├── final │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── orchestrator │ │ │ │ │ ├── Form.tsx │ │ │ │ │ ├── Header.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── schema.ts │ │ │ │ │ └── useOrchestrator.ts │ │ │ │ │ └── provider │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── schema.ts │ │ │ ├── layout.tsx │ │ │ ├── manual │ │ │ │ ├── ComponentSelection.tsx │ │ │ │ ├── ComponentsTooltip.tsx │ │ │ │ ├── TypeOverview.tsx │ │ │ │ ├── TypeOverviewItem.tsx │ │ │ │ ├── create-component-modal │ │ │ │ │ ├── configuration-step.tsx │ │ │ │ │ ├── flavor-select.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── page.tsx │ │ │ │ ├── schema.ts │ │ │ │ └── useManualStack.tsx │ │ │ ├── new-infrastructure │ │ │ │ ├── CloudProvider.tsx │ │ │ │ ├── NewInfraFormContext.tsx │ │ │ │ ├── Providers │ │ │ │ │ ├── AWS.tsx │ │ │ │ │ ├── Azure.tsx │ │ │ │ │ ├── GCP.tsx │ │ │ │ │ ├── PermissionsCard.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── Steps │ │ │ │ │ ├── Configuration │ │ │ │ │ │ ├── LocationSelect.tsx │ │ │ │ │ │ ├── Partials.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── Deploy │ │ │ │ │ │ ├── ButtonStep.tsx │ │ │ │ │ │ ├── ProvisioningStep.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── Provider.tsx │ │ │ │ │ ├── Success │ │ │ │ │ │ ├── SuccessList.tsx │ │ │ │ │ │ └── SuccessStep.tsx │ │ │ │ │ └── schemas.ts │ │ │ │ ├── Wizard.tsx │ │ │ │ ├── page.tsx │ │ │ │ └── persist.ts │ │ │ ├── page.tsx │ │ │ └── terraform │ │ │ │ ├── TerraformContext.tsx │ │ │ │ ├── TerraformWizard.tsx │ │ │ │ ├── page.tsx │ │ │ │ ├── persist.ts │ │ │ │ └── steps │ │ │ │ ├── configuration │ │ │ │ └── index.tsx │ │ │ │ ├── deploy │ │ │ │ ├── Pollingstep.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── usePollinglist.ts │ │ │ │ ├── provider │ │ │ │ ├── index.tsx │ │ │ │ └── schema.ts │ │ │ │ └── success │ │ │ │ └── index.tsx │ │ ├── page.tsx │ │ ├── service.ts │ │ └── useDeleteStack.tsx │ ├── survey │ │ ├── AccountDetailsStep.tsx │ │ ├── InfrastructureStep.tsx │ │ ├── PrimaryUseStep.tsx │ │ ├── SurveyUserContext.tsx │ │ ├── UsageReasonStep.tsx │ │ ├── Wizard.tsx │ │ ├── loader.ts │ │ └── page.tsx │ └── upgrade │ │ ├── components │ │ ├── Context.tsx │ │ ├── Success.tsx │ │ └── form │ │ │ ├── Form.tsx │ │ │ ├── Steps.tsx │ │ │ ├── Wrapper.tsx │ │ │ ├── index.tsx │ │ │ ├── schema.ts │ │ │ └── useUpgradeForm.ts │ │ └── page.tsx ├── assets │ ├── icons │ │ ├── Lock.svg │ │ ├── alert-circle.svg │ │ ├── alert-triangle.svg │ │ ├── analysis.svg │ │ ├── annotation-alert.svg │ │ ├── announcement.svg │ │ ├── arrow-left.svg │ │ ├── arrow-right.svg │ │ ├── bar-chart-square-check.svg │ │ ├── bar-chart.svg │ │ ├── building.svg │ │ ├── cached.svg │ │ ├── calendar.svg │ │ ├── check-circle.svg │ │ ├── check.svg │ │ ├── chevron-down.svg │ │ ├── chevron-left-double.svg │ │ ├── chevron-left.svg │ │ ├── chevron-right-double.svg │ │ ├── chevron-right.svg │ │ ├── chip.svg │ │ ├── clipboard.svg │ │ ├── clock.svg │ │ ├── close.svg │ │ ├── cloud-tenant.svg │ │ ├── cloud.svg │ │ ├── code-box.svg │ │ ├── code-browser.svg │ │ ├── code-square.svg │ │ ├── coin.svg │ │ ├── collapse.svg │ │ ├── connector-add.svg │ │ ├── container.svg │ │ ├── copy.svg │ │ ├── credit-card.svg │ │ ├── database.svg │ │ ├── dataflow-2.svg │ │ ├── dataflow.svg │ │ ├── dots-circle.svg │ │ ├── dots-horizontal.svg │ │ ├── download-01.svg │ │ ├── edit.svg │ │ ├── expand-full.svg │ │ ├── expand.svg │ │ ├── eye-off.svg │ │ ├── eye.svg │ │ ├── file-question.svg │ │ ├── file-text.svg │ │ ├── file.svg │ │ ├── folder.svg │ │ ├── github.svg │ │ ├── gitlab.svg │ │ ├── hash.svg │ │ ├── hat.svg │ │ ├── help.svg │ │ ├── info.svg │ │ ├── key-icon.svg │ │ ├── layout.svg │ │ ├── link-external.svg │ │ ├── logout.svg │ │ ├── logs.svg │ │ ├── mail.svg │ │ ├── maximize.svg │ │ ├── message-chat-square.svg │ │ ├── minus.svg │ │ ├── ml_model.svg │ │ ├── name.svg │ │ ├── package-plus.svg │ │ ├── package-search.svg │ │ ├── package.svg │ │ ├── pin-01.svg │ │ ├── pin.svg │ │ ├── pipeline-template.svg │ │ ├── pipeline.svg │ │ ├── play-circle.svg │ │ ├── plus.svg │ │ ├── redirect.svg │ │ ├── refresh.svg │ │ ├── rocket.svg │ │ ├── route.svg │ │ ├── running.svg │ │ ├── search.svg │ │ ├── services │ │ │ ├── airflow.svg │ │ │ ├── aws.svg │ │ │ ├── azure.svg │ │ │ ├── docker.svg │ │ │ ├── flyte.svg │ │ │ ├── gcp.svg │ │ │ ├── kedro.svg │ │ │ ├── kubernetes.svg │ │ │ ├── metaflow.svg │ │ │ ├── openshift.svg │ │ │ ├── prefect.svg │ │ │ ├── slack.svg │ │ │ └── terraform.svg │ │ ├── settings.svg │ │ ├── side-collapse.svg │ │ ├── side-expand.svg │ │ ├── slash-circle.svg │ │ ├── slash-divider.svg │ │ ├── spinner.svg │ │ ├── stack.svg │ │ ├── stars.svg │ │ ├── storefront.svg │ │ ├── switch-horizontal.svg │ │ ├── terminal-square.svg │ │ ├── terminal.svg │ │ ├── tick-circle.svg │ │ ├── tool-02.svg │ │ ├── tool.svg │ │ ├── transform.svg │ │ ├── trash.svg │ │ ├── trigger.svg │ │ ├── user-plus.svg │ │ ├── user.svg │ │ ├── users.svg │ │ ├── zenml-icon.svg │ │ └── zenml.svg │ ├── illustrations │ │ ├── cloud-squares.svg │ │ ├── connectivity.webp │ │ ├── connectors-video.svg │ │ ├── repos-video.svg │ │ ├── secrets-video.svg │ │ └── stacks-video.svg │ ├── images │ │ ├── acp.webp │ │ ├── mcp.webp │ │ ├── portraits │ │ │ ├── adam.webp │ │ │ ├── alex.webp │ │ │ ├── baris.webp │ │ │ ├── hamza.webp │ │ │ └── stefan.webp │ │ ├── product-tour │ │ │ ├── settings_preview.webp │ │ │ └── tour-cover.webp │ │ ├── templates.webp │ │ └── upgrade-form.webp │ └── styles │ │ ├── index.css │ │ └── prism-github-light.css ├── components │ ├── AlertDialogDropdownItem.tsx │ ├── Analytics.tsx │ ├── ArtifactIcon.tsx │ ├── CodeHighlighter.tsx │ ├── CodeSnippet.tsx │ ├── CollapsibleCard.tsx │ ├── ComponentIcon.tsx │ ├── CopyButton.tsx │ ├── DeleteAlertDialog.tsx │ ├── DisplayDate.tsx │ ├── EmptyState.tsx │ ├── Error.tsx │ ├── ExecutionStatus.tsx │ ├── Infobox.tsx │ ├── InlineAvatar.tsx │ ├── KeyValue.tsx │ ├── MetadataCards.tsx │ ├── NavLink.tsx │ ├── NestedCollapsible.tsx │ ├── NumberBox.tsx │ ├── PageHeader.tsx │ ├── Pagination.tsx │ ├── ProviderIcon.tsx │ ├── SearchField.tsx │ ├── Tick.tsx │ ├── VideoModal.tsx │ ├── artifacts │ │ ├── CsvVizualization.tsx │ │ ├── HtmlVisualization.tsx │ │ ├── ImageVisualization.tsx │ │ ├── JsonVisualization.tsx │ │ ├── MarkdownVisualization.tsx │ │ ├── Visualization.tsx │ │ ├── artifact-node-sheet │ │ │ ├── DetailCards.tsx │ │ │ ├── DetailsTab.tsx │ │ │ ├── MetadataTab.tsx │ │ │ ├── SheetContent.tsx │ │ │ ├── VisualizationTab.tsx │ │ │ └── index.tsx │ │ ├── download-artifact-button.tsx │ │ ├── visualization-combobox.tsx │ │ └── visualization-download-button.tsx │ ├── avatar-stack.tsx │ ├── breadcrumbs │ │ ├── Breadcrumbs.tsx │ │ ├── SegmentsBreadcrumbs.tsx │ │ ├── library.ts │ │ ├── project-link.tsx │ │ ├── types.ts │ │ └── workspace-link.tsx │ ├── buttons │ │ └── update-button-content.tsx │ ├── copy-metadata-button.tsx │ ├── dag-visualizer │ │ ├── ArtifactNode.tsx │ │ ├── BaseNode.tsx │ │ ├── Controls.tsx │ │ ├── NodeCopyButton.tsx │ │ ├── PreviewArtifact.tsx │ │ ├── PreviewStep.tsx │ │ ├── SmartEdge.tsx │ │ ├── StepNode.tsx │ │ ├── layout.ts │ │ └── layout │ │ │ ├── compute.ts │ │ │ └── status.ts │ ├── dialog │ │ └── DialogItem.tsx │ ├── fallback-pages │ │ ├── Cards.tsx │ │ ├── Commands.tsx │ │ └── Helpbox.tsx │ ├── fallback │ │ └── icon.tsx │ ├── form │ │ ├── array-field-renderer.tsx │ │ ├── boolean-field-renderer.tsx │ │ ├── common.tsx │ │ ├── enum-field-renderer.tsx │ │ ├── form.tsx │ │ ├── helper.spec.ts │ │ ├── helper.ts │ │ ├── integer-field-renderer.tsx │ │ ├── object-field-renderer.tsx │ │ ├── sensitive-text-renderer.tsx │ │ └── text-field-renderer.tsx │ ├── login-command.tsx │ ├── not-available.tsx │ ├── onboarding │ │ ├── ChecklistItem.tsx │ │ ├── SkippedStep.tsx │ │ └── floating-progress-link.tsx │ ├── password │ │ ├── PasswordChecker.tsx │ │ └── UpdatePasswordSchemas.ts │ ├── pro │ │ └── ProCta.tsx │ ├── repositories │ │ └── RepoBadge.tsx │ ├── runs │ │ ├── auth-required.tsx │ │ └── refresh-group │ │ │ ├── deep-refresh.ts │ │ │ ├── dropdown.tsx │ │ │ └── index.tsx │ ├── sensitive-value.tsx │ ├── service-connectors │ │ ├── connector-tag.tsx │ │ ├── connector-type-tooltip.tsx │ │ ├── create-fallback.tsx │ │ ├── delete-connector.tsx │ │ ├── expiry.tsx │ │ ├── resource-name-tooltip.tsx │ │ ├── resource-tyes-list.tsx │ │ └── resource-type-tooltip.tsx │ ├── sheet │ │ └── SheetHeader.tsx │ ├── stack-components │ │ ├── ComponentBadge.tsx │ │ ├── component-detail │ │ │ ├── ConfigTab.tsx │ │ │ ├── Header.tsx │ │ │ ├── Tabs.tsx │ │ │ ├── hooks.ts │ │ │ └── service.ts │ │ ├── component-sheet │ │ │ ├── index.tsx │ │ │ ├── runs-tab │ │ │ │ ├── RunsList.tsx │ │ │ │ └── columns.tsx │ │ │ └── stacks-tab │ │ │ │ ├── StackList.tsx │ │ │ │ └── columns.tsx │ │ ├── create-component │ │ │ ├── configuration-form.tsx │ │ │ ├── connector-section.tsx │ │ │ ├── connector-select.tsx │ │ │ ├── flavor-select.tsx │ │ │ ├── info-tile.tsx │ │ │ └── schema.ts │ │ └── delete-component │ │ │ └── delete-alert.tsx │ ├── stacks │ │ ├── Sheet │ │ │ ├── IntegrationsContext.tsx │ │ │ ├── index.tsx │ │ │ └── update-stacks-dialog.tsx │ │ ├── info │ │ │ ├── ComponentCollapsible.tsx │ │ │ ├── StackHeader.tsx │ │ │ └── index.tsx │ │ └── new-infra │ │ │ └── TerraformCommands.tsx │ ├── steps │ │ └── step-sheet │ │ │ ├── CodeTab.tsx │ │ │ ├── ConfigurationTab.tsx │ │ │ ├── DetailsTab.tsx │ │ │ ├── LogsTab.tsx │ │ │ ├── MetadataTab.tsx │ │ │ ├── SheetContent.tsx │ │ │ ├── StacksTab.tsx │ │ │ ├── index.tsx │ │ │ ├── sheet-headline.tsx │ │ │ └── step-status-tooltip.tsx │ ├── survey │ │ ├── AccountDetailsForm.tsx │ │ ├── Infrastructure.tsx │ │ ├── PrimaryUse.tsx │ │ ├── ServerName.tsx │ │ ├── SetPassword.tsx │ │ ├── SlackStep.tsx │ │ ├── StepDisplay.tsx │ │ ├── SuccessStep.tsx │ │ ├── SurveyContext.tsx │ │ ├── UsageReason.tsx │ │ ├── UseRadioButton.tsx │ │ └── form-schemas.ts │ ├── tables │ │ └── helper.tsx │ ├── tour │ │ ├── StartTourDialog.tsx │ │ ├── Tour.tsx │ │ └── TourContext.tsx │ └── wizard │ │ ├── LeftSideMenu.tsx │ │ └── Wizard.tsx ├── contents │ ├── components.tsx │ ├── repositories.ts │ ├── secrets.ts │ └── stack.ts ├── context │ ├── AuthContext.tsx │ ├── SchemaContext.tsx │ ├── VisualizationConfirmationContext.tsx │ └── WizardContext.tsx ├── data │ ├── api.ts │ ├── artifact-versions │ │ ├── artifact-download-token.ts │ │ ├── artifact-version-detail-query.ts │ │ └── artifact-visualization-query.ts │ ├── auth │ │ └── create-api-token.ts │ ├── code-repositories │ │ └── code-repositories-detail-query.ts │ ├── components │ │ ├── component-detail-query.ts │ │ ├── components-list.ts │ │ ├── create-component.ts │ │ ├── delete-component.ts │ │ ├── index.ts │ │ └── update-component.ts │ ├── devices │ │ ├── device-detail-query.ts │ │ └── device-verify-mutation.ts │ ├── fetch.ts │ ├── flavors │ │ ├── flavor-detail.ts │ │ ├── flavors-list.ts │ │ └── index.ts │ ├── pipeline-builds │ │ └── all-pipeline-builds-query.ts │ ├── pipeline-deployments │ │ ├── index.ts │ │ └── pipeline-deployments-detail.ts │ ├── pipeline-runs │ │ ├── all-pipeline-runs-query.ts │ │ ├── delete-run.ts │ │ ├── fetch-orchestrator-run.ts │ │ ├── pipeline-run-detail-query.ts │ │ └── run-dag.ts │ ├── pipelines │ │ ├── delete-pipeline.ts │ │ ├── index.ts │ │ └── pipeline-list-query.ts │ ├── projects │ │ ├── index.ts │ │ ├── project-detail.ts │ │ └── project-statistics.ts │ ├── secrets │ │ ├── create-secret-query.ts │ │ ├── delete-secret-query.ts │ │ ├── get-secret-detail.ts │ │ ├── index.ts │ │ ├── secrets-all-query.ts │ │ └── update-secret-query.ts │ ├── server │ │ ├── activate-server-mutation.ts │ │ ├── get-server-settings.ts │ │ ├── info-query.ts │ │ ├── onboarding-state.ts │ │ └── update-server-settings-mutation.ts │ ├── service-accounts │ │ ├── create-api-key.ts │ │ ├── create-service-account.ts │ │ ├── delete-api-key.ts │ │ ├── delete-service-account.ts │ │ ├── get-api-key.ts │ │ ├── get-service-account.ts │ │ ├── index.ts │ │ ├── list-api-keys.ts │ │ ├── list-service-accounts.ts │ │ ├── rotate-api-key.ts │ │ ├── update-api-key.ts │ │ └── update-service-account.ts │ ├── service-connectors │ │ ├── connector-detail.ts │ │ ├── connector-list.ts │ │ ├── connector-type-detail.ts │ │ ├── connector-type-list.ts │ │ ├── create-service-connector.ts │ │ ├── delete-service-connector.ts │ │ ├── index.ts │ │ ├── resources-info.ts │ │ ├── verify-config.ts │ │ └── verify-existing-connectors.ts │ ├── session │ │ ├── login-mutation.ts │ │ └── logout-mutation.ts │ ├── stacks │ │ ├── create-stack.ts │ │ ├── delete-stack.ts │ │ ├── index.ts │ │ ├── stack-deployment-config.ts │ │ ├── stack-deployment-info.ts │ │ ├── stack-deployment-stack.ts │ │ ├── stack-detail-query.ts │ │ └── stacklist-query.ts │ ├── steps │ │ ├── step-detail-query.ts │ │ └── step-logs-query.ts │ └── users │ │ ├── activate-user-mutation.ts │ │ ├── create-user-mutation.ts │ │ ├── current-user-query.ts │ │ ├── delete-user-mutation.ts │ │ ├── update-current-user-mutation.ts │ │ └── users-all-query.ts ├── error-boundaries │ ├── PageBoundary.tsx │ └── RootBoundary.tsx ├── hooks │ └── use-route-segment.tsx ├── layouts │ ├── AuthenticatedLayout │ │ ├── AuthenticatedHeader.tsx │ │ ├── BreadcrumbsContext.tsx │ │ ├── LocalBanner.tsx │ │ ├── UserDropdown.tsx │ │ ├── changelog-button.tsx │ │ └── index.tsx │ ├── GradientLayout.tsx │ ├── RootLayout.tsx │ ├── connectors-detail │ │ ├── breadcrumbs.ts │ │ ├── header.tsx │ │ ├── layout.tsx │ │ ├── resources-context.tsx │ │ └── tabs.tsx │ ├── non-project-scoped │ │ ├── dropdown.tsx │ │ ├── header.tsx │ │ ├── layout.tsx │ │ ├── name-section.tsx │ │ ├── server-members.tsx │ │ └── tabs.tsx │ ├── project-tabs │ │ ├── header.tsx │ │ ├── id-section.tsx │ │ ├── layout.tsx │ │ ├── name-section.tsx │ │ └── tabs.tsx │ └── settings │ │ ├── Menu.tsx │ │ ├── project-settings │ │ ├── layout.tsx │ │ └── project-display.tsx │ │ └── settings-layout │ │ ├── LayoutSidebar.tsx │ │ ├── VersionDisplay.tsx │ │ └── layout.tsx ├── lib │ ├── analytics.ts │ ├── bulk-delete.tsx │ ├── code-snippets.ts │ ├── common.ts │ ├── components.ts │ ├── constants.ts │ ├── copy.ts │ ├── dates.ts │ ├── fetch-error.ts │ ├── forms.spec.ts │ ├── forms.ts │ ├── images.ts │ ├── login-command.ts │ ├── not-found-error.ts │ ├── onboarding.spec.ts │ ├── onboarding.ts │ ├── provider.ts │ ├── search.spec.ts │ ├── search.ts │ ├── server.spec.ts │ ├── server.ts │ ├── service-connectors.ts │ ├── sessions.ts │ ├── stacks.ts │ ├── strings.spec.ts │ ├── strings.ts │ ├── type-guards.ts │ ├── url.ts │ ├── user.spec.ts │ └── user.ts ├── main.tsx ├── router │ ├── ProtectedRoute.tsx │ ├── Router.tsx │ ├── loaders.ts │ ├── queryclient.ts │ └── routes.tsx ├── types │ ├── .gitkeep │ ├── analytics.ts │ ├── artifact-versions.ts │ ├── auth.ts │ ├── code-repository.ts │ ├── common.ts │ ├── components.ts │ ├── core.ts │ ├── dag-visualizer.ts │ ├── devices.ts │ ├── flavors.ts │ ├── forms.ts │ ├── onboarding.ts │ ├── pipeline-builds.ts │ ├── pipeline-deployments.ts │ ├── pipeline-runs.ts │ ├── pipelines.ts │ ├── projects.ts │ ├── reodotdev.d.ts │ ├── secret.ts │ ├── server.ts │ ├── service-accounts.ts │ ├── service-connectors.ts │ ├── session.ts │ ├── stack.ts │ ├── steps.ts │ └── user.ts └── vite-env.d.ts ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.env.example: -------------------------------------------------------------------------------- 1 | VITE_API_BASE_URL= 2 | VITE_FRONTEND_VERSION= 3 | VITE_FEATURE_OS_KEY= 4 | VITE_REO_KEY= 5 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | .env* 27 | !.env.example 28 | 29 | pnpm-lock.yaml 30 | yarn.lock 31 | 32 | scripts/types.js 33 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | "eslint:recommended", 6 | "plugin:@typescript-eslint/recommended", 7 | "plugin:react-hooks/recommended" 8 | ], 9 | ignorePatterns: ["dist", ".eslintrc.cjs"], 10 | parser: "@typescript-eslint/parser", 11 | plugins: ["react-refresh"], 12 | rules: { 13 | "react-refresh/only-export-components": ["warn", { allowConstantExport: true }], 14 | "no-mixed-spaces-and-tabs": "off", 15 | "@typescript-eslint/no-explicit-any": "warn", 16 | "@typescript-eslint/no-unused-vars": [ 17 | "warn", // or "error" 18 | { 19 | argsIgnorePattern: "^_", 20 | varsIgnorePattern: "^_", 21 | caughtErrorsIgnorePattern: "^_" 22 | } 23 | ] 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /.github/workflows/check-links.yml: -------------------------------------------------------------------------------- 1 | name: Check Links 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | - future 9 | pull_request: 10 | types: [opened, synchronize, ready_for_review] 11 | branches: 12 | - main 13 | - future 14 | concurrency: 15 | # New commit on branch cancels running workflows of the same branch 16 | group: ${{ github.workflow }}-${{ github.ref }} 17 | cancel-in-progress: true 18 | 19 | jobs: 20 | check: 21 | if: github.event.pull_request.draft == false 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Checkout Repository 25 | uses: actions/checkout@v4 26 | 27 | - name: Set up Node.js 28 | uses: actions/setup-node@v4 29 | with: 30 | node-version: "20" 31 | 32 | - name: Check Links 33 | run: bash scripts/check-links.sh 34 | -------------------------------------------------------------------------------- /.github/workflows/playwright.yml: -------------------------------------------------------------------------------- 1 | name: Playwright Tests 2 | on: 3 | push: 4 | branches: [main, master] 5 | pull_request: 6 | branches: [main, master] 7 | jobs: 8 | test: 9 | timeout-minutes: 60 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: actions/setup-node@v4 14 | with: 15 | node-version: lts/* 16 | - name: Install dependencies 17 | run: npm install -g pnpm && pnpm install 18 | - name: Install Playwright Browsers 19 | run: pnpm exec playwright install --with-deps 20 | - name: Run Playwright tests 21 | run: pnpm exec playwright test 22 | - uses: actions/upload-artifact@v4 23 | if: always() 24 | with: 25 | name: playwright-report 26 | path: playwright-report/ 27 | retention-days: 30 28 | -------------------------------------------------------------------------------- /.github/workflows/unit-tests.yml: -------------------------------------------------------------------------------- 1 | name: Build & Unit Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout Repository 16 | uses: actions/checkout@v2 17 | 18 | - name: Set up Node.js 19 | uses: actions/setup-node@v2 20 | with: 21 | node-version: "20" 22 | 23 | - name: Install pnpm 24 | uses: pnpm/action-setup@v2 25 | with: 26 | version: latest 27 | 28 | - name: Install project dependencies 29 | run: pnpm install --frozen-lockfile 30 | 31 | - name: "Run Build" 32 | run: pnpm build 33 | 34 | - name: Run Unit Tests 35 | run: pnpm test:unit 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | /test-results/ 26 | /playwright-report/ 27 | /blob-report/ 28 | /playwright/.cache/ 29 | 30 | .env* 31 | !.env.example 32 | 33 | urls.txt 34 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npx lint-staged 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | .env* 27 | !.env.example 28 | 29 | pnpm-lock.yaml 30 | yarn.lock 31 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": false, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-tailwindcss"] 7 | } 8 | -------------------------------------------------------------------------------- /e2e-tests/example.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "@playwright/test"; 2 | 3 | test("has title", async ({ page }) => { 4 | await page.goto("https://playwright.dev/"); 5 | 6 | // Expect a title "to contain" a substring. 7 | await expect(page).toHaveTitle(/Playwright/); 8 | }); 9 | 10 | test("get started link", async ({ page }) => { 11 | await page.goto("https://playwright.dev/"); 12 | 13 | // Click the get started link. 14 | await page.getByRole("link", { name: "Get started" }).click(); 15 | 16 | // Expects page to have a heading with the name of Installation. 17 | await expect(page.getByRole("heading", { name: "Installation" })).toBeVisible(); 18 | }); 19 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ZenML Dashboard 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {} 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenml-io/zenml-dashboard/90236a062535d2182b67f20f23cd8c8280f01921/public/favicon.ico -------------------------------------------------------------------------------- /scripts/check-links.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Define the file patterns to search for URLs 4 | file_patterns=("*.json" "*.tsx" "*.ts") 5 | 6 | # Define the output file for the URLs 7 | output_file="urls.txt" 8 | 9 | # Find unique URLs matching the specified pattern in the specified file types 10 | find_unique_urls() { 11 | include_patterns="" 12 | for pattern in "${file_patterns[@]}"; do 13 | include_patterns+="--include=${pattern} " 14 | done 15 | grep -E -o 'https?:\/\/([a-zA-Z0-9.-]*\.)?zenml\.io[^"'\''[:space:]]*' -r --no-filename $include_patterns "$@" | sort -u 16 | } 17 | 18 | # Find unique URLs in the specified file patterns within the "src" directory 19 | find_unique_urls src | sort -u > "$output_file" 20 | 21 | # Run the link checker script 22 | node scripts/check-links.js 23 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { QueryClientProvider } from "@tanstack/react-query"; 2 | import { Toaster } from "@zenml-io/react-component-library"; 3 | import { Suspense } from "react"; 4 | import { RouterProvider } from "react-router-dom"; 5 | import { ReactFlowProvider } from "reactflow"; 6 | import "reactflow/dist/style.css"; 7 | import { AuthProvider } from "./context/AuthContext"; 8 | import { router } from "./router/Router"; 9 | import { queryClient } from "./router/queryclient"; 10 | import { TourProvider } from "./components/tour/TourContext"; 11 | 12 | export function App() { 13 | return ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/app/activate-server/PasswordStep.tsx: -------------------------------------------------------------------------------- 1 | import { SetPasswordForm } from "@/components/survey/SetPassword"; 2 | import { useSurveyContext } from "@/components/survey/SurveyContext"; 3 | import { useServerActivationContext } from "./ServerActivationContext"; 4 | import { SetPasswordStepType } from "@/components/survey/form-schemas"; 5 | 6 | export function SetPasswordStep() { 7 | const { setSurveyStep } = useSurveyContext(); 8 | const { setServerSettings } = useServerActivationContext(); 9 | 10 | function handlePasswordSubmit({ newPassword, username }: SetPasswordStepType) { 11 | setServerSettings((prev) => ({ 12 | ...prev, 13 | admin_password: newPassword, 14 | admin_username: username 15 | })); 16 | setSurveyStep((prev) => prev + 1); 17 | } 18 | 19 | return ( 20 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/app/activate-server/Wizard.tsx: -------------------------------------------------------------------------------- 1 | import StepDisplay from "@/components/survey/StepDisplay"; 2 | import { SuccessStep } from "@/components/survey/SuccessStep"; 3 | import { useSurveyContext } from "@/components/survey/SurveyContext"; 4 | import { SetPasswordStep } from "./PasswordStep"; 5 | import { ServerActivationProvider } from "./ServerActivationContext"; 6 | import { SetServerNameStep } from "./ServerNameStep"; 7 | import { useState } from "react"; 8 | 9 | export function ServerActivationWizard() { 10 | const { surveyStep } = useSurveyContext(); 11 | const [username, setUsername] = useState(""); 12 | 13 | return ( 14 | <> 15 | 16 | 17 | {surveyStep === 1 && } 18 | {surveyStep === 2 && } 19 | {surveyStep === 3 && ( 20 | 21 | )} 22 | 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/app/activate-server/page.tsx: -------------------------------------------------------------------------------- 1 | import { SurveyProvider } from "@/components/survey/SurveyContext"; 2 | import { ServerActivationWizard } from "./Wizard"; 3 | 4 | export default function ServerActivationPage() { 5 | return ( 6 | 7 | 8 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/app/activate-user/AccountDetailsStep.tsx: -------------------------------------------------------------------------------- 1 | import { AccountDetailsForm } from "@/components/survey/AccountDetailsForm"; 2 | import { useSurveyContext } from "@/components/survey/SurveyContext"; 3 | import { AccountDetailForm } from "@/components/survey/form-schemas"; 4 | import { useActivationContext } from "./ActivationContext"; 5 | 6 | export function AccountDetailsStep() { 7 | const { setSurveyStep } = useSurveyContext(); 8 | const { setNewUser, newUser } = useActivationContext(); 9 | 10 | function handleDetailsSubmit({ fullName, getUpdates, email }: AccountDetailForm) { 11 | setNewUser((prev) => ({ 12 | ...prev, 13 | ...(email ? { email } : { email: null }), 14 | full_name: fullName, 15 | email_opted_in: getUpdates 16 | })); 17 | 18 | setSurveyStep((prev) => prev + 1); 19 | } 20 | 21 | return ( 22 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/app/activate-user/PasswordStep.tsx: -------------------------------------------------------------------------------- 1 | import { UpdatePasswordFormType } from "@/components/password/UpdatePasswordSchemas"; 2 | import { SetPasswordForm } from "@/components/survey/SetPassword"; 3 | import { useSurveyContext } from "@/components/survey/SurveyContext"; 4 | import { useActivationContext } from "./ActivationContext"; 5 | 6 | export function SetPasswordStep() { 7 | const { setSurveyStep } = useSurveyContext(); 8 | const { setNewUser } = useActivationContext(); 9 | 10 | function handlePasswordSubmit({ newPassword }: UpdatePasswordFormType) { 11 | setNewUser((prev) => ({ 12 | ...prev, 13 | password: newPassword 14 | })); 15 | setSurveyStep((prev) => prev + 1); 16 | } 17 | 18 | return ; 19 | } 20 | -------------------------------------------------------------------------------- /src/app/activate-user/PrimaryUseStep.tsx: -------------------------------------------------------------------------------- 1 | import { PrimaryUseForm } from "@/components/survey/PrimaryUse"; 2 | import { useSurveyContext } from "@/components/survey/SurveyContext"; 3 | import { PrimaryUseFormType } from "@/components/survey/form-schemas"; 4 | import { useActivationContext } from "./ActivationContext"; 5 | import { UserMetadata } from "@/types/user"; 6 | 7 | export function PrimaryUseStep() { 8 | const { setSurveyStep } = useSurveyContext(); 9 | const { setNewUser } = useActivationContext(); 10 | 11 | function handlePrimaryUseSubmit({ primaryUse }: PrimaryUseFormType) { 12 | const newMetadata: UserMetadata = { 13 | primary_use: primaryUse 14 | }; 15 | setNewUser((prev) => ({ 16 | ...prev, 17 | user_metadata: { ...prev.user_metadata, ...newMetadata } 18 | })); 19 | setSurveyStep((prev) => prev + 1); 20 | } 21 | 22 | return ; 23 | } 24 | -------------------------------------------------------------------------------- /src/app/activate-user/page.tsx: -------------------------------------------------------------------------------- 1 | import { SurveyProvider } from "@/components/survey/SurveyContext"; 2 | import { ActivateWizard } from "./Wizard"; 3 | 4 | export default function ActivatePage() { 5 | return ( 6 |
7 | 8 | 9 | 10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/app/artifacts/Fragments.tsx: -------------------------------------------------------------------------------- 1 | import { Codesnippet } from "@/components/CodeSnippet"; 2 | import { Box } from "@zenml-io/react-component-library"; 3 | import { useSearchParams } from "react-router-dom"; 4 | 5 | export function CommandSection() { 6 | const [searchParams] = useSearchParams(); 7 | const artifactName = searchParams.get("artifact"); 8 | 9 | function getCommand() { 10 | if (artifactName) return `zenml artifact list --name='contains:${artifactName}'`; 11 | return "zenml artifact list"; 12 | } 13 | 14 | return ( 15 | 16 |
17 |

Staying Open Source?

18 |

19 | No problem! Use this CLI command to list your artifacts 20 |

21 |
22 | 23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/app/components/[componentId]/edit/page.tsx: -------------------------------------------------------------------------------- 1 | import { Wrapper } from "@/components/wizard/Wizard"; 2 | import { EditComponentConfig } from "./form-step"; 3 | 4 | export default function ComponentEditPage() { 5 | return ( 6 |
7 | 8 | 9 | 10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/app/components/[componentId]/layout.tsx: -------------------------------------------------------------------------------- 1 | import { StackComponentsDetailHeader } from "@/components/stack-components/component-detail/Header"; 2 | import { Outlet, useParams } from "react-router-dom"; 3 | 4 | export default function ComponentLayout() { 5 | const { componentId } = useParams() as { componentId: string }; 6 | return ( 7 |
8 | 9 | 10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/app/components/[componentId]/page.tsx: -------------------------------------------------------------------------------- 1 | import { StackComponentTabs } from "@/components/stack-components/component-detail/Tabs"; 2 | import { useParams } from "react-router-dom"; 3 | import { RunsBody } from "../../runs/RunsBody"; 4 | import { RunsSelectorContextProvider } from "../../runs/RunsSelectorContext"; 5 | import { StackList } from "../../stacks/StackList"; 6 | 7 | export default function ComponentDetailPage() { 8 | const { componentId } = useParams() as { componentId: string }; 9 | 10 | return ( 11 |
12 | } 15 | runsTabContent={ 16 | 17 | 18 | 19 | } 20 | componentId={componentId} 21 | /> 22 |
23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /src/app/components/create/breadcrumb.ts: -------------------------------------------------------------------------------- 1 | import { useBreadcrumbsContext } from "@/layouts/AuthenticatedLayout/BreadcrumbsContext"; 2 | import { componentBreadcrumb } from "@/components/breadcrumbs/library"; 3 | import { routes } from "@/router/routes"; 4 | import { useEffect } from "react"; 5 | 6 | export function useComponentCreateBreadcrumbs() { 7 | const { setBreadcrumbs } = useBreadcrumbsContext(); 8 | 9 | useEffect(() => { 10 | setBreadcrumbs([ 11 | componentBreadcrumb, 12 | { 13 | label: "Create", 14 | href: routes.components.create 15 | } 16 | ]); 17 | }, [setBreadcrumbs]); 18 | } 19 | -------------------------------------------------------------------------------- /src/app/components/create/header.tsx: -------------------------------------------------------------------------------- 1 | import Container from "@/assets/icons/container.svg?react"; 2 | import { PageHeader } from "../../../components/PageHeader"; 3 | import { useComponentCreateBreadcrumbs } from "./breadcrumb"; 4 | 5 | export function CreateComponentHeader() { 6 | useComponentCreateBreadcrumbs(); 7 | 8 | return ( 9 | 10 |
11 | 12 |

Register new Component

13 |
14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/app/components/create/page.tsx: -------------------------------------------------------------------------------- 1 | import { CreateComponentHeader } from "./header"; 2 | import { RegisterComponentWizard } from "./wizard"; 3 | 4 | export default function ComponentCreatePage() { 5 | return ( 6 |
7 | 8 |
9 | 10 |
11 |
12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /src/app/components/page.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentSelectorContextProvider } from "./selector-context"; 2 | import { StackComponentList } from "./StackComponentList"; 3 | 4 | export default function ComponentsPage() { 5 | return ( 6 |
7 | 8 | 9 | 10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/app/components/selector-context.tsx: -------------------------------------------------------------------------------- 1 | import { createDataTableConsumerContext } from "@/components/tables/helper"; 2 | import { componentQueries } from "@/data/components"; 3 | import { useDeleteComponent } from "@/data/components/delete-component"; 4 | import { useBulkDelete } from "@/lib/bulk-delete"; 5 | 6 | export const { 7 | Context: ComponentSelectorContext, 8 | ContextProvider: ComponentSelectorContextProvider, 9 | useContext: useComponentSelectorContext 10 | } = createDataTableConsumerContext(); 11 | 12 | export function useComponentBulkDelete() { 13 | const { setRowSelection } = useComponentSelectorContext(); 14 | 15 | const { mutateAsync } = useDeleteComponent(); 16 | 17 | async function handleDelete(id: string) { 18 | await mutateAsync({ componentId: id }); 19 | } 20 | 21 | return useBulkDelete({ 22 | deleteFn: handleDelete, 23 | queryKeyToInvalidate: componentQueries.all, 24 | setRowSelection 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /src/app/components/service.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { StackComponentListParams } from "@/types/components"; 3 | import { useSearchParams } from "react-router-dom"; 4 | 5 | const DEFAULT_PAGE = 1; 6 | 7 | const filterParamsSchema = z.object({ 8 | page: z.coerce.number().min(DEFAULT_PAGE).optional().default(DEFAULT_PAGE).catch(DEFAULT_PAGE), 9 | name: z.string().optional(), 10 | operator: z.enum(["and", "or"]).optional() 11 | }); 12 | 13 | export function useComponentlistQueryParams(): StackComponentListParams { 14 | const [searchParams] = useSearchParams(); 15 | 16 | const { page, name, operator } = filterParamsSchema.parse({ 17 | page: searchParams.get("page") || undefined, 18 | name: searchParams.get("name") || undefined 19 | }); 20 | 21 | return { page, name, logical_operator: operator }; 22 | } 23 | -------------------------------------------------------------------------------- /src/app/devices/verify/DeviceInfo.tsx: -------------------------------------------------------------------------------- 1 | import { Device } from "@/types/devices"; 2 | import { Box } from "@zenml-io/react-component-library"; 3 | 4 | type Props = { 5 | device: Device; 6 | }; 7 | 8 | export function DeviceInfo({ device }: Props) { 9 | return ( 10 | 11 |
12 |
13 |
IP Address
14 |
{device.body?.ip_address}
15 |
16 | {device.metadata?.city && device.metadata?.country && ( 17 |
18 |
Location
19 |
20 | {device.metadata?.city}, {device.metadata?.country} 21 |
22 |
23 | )} 24 |
25 |
Hostname
26 |
{device.body?.hostname}
27 |
28 |
29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /src/app/devices/verify/Success.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from "@zenml-io/react-component-library"; 2 | import CheckCircle from "@/assets/icons/check-circle.svg?react"; 3 | 4 | export function DeviceVerificationSuccess() { 5 | return ( 6 | 7 | 8 |
9 |

You successfully added your device

10 |

11 | You may close this screen and return to your CLI. 12 |

13 |
14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/app/devices/verify/service.ts: -------------------------------------------------------------------------------- 1 | import { DeviceQueryParams } from "@/types/devices"; 2 | import { useSearchParams } from "react-router-dom"; 3 | 4 | import { z } from "zod"; 5 | 6 | const filterParamsSchema = z.object({ 7 | device_id: z.string().optional(), 8 | user_code: z.string().optional() 9 | }); 10 | 11 | export function useDeviceSearchParams(): DeviceQueryParams & { device_id?: string } { 12 | const [searchParams] = useSearchParams(); 13 | 14 | const { device_id, user_code } = filterParamsSchema.parse({ 15 | device_id: searchParams.get("device_id") || undefined, 16 | user_code: searchParams.get("user_code") || undefined 17 | }); 18 | 19 | return { user_code, device_id }; 20 | } 21 | 22 | export const deviceRegistrationFormSchema = z.object({ 23 | trustDevice: z.boolean() 24 | }); 25 | 26 | export type DeviceRegistrationFormType = z.infer; 27 | -------------------------------------------------------------------------------- /src/app/login/page.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from "@zenml-io/react-component-library"; 2 | import { LoginForm } from "./LoginForm"; 3 | 4 | export default function LoginPage() { 5 | return ( 6 | 7 |
8 |

Log in to your account

9 |

10 | Please, fill in your details to log in to your ZenML account. 11 |

12 |
13 | 14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/app/models/Fragments.tsx: -------------------------------------------------------------------------------- 1 | import { Codesnippet } from "@/components/CodeSnippet"; 2 | import { Box } from "@zenml-io/react-component-library"; 3 | import { useSearchParams } from "react-router-dom"; 4 | 5 | export function CommandSection() { 6 | const [searchParams] = useSearchParams(); 7 | const modelName = searchParams.get("model"); 8 | 9 | function getCommand() { 10 | if (modelName) return `zenml model list --name='contains:${modelName}'`; 11 | return "zenml model list"; 12 | } 13 | 14 | return ( 15 | 16 |
17 |

Staying Open Source?

18 |

19 | No problem! Use this CLI command to list your models 20 |

21 |
22 | 23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/app/onboarding/Header.tsx: -------------------------------------------------------------------------------- 1 | import { useCurrentUser } from "@/data/users/current-user-query"; 2 | import { getUsername } from "@/lib/user"; 3 | import { Skeleton } from "@zenml-io/react-component-library"; 4 | 5 | export function HeaderOnboardingBox() { 6 | return ( 7 |
8 |
9 |

10 | Hi 11 | 12 |

13 |

14 | Welcome to ZenML. This is the beginning to get the most advantage of all the Pro features 15 | of ZenML. 16 |

17 |
18 |
19 | ); 20 | } 21 | 22 | function Username() { 23 | const user = useCurrentUser(); 24 | 25 | if (user.isError) return null; 26 | if (user.isPending) return ; 27 | const name = getUsername(user.data); 28 | return <>{name ? `, ${name}` : ""}; 29 | } 30 | -------------------------------------------------------------------------------- /src/app/onboarding/Setup/constant.ts: -------------------------------------------------------------------------------- 1 | export const SCRIPT_CONFIG = { 2 | aws: { 3 | stackName: "zenml-aws", 4 | region: "eu-central-1", 5 | scriptLink: 6 | "https://raw.githubusercontent.com/zenml-io/zenml/refs/heads/main/infra/scripts/aws-artifact-store-setup.sh", 7 | cliName: "AWS CLI", 8 | integration: "s3" 9 | }, 10 | azure: { 11 | stackName: "zenml-azure", 12 | region: "francecentral", 13 | scriptLink: 14 | "https://raw.githubusercontent.com/zenml-io/zenml/refs/heads/main/infra/scripts/azure-artifact-store-setup.sh", 15 | cliName: "Azure CLI", 16 | integration: "azure" 17 | }, 18 | 19 | gcp: { 20 | stackName: "zenml-gcp", 21 | region: "europe-west1", 22 | scriptLink: 23 | "https://raw.githubusercontent.com/zenml-io/zenml/refs/heads/main/infra/scripts/gcp-artifact-store-setup.sh", 24 | cliName: "gcloud CLI", 25 | integration: "gcp" 26 | } 27 | } as const; 28 | -------------------------------------------------------------------------------- /src/app/onboarding/Setup/set-project.tsx: -------------------------------------------------------------------------------- 1 | import { Codesnippet } from "@/components/CodeSnippet"; 2 | 3 | type Props = { 4 | projectName: string; 5 | }; 6 | 7 | export function SetProject({ projectName }: Props) { 8 | return ( 9 |
10 |

Set your project

11 | "}`} 14 | /> 15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/app/pipelines/PipelinesTab/ButtonGroup.tsx: -------------------------------------------------------------------------------- 1 | import { DeletePipelineAlert } from "./DeletePipelineAlert"; 2 | import { usePipelineSelectorContext } from "./PipelineSelectorContext"; 3 | 4 | export function PipelinesButtonGroup() { 5 | const { selectedRowCount } = usePipelineSelectorContext(); 6 | return ( 7 |
8 |
{`${selectedRowCount} Pipeline${selectedRowCount > 1 ? "s" : ""} selected`}
9 | 10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/app/pipelines/PipelinesTab/PipelineSelectorContext.tsx: -------------------------------------------------------------------------------- 1 | import { createDataTableConsumerContext } from "@/components/tables/helper"; 2 | import { pipelineQueries } from "@/data/pipelines"; 3 | import { useDeletePipeline } from "@/data/pipelines/delete-pipeline"; 4 | 5 | import { useBulkDelete } from "@/lib/bulk-delete"; 6 | 7 | export const { 8 | Context: PipelineSelectorContext, 9 | ContextProvider: PipelineSelectorContextProvider, 10 | useContext: usePipelineSelectorContext 11 | } = createDataTableConsumerContext(); 12 | 13 | export function usePipelineBulkDelete() { 14 | const { setRowSelection } = usePipelineSelectorContext(); 15 | 16 | const { mutateAsync } = useDeletePipeline(); 17 | 18 | async function handleDelete(id: string) { 19 | await mutateAsync({ pipelineId: id }); 20 | } 21 | 22 | return useBulkDelete({ 23 | deleteFn: handleDelete, 24 | queryKeyToInvalidate: pipelineQueries.all, 25 | setRowSelection 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /src/app/pipelines/PipelinesTab/service.ts: -------------------------------------------------------------------------------- 1 | import { PipelineListParams } from "@/types/pipelines"; 2 | import { useSearchParams } from "react-router-dom"; 3 | 4 | import { z } from "zod"; 5 | 6 | const DEFAULT_PAGE = 1; 7 | 8 | const filterParamsSchema = z.object({ 9 | page: z.coerce.number().min(DEFAULT_PAGE).optional().default(DEFAULT_PAGE).catch(DEFAULT_PAGE), 10 | name: z.string().optional(), 11 | operator: z.enum(["and", "or"]).optional() 12 | }); 13 | 14 | export function usePipelineOverviewSearchParams(): PipelineListParams { 15 | const [searchParams] = useSearchParams(); 16 | 17 | const { page, name, operator } = filterParamsSchema.parse({ 18 | page: searchParams.get("page") || undefined, 19 | name: searchParams.get("name") || undefined, 20 | operator: searchParams.get("operator") || undefined 21 | }); 22 | 23 | return { page, name, logical_operator: operator }; 24 | } 25 | -------------------------------------------------------------------------------- /src/app/pipelines/[namespace]/Header.tsx: -------------------------------------------------------------------------------- 1 | import Pipeline from "@/assets/icons/pipeline.svg?react"; 2 | import { PageHeader } from "@/components/PageHeader"; 3 | type Props = { 4 | namespace: string; 5 | }; 6 | 7 | export function Header({ namespace }: Props) { 8 | return ( 9 | 10 |
11 | 12 |

{decodeURIComponent(namespace)}

13 |
14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/app/pipelines/[namespace]/breadcrumb.ts: -------------------------------------------------------------------------------- 1 | import { pipelineBreadcrumb } from "@/components/breadcrumbs/library"; 2 | import { useBreadcrumbsContext } from "@/layouts/AuthenticatedLayout/BreadcrumbsContext"; 3 | import { routes } from "@/router/routes"; 4 | import { useEffect } from "react"; 5 | 6 | export function usePipelineDetailBreadcrumbs(namespace?: string) { 7 | const { setBreadcrumbs } = useBreadcrumbsContext(); 8 | 9 | useEffect(() => { 10 | if (namespace) { 11 | setBreadcrumbs([ 12 | pipelineBreadcrumb, 13 | { 14 | label: namespace, 15 | href: routes.projects.pipelines.namespace(namespace) 16 | } 17 | ]); 18 | } 19 | }, [setBreadcrumbs, namespace]); 20 | } 21 | -------------------------------------------------------------------------------- /src/app/pipelines/[namespace]/page.tsx: -------------------------------------------------------------------------------- 1 | import { useParams } from "react-router-dom"; 2 | import { RunsSelectorContextProvider } from "../../runs/RunsSelectorContext"; 3 | import { usePipelineDetailBreadcrumbs } from "./breadcrumb"; 4 | import { Header } from "./Header"; 5 | import { PipelineRunsTable } from "./RunsTable"; 6 | 7 | export default function PipelineNamespacePage() { 8 | const { namespace } = useParams() as { namespace: string }; 9 | usePipelineDetailBreadcrumbs(namespace); 10 | 11 | return ( 12 |
13 | 14 |
15 | 16 | 17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/app/pipelines/[namespace]/service.ts: -------------------------------------------------------------------------------- 1 | import { PipelineRunOvervieweParams } from "@/types/pipeline-runs"; 2 | import { useSearchParams } from "react-router-dom"; 3 | import { z } from "zod"; 4 | 5 | const DEFAULT_PAGE = 1; 6 | 7 | const filterParamsSchema = z.object({ 8 | page: z.coerce.number().min(DEFAULT_PAGE).optional().default(DEFAULT_PAGE).catch(DEFAULT_PAGE), 9 | name: z.string().optional(), 10 | 11 | operator: z.enum(["and", "or"]).optional() 12 | }); 13 | 14 | export function usePipelineRunParams(): PipelineRunOvervieweParams { 15 | const [searchParams] = useSearchParams(); 16 | 17 | const { page, name, operator } = filterParamsSchema.parse({ 18 | page: searchParams.get("page") || undefined, 19 | name: searchParams.get("name") || undefined 20 | }); 21 | 22 | return { page, name, logical_operator: operator }; 23 | } 24 | -------------------------------------------------------------------------------- /src/app/pipelines/page.tsx: -------------------------------------------------------------------------------- 1 | import { PipelineSelectorContextProvider } from "./PipelinesTab/PipelineSelectorContext"; 2 | import { PipelinesBody } from "./PipelinesTab/PipelinesBody"; 3 | 4 | export default function PipelinesPage() { 5 | return ( 6 | 7 | 8 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/app/projects/page.tsx: -------------------------------------------------------------------------------- 1 | import { ProjectList } from "./project-list"; 2 | import { ProjectsSearchBar } from "./searchbar"; 3 | 4 | export default function ProjectsPage() { 5 | return ( 6 |
7 | 8 | 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /src/app/run-templates/page.tsx: -------------------------------------------------------------------------------- 1 | import { CtaSection } from "./TemplateBody"; 2 | 3 | export default function TemplatesPage() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /src/app/runs/ButtonGroup.tsx: -------------------------------------------------------------------------------- 1 | import { DeleteRunAlert } from "./DeleteRunAlert"; 2 | import { useRunsSelectorContext } from "./RunsSelectorContext"; 3 | 4 | export function RunsButtonGroup() { 5 | const { selectedRowCount } = useRunsSelectorContext(); 6 | return ( 7 |
8 |
{`${selectedRowCount} Run${selectedRowCount > 1 ? "s" : ""} selected`}
9 | 10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/app/runs/RunsSelectorContext.tsx: -------------------------------------------------------------------------------- 1 | import { createDataTableConsumerContext } from "@/components/tables/helper"; 2 | import { useDeleteRun } from "@/data/pipeline-runs/delete-run"; 3 | import { useBulkDelete } from "@/lib/bulk-delete"; 4 | 5 | export const { 6 | Context: RunsSelectorContext, 7 | ContextProvider: RunsSelectorContextProvider, 8 | useContext: useRunsSelectorContext 9 | } = createDataTableConsumerContext(); 10 | 11 | export function useRunBulkDelete() { 12 | const { setRowSelection } = useRunsSelectorContext(); 13 | 14 | const { mutateAsync } = useDeleteRun(); 15 | 16 | async function handleDelete(id: string) { 17 | await mutateAsync({ runId: id }); 18 | } 19 | 20 | return useBulkDelete({ 21 | deleteFn: handleDelete, 22 | queryKeyToInvalidate: ["runs"], 23 | setRowSelection 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /src/app/runs/[id]/_Tabs/Overview/index.tsx: -------------------------------------------------------------------------------- 1 | import { AlertPanels } from "./AlertPanels"; 2 | import { Details } from "./Details"; 3 | import { OrchestratorCollapsible } from "./Orchestrator"; 4 | 5 | export function OverviewTab() { 6 | return ( 7 |
8 | 9 |
10 | 11 |
12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /src/app/runs/[id]/_Tabs/service.ts: -------------------------------------------------------------------------------- 1 | import { useSearchParams } from "react-router-dom"; 2 | import { z } from "zod"; 3 | 4 | const tabParamSchema = z.object({ 5 | tab: z 6 | .enum(["overview", "configuration", "metadata", "stack"]) 7 | .optional() 8 | .default("overview") 9 | .catch("overview") 10 | }); 11 | 12 | export function useSelectedTab() { 13 | const [searchParams] = useSearchParams(); 14 | const { tab } = tabParamSchema.parse({ 15 | tab: searchParams.get("tab") || undefined 16 | }); 17 | 18 | return tab; 19 | } 20 | -------------------------------------------------------------------------------- /src/app/runs/page.tsx: -------------------------------------------------------------------------------- 1 | import { RunsBody } from "./RunsBody"; 2 | import { RunsSelectorContextProvider } from "./RunsSelectorContext"; 3 | 4 | export default function RunsPage() { 5 | return ( 6 | 7 | 8 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/app/settings/connectors/[id]/components/page.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentSelectorContextProvider } from "@/app/components/selector-context"; 2 | import { StackComponentList } from "@/app/components/StackComponentList"; 3 | import { useParams } from "react-router-dom"; 4 | 5 | export default function ConnectorComponentPage() { 6 | const { connectorId } = useParams() as { connectorId: string }; 7 | 8 | return ( 9 | 10 | 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/app/settings/connectors/[id]/configuration/page.tsx: -------------------------------------------------------------------------------- 1 | import { BasicParams } from "./params"; 2 | import { ConfigurationPanel } from "./configuration"; 3 | 4 | export default function ConnectorConfigPage() { 5 | return ( 6 |
7 |
8 | 9 |
10 |
11 | 12 |
13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/app/settings/connectors/[id]/resources/page.tsx: -------------------------------------------------------------------------------- 1 | import { useResourcesContext } from "@/layouts/connectors-detail/resources-context"; 2 | import { ResourcesList } from "./resources-list"; 3 | import { ConnectorVerifyBox } from "./verify-box"; 4 | export default function ResourcesContent() { 5 | const { resources } = useResourcesContext(); 6 | if (resources === null) { 7 | return ; 8 | } 9 | 10 | return ; 11 | } 12 | -------------------------------------------------------------------------------- /src/app/settings/connectors/[id]/resources/verify-box.tsx: -------------------------------------------------------------------------------- 1 | import AlertCircle from "@/assets/icons/alert-circle.svg?react"; 2 | import { VerifyButton } from "./verify-button"; 3 | import { FallbackIcon } from "@/components/fallback/icon"; 4 | 5 | export function ConnectorVerifyBox() { 6 | return ( 7 |
8 |
9 | } /> 10 |
11 |

Please verify your service connector

12 |

13 | We'll check your connection and load available resources. 14 |
15 | This may take up to a minute. 16 |

17 |
18 | 19 | Verify now 20 | 21 |
22 |
23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /src/app/settings/connectors/create/breadcrumbs.ts: -------------------------------------------------------------------------------- 1 | import { connectorBreadcrumb } from "@/components/breadcrumbs/library"; 2 | import { useBreadcrumbsContext } from "@/layouts/AuthenticatedLayout/BreadcrumbsContext"; 3 | import { routes } from "@/router/routes"; 4 | import { useEffect } from "react"; 5 | 6 | export function useCreateConnectorBreadcrumbs() { 7 | const { setBreadcrumbs } = useBreadcrumbsContext(); 8 | 9 | useEffect(() => { 10 | setBreadcrumbs([ 11 | ...connectorBreadcrumb, 12 | { 13 | label: "New Connector", 14 | href: routes.settings.connectors.create 15 | } 16 | ]); 17 | }, [setBreadcrumbs]); 18 | } 19 | -------------------------------------------------------------------------------- /src/app/settings/connectors/create/configuration/debounced-check.ts: -------------------------------------------------------------------------------- 1 | import { fetchServiceConnectorList } from "@/data/service-connectors/connector-list"; 2 | import AwesomeDebouncePromise from "awesome-debounce-promise"; 3 | 4 | export const validateConnectorName = AwesomeDebouncePromise(async (name: string) => { 5 | try { 6 | const resData = await fetchServiceConnectorList({ name }); 7 | if (resData.total > 0) { 8 | return false; 9 | } 10 | return true; 11 | } catch (error) { 12 | console.error(error); 13 | return false; 14 | } 15 | }, 500); 16 | -------------------------------------------------------------------------------- /src/app/settings/connectors/create/configuration/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const connectorConfigurationSchema = z.object({ 4 | description: z.string().trim(), 5 | authMethod: z.string().trim().min(1, "Auth method is required"), 6 | resourceType: z.string().trim().min(1, "Resource type is required"), 7 | isValid: z.boolean().nullable(), 8 | skipValidation: z.boolean() 9 | }); 10 | 11 | // define a zod schema for this ts type {name: string; skipValidation:boolean; [key: string]: unknown} 12 | 13 | export type ConnectorConfigForm = z.infer & { 14 | name: string; 15 | [key: string]: unknown; 16 | }; 17 | -------------------------------------------------------------------------------- /src/app/settings/connectors/create/configuration/skip-verify.tsx: -------------------------------------------------------------------------------- 1 | import { Switch } from "@zenml-io/react-component-library/components/client"; 2 | import { Controller, useFormContext } from "react-hook-form"; 3 | import { ConnectorConfigForm } from "./schema"; 4 | export function SkipVerify() { 5 | const { control } = useFormContext(); 6 | 7 | return ( 8 | ( 12 |
13 | 17 | {error &&

{error.message?.toString()}

} 18 |
19 | )} 20 | /> 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/app/settings/connectors/create/connector-type/description.tsx: -------------------------------------------------------------------------------- 1 | import Cloud from "@/assets/icons/cloud.svg?react"; 2 | 3 | export function StepDescription() { 4 | return ( 5 |
6 |
7 | 8 |

Select a Service Connector Type

9 |
10 |

11 | Select the type of service connector you want to create. This will allow you to integrate 12 | your cloud resources with ZenML for model deployment and management. 13 |

14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/app/settings/connectors/create/connector-type/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const connectorTypeSelectSchema = z.object({ 4 | connectorType: z.string().trim().min(1) 5 | }); 6 | 7 | export type ConnectorTypeSelectForm = z.infer; 8 | -------------------------------------------------------------------------------- /src/app/settings/connectors/create/header.tsx: -------------------------------------------------------------------------------- 1 | import { PageHeader } from "@/components/PageHeader"; 2 | import { useCreateConnectorBreadcrumbs } from "./breadcrumbs"; 3 | 4 | export function CreateConnectorHeader() { 5 | useCreateConnectorBreadcrumbs(); 6 | 7 | return ( 8 | 9 |

Register a Service Connector

10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/app/settings/connectors/create/page.tsx: -------------------------------------------------------------------------------- 1 | import { WizardProvider } from "@/context/WizardContext"; 2 | import { CreateConnectorHeader } from "./header"; 3 | import { LeftSideMenu } from "@/components/wizard/LeftSideMenu"; 4 | import { ConnectorRegistrationWizard } from "./wizard"; 5 | const menuEntries = ["Connector Type", "Configuration"]; 6 | 7 | export default function RegisterConnector() { 8 | return ( 9 |
10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 | 18 |
19 |
20 |
21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/app/settings/connectors/create/success/success-box.tsx: -------------------------------------------------------------------------------- 1 | import CheckCircle from "@/assets/icons/check-circle.svg?react"; 2 | 3 | type Props = { 4 | connectorName: string; 5 | }; 6 | 7 | export function ConnectorSuccessBox({ connectorName }: Props) { 8 | return ( 9 |
10 | 11 |

12 | You successfully registered the service connector `{connectorName}` and can now create new 13 | resources 14 |

15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/app/settings/connectors/create/success/table.tsx: -------------------------------------------------------------------------------- 1 | import { DataTable } from "@zenml-io/react-component-library/components/client"; 2 | import { ServiceConnector } from "@/types/service-connectors"; 3 | import { useServiceConnectorListColumns } from "./columns"; 4 | 5 | type Props = { 6 | connector: ServiceConnector; 7 | }; 8 | export function ConnectorSuccessTable({ connector }: Props) { 9 | const columns = useServiceConnectorListColumns(); 10 | return ( 11 |
12 |
13 |
14 | 15 |
16 |
17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/app/settings/connectors/create/wizard.tsx: -------------------------------------------------------------------------------- 1 | import { useWizardContext } from "@/context/WizardContext"; 2 | import { RegisterConnectorProvider } from "./create-context"; 3 | import { ConnectorTypeStep } from "./connector-type"; 4 | import { ConnectorConfigurationStep } from "./configuration"; 5 | import { ConnectorSuccessStep } from "./success"; 6 | 7 | export function ConnectorRegistrationWizard() { 8 | return ( 9 | 10 | 11 | 12 | ); 13 | } 14 | 15 | function WizardContent() { 16 | const { currentStep } = useWizardContext(); 17 | 18 | if (currentStep === 0) return ; 19 | if (currentStep === 1) return ; 20 | if (currentStep === 2) return ; 21 | } 22 | -------------------------------------------------------------------------------- /src/app/settings/connectors/header.tsx: -------------------------------------------------------------------------------- 1 | export function ServiceConnectorListHeader() { 2 | return ( 3 |
4 |

Service Connectors

5 |

6 | Configure and manage your service connectors.{" "} 7 | 13 | Learn More 14 | 15 |

16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/app/settings/connectors/list-content.tsx: -------------------------------------------------------------------------------- 1 | import { SearchBar } from "./searchbar"; 2 | import { ServiceConnectorListQueryParams } from "@/types/service-connectors"; 3 | import { useConnectorListParams } from "./use-connector-params"; 4 | import { ServiceConnectorList } from "./connector-list"; 5 | 6 | export function ServiceConnectorListContent() { 7 | const params = useConnectorListParams(); 8 | 9 | const queryParams: ServiceConnectorListQueryParams = { 10 | ...params, 11 | sort_by: "desc:updated" 12 | }; 13 | return ( 14 | <> 15 | 16 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/app/settings/connectors/new-connector-button.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@zenml-io/react-component-library/components/server"; 2 | import Plus from "@/assets/icons/plus.svg?react"; 3 | import { routes } from "@/router/routes"; 4 | 5 | import { Link } from "react-router-dom"; 6 | export function NewConnectorButton() { 7 | return ( 8 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/app/settings/connectors/page.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from "@zenml-io/react-component-library/components/server"; 2 | import { ServiceConnectorListHeader } from "./header"; 3 | import { ServiceConnectorListContent } from "./list-content"; 4 | import { ConnectorSelectorContextProvider } from "./selector-context"; 5 | export default function ConnectorsPage() { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/app/settings/connectors/selector-context.ts: -------------------------------------------------------------------------------- 1 | import { createDataTableConsumerContext } from "@/components/tables/helper"; 2 | import { serviceConnectorQueries } from "@/data/service-connectors"; 3 | import { useDeleteConnector } from "@/data/service-connectors/delete-service-connector"; 4 | import { useBulkDelete } from "@/lib/bulk-delete"; 5 | 6 | export const { 7 | Context: ConnectorSelectorContext, 8 | ContextProvider: ConnectorSelectorContextProvider, 9 | useContext: useConnectorSelectorContext 10 | } = createDataTableConsumerContext(); 11 | 12 | export function useConnectorBulkDelete() { 13 | const { setRowSelection } = useConnectorSelectorContext(); 14 | 15 | const { mutateAsync } = useDeleteConnector(); 16 | 17 | async function handleDelete(id: string) { 18 | await mutateAsync({ connectorId: id }); 19 | } 20 | 21 | return useBulkDelete({ 22 | deleteFn: handleDelete, 23 | queryKeyToInvalidate: [...serviceConnectorQueries.connectors], 24 | setRowSelection 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /src/app/settings/connectors/use-connector-params.ts: -------------------------------------------------------------------------------- 1 | import { ServiceConnectorListQueryParams } from "@/types/service-connectors"; 2 | import { useSearchParams } from "react-router-dom"; 3 | import { z } from "zod"; 4 | 5 | const DEFAULT_PAGE = 1; 6 | 7 | const filterParamsSchema = z.object({ 8 | page: z.coerce.number().min(DEFAULT_PAGE).optional().default(DEFAULT_PAGE).catch(DEFAULT_PAGE), 9 | name: z.string().optional(), 10 | operator: z.enum(["and", "or"]).optional() 11 | }); 12 | 13 | export function useConnectorListParams(): ServiceConnectorListQueryParams { 14 | const [searchParams] = useSearchParams(); 15 | 16 | const { page, name, operator } = filterParamsSchema.parse({ 17 | page: searchParams.get("page") || undefined, 18 | name: searchParams.get("name") || undefined, 19 | sortBy: searchParams.get("sort_by") || undefined 20 | }); 21 | 22 | return { page, name, logical_operator: operator }; 23 | } 24 | -------------------------------------------------------------------------------- /src/app/settings/general/GeneralFormSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const generalFormSchema = z.object({ 4 | serverName: z.string().min(1) 5 | }); 6 | 7 | export type GeneralFormType = z.infer; 8 | -------------------------------------------------------------------------------- /src/app/settings/general/page.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from "@zenml-io/react-component-library"; 2 | import { GeneralForm } from "./GeneralForm"; 3 | 4 | export default function GeneralSettingsPage() { 5 | return ( 6 | 7 |

General

8 | 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /src/app/settings/members/page.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from "@zenml-io/react-component-library"; 2 | import MembersTable from "./MembersTable"; 3 | 4 | export default function MembersPage() { 5 | return ( 6 | 7 |

Members

8 | 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /src/app/settings/members/service.ts: -------------------------------------------------------------------------------- 1 | import { ListUserParams } from "@/types/user"; 2 | import { useSearchParams } from "react-router-dom"; 3 | 4 | import { z } from "zod"; 5 | 6 | const DEFAULT_PAGE = 1; 7 | 8 | const filterParamsSchema = z.object({ 9 | page: z.coerce.number().min(DEFAULT_PAGE).optional().default(DEFAULT_PAGE).catch(DEFAULT_PAGE), 10 | name: z.string().optional(), 11 | operator: z.enum(["and", "or"]).optional() 12 | }); 13 | 14 | export function useUserOverviewSearchParams(): ListUserParams { 15 | const [searchParams] = useSearchParams(); 16 | 17 | const { page, name, operator } = filterParamsSchema.parse({ 18 | page: searchParams.get("page") || undefined, 19 | name: searchParams.get("name") || undefined, 20 | operator: searchParams.get("operator") || undefined 21 | }); 22 | 23 | return { page, name, logical_operator: operator }; 24 | } 25 | -------------------------------------------------------------------------------- /src/app/settings/notifications/page.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Skeleton } from "@zenml-io/react-component-library"; 2 | import { NotificationsForm } from "./NotificationsForm"; 3 | import { useServerSettings } from "@/data/server/get-server-settings"; 4 | 5 | export default function NotificationsPage() { 6 | const { data } = useServerSettings({ throwOnError: true }); 7 | 8 | return ( 9 | 10 |
11 |

Notifications

12 |

13 | ZenML comes equipped with default widgets designed to enhance your experience by analyzing 14 | usage patterns, gathering your feedback, and ensuring you stay informed about our latest 15 | updates and features. 16 |

17 |
18 |
19 | {data ? : } 20 |
21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/app/settings/profile/UpdateProfileSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const UpdateProfileFormSchema = z 4 | .object({ 5 | fullName: z.union([z.string(), z.literal("")]), 6 | username: z.union([z.string(), z.literal("")]), 7 | email: z.union([z.string().email(), z.literal("")]) 8 | }) 9 | .refine((data) => { 10 | return data.fullName !== "" || data.username !== "" || data.email !== ""; 11 | }); 12 | 13 | export type UpdateProfileForm = z.infer; 14 | -------------------------------------------------------------------------------- /src/app/settings/profile/page.tsx: -------------------------------------------------------------------------------- 1 | import { Avatar, AvatarFallback, Box, Skeleton } from "@zenml-io/react-component-library"; 2 | import { UpdateProfileForm } from "./UpdateProfileForm"; 3 | import { useCurrentUser } from "@/data/users/current-user-query"; 4 | 5 | export default function ProfilePage() { 6 | const { data: user } = useCurrentUser(); 7 | 8 | return ( 9 | 10 |

Profile

11 |
12 | {user ? : } 13 | 14 | {user ? ( 15 | 16 | 17 | {user.name[0]} 18 | 19 | 20 | ) : ( 21 | 22 | )} 23 |
24 |
25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /src/app/settings/repositories/page.tsx: -------------------------------------------------------------------------------- 1 | import { Box, ProgressOutstanding } from "@zenml-io/react-component-library"; 2 | import { CommandSection, HeaderBox, InfoBox } from "./Fragments"; 3 | 4 | export default function RepositoriesPage() { 5 | return ( 6 | 7 |

Repositories

8 | 9 | 10 |
11 | 12 | Administering your Code Repositories 13 |
14 | 15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/app/settings/secrets/[id]/breadcrumbs.ts: -------------------------------------------------------------------------------- 1 | import { SecretsBreadcrumb } from "@/components/breadcrumbs/library"; 2 | import { useBreadcrumbsContext } from "@/layouts/AuthenticatedLayout/BreadcrumbsContext"; 3 | import { routes } from "@/router/routes"; 4 | import { Secret } from "@/types/secret"; 5 | import { useEffect } from "react"; 6 | 7 | export function useSecretDetailBreadcrumbs(secret?: Secret) { 8 | const { setBreadcrumbs } = useBreadcrumbsContext(); 9 | 10 | useEffect(() => { 11 | if (secret) { 12 | setBreadcrumbs([ 13 | ...SecretsBreadcrumb, 14 | { 15 | label: secret.name, 16 | href: routes.settings.secrets.detail(secret.id) 17 | } 18 | ]); 19 | } 20 | }, [secret, setBreadcrumbs]); 21 | } 22 | -------------------------------------------------------------------------------- /src/app/settings/secrets/form-schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const secretFormSchema = z.object({ 4 | secretName: z.string().min(1, "Secret Name is required"), 5 | keysValues: z.array( 6 | z.object({ 7 | key: z.string().min(1, "Key is required"), 8 | value: z.string().min(1, "Value is required"), 9 | showPassword: z.boolean().optional() 10 | }) 11 | ) 12 | }); 13 | 14 | export type SecretFormType = z.infer; 15 | -------------------------------------------------------------------------------- /src/app/settings/secrets/page.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from "@zenml-io/react-component-library"; 2 | import SecretsTable from "./SecretsTable"; 3 | 4 | export default function SecretsPage() { 5 | return ( 6 | 7 |
8 |

Secrets

9 | 10 |

11 | Configure and manage your pipeline secrets and configurations.{" "} 12 | 18 | Learn More 19 | 20 |

21 |
22 | 23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/app/settings/secrets/service.ts: -------------------------------------------------------------------------------- 1 | import { ListSecretsParams } from "@/types/secret"; 2 | import { useSearchParams } from "react-router-dom"; 3 | 4 | import { z } from "zod"; 5 | 6 | const DEFAULT_PAGE = 1; 7 | 8 | const filterParamsSchema = z.object({ 9 | page: z.coerce.number().min(DEFAULT_PAGE).optional().default(DEFAULT_PAGE).catch(DEFAULT_PAGE), 10 | name: z.string().optional(), 11 | operator: z.enum(["and", "or"]).optional() 12 | }); 13 | 14 | export function useSecretOverviewSearchParams(): ListSecretsParams { 15 | const [searchParams] = useSearchParams(); 16 | 17 | const { page, name, operator } = filterParamsSchema.parse({ 18 | page: searchParams.get("page") || undefined, 19 | name: searchParams.get("name") || undefined, 20 | operator: searchParams.get("operator") || undefined 21 | }); 22 | 23 | return { page, name, logical_operator: operator }; 24 | } 25 | -------------------------------------------------------------------------------- /src/app/settings/service-accounts/ButtonGroup.tsx: -------------------------------------------------------------------------------- 1 | import { DeleteServiceAccountAlert } from "./DeleteAlert"; 2 | import { useServiceAccountSelectorContext } from "./SelectorContext"; 3 | 4 | export function ServiceAccountsButtonGroup() { 5 | const { selectedRowCount } = useServiceAccountSelectorContext(); 6 | return ( 7 |
8 |
{`${ 9 | selectedRowCount 10 | } Service Account${selectedRowCount > 1 ? "s" : ""} selected`}
11 | 12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/app/settings/service-accounts/Fallback.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from "@zenml-io/react-component-library/components/server"; 2 | import { AddServiceAccountDialog } from "./AddServiceAccount"; 3 | 4 | export default function ServiceAccountFallback() { 5 | return ( 6 | 7 |
8 |

9 | There are no service accounts yet. 10 |

11 |

12 | Create your first one now to automate your processes securely with ZenML. 13 |

14 |
15 | 16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/app/settings/service-accounts/[service-account-id]/ButtonGroup.tsx: -------------------------------------------------------------------------------- 1 | import { DeleteApiKey } from "./DeleteApiKeyDialog"; 2 | import { useApiKeySelectorContext } from "./SelectorContext"; 3 | 4 | export function ApiKeyButtonGroup({ serviceAccountId }: { serviceAccountId: string }) { 5 | const { selectedRowCount } = useApiKeySelectorContext(); 6 | return ( 7 |
8 |
{`${ 9 | selectedRowCount 10 | } Api Key${selectedRowCount > 1 ? "s" : ""} selected`}
11 | 12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/app/settings/service-accounts/[service-account-id]/Fallback.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from "@zenml-io/react-component-library/components/server"; 2 | import { useParams } from "react-router-dom"; 3 | import { AddApiKeyDialog } from "./AddApiKeyDialog"; 4 | 5 | export default function ApiKeyFallback() { 6 | const { serviceAccountId } = useParams() as { serviceAccountId: string }; 7 | 8 | return ( 9 | 10 |
11 |

12 | This service account doesn't have any API Keys yet 13 |

14 |

15 | Generate your first API Key to enable secure interactions with the ZenML Server. 16 |

17 |
18 | 19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/app/settings/service-accounts/[service-account-id]/breadcrumb.ts: -------------------------------------------------------------------------------- 1 | import { serviceAccountBreadcrumb } from "@/components/breadcrumbs/library"; 2 | import { useBreadcrumbsContext } from "@/layouts/AuthenticatedLayout/BreadcrumbsContext"; 3 | import { routes } from "@/router/routes"; 4 | import { ServiceAccount } from "@/types/service-accounts"; 5 | import { useEffect } from "react"; 6 | 7 | export function useServiceAccountDetailBreadcrumbs(serviceAccount?: ServiceAccount) { 8 | const { setBreadcrumbs } = useBreadcrumbsContext(); 9 | 10 | useEffect(() => { 11 | if (serviceAccount) { 12 | setBreadcrumbs([ 13 | ...serviceAccountBreadcrumb, 14 | { 15 | label: serviceAccount.name, 16 | href: routes.settings.service_accounts.detail(serviceAccount.id) 17 | } 18 | ]); 19 | } 20 | }, [setBreadcrumbs, serviceAccount]); 21 | } 22 | -------------------------------------------------------------------------------- /src/app/settings/service-accounts/form-schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const serviceAccountFormSchema = z.object({ 4 | name: z.string().min(1, "Service Account Name is required"), 5 | description: z.string() 6 | }); 7 | 8 | export type ServiceAccountFormType = z.infer; 9 | 10 | export const createServiceAccountFormSchema = serviceAccountFormSchema.extend({ 11 | createDefault: z.boolean() 12 | }); 13 | 14 | export type CreateServiceAccountForm = z.infer; 15 | -------------------------------------------------------------------------------- /src/app/settings/service-accounts/service.ts: -------------------------------------------------------------------------------- 1 | import { useSearchParams } from "react-router-dom"; 2 | 3 | import { ListServiceAccountsParams } from "@/types/service-accounts"; 4 | import { z } from "zod"; 5 | 6 | const DEFAULT_PAGE = 1; 7 | 8 | const filterParamsSchema = z.object({ 9 | page: z.coerce.number().min(DEFAULT_PAGE).optional().default(DEFAULT_PAGE).catch(DEFAULT_PAGE), 10 | name: z.string().optional(), 11 | operator: z.enum(["and", "or"]).optional() 12 | }); 13 | 14 | export function useServiceAccountOverviewSearchParams(): ListServiceAccountsParams { 15 | const [searchParams] = useSearchParams(); 16 | 17 | const { page, name, operator } = filterParamsSchema.parse({ 18 | page: searchParams.get("page") || undefined, 19 | name: searchParams.get("name") || undefined, 20 | operator: searchParams.get("operator") || undefined 21 | }); 22 | 23 | return { page, name, logical_operator: operator }; 24 | } 25 | -------------------------------------------------------------------------------- /src/app/stacks/ResumeBanner.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { parseWizardData } from "./create/new-infrastructure/persist"; 3 | import { parseWizardData as parseTerraform } from "./create/terraform/persist"; 4 | import { ResumeStackBanner } from "./ResumeStackBanner"; 5 | import { ResumeTerraformBanner } from "./ResumeTerraformBanner"; 6 | 7 | export function ResumeBanners() { 8 | const [hasResumeableStack, setResumeableStack] = useState(parseWizardData().success); 9 | const [hasResumeableTerraform, setResumeableTerraform] = useState( 10 | parseTerraform().success 11 | ); 12 | 13 | if (!hasResumeableStack && !hasResumeableTerraform) return null; 14 | 15 | return ( 16 |
17 | {hasResumeableStack && } 18 | {hasResumeableTerraform && ( 19 | 20 | )} 21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/app/stacks/create/LocalOverlay.tsx: -------------------------------------------------------------------------------- 1 | export function LocalOverlay() { 2 | return ( 3 |
4 |
5 | This option is not available for local deployments 6 |
7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /src/app/stacks/create/breadcrumb.ts: -------------------------------------------------------------------------------- 1 | import { stacksBreadcrumb } from "@/components/breadcrumbs/library"; 2 | import { useBreadcrumbsContext } from "@/layouts/AuthenticatedLayout/BreadcrumbsContext"; 3 | import { routes } from "@/router/routes"; 4 | import { useEffect } from "react"; 5 | 6 | export function useStackCreateBreadcrumbs() { 7 | const { setBreadcrumbs } = useBreadcrumbsContext(); 8 | 9 | useEffect(() => { 10 | setBreadcrumbs([ 11 | stacksBreadcrumb, 12 | { 13 | label: "New Stack", 14 | href: routes.stacks.create.index 15 | } 16 | ]); 17 | }, [setBreadcrumbs]); 18 | } 19 | -------------------------------------------------------------------------------- /src/app/stacks/create/components/CancelButton.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@zenml-io/react-component-library/components/server"; 2 | import { useNavigate } from "react-router-dom"; 3 | import { clearWizardData } from "../new-infrastructure/persist"; 4 | import { routes } from "@/router/routes"; 5 | 6 | export function CancelButton() { 7 | const navigate = useNavigate(); 8 | 9 | function cancel() { 10 | clearWizardData(); 11 | navigate(routes.stacks.create.index); 12 | } 13 | return ( 14 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/app/stacks/create/components/WizardFooter.tsx: -------------------------------------------------------------------------------- 1 | import { Footer } from "@/components/wizard/Wizard"; 2 | import { PropsWithChildren } from "react"; 3 | import { CancelButton } from "./CancelButton"; 4 | 5 | type Props = { 6 | displayCancel?: boolean; 7 | }; 8 | export function StackWizardFooter({ children, displayCancel = true }: PropsWithChildren) { 9 | return ( 10 |
11 | {displayCancel && } 12 | {children} 13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/app/stacks/create/components/sharedSchema.ts: -------------------------------------------------------------------------------- 1 | import { validateStackName } from "@/lib/stacks"; 2 | import { z } from "zod"; 3 | 4 | export const stackNameSchema = z 5 | .string() 6 | .trim() 7 | .min(1, "Stack name is required") 8 | .max(255, "Stack name must be less than 255 characters") 9 | .refine((name) => validateStackName(name), "Stack name is already in use"); 10 | -------------------------------------------------------------------------------- /src/app/stacks/create/existing-infrastructure/page.tsx: -------------------------------------------------------------------------------- 1 | import { LeftSideMenu } from "@/components/wizard/LeftSideMenu"; 2 | import { WizardProvider } from "@/context/WizardContext"; 3 | import { ExistingInfraProvider } from "./ExistingInfraContext"; 4 | import { ExistingInfraWizard } from "./Wizard"; 5 | 6 | const menuEntries = [ 7 | "Infrastructure Type", 8 | "Cloud Provider", 9 | "Connect your Cloud", 10 | "Artifact Store", 11 | "Orchestrator", 12 | "Container Registry" 13 | ]; 14 | 15 | export default function ExistingInfraPage() { 16 | return ( 17 | 18 | 19 |
20 | 21 |
22 | 23 |
24 |
25 |
26 |
27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/app/stacks/create/existing-infrastructure/steps/artifact_store/Header.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentIcon } from "@/components/ComponentIcon"; 2 | 3 | export function ArtifactStoreHeader() { 4 | return ( 5 |
6 |

7 | 8 | Select your Artifact Store 9 |

10 |

11 | Choose one of the storages for the new Artifact Store. 12 |

13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/app/stacks/create/existing-infrastructure/steps/artifact_store/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const artifactStoreSchema = z.object({ 4 | resourceId: z.string().min(1), 5 | flavor: z.string() 6 | }); 7 | 8 | export type ArtifactStoreForm = z.infer; 9 | -------------------------------------------------------------------------------- /src/app/stacks/create/existing-infrastructure/steps/connect/AuthenticationMethod.tsx: -------------------------------------------------------------------------------- 1 | import Transform from "@/assets/icons/transform.svg?react"; 2 | import { AuthMethodSelect } from "./AuthMethodSelect"; 3 | 4 | export function AuthenticationMethod() { 5 | return ( 6 |
7 |
8 |

9 | 10 | Select an Authentication Method 11 |

12 |

13 | Connect ZenML to your resources for seamless integration of cloud services into your ML 14 | pipelines. 15 |

16 |
17 | 18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /src/app/stacks/create/existing-infrastructure/steps/connect/NewConnectorForm.tsx: -------------------------------------------------------------------------------- 1 | import { useFormContext } from "react-hook-form"; 2 | import { StackName } from "../../../components/StackName"; 3 | import { AuthenticationMethod } from "./AuthenticationMethod"; 4 | import { ConnectorConfig } from "./Configuration"; 5 | import { LoadingModal } from "./LoadingModal"; 6 | import { useNewConnector } from "./useNewConnector"; 7 | 8 | export function NewConnector() { 9 | const { 10 | handleSubmit, 11 | formState: { isSubmitting } 12 | } = useFormContext(); 13 | const { handleFormSubmit, fullStackResources, loadingComponents } = useNewConnector(); 14 | 15 | return ( 16 |
17 | 21 | 22 | 23 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /src/app/stacks/create/existing-infrastructure/steps/connect/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { stackNameSchema } from "../../../components/sharedSchema"; 3 | 4 | export const newConnectorBaseSchema = z.object({ 5 | authMethod: z.string().min(1), 6 | stackName: stackNameSchema 7 | }); 8 | 9 | export type NewConnectorBaseForm = z.infer; 10 | -------------------------------------------------------------------------------- /src/app/stacks/create/existing-infrastructure/steps/container_registry/Header.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentIcon } from "@/components/ComponentIcon"; 2 | 3 | export function ContainerRegistryHeader() { 4 | return ( 5 |
6 |
7 |

8 | 9 | Select your Container Registry 10 |

11 |

12 | Select one of the Container Registries for your new stack 13 |

14 |
15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/app/stacks/create/existing-infrastructure/steps/container_registry/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const containerRegistrySchema = z.object({ 4 | flavor: z.string().min(1), 5 | resourceId: z.string().min(1) 6 | }); 7 | 8 | export type ContainerRegistryFormType = z.infer; 9 | -------------------------------------------------------------------------------- /src/app/stacks/create/existing-infrastructure/steps/orchestrator/Header.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentIcon } from "@/components/ComponentIcon"; 2 | 3 | export function OrchestratorHeader() { 4 | return ( 5 |
6 |
7 |

8 | 9 | Select your Orchestrator 10 |

11 |

12 | Select one of the connected orchestrators for your new stack 13 |

14 |
15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/app/stacks/create/existing-infrastructure/steps/orchestrator/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const orchestratorFormBaseSchema = z.object({ 4 | flavor: z.string().min(1), 5 | resourceId: z.string().min(1) 6 | }); 7 | 8 | export type OrchestratorForm = z.infer; 9 | -------------------------------------------------------------------------------- /src/app/stacks/create/existing-infrastructure/steps/provider/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const providerSchema = z.object({ 4 | provider: z.string().min(1) 5 | }); 6 | 7 | export type ProviderForm = z.infer; 8 | -------------------------------------------------------------------------------- /src/app/stacks/create/layout.tsx: -------------------------------------------------------------------------------- 1 | import Stack from "@/assets/icons/stack.svg?react"; 2 | import { PageHeader } from "@/components/PageHeader"; 3 | import { Outlet } from "react-router-dom"; 4 | import { useStackCreateBreadcrumbs } from "./breadcrumb"; 5 | 6 | export function CreateStacksLayout() { 7 | useStackCreateBreadcrumbs(); 8 | return ( 9 |
10 | 11 |
12 | 13 |

Register a Stack

14 |
15 |
16 | 17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/app/stacks/create/manual/create-component-modal/flavor-select.tsx: -------------------------------------------------------------------------------- 1 | import { SelectFlavorList } from "@/components/stack-components/create-component/flavor-select"; 2 | import { snakeCaseToTitleCase } from "@/lib/strings"; 3 | import { StackComponentType } from "@/types/components"; 4 | import { Flavor } from "@/types/flavors"; 5 | import { 6 | DialogHeader, 7 | DialogTitle, 8 | ScrollArea 9 | } from "@zenml-io/react-component-library/components/client"; 10 | 11 | type Props = { 12 | type: StackComponentType; 13 | setSelectedFlavor: (flavor: Flavor) => void; 14 | }; 15 | export function SelectFlavorStep({ type, setSelectedFlavor }: Props) { 16 | return ( 17 | <> 18 | 19 | Select {snakeCaseToTitleCase(type)} Flavor 20 | 21 | 22 |
23 | 24 |
25 |
26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/app/stacks/create/manual/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { stackNameSchema } from "../components/sharedSchema"; 3 | import { stackComponentTypes } from "@/lib/constants"; 4 | 5 | const componentSchema = z.object({ 6 | id: z.string().trim().min(1), 7 | name: z.string().trim().min(1), 8 | logoUrl: z.string().trim().min(1) 9 | }); 10 | 11 | const typeSchema = stackComponentTypes.reduce( 12 | (schema, type) => { 13 | schema[type] = componentSchema.nullable(); 14 | return schema; 15 | }, 16 | {} as Record< 17 | (typeof stackComponentTypes)[number], 18 | z.ZodNullable> 19 | > 20 | ); 21 | 22 | export const formSchema = z.object({ 23 | stackName: stackNameSchema, 24 | components: z.object({ 25 | ...typeSchema, 26 | orchestrator: componentSchema, 27 | artifact_store: componentSchema 28 | }) 29 | }); 30 | 31 | export type FormType = z.infer; 32 | -------------------------------------------------------------------------------- /src/app/stacks/create/new-infrastructure/Steps/Deploy/index.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useNewInfraFormContext } from "../../NewInfraFormContext"; 3 | import { WizardStepWrapper } from "../../Wizard"; 4 | import { DeployButtonPart } from "./ButtonStep"; 5 | import { ProvisioningStep } from "./ProvisioningStep"; 6 | 7 | export function DeployStep() { 8 | return ( 9 | 10 | 11 | 12 | ); 13 | } 14 | 15 | function DisplaySteps() { 16 | const { setIsNextButtonDisabled, isLoading } = useNewInfraFormContext(); 17 | useEffect(() => { 18 | setIsNextButtonDisabled(true); 19 | }, []); 20 | if (isLoading) return ; 21 | return ; 22 | } 23 | -------------------------------------------------------------------------------- /src/app/stacks/create/new-infrastructure/Steps/Success/SuccessStep.tsx: -------------------------------------------------------------------------------- 1 | import { useNewInfraFormContext } from "../../NewInfraFormContext"; 2 | import { WizardStepWrapper } from "../../Wizard"; 3 | import { SuccessList } from "./SuccessList"; 4 | 5 | export function SuccessStep() { 6 | const { setIsNextButtonDisabled, data, timestamp } = useNewInfraFormContext(); 7 | setIsNextButtonDisabled(false); 8 | return ( 9 | 10 |
11 |

12 | Here you can review the created stack and stack components. Now you can start running 13 | pipelines using this new configuration. 14 |

15 | 20 |
21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/app/stacks/create/new-infrastructure/Steps/schemas.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { stackNameSchema } from "../../components/sharedSchema"; 3 | 4 | export const providerSchema = z.enum(["aws", "gcp", "azure"]); 5 | 6 | export const providerFormSchema = z.object({ 7 | provider: providerSchema 8 | }); 9 | 10 | export type ProviderForm = z.infer; 11 | 12 | export const configurationSchema = z.object({ 13 | region: z.string().trim().min(1), 14 | stackName: stackNameSchema 15 | }); 16 | 17 | export type ConfigurationForm = z.infer; 18 | -------------------------------------------------------------------------------- /src/app/stacks/create/new-infrastructure/page.tsx: -------------------------------------------------------------------------------- 1 | import { LeftSideMenu } from "@/components/wizard/LeftSideMenu"; 2 | import { WizardProvider } from "@/context/WizardContext"; 3 | import { NewInfraFormProvider } from "./NewInfraFormContext"; 4 | import { CreateNewInfraWizard } from "./Wizard"; 5 | import { parseWizardData } from "./persist"; 6 | 7 | const entries = ["Infrastructure Type", "Cloud Provider", "Review Configuration", "Deploy Stack"]; 8 | 9 | export default function StackWithNewInfrastructurePage() { 10 | const { success } = parseWizardData(); 11 | return ( 12 | 13 | 14 |
15 | 16 |
17 | 18 |
19 |
20 |
21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/app/stacks/create/new-infrastructure/persist.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { providerSchema } from "./Steps/schemas"; 3 | 4 | const NEWINFRAKEY = "new-infra-data"; 5 | 6 | const wizardSchema = z.object({ 7 | provider: providerSchema, 8 | stackName: z.string().min(1).trim(), 9 | location: z.string().min(1).trim(), 10 | timestamp: z.string().min(1).trim() 11 | }); 12 | 13 | export type WizardData = z.infer; 14 | 15 | export function setWizardData(data: WizardData) { 16 | localStorage.setItem(NEWINFRAKEY, JSON.stringify(data)); 17 | } 18 | 19 | export function parseWizardData() { 20 | try { 21 | const localData = localStorage.getItem(NEWINFRAKEY); 22 | const parsedData = localData ? JSON.parse(localData) : {}; 23 | return wizardSchema.safeParse(parsedData); 24 | } catch (e) { 25 | clearWizardData(); 26 | return wizardSchema.safeParse({}); 27 | } 28 | } 29 | 30 | export function clearWizardData() { 31 | localStorage.removeItem(NEWINFRAKEY); 32 | } 33 | -------------------------------------------------------------------------------- /src/app/stacks/create/page.tsx: -------------------------------------------------------------------------------- 1 | import { NewInfrastructure } from "./NewInfra"; 2 | import { ExistingInfrastructure } from "./ExistingInfra"; 3 | 4 | export default function NewStacksPage() { 5 | return ( 6 |
7 | 8 | 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /src/app/stacks/create/terraform/TerraformWizard.tsx: -------------------------------------------------------------------------------- 1 | import { useWizardContext } from "@/context/WizardContext"; 2 | import { ConfigurationStep } from "./steps/configuration"; 3 | import { DeploymentStep } from "./steps/deploy"; 4 | import { ProviderStep } from "./steps/provider"; 5 | // import { SuccessStep } from "./steps/success"; 6 | 7 | export function TerraformWizard() { 8 | const { currentStep } = useWizardContext(); 9 | 10 | if (currentStep === 1) return ; 11 | if (currentStep === 2) return ; 12 | if (currentStep === 3) return ; 13 | // if (currentStep === 4) return ; 14 | } 15 | -------------------------------------------------------------------------------- /src/app/stacks/create/terraform/page.tsx: -------------------------------------------------------------------------------- 1 | import { WizardProvider } from "@/context/WizardContext"; 2 | import { CreateTerraformProvider } from "./TerraformContext"; 3 | import { TerraformWizard } from "./TerraformWizard"; 4 | import { LeftSideMenu } from "@/components/wizard/LeftSideMenu"; 5 | 6 | const entries = ["Infrastructure Type", "Cloud Provider", "Review Configuration", "Deploy Stack"]; 7 | 8 | export default function TerraformPage() { 9 | return ( 10 | 11 | 12 |
13 | 14 |
15 | 16 |
17 |
18 |
19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/app/stacks/create/terraform/persist.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { providerSchema } from "../new-infrastructure/Steps/schemas"; 3 | 4 | export const storageKey = "create-terraform-data"; 5 | 6 | const wizardSchema = z.object({ 7 | provider: providerSchema, 8 | stackName: z.string().min(1).trim(), 9 | location: z.string().min(1).trim(), 10 | timestamp: z.string().min(1).trim() 11 | }); 12 | 13 | export type WizardData = z.infer; 14 | 15 | export function setWizardData(data: WizardData) { 16 | localStorage.setItem(storageKey, JSON.stringify(data)); 17 | } 18 | 19 | export function parseWizardData() { 20 | try { 21 | const localData = localStorage.getItem(storageKey); 22 | const parsedData = localData ? JSON.parse(localData) : {}; 23 | return wizardSchema.safeParse(parsedData); 24 | } catch (e) { 25 | clearWizardData(); 26 | return wizardSchema.safeParse({}); 27 | } 28 | } 29 | 30 | export function clearWizardData() { 31 | localStorage.removeItem(storageKey); 32 | } 33 | -------------------------------------------------------------------------------- /src/app/stacks/create/terraform/steps/provider/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { providerSchema } from "../../../new-infrastructure/Steps/schemas"; 3 | 4 | export const providerFormSchema = z.object({ 5 | provider: providerSchema 6 | }); 7 | 8 | export type ProviderForm = z.infer; 9 | -------------------------------------------------------------------------------- /src/app/stacks/page.tsx: -------------------------------------------------------------------------------- 1 | import { useTourContext } from "@/components/tour/TourContext"; 2 | import { useEffect } from "react"; 3 | import { StackList } from "./StackList"; 4 | import { ResumeBanners } from "./ResumeBanner"; 5 | 6 | export default function StacksPage() { 7 | const { 8 | setTourState, 9 | tourState: { tourActive } 10 | } = useTourContext(); 11 | 12 | useEffect(() => { 13 | if (tourActive) { 14 | setTourState((prev) => ({ ...prev, run: true, stepIndex: prev.stepIndex })); 15 | } 16 | }, [tourActive]); 17 | 18 | return ( 19 |
20 | 21 | 22 |
23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /src/app/stacks/service.ts: -------------------------------------------------------------------------------- 1 | import { StackListQueryParams } from "@/types/stack"; 2 | import { useSearchParams } from "react-router-dom"; 3 | import { z } from "zod"; 4 | 5 | const DEFAULT_PAGE = 1; 6 | 7 | const filterParamsSchema = z.object({ 8 | page: z.coerce.number().min(DEFAULT_PAGE).optional().default(DEFAULT_PAGE).catch(DEFAULT_PAGE), 9 | name: z.string().optional(), 10 | 11 | operator: z.enum(["and", "or"]).optional() 12 | }); 13 | 14 | export function useStacklistQueryParams(): StackListQueryParams { 15 | const [searchParams] = useSearchParams(); 16 | 17 | const { page, name, operator } = filterParamsSchema.parse({ 18 | page: searchParams.get("page") || undefined, 19 | name: searchParams.get("name") || undefined 20 | }); 21 | 22 | return { page, name, logical_operator: operator }; 23 | } 24 | -------------------------------------------------------------------------------- /src/app/survey/PrimaryUseStep.tsx: -------------------------------------------------------------------------------- 1 | import { PrimaryUseForm } from "@/components/survey/PrimaryUse"; 2 | import { useSurveyContext } from "@/components/survey/SurveyContext"; 3 | import { PrimaryUseFormType } from "@/components/survey/form-schemas"; 4 | import { User, UserMetadata } from "@/types/user"; 5 | import { useSurveyUserContext } from "./SurveyUserContext"; 6 | 7 | type Props = { 8 | user: User; 9 | }; 10 | 11 | export function PrimaryUseStep({ user }: Props) { 12 | const { setSurveyStep } = useSurveyContext(); 13 | const { setUser } = useSurveyUserContext(); 14 | 15 | function handlePrimaryUseSubmit({ primaryUse }: PrimaryUseFormType) { 16 | const metadata: UserMetadata = { 17 | primary_use: primaryUse 18 | }; 19 | setUser((prev) => ({ 20 | ...prev, 21 | user_metadata: { ...prev.user_metadata, ...metadata } 22 | })); 23 | setSurveyStep(3); 24 | } 25 | 26 | return ; 27 | } 28 | -------------------------------------------------------------------------------- /src/app/survey/SurveyUserContext.tsx: -------------------------------------------------------------------------------- 1 | import { UpdateUser } from "@/types/user"; 2 | import { Dispatch, SetStateAction, createContext, useContext, useState } from "react"; 3 | 4 | type SurveyUserContextType = { 5 | user: UpdateUser; 6 | setUser: Dispatch>; 7 | }; 8 | 9 | export const SurveyUserContext = createContext(null); 10 | 11 | export function SurveyUserProvider({ 12 | children, 13 | initialUser 14 | }: { 15 | children: React.ReactNode; 16 | initialUser?: UpdateUser; 17 | }) { 18 | const [user, setUser] = useState(initialUser || {}); 19 | return ( 20 | {children} 21 | ); 22 | } 23 | 24 | export function useSurveyUserContext() { 25 | const context = useContext(SurveyUserContext); 26 | if (context === null) { 27 | throw new Error("useSurveyUserContext must be used within an SurveyUserProvider"); 28 | } 29 | return context; 30 | } 31 | -------------------------------------------------------------------------------- /src/app/survey/loader.ts: -------------------------------------------------------------------------------- 1 | import { QueryClient } from "@tanstack/react-query"; 2 | import { fetchCurrentUser, getCurrentUserKey } from "@/data/users/current-user-query"; 3 | 4 | export const surveyLoader = (queryClient: QueryClient) => async () => { 5 | await queryClient.ensureQueryData({ queryKey: getCurrentUserKey(), queryFn: fetchCurrentUser }); 6 | return null; 7 | }; 8 | -------------------------------------------------------------------------------- /src/app/survey/page.tsx: -------------------------------------------------------------------------------- 1 | import { SurveyProvider } from "@/components/survey/SurveyContext"; 2 | import { SurveyWizard } from "./Wizard"; 3 | 4 | export default function SurveyPage() { 5 | return ( 6 |
7 | 8 | 9 | 10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/app/upgrade/components/Context.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, Dispatch, SetStateAction, useContext, useState } from "react"; 2 | 3 | type UpgradeContextType = { 4 | submitSuccess: boolean; 5 | setSubmitSuccess: Dispatch>; 6 | }; 7 | 8 | export const UpgradeContext = createContext(null); 9 | 10 | export function UpgradeProvider({ children }: { children: React.ReactNode }) { 11 | const [success, setSuccess] = useState(false); 12 | return ( 13 | 19 | {children} 20 | 21 | ); 22 | } 23 | 24 | export function useUpgradeContext() { 25 | const context = useContext(UpgradeContext); 26 | if (context === null) { 27 | throw new Error("useUpgradeContext must be used within an UpgradeProvider"); 28 | } 29 | return context; 30 | } 31 | -------------------------------------------------------------------------------- /src/app/upgrade/components/form/Wrapper.tsx: -------------------------------------------------------------------------------- 1 | import { ProWrapper } from "@/components/pro/ProCta"; 2 | import { useUpgradeContext } from "../Context"; 3 | import { UpgradeFormStep } from "./index"; 4 | import Image from "@/assets/images/upgrade-form.webp"; 5 | import { SubmitSuccess } from "../Success"; 6 | 7 | export function UpgradeWrapperBox() { 8 | const { submitSuccess } = useUpgradeContext(); 9 | return ( 10 | 11 | 15 | {submitSuccess === false && } 16 | {submitSuccess === true && } 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/app/upgrade/components/form/index.tsx: -------------------------------------------------------------------------------- 1 | import { UpgradeFormSection } from "./Form"; 2 | import { UpgradeSteps } from "./Steps"; 3 | 4 | export function UpgradeFormStep() { 5 | return ( 6 |
7 | 8 | 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /src/app/upgrade/components/form/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const upgradeFormSchema = z.object({ 4 | name: z.string().min(1), 5 | company: z.string().min(1), 6 | email: z.string().email() 7 | }); 8 | 9 | export type UpgradeForm = z.infer; 10 | -------------------------------------------------------------------------------- /src/app/upgrade/page.tsx: -------------------------------------------------------------------------------- 1 | import { UpgradeProvider } from "./components/Context"; 2 | import { UpgradeWrapperBox } from "./components/form/Wrapper"; 3 | 4 | export default function UpgradePage() { 5 | return ( 6 |
7 |
8 | 9 | 10 | 11 |
12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/assets/icons/alert-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/alert-triangle.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /src/assets/icons/arrow-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/arrow-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/cached.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/check-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/chevron-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/chevron-left-double.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/icons/chevron-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/icons/chevron-right-double.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/icons/chevron-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/icons/clock.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/cloud.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/collapse.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/dots-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | -------------------------------------------------------------------------------- /src/assets/icons/dots-horizontal.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/icons/expand-full.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/expand.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/gitlab.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/assets/icons/info.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/minus.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/refresh.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/icons/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/services/terraform.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/icons/side-collapse.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/side-expand.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/slash-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/slash-divider.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/spinner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/switch-horizontal.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/tick-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/illustrations/connectivity.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenml-io/zenml-dashboard/90236a062535d2182b67f20f23cd8c8280f01921/src/assets/illustrations/connectivity.webp -------------------------------------------------------------------------------- /src/assets/images/acp.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenml-io/zenml-dashboard/90236a062535d2182b67f20f23cd8c8280f01921/src/assets/images/acp.webp -------------------------------------------------------------------------------- /src/assets/images/mcp.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenml-io/zenml-dashboard/90236a062535d2182b67f20f23cd8c8280f01921/src/assets/images/mcp.webp -------------------------------------------------------------------------------- /src/assets/images/portraits/adam.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenml-io/zenml-dashboard/90236a062535d2182b67f20f23cd8c8280f01921/src/assets/images/portraits/adam.webp -------------------------------------------------------------------------------- /src/assets/images/portraits/alex.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenml-io/zenml-dashboard/90236a062535d2182b67f20f23cd8c8280f01921/src/assets/images/portraits/alex.webp -------------------------------------------------------------------------------- /src/assets/images/portraits/baris.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenml-io/zenml-dashboard/90236a062535d2182b67f20f23cd8c8280f01921/src/assets/images/portraits/baris.webp -------------------------------------------------------------------------------- /src/assets/images/portraits/hamza.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenml-io/zenml-dashboard/90236a062535d2182b67f20f23cd8c8280f01921/src/assets/images/portraits/hamza.webp -------------------------------------------------------------------------------- /src/assets/images/portraits/stefan.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenml-io/zenml-dashboard/90236a062535d2182b67f20f23cd8c8280f01921/src/assets/images/portraits/stefan.webp -------------------------------------------------------------------------------- /src/assets/images/product-tour/settings_preview.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenml-io/zenml-dashboard/90236a062535d2182b67f20f23cd8c8280f01921/src/assets/images/product-tour/settings_preview.webp -------------------------------------------------------------------------------- /src/assets/images/product-tour/tour-cover.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenml-io/zenml-dashboard/90236a062535d2182b67f20f23cd8c8280f01921/src/assets/images/product-tour/tour-cover.webp -------------------------------------------------------------------------------- /src/assets/images/templates.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenml-io/zenml-dashboard/90236a062535d2182b67f20f23cd8c8280f01921/src/assets/images/templates.webp -------------------------------------------------------------------------------- /src/assets/images/upgrade-form.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zenml-io/zenml-dashboard/90236a062535d2182b67f20f23cd8c8280f01921/src/assets/images/upgrade-form.webp -------------------------------------------------------------------------------- /src/assets/styles/index.css: -------------------------------------------------------------------------------- 1 | @import "@fontsource/inter/400"; 2 | @import "@fontsource/inter/500"; 3 | @import "@fontsource/inter/600"; 4 | 5 | @tailwind base; 6 | @tailwind components; 7 | @tailwind utilities; 8 | 9 | @layer utilities { 10 | .layout-container { 11 | @apply mx-auto w-11/12 max-w-[1440px]; 12 | } 13 | .link { 14 | @apply underline transition-all duration-150 hover:decoration-transparent; 15 | } 16 | } 17 | 18 | @layer components { 19 | [type="radio"]:checked { 20 | background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 16 16' fill='%237A3EF4' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='8' cy='8' r='5'/%3E%3C/svg%3E"); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/components/CodeHighlighter.tsx: -------------------------------------------------------------------------------- 1 | import Prism from "prismjs"; 2 | import "prismjs/components/prism-python"; 3 | import "@/assets/styles/prism-github-light.css"; 4 | import { useEffect } from "react"; 5 | 6 | type Props = { 7 | code: string; 8 | }; 9 | 10 | export function CodeHighlighter({ code }: Props) { 11 | useEffect(() => { 12 | Prism.highlightAll(); 13 | }, []); 14 | return {code}; 15 | } 16 | -------------------------------------------------------------------------------- /src/components/DisplayDate.tsx: -------------------------------------------------------------------------------- 1 | export function DisplayDate({ 2 | dateString, 3 | short = false 4 | }: { 5 | dateString: string; 6 | short?: boolean; 7 | }) { 8 | const date = new Date(`${dateString}Z`); 9 | 10 | return <>{short ? formatShortDate(date) : date.toLocaleString()}; 11 | } 12 | 13 | function formatShortDate(date: Date) { 14 | const dateOptions: Intl.DateTimeFormatOptions = { 15 | month: "short", 16 | day: "numeric", 17 | year: "numeric" 18 | }; 19 | const timeOptions: Intl.DateTimeFormatOptions = { 20 | hour: "numeric", 21 | minute: "numeric", 22 | hour12: false 23 | }; 24 | 25 | const formattedDate = date.toLocaleDateString("en-US", dateOptions); 26 | const formattedTime = date.toLocaleTimeString("en-US", timeOptions); 27 | return `${formattedDate} ${formattedTime}`; 28 | } 29 | -------------------------------------------------------------------------------- /src/components/EmptyState.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren, ReactNode } from "react"; 2 | 3 | type Props = { 4 | icon?: ReactNode; 5 | }; 6 | 7 | export function EmptyState({ children, icon }: PropsWithChildren) { 8 | return ( 9 |
10 | {icon} 11 | {children} 12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/components/Error.tsx: -------------------------------------------------------------------------------- 1 | import File from "@/assets/icons/file.svg?react"; 2 | import AlertCircle from "@/assets/icons/alert-circle.svg?react"; 3 | import { EmptyState } from "@/components/EmptyState"; 4 | 5 | type Props = { 6 | isAlertCircle?: boolean; 7 | err: Error; 8 | }; 9 | 10 | export function ErrorFallback({ err, isAlertCircle = false }: Props) { 11 | return ( 12 | 16 | ) : ( 17 | 18 | ) 19 | } 20 | > 21 |
22 |

{err.message}

23 |
24 |
25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /src/components/InlineAvatar.tsx: -------------------------------------------------------------------------------- 1 | import { Avatar, AvatarFallback, cn } from "@zenml-io/react-component-library"; 2 | 3 | type Props = { 4 | username: string; 5 | className?: string; 6 | }; 7 | 8 | export function InlineAvatar({ username, className }: Props) { 9 | return ( 10 |
11 | 12 | {username[0]} 13 | 14 |

{username}

15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/components/NavLink.tsx: -------------------------------------------------------------------------------- 1 | import { NavLink as NavLinkPrimitive, NavLinkProps, useLocation } from "react-router-dom"; 2 | 3 | interface CustomNavLinkProps extends NavLinkProps { 4 | isActiveOverride?: (pathname: string) => boolean; 5 | } 6 | 7 | export default function NavLink({ children, isActiveOverride, ...rest }: CustomNavLinkProps) { 8 | const location = useLocation(); 9 | const isActive = isActiveOverride ? isActiveOverride(location.pathname) : false; 10 | 11 | return ( 12 | 15 | ` ${ 16 | isActive || defaultIsActive 17 | ? "bg-primary-50 text-theme-text-brand" 18 | : "hover:bg-neutral-200" 19 | } block rounded-md px-4 py-1 text-text-sm font-semibold ` 20 | } 21 | > 22 | {children} 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/components/NumberBox.tsx: -------------------------------------------------------------------------------- 1 | import { cva, VariantProps } from "class-variance-authority"; 2 | import { PropsWithChildren } from "react"; 3 | 4 | const numberBoxVariants = cva( 5 | "flex h-7 w-7 items-center justify-center rounded-sm text-text-lg font-semibold", 6 | { 7 | variants: { 8 | intent: { 9 | default: "bg-primary-100 text-theme-text-brand", 10 | disabled: "bg-neutral-100 text-theme-text-tertiary" 11 | } 12 | }, 13 | defaultVariants: { 14 | intent: "default" 15 | } 16 | } 17 | ); 18 | 19 | type Props = VariantProps & PropsWithChildren; 20 | 21 | export function Numberbox({ children, intent }: Props) { 22 | return
{children}
; 23 | } 24 | -------------------------------------------------------------------------------- /src/components/PageHeader.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@zenml-io/react-component-library"; 2 | import { HTMLAttributes, PropsWithChildren } from "react"; 3 | 4 | type Props = HTMLAttributes; 5 | 6 | export function PageHeader({ children, className, ...rest }: PropsWithChildren) { 7 | return ( 8 |
15 | {children} 16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/components/ProviderIcon.tsx: -------------------------------------------------------------------------------- 1 | import Aws from "@/assets/icons/services/aws.svg?react"; 2 | import Gcp from "@/assets/icons/services/gcp.svg?react"; 3 | import Azure from "@/assets/icons/services/azure.svg?react"; 4 | import { HTMLAttributes } from "react"; 5 | import { cn } from "@zenml-io/react-component-library"; 6 | type Props = HTMLAttributes & { 7 | provider: "aws" | "gcp" | "azure"; 8 | }; 9 | export function CloudProviderIcon({ provider, className, ...rest }: Props) { 10 | const classname = cn("w-5 h-5 shrink-0", className); 11 | switch (provider) { 12 | case "aws": 13 | return ; 14 | case "azure": 15 | return ; 16 | case "gcp": 17 | return ; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/components/Tick.tsx: -------------------------------------------------------------------------------- 1 | import { ProgressTick, cn } from "@zenml-io/react-component-library"; 2 | import Check from "@/assets/icons/check.svg?react"; 3 | import { ComponentPropsWithoutRef } from "react"; 4 | 5 | type Props = ComponentPropsWithoutRef & { tickClasses?: string }; 6 | export function Tick({ tickClasses, ...rest }: Props) { 7 | return ( 8 | 9 | 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/components/artifacts/HtmlVisualization.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react"; 2 | import { Props } from "./Visualization"; 3 | 4 | export function HTMLVisualization({ content }: Props) { 5 | const iframeRef = useRef(null); 6 | 7 | const handleIframeLoad = () => { 8 | if (iframeRef.current) { 9 | const contentHeight = iframeRef.current.contentWindow?.document.documentElement.scrollHeight; 10 | iframeRef.current.height = contentHeight ? `${contentHeight}px` : "100%"; 11 | } 12 | }; 13 | 14 | useEffect(() => { 15 | handleIframeLoad(); 16 | window.addEventListener("resize", handleIframeLoad); 17 | return () => { 18 | window.removeEventListener("resize", handleIframeLoad); 19 | }; 20 | }, []); 21 | 22 | return ( 23 |
24 |