├── .DS_Store
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── scripts
│ ├── create_release_manifest.py
│ ├── get_released_apps.py
│ ├── update_helm_chart.py
│ └── update_package_json.py
└── workflows
│ ├── build_and_push.yaml
│ └── release.yaml
├── .gitignore
├── .prettierrc
├── .vscode
└── settings.json
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── PR_TEMPLATE.md
├── README.md
├── agnost.svg
├── k8s
├── monitor.yaml
├── package-lock.json
├── platform.yaml
├── registry.yaml
├── skaffold.yaml
├── sync.yaml
└── webhook.yaml
├── monitor
├── .dockerignore
├── Dockerfile
├── Dockerfile.dev
├── config
│ ├── default.json
│ ├── development.json
│ ├── errorCodes.js
│ └── production.json
├── eslint.config.js
├── handler
│ ├── deleteUnusedImagesInRegistry.js
│ ├── monitorAccessTokens.js
│ ├── monitorContainers.js
│ └── watchTaskRuns.js
├── init
│ ├── db.js
│ └── logger.js
├── middlewares
│ ├── logRequest.js
│ └── undefinedPaths.js
├── package-lock.json
├── package.json
├── routes
│ └── system.js
├── server.js
└── util
│ └── helper.js
├── platform
├── .dockerignore
├── .gitignore
├── Dockerfile
├── Dockerfile.dev
├── config
│ ├── clusterContainers.js
│ ├── constants.js
│ ├── default.json
│ ├── development.json
│ ├── errorCodes.js
│ ├── production.json
│ └── timezones.js
├── controllers
│ ├── audit.js
│ ├── auth.js
│ ├── base.js
│ ├── cluster.js
│ ├── container.js
│ ├── domain.js
│ ├── environment.js
│ ├── gitProvider.js
│ ├── organization.js
│ ├── organizationInvitation.js
│ ├── organizationMember.js
│ ├── project.js
│ ├── projectInvitation.js
│ ├── registry.js
│ ├── tcpProxyPort.js
│ └── user.js
├── eslint.config.js
├── handlers
│ ├── certificate.js
│ ├── cluster.js
│ ├── cronjob.js
│ ├── deployment.js
│ ├── git.js
│ ├── hpa.js
│ ├── ingress.js
│ ├── k8s.js
│ ├── manifests
│ │ ├── bitbucket-pipeline.yaml
│ │ ├── cronjob.yaml
│ │ ├── deployment.yaml
│ │ ├── github-pipeline.yaml
│ │ ├── gitlab-pipeline.yaml
│ │ ├── hpa.yaml
│ │ ├── namespace.yaml
│ │ ├── pvc.yaml
│ │ ├── service.yaml
│ │ └── statefulset.yaml
│ ├── ns.js
│ ├── pvc.js
│ ├── service.js
│ ├── statefulset.js
│ ├── status.js
│ ├── tcpproxy.js
│ ├── tekton.js
│ ├── templates
│ │ ├── index.js
│ │ ├── manifests
│ │ │ ├── mariadbv1.0.yaml
│ │ │ ├── memcachedv1.0.yaml
│ │ │ ├── miniov1.0.yaml
│ │ │ ├── mongodbv1.0.yaml
│ │ │ ├── mysqlv1.0.yaml
│ │ │ ├── postgresqlv1.0.yaml
│ │ │ └── redisv1.0.yaml
│ │ └── middlewares.js
│ ├── usage.js
│ └── util.js
├── init
│ ├── cache.js
│ ├── db.js
│ ├── logger.js
│ ├── storage.js
│ └── sync.js
├── middlewares
│ ├── authMasterToken.js
│ ├── authSession.js
│ ├── authorizeOrgAction.js
│ ├── authorizeProjectAction.js
│ ├── checkClusterSetupStatus.js
│ ├── contentType.js
│ ├── handleFile.js
│ ├── logRequest.js
│ ├── rateLimiter.js
│ ├── undefinedPaths.js
│ ├── validate.js
│ ├── validateCluster.js
│ ├── validateClusterIPs.js
│ ├── validateContainer.js
│ ├── validateEnvironment.js
│ ├── validateGitProvider.js
│ ├── validateOrg.js
│ └── validateProject.js
├── package-lock.json
├── package.json
├── routes
│ ├── auth.js
│ ├── cluster.js
│ ├── container.js
│ ├── environment.js
│ ├── git.js
│ ├── log.js
│ ├── organization.js
│ ├── organizationInvites.js
│ ├── organizationTeam.js
│ ├── project.js
│ ├── projectInvites.js
│ ├── projectTeam.js
│ ├── registry.js
│ ├── storage.js
│ ├── system.js
│ ├── telemetry.js
│ ├── types.js
│ └── user.js
├── schemas
│ ├── audit.js
│ ├── cluster.js
│ ├── container.js
│ ├── domain.js
│ ├── environment.js
│ ├── gitProvider.js
│ ├── organization.js
│ ├── organizationInvitation.js
│ ├── organizationMember.js
│ ├── project.js
│ ├── projectInvitation.js
│ ├── registry.js
│ ├── rules
│ │ ├── checks.js
│ │ ├── cronJob.js
│ │ ├── deployment.js
│ │ └── statefulSet.js
│ ├── tcpProxyPort.js
│ └── user.js
├── server.js
└── util
│ └── helper.js
├── releases
├── .gitkeep
├── latest.json
├── v0.0.10.json
├── v0.0.11.json
├── v0.0.12.json
├── v0.0.13.json
├── v0.0.14.json
├── v0.0.15.json
├── v0.0.16.json
├── v0.0.17.json
├── v0.0.18.json
├── v0.0.19.json
├── v0.0.2.json
├── v0.0.20.json
├── v0.0.21.json
├── v0.0.22.json
├── v0.0.23.json
├── v0.0.24.json
├── v0.0.25.json
├── v0.0.26.json
├── v0.0.27.json
├── v0.0.28.json
├── v0.0.29.json
├── v0.0.3.json
├── v0.0.30.json
├── v0.0.31.json
├── v0.0.32.json
├── v0.0.33.json
├── v0.0.34.json
├── v0.0.35.json
├── v0.0.36.json
├── v0.0.37.json
├── v0.0.38.json
├── v0.0.39.json
├── v0.0.4.json
├── v0.0.40.json
├── v0.0.41.json
├── v0.0.42.json
├── v0.0.43.json
├── v0.0.44.json
├── v0.0.45.json
├── v0.0.46.json
├── v0.0.47.json
├── v0.0.48.json
├── v0.0.5.json
├── v0.0.6.json
├── v0.0.7.json
├── v0.0.8.json
├── v0.0.9.json
├── v1.0.0.json
├── v1.0.1.json
├── v1.0.10.json
├── v1.0.2.json
├── v1.0.3.json
├── v1.0.4.json
├── v1.0.5.json
├── v1.0.6.json
├── v1.0.7.json
├── v1.0.8.json
└── v1.0.9.json
├── studio
├── .Dockerignore
├── .gitignore
├── .prettierrc
├── Dockerfile
├── Dockerfile.dev
├── biome.json
├── index.html
├── nginx
│ └── default.conf
├── package-lock.json
├── package.json
├── postcss.config.js
├── src
│ ├── App.tsx
│ ├── assets
│ │ ├── browserconfig.xml
│ │ ├── images
│ │ │ ├── agnost-dark-bg-logo.png
│ │ │ ├── android-chrome-192x192.png
│ │ │ ├── android-chrome-512x512.png
│ │ │ ├── apple-touch-icon.png
│ │ │ ├── favicon-16x16.png
│ │ │ ├── favicon-32x32.png
│ │ │ ├── favicon.ico
│ │ │ ├── ms-icon-310x310.png
│ │ │ ├── mstile-150x150.png
│ │ │ └── safari-pinned-tab.svg
│ │ ├── manifest.json
│ │ └── site.webmanifest
│ ├── components
│ │ ├── Accordion
│ │ │ ├── Accordion.tsx
│ │ │ └── index.ts
│ │ ├── ActionsCell
│ │ │ ├── ActionsCell.tsx
│ │ │ └── index.ts
│ │ ├── Alert
│ │ │ ├── Alert.tsx
│ │ │ ├── Feedback.tsx
│ │ │ ├── alert.scss
│ │ │ └── index.ts
│ │ ├── AuthUserAvatar
│ │ │ ├── AuthUserAvatar.tsx
│ │ │ └── index.ts
│ │ ├── Avatar
│ │ │ ├── Avatar.tsx
│ │ │ ├── avatar.scss
│ │ │ └── index.ts
│ │ ├── Badge
│ │ │ ├── Badge.tsx
│ │ │ ├── badge.scss
│ │ │ └── index.ts
│ │ ├── Button
│ │ │ ├── Button.scss
│ │ │ ├── Button.tsx
│ │ │ ├── ButtonGroup.tsx
│ │ │ └── index.ts
│ │ ├── ChangeAvatar
│ │ │ ├── ChangeAvatar.tsx
│ │ │ ├── changeAvatar.scss
│ │ │ └── index.ts
│ │ ├── ChangeNameForm
│ │ │ ├── ChangeNameForm.tsx
│ │ │ └── index.ts
│ │ ├── Checkbox
│ │ │ ├── Checkbox.tsx
│ │ │ ├── checkbox.scss
│ │ │ └── index.ts
│ │ ├── ConfirmationModal
│ │ │ ├── ConfirmationModal.tsx
│ │ │ ├── confirmationModal.scss
│ │ │ └── index.ts
│ │ ├── CopyButton
│ │ │ ├── CopyButton.tsx
│ │ │ └── index.ts
│ │ ├── CopyInput
│ │ │ ├── CopyInput.tsx
│ │ │ ├── copyInput.scss
│ │ │ └── index.ts
│ │ ├── DataTable
│ │ │ ├── DataTable.tsx
│ │ │ ├── SortButton.tsx
│ │ │ ├── index.ts
│ │ │ └── sortButton.scss
│ │ ├── DateRangePicker
│ │ │ ├── DateRangePicker.tsx
│ │ │ └── index.ts
│ │ ├── DateText
│ │ │ ├── DateText.tsx
│ │ │ └── index.ts
│ │ ├── Description
│ │ │ ├── Description.scss
│ │ │ ├── Description.tsx
│ │ │ └── index.ts
│ │ ├── Dialog
│ │ │ ├── Dialog.tsx
│ │ │ ├── dialog.scss
│ │ │ └── index.ts
│ │ ├── Drawer
│ │ │ ├── Drawer.tsx
│ │ │ ├── drawer.scss
│ │ │ └── index.ts
│ │ ├── Dropdown
│ │ │ ├── Dropdown.tsx
│ │ │ ├── dropdown.scss
│ │ │ └── index.ts
│ │ ├── EmptyState
│ │ │ ├── EmptyState.tsx
│ │ │ └── index.ts
│ │ ├── Error
│ │ │ ├── Error.tsx
│ │ │ ├── NotFound.tsx
│ │ │ └── index.ts
│ │ ├── Form
│ │ │ ├── Form.tsx
│ │ │ ├── form.scss
│ │ │ └── index.ts
│ │ ├── Header
│ │ │ ├── Feedback.tsx
│ │ │ ├── Header.tsx
│ │ │ ├── header.scss
│ │ │ └── index.ts
│ │ ├── InfoModal
│ │ │ ├── InfoModal.tsx
│ │ │ ├── index.ts
│ │ │ └── infoModal.scss
│ │ ├── Input
│ │ │ ├── Input.tsx
│ │ │ ├── Textarea.tsx
│ │ │ ├── index.ts
│ │ │ └── input.scss
│ │ ├── InviteMemberForm
│ │ │ ├── InviteMemberForm.tsx
│ │ │ ├── index.ts
│ │ │ └── inviteMemberForm.scss
│ │ ├── Label
│ │ │ ├── Label.tsx
│ │ │ ├── index.ts
│ │ │ └── label.scss
│ │ ├── Loading
│ │ │ ├── Loading.tsx
│ │ │ └── index.ts
│ │ ├── LogViewer
│ │ │ ├── LogViewer.tsx
│ │ │ └── index.ts
│ │ ├── PasswordInput
│ │ │ ├── PasswordInput.tsx
│ │ │ ├── index.ts
│ │ │ └── password-input.scss
│ │ ├── Popover
│ │ │ ├── Popover.tsx
│ │ │ ├── index.ts
│ │ │ └── popover.scss
│ │ ├── RadioGroup
│ │ │ ├── RadioGroup.tsx
│ │ │ └── index.ts
│ │ ├── RoleDropdown
│ │ │ ├── RoleDropdown.tsx
│ │ │ ├── RoleSelect.tsx
│ │ │ └── index.ts
│ │ ├── SearchInput
│ │ │ ├── SearchInput.tsx
│ │ │ ├── index.ts
│ │ │ └── searchInput.scss
│ │ ├── Select
│ │ │ ├── Select.tsx
│ │ │ ├── index.ts
│ │ │ └── select.scss
│ │ ├── SelectionDropdown
│ │ │ ├── SelectionDropdown.tsx
│ │ │ └── index.ts
│ │ ├── Separator
│ │ │ ├── Separator.tsx
│ │ │ └── index.ts
│ │ ├── SettingsFormItem
│ │ │ ├── SettingsFormItem.tsx
│ │ │ ├── index.ts
│ │ │ └── settingsFormItem.scss
│ │ ├── Switch
│ │ │ ├── Switch.tsx
│ │ │ ├── index.ts
│ │ │ └── switch.scss
│ │ ├── Table
│ │ │ ├── SelectedRowButton.tsx
│ │ │ ├── Table.tsx
│ │ │ ├── TableConfirmation.tsx
│ │ │ ├── index.ts
│ │ │ └── table.scss
│ │ ├── Toast
│ │ │ ├── Toast.tsx
│ │ │ ├── Toaster.tsx
│ │ │ └── index.ts
│ │ ├── Tooltip
│ │ │ ├── Tooltip.tsx
│ │ │ └── index.ts
│ │ ├── TransferOwnership
│ │ │ ├── TransferOwnership.tsx
│ │ │ └── index.ts
│ │ └── icons
│ │ │ ├── 401.tsx
│ │ │ ├── 404.tsx
│ │ │ ├── Agnost.tsx
│ │ │ ├── Awss3.tsx
│ │ │ ├── AzureBlobStorage.tsx
│ │ │ ├── Bitbucket.tsx
│ │ │ ├── Docker.tsx
│ │ │ ├── Error.tsx
│ │ │ ├── ErrorPage.tsx
│ │ │ ├── GcpStorage.tsx
│ │ │ ├── GitLab.tsx
│ │ │ ├── Github.tsx
│ │ │ ├── Kafka.tsx
│ │ │ ├── Kubernetes.tsx
│ │ │ ├── Logo.tsx
│ │ │ ├── MariaDb.tsx
│ │ │ ├── Memcached.tsx
│ │ │ ├── MinIo.tsx
│ │ │ ├── MongoDb.tsx
│ │ │ ├── MySql.tsx
│ │ │ ├── Nodejs.tsx
│ │ │ ├── PostgreSql.tsx
│ │ │ ├── RabbitMq.tsx
│ │ │ ├── React.tsx
│ │ │ ├── Redis.tsx
│ │ │ ├── Resource.tsx
│ │ │ ├── SocketIo.tsx
│ │ │ ├── SuccessCheck.tsx
│ │ │ ├── Warning.tsx
│ │ │ └── index.ts
│ ├── constants
│ │ ├── constants.ts
│ │ ├── index.ts
│ │ └── stateList.ts
│ ├── features
│ │ ├── auth
│ │ │ ├── AcceptInvitation
│ │ │ │ ├── AcceptInvitation.tsx
│ │ │ │ └── index.ts
│ │ │ ├── AuthUserDropdown
│ │ │ │ ├── AuthUserDropdown.tsx
│ │ │ │ └── index.ts
│ │ │ ├── ChangeAvatar
│ │ │ │ ├── ChangeAvatar.tsx
│ │ │ │ └── index.ts
│ │ │ ├── ChangeName
│ │ │ │ ├── ChangeName.tsx
│ │ │ │ ├── changeName.sass
│ │ │ │ └── index.ts
│ │ │ ├── DeleteAccount
│ │ │ │ ├── DeleteAccount.tsx
│ │ │ │ ├── deleteAccount.scss
│ │ │ │ └── index.ts
│ │ │ ├── Notifications
│ │ │ │ ├── Filters
│ │ │ │ │ ├── ActionFilter.tsx
│ │ │ │ │ ├── DateFilter.tsx
│ │ │ │ │ ├── EnvironmentFilter.tsx
│ │ │ │ │ ├── OrganizationsFilter.tsx
│ │ │ │ │ ├── ProjectFilter.tsx
│ │ │ │ │ └── TeamMemberFilter.tsx
│ │ │ │ ├── Notification.tsx
│ │ │ │ ├── NotificationDropdown.tsx
│ │ │ │ ├── NotificationFilter.tsx
│ │ │ │ ├── NotificationItem.tsx
│ │ │ │ ├── Notifications.tsx
│ │ │ │ ├── index.ts
│ │ │ │ └── notifications.scss
│ │ │ ├── ProfileSettings
│ │ │ │ ├── ProfileSettings.tsx
│ │ │ │ └── index.ts
│ │ │ ├── Providers
│ │ │ │ └── Providers.tsx
│ │ │ └── UserProviders
│ │ │ │ ├── UserProviders.tsx
│ │ │ │ └── index.ts
│ │ ├── cluster
│ │ │ ├── CustomDomain
│ │ │ │ ├── CustomDomainForm.tsx
│ │ │ │ ├── CustomDomains.tsx
│ │ │ │ ├── DnsSettings.tsx
│ │ │ │ ├── DomainList.tsx
│ │ │ │ └── ReverseProxyURL.tsx
│ │ │ ├── ReleaseColumns.tsx
│ │ │ ├── ReleaseDropdown.tsx
│ │ │ ├── ReleaseHistory.tsx
│ │ │ ├── ReleaseHistoryColumns.tsx
│ │ │ └── index.ts
│ │ ├── container
│ │ │ ├── ContainerColumns.tsx
│ │ │ ├── CreateContainerButton.tsx
│ │ │ ├── CreateContainerDrawer.tsx
│ │ │ ├── CreateForms
│ │ │ │ ├── CronJobFrom.tsx
│ │ │ │ ├── DeploymentForm.tsx
│ │ │ │ └── StatefulForm.tsx
│ │ │ ├── DeleteContainer.tsx
│ │ │ ├── EditContainer.tsx
│ │ │ └── config
│ │ │ │ ├── AutoScaleConfig.tsx
│ │ │ │ ├── BuildLogs.tsx
│ │ │ │ ├── Builds.tsx
│ │ │ │ ├── ContainerFormLayout.tsx
│ │ │ │ ├── CronExamples.tsx
│ │ │ │ ├── Events.tsx
│ │ │ │ ├── Logs.tsx
│ │ │ │ ├── Networking.tsx
│ │ │ │ ├── PodConfiguration.tsx
│ │ │ │ ├── Pods.tsx
│ │ │ │ ├── Probes.tsx
│ │ │ │ ├── SourceConfig.tsx
│ │ │ │ ├── StorageConfig.tsx
│ │ │ │ ├── Variables.tsx
│ │ │ │ └── index.ts
│ │ ├── organization
│ │ │ ├── OrganizationCreateButton.tsx
│ │ │ ├── OrganizationCreateModal.tsx
│ │ │ ├── OrganizationDropdown.tsx
│ │ │ ├── OrganizationSettings.tsx
│ │ │ ├── Settings
│ │ │ │ ├── ChangeOrganizationAvatar.tsx
│ │ │ │ ├── ChangeOrganizationName.tsx
│ │ │ │ ├── DeleteOrganization.tsx
│ │ │ │ ├── Members
│ │ │ │ │ ├── InviteOrganization.tsx
│ │ │ │ │ ├── OrganizationInvitationDrawer.tsx
│ │ │ │ │ ├── OrganizationInvitations.tsx
│ │ │ │ │ ├── OrganizationInvitationsColumns.tsx
│ │ │ │ │ ├── OrganizationMembers.tsx
│ │ │ │ │ ├── OrganizationMembersColumns.tsx
│ │ │ │ │ └── OrganizationMembersTableHeader.tsx
│ │ │ │ └── TransferOrganization.tsx
│ │ │ ├── index.ts
│ │ │ ├── navbar
│ │ │ │ ├── OrganizationMenuItem.tsx
│ │ │ │ └── organizationMenu.scss
│ │ │ └── organization.scss
│ │ ├── profile
│ │ │ ├── ClusterManagement.tsx
│ │ │ ├── ClusterManagementGeneral.tsx
│ │ │ ├── ClusterManagementUsage.tsx
│ │ │ └── TransferClusterOwnership.tsx
│ │ └── projects
│ │ │ ├── CreateEnvironment.tsx
│ │ │ ├── CreateProject.tsx
│ │ │ ├── DeleteProject.tsx
│ │ │ ├── EditProject.tsx
│ │ │ ├── EnvironmentDropdown.tsx
│ │ │ ├── Environments.tsx
│ │ │ ├── EnvironmentsColumns.tsx
│ │ │ ├── ProjectActions.tsx
│ │ │ ├── ProjectCard.tsx
│ │ │ ├── ProjectColumns.tsx
│ │ │ ├── ProjectGeneralSettings.tsx
│ │ │ ├── ProjectInvitationFilter.tsx
│ │ │ ├── ProjectInvitations.tsx
│ │ │ ├── ProjectInviteMember.tsx
│ │ │ ├── ProjectMembers.tsx
│ │ │ ├── ProjectMembersColumns.tsx
│ │ │ ├── ProjectSelectDropdown.tsx
│ │ │ ├── ProjectSettings.tsx
│ │ │ ├── ProjectTeam.tsx
│ │ │ └── Settings
│ │ │ ├── ChangeProjectAvatar.tsx
│ │ │ ├── ChangeProjectName.tsx
│ │ │ ├── ProjectInvitationsColumns.tsx
│ │ │ └── TransferProject.tsx
│ ├── helpers
│ │ ├── axios.ts
│ │ ├── componentLoader.ts
│ │ ├── index.ts
│ │ ├── realtime
│ │ │ ├── Cluster.ts
│ │ │ ├── Container.ts
│ │ │ ├── Environment.ts
│ │ │ ├── OrgMember.ts
│ │ │ ├── Organization.ts
│ │ │ ├── Project.ts
│ │ │ ├── ProjectTeam.ts
│ │ │ ├── RealtimeActions.ts
│ │ │ ├── User.ts
│ │ │ └── index.ts
│ │ └── socket.ts
│ ├── hooks
│ │ ├── index.ts
│ │ ├── useAuthorizeOrg.tsx
│ │ ├── useAuthorizeProject.tsx
│ │ ├── useDebounce.tsx
│ │ ├── useEnvironmentDropdown.tsx
│ │ ├── useRealtime.tsx
│ │ ├── useSearch.tsx
│ │ ├── useTable.tsx
│ │ ├── useToast.tsx
│ │ └── useUpdateEffect.tsx
│ ├── i18n
│ │ ├── config.ts
│ │ └── en
│ │ │ ├── cluster.json
│ │ │ ├── container.json
│ │ │ ├── forms.json
│ │ │ ├── general.json
│ │ │ ├── index.ts
│ │ │ ├── login.json
│ │ │ ├── onboarding.json
│ │ │ ├── organization.json
│ │ │ ├── profileSettings.json
│ │ │ └── project.json
│ ├── index.scss
│ ├── layouts
│ │ ├── AuthLayout
│ │ │ ├── AuthLayout.tsx
│ │ │ └── index.ts
│ │ └── Layout
│ │ │ ├── Layout.tsx
│ │ │ └── index.ts
│ ├── main.tsx
│ ├── pages
│ │ ├── auth
│ │ │ ├── Login.tsx
│ │ │ ├── OrgAcceptInvitation.tsx
│ │ │ └── ProjectAcceptInvitation.tsx
│ │ ├── environment
│ │ │ ├── Environment.tsx
│ │ │ └── EnvironmentContainers.tsx
│ │ ├── errors
│ │ │ ├── 401.tsx
│ │ │ ├── 404.tsx
│ │ │ └── ErrorBoundary.tsx
│ │ ├── home
│ │ │ ├── Health.tsx
│ │ │ └── Home.tsx
│ │ ├── notifications
│ │ │ └── Notifications.tsx
│ │ ├── onboarding
│ │ │ ├── AccountSetup.tsx
│ │ │ ├── Onboarding.tsx
│ │ │ └── Register.tsx
│ │ ├── organization
│ │ │ ├── Organization.tsx
│ │ │ ├── OrganizationDetails.tsx
│ │ │ ├── OrganizationProjects.tsx
│ │ │ ├── OrganizationSelect.tsx
│ │ │ └── organization.scss
│ │ ├── redirect-handle
│ │ │ └── RedirectHandle.tsx
│ │ └── root
│ │ │ ├── Root.tsx
│ │ │ └── index.ts
│ ├── router
│ │ ├── index.ts
│ │ ├── loader
│ │ │ ├── AuthLoader.ts
│ │ │ ├── HomeLoader.ts
│ │ │ └── OnboardingLoader.ts
│ │ └── router.tsx
│ ├── services
│ │ ├── AuthService.ts
│ │ ├── ClusterService.ts
│ │ ├── ContainerService.ts
│ │ ├── EnvironmentService.ts
│ │ ├── NotificationService.ts
│ │ ├── OrganizationService.ts
│ │ ├── ProjectService.ts
│ │ ├── TypesService.ts
│ │ ├── UserService.ts
│ │ └── index.ts
│ ├── store
│ │ ├── auth
│ │ │ └── authStore.ts
│ │ ├── cluster
│ │ │ └── clusterStore.ts
│ │ ├── container
│ │ │ └── containerStore.ts
│ │ ├── environment
│ │ │ └── environmentStore.ts
│ │ ├── notification
│ │ │ └── notificationStore.ts
│ │ ├── organization
│ │ │ └── organizationStore.ts
│ │ ├── project
│ │ │ └── projectStore.ts
│ │ ├── theme
│ │ │ └── themeStore.ts
│ │ └── types
│ │ │ └── typeStore.ts
│ ├── types
│ │ ├── cluster.ts
│ │ ├── container.ts
│ │ ├── environment.ts
│ │ ├── index.ts
│ │ ├── organization.ts
│ │ ├── project.ts
│ │ ├── schema.ts
│ │ └── type.ts
│ ├── utils
│ │ ├── index.ts
│ │ ├── redirect.ts
│ │ ├── time.ts
│ │ └── utils.ts
│ └── vite-env.d.ts
├── tailwind.config.js
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
├── sync
├── .dockerignore
├── Dockerfile
├── Dockerfile.dev
├── config
│ ├── default.json
│ ├── development.json
│ ├── errorCodes.js
│ └── production.json
├── eslint.config.js
├── init
│ ├── cache.js
│ ├── logger.js
│ └── sync.js
├── middlewares
│ ├── logRequest.js
│ ├── rateLimiter.js
│ └── undefinedPaths.js
├── package-lock.json
├── package.json
├── routes
│ └── system.js
├── server.js
└── util
│ └── helper.js
└── webhook
├── Dockerfile
├── Dockerfile.dev
├── README.md
├── agnost.go
├── go.mod
├── go.sum
├── main.go
└── package.json
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cloud-agnost/agnost-gitops/6c18a641b9dce8dea274b8797ee7a86ee474d8f2/.DS_Store
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: "[BUG]"
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, could you add screenshots to help explain your problem?
25 |
26 | **Suggested Fixes**
27 | "If you know of any possible way to resolve the issue, please let us know."
28 |
29 | **Desktop (please complete the following information):**
30 | - OS: [e.g. iOS]
31 | - Browser [e.g., chrome, safari]
32 | - Version [e.g. 22]
33 |
34 | **Smartphone (please complete the following information):**
35 | - Device: [e.g. iPhone6]
36 | - OS: [e.g. iOS8.1]
37 | - Browser [e.g. stock browser, safari]
38 | - Version [e.g. 22]
39 |
40 | **Additional context**
41 | Please feel free to add any other context about the problem here.
42 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/scripts/create_release_manifest.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import json
3 | import os
4 |
5 | release_number = os.environ['RELEASE_NUMBER']
6 | release_file = os.path.join('releases', release_number + '.json')
7 | latest_file = os.path.join('releases', 'latest.json')
8 | applications = ['monitor', 'platform', 'sync', 'webhook', 'studio']
9 |
10 | new_release_dict = {}
11 | modules_dict = {}
12 |
13 | for app in applications:
14 | package_file = os.path.join(app, 'package.json')
15 | with open(package_file) as fp:
16 | package_info = json.load(fp)
17 | version = package_info['version']
18 | app_name = app.replace('/', '-')
19 | modules_dict[app_name] = version
20 |
21 | new_release_dict["release"] = release_number
22 | new_release_dict["modules"] = modules_dict
23 |
24 | with open(release_file, 'w') as fp:
25 | fp.write(json.dumps(new_release_dict, indent=3))
26 | fp.close()
27 |
28 | with open(latest_file, 'w') as fp:
29 | fp.write(json.dumps(new_release_dict, indent=3))
30 | fp.close()
31 |
--------------------------------------------------------------------------------
/.github/scripts/update_helm_chart.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import json
3 | import os
4 | from ruamel.yaml import YAML
5 |
6 | yaml=YAML()
7 | yaml.preserve_quotes = True
8 | values_yaml = "base/values.yaml"
9 | chart_yaml = "base/Chart.yaml"
10 | applications = json.loads(sys.argv[1])
11 |
12 | ## Update values.yaml with the new image tags
13 | values_data = yaml.load(open(values_yaml).read())
14 |
15 | for app in applications:
16 | app_name = app['application']
17 | if app_name == 'webhook':
18 | values_data['agnost-webhook']['image']['tag'] = app['version']
19 | else:
20 | values_data[app_name]['tag'] = app['version']
21 |
22 | with open(values_yaml, 'w') as outfile:
23 | yaml.dump(values_data, outfile)
24 |
25 | ## Update Chart.yaml with a new version
26 | chart_data = yaml.load(open(chart_yaml).read())
27 | command = 'semver next ' + os.environ['RELEASE_TYPE'] + ' ' + chart_data['version']
28 | chart_data['version'] = os.popen(command).read().strip()
29 |
30 | chart_data['appVersion'] = os.environ['RELEASE_NUMBER']
31 |
32 | with open(chart_yaml, 'w') as outfile:
33 | yaml.dump(chart_data, outfile)
34 |
--------------------------------------------------------------------------------
/.github/scripts/update_package_json.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import json
3 | import os
4 |
5 | applications = json.loads(sys.argv[1])
6 |
7 | for app in applications:
8 | package_json_file = os.path.join('.', app['application'], 'package.json')
9 | with open(package_json_file) as fp:
10 | package_json = json.load(fp)
11 |
12 | package_json['version'] = app['version']
13 |
14 | with open(package_json_file, 'w', encoding='utf8') as fp:
15 | fp.write(json.dumps(package_json, indent=3, ensure_ascii=False))
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "tabWidth": 2,
4 | "printWidth": 100,
5 | "singleQuote": true,
6 | "trailingComma": "all",
7 | "jsxSingleQuote": true,
8 | "bracketSpacing": true,
9 | "useTabs": true
10 | }
11 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/PR_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # Pull Request Template
2 |
3 | ## Description
4 |
5 | Please include a summary of the change and which issue is fixed. Include relevant motivation and context. List any dependencies that are required for this change.
6 |
7 | Fixes # (issue)
8 |
9 | ## Type of change
10 |
11 | Please delete options that are not relevant.
12 |
13 | - [ ] Bug fix (non-breaking change which fixes an issue)
14 | - [ ] New feature (non-breaking change which adds functionality)
15 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
16 | - [ ] This change requires a documentation update
17 |
18 | ## How Has This Been Tested?
19 |
20 | Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration.
21 |
22 | - [ ] Test A
23 | - [ ] Test B
24 |
25 | ## Checklist:
26 |
27 | - [ ] My code follows the style guidelines of this project
28 | - [ ] I have performed a self-review of my own code
29 | - [ ] I have commented my code, particularly in hard-to-understand areas
30 | - [ ] I have made corresponding changes to the documentation
31 | - [ ] My changes generate no new warnings
32 | - [ ] I have added tests that prove my fix is effective or that my feature works
33 | - [ ] New and existing unit tests pass locally with my changes
34 | - [ ] Any dependent changes have been merged and published in downstream modules
35 |
--------------------------------------------------------------------------------
/k8s/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "k8s",
3 | "lockfileVersion": 3,
4 | "requires": true,
5 | "packages": {}
6 | }
7 |
--------------------------------------------------------------------------------
/k8s/skaffold.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: skaffold/v2beta14
2 | kind: Config
3 | build:
4 | artifacts:
5 | - image: cloudagnost/platform
6 | context: ../platform
7 | sync:
8 | infer:
9 | - '**/*.js'
10 | - '**/*.json'
11 | - '**/*.yaml'
12 | docker:
13 | dockerfile: Dockerfile.dev
14 | - image: cloudagnost/sync
15 | context: ../sync
16 | sync:
17 | infer:
18 | - '**/*.js'
19 | - '**/*.json'
20 | docker:
21 | dockerfile: Dockerfile.dev
22 | - image: cloudagnost/monitor
23 | context: ../monitor
24 | sync:
25 | infer:
26 | - '**/*.js'
27 | - '**/*.json'
28 | docker:
29 | dockerfile: Dockerfile.dev
30 | - image: cloudagnost/webhook
31 | context: ../webhook
32 | sync:
33 | infer:
34 | - '**/*.go'
35 | docker:
36 | dockerfile: Dockerfile.dev
37 | deploy:
38 | statusCheckDeadlineSeconds: 300
39 | kubectl:
40 | defaultNamespace: agnost
41 | manifests:
42 | - platform.yaml
43 | - monitor.yaml
44 | - sync.yaml
45 | - webhook.yaml
46 |
47 |
--------------------------------------------------------------------------------
/k8s/sync.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: sync
5 | namespace: agnost
6 | labels:
7 | app: sync
8 | spec:
9 | replicas: 1
10 | selector:
11 | matchLabels:
12 | app: sync
13 | template:
14 | metadata:
15 | labels:
16 | app: sync
17 | spec:
18 | containers:
19 | - name: sync
20 | image: cloudagnost/sync:v4
21 | # We will be using the minikube docker daemon, since the actual docker daemon on local machine are different
22 | # Prevent minikube docker daemon to pull images from central docker hub set imagePullPolicy to Never, so that
23 | imagePullPolicy: Never
24 | ports:
25 | - containerPort: 4000
26 | env:
27 | - name: CACHE_HOSTNAME
28 | valueFrom:
29 | secretKeyRef:
30 | name: redis
31 | key: hostname
32 | - name: CACHE_PWD
33 | valueFrom:
34 | secretKeyRef:
35 | name: redis
36 | key: password
37 | - name: NAMESPACE
38 | value: agnost
39 | - name: RELEASE_NUMBER
40 | value: "v0.0.2"
--------------------------------------------------------------------------------
/monitor/.dockerignore:
--------------------------------------------------------------------------------
1 | Dockerfile
2 | Dockerfile.dev
3 | package-lock.json
4 | eslint.config.js
5 | node_modules
--------------------------------------------------------------------------------
/monitor/Dockerfile:
--------------------------------------------------------------------------------
1 | # Get the base node image from docker hub
2 | FROM node:20-alpine
3 | # Set the working directory in our docker image, anything that is copied from now on
4 | # from local machine to the image will be moved under '/app' directory in docker image
5 | WORKDIR '/app'
6 | # Copy package.json file from local machine to the image under '/app' directory
7 | COPY ./package.json ./
8 | # Set the node environment variable
9 | ENV NODE_ENV production
10 | # Run npm install to install dependent modules
11 | RUN npm install --omit=dev
12 | # Copy everything to the image under '/app' directory
13 | COPY ./ ./
14 | # This will be the start up command of the docker container, run the development react server
15 | CMD ["node", "--expose-gc", "server"]
16 |
--------------------------------------------------------------------------------
/monitor/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | # Get the base node image from docker hub
2 | FROM node:20-alpine
3 | # Set the working directory in our docker image, anything that is copied from now on
4 | # from local machine to the image will be moved under '/app' directory in docker image
5 | WORKDIR '/app'
6 | # Copy package.json file from local machine to the image under '/app' directory
7 | COPY ./package.json ./
8 | # Set the node environment variable
9 | ENV NODE_ENV development
10 | # Run npm install to install dependent modules
11 | RUN npm install -g nodemon
12 | RUN npm install --omit=dev
13 | # Copy everything to the image under '/app' directory
14 | COPY ./ ./
15 | # This will be the start up command of the docker container, run the development react server
16 | CMD ["nodemon", "--expose-gc", "server"]
17 |
18 |
--------------------------------------------------------------------------------
/monitor/config/default.json:
--------------------------------------------------------------------------------
1 | {
2 | "server": {
3 | "host": "localhost",
4 | "port": 4000,
5 | "timeout": 3600000
6 | },
7 | "general": {
8 | "monitoringInterval": 3000,
9 | "monitoringIntervalTokens": 600000,
10 | "monitoringIntervalRepositories": 3600000,
11 | "resourcePaginationSize": 100,
12 | "containerPaginationSize": 100,
13 | "gitProviderPaginationSize": 100,
14 | "gcSeconds": 30,
15 | "registry": {
16 | "maxImages": 5
17 | }
18 | },
19 | "database": {
20 | "maxPoolSize": 3
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/monitor/config/development.json:
--------------------------------------------------------------------------------
1 | {
2 | }
3 |
--------------------------------------------------------------------------------
/monitor/config/errorCodes.js:
--------------------------------------------------------------------------------
1 | const ERROR_CODES = {
2 | internalServerError: "internal_server_error",
3 | missingAccessToken: "missing_access_token",
4 | invalidCredentials: "invalid_credentials",
5 | invalidContentType: "invalid_content_type",
6 | invalidRequestBody: "invalid_request_body",
7 | rateLimitExceeded: "rate_limit_exceeded",
8 | resourceNotFound: "resource_not_found",
9 | };
10 | export default ERROR_CODES;
11 |
--------------------------------------------------------------------------------
/monitor/config/production.json:
--------------------------------------------------------------------------------
1 | {
2 | }
3 |
--------------------------------------------------------------------------------
/monitor/eslint.config.js:
--------------------------------------------------------------------------------
1 | import globals from "globals";
2 | import pluginJs from "@eslint/js";
3 |
4 | export default [
5 | { languageOptions: { globals: { ...globals.browser, ...globals.node } } },
6 | pluginJs.configs.recommended,
7 | ];
8 |
--------------------------------------------------------------------------------
/monitor/init/db.js:
--------------------------------------------------------------------------------
1 | import config from "config";
2 | import mongo from "mongodb";
3 |
4 | //MongoDB client
5 | var client;
6 |
7 | export const connectToDatabase = async () => {
8 | try {
9 | client = new mongo.MongoClient(process.env.CLUSTER_DB_URI, {
10 | maxPoolSize: config.get("database.maxPoolSize"),
11 | auth: {
12 | username: process.env.CLUSTER_DB_USER,
13 | password: process.env.CLUSTER_DB_PWD,
14 | },
15 | });
16 | //Connect to the database of the application
17 | await client.connect();
18 | console.info(`Connected to the database ${process.env.CLUSTER_DB_URI}`);
19 | } catch (err) {
20 | console.error(`Cannot connect to the database. ${err}`);
21 | process.exit(1);
22 | }
23 | };
24 |
25 | export const disconnectFromDatabase = async () => {
26 | await client.close();
27 | console.info("Disconnected from the database");
28 | };
29 |
30 | export const getDBClient = () => {
31 | return client;
32 | };
33 |
--------------------------------------------------------------------------------
/monitor/middlewares/logRequest.js:
--------------------------------------------------------------------------------
1 | // Log requests to console
2 | export const logRequest = (req, res, time) => {
3 | if (req.originalUrl !== "/health")
4 | console.info(
5 | `${req.method} (${res.statusCode}) ${Math.round(time * 10) / 10}ms ${
6 | req.originalUrl
7 | }`
8 | );
9 | };
10 |
--------------------------------------------------------------------------------
/monitor/middlewares/undefinedPaths.js:
--------------------------------------------------------------------------------
1 | import ERROR_CODES from "../config/errorCodes.js";
2 |
3 | // Middleware to handle undefined paths or posts
4 | export const handleUndefinedPaths = (req, res) => {
5 | return res.status(404).json({
6 | error: "Not Found",
7 | details: "The server can not find the requested resource.",
8 | code: ERROR_CODES.resourceNotFound,
9 | });
10 | };
11 |
--------------------------------------------------------------------------------
/monitor/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "engine-core",
3 | "version": "v1.0.0",
4 | "description": "Engine core api server",
5 | "main": "server.js",
6 | "type": "module",
7 | "scripts": {
8 | "prod": "NODE_ENV=production node server",
9 | "dev": "NODE_ENV=development nodemon server"
10 | },
11 | "author": "Ümit Çakmak",
12 | "license": "ISC",
13 | "dependencies": {
14 | "@kubernetes/client-node": "0.18.1",
15 | "axios": "1.3.3",
16 | "config": "3.3.9",
17 | "crypto-js": "4.1.1",
18 | "express": "4.18.2",
19 | "mongodb": "5.1.0",
20 | "response-time": "2.3.2",
21 | "winston": "3.8.2",
22 | "winston-transport": "4.5.0"
23 | },
24 | "devDependencies": {
25 | "@eslint/js": "^9.4.0",
26 | "eslint": "^9.4.0",
27 | "globals": "^15.3.0",
28 | "nodemon": "2.0.20"
29 | }
30 | }
--------------------------------------------------------------------------------
/monitor/routes/system.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 |
3 | const router = express.Router({ mergeParams: true });
4 |
5 | /*
6 | @route /health
7 | @method GET
8 | @desc Checks liveliness of monitoring server
9 | @access public
10 | */
11 | router.get("/health", (req, res) => {
12 | res
13 | .status(200)
14 | .send(
15 | new Date().toISOString() +
16 | " - Healthy monitoring server" +
17 | " - " +
18 | process.env.RELEASE_NUMBER
19 | );
20 | });
21 |
22 | /*
23 | @route /ping
24 | @method GET
25 | @desc Checks liveliness of monitoring server
26 | @access public
27 | */
28 | router.get("/ping", (req, res) => {
29 | res.status(200).send(new Date().toISOString() + " - Pong!");
30 | });
31 |
32 | export default router;
33 |
--------------------------------------------------------------------------------
/monitor/util/helper.js:
--------------------------------------------------------------------------------
1 | import cyripto from "crypto-js";
2 |
3 | /**
4 | * Returns the platform URL based on the current environment.
5 | * @returns {string} The platform URL.
6 | */
7 | function getPlatformUrl() {
8 | return `http://platform.${process.env.NAMESPACE}.svc.cluster.local:4000`;
9 | }
10 |
11 | /**
12 | * Decrypts the encrypted text and returns the decrypted string value
13 | * @param {string} ciphertext The encrypted input text
14 | */
15 | function decryptText(cipherText) {
16 | const bytes = cyripto.AES.decrypt(cipherText, process.env.PASSPHRASE);
17 | return bytes.toString(cyripto.enc.Utf8);
18 | }
19 |
20 | export default {
21 | getPlatformUrl,
22 | decryptText,
23 | };
24 |
--------------------------------------------------------------------------------
/platform/.dockerignore:
--------------------------------------------------------------------------------
1 | Dockerfile
2 | Dockerfile.dev
3 | package-lock.json
4 | eslint.config.js
5 | node_modules
--------------------------------------------------------------------------------
/platform/.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 |
--------------------------------------------------------------------------------
/platform/Dockerfile:
--------------------------------------------------------------------------------
1 | # Get the base node image from docker hub
2 | FROM node:20-alpine
3 | # Set the working directory in our docker image, anything that is copied from now on
4 | # from local machine to the image will be moved under '/app' directory in docker image
5 | WORKDIR '/app'
6 | # Copy package.json file from local machine to the image under '/app' directory
7 | COPY ./package.json ./
8 | # Set the node environment variable
9 | ENV NODE_ENV production
10 | # Run npm install to install dependent modules
11 | RUN npm install --omit=dev
12 | # Copy everything to the image under '/app' directory
13 | COPY ./ ./
14 | # This will be the start up command of the docker container, run the development react server
15 | CMD ["node", "server"]
16 |
--------------------------------------------------------------------------------
/platform/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | # Get the base node image from docker hub
2 | FROM node:20-alpine
3 | # Set the working directory in our docker image, anything that is copied from now on
4 | # from local machine to the image will be moved under '/app' directory in docker image
5 | WORKDIR '/app'
6 | # Copy package.json file from local machine to the image under '/app' directory
7 | COPY ./package.json ./
8 | # Set the node environment variable
9 | ENV NODE_ENV development
10 | # Run npm install to install dependent modules
11 | RUN npm install -g nodemon
12 | RUN npm install --omit=dev
13 | # Copy everything to the image under '/app' directory
14 | COPY ./ ./
15 | # This will be the start up command of the docker container, run the development react server
16 | CMD ["nodemon", "--trace-warnings", "server"]
17 |
--------------------------------------------------------------------------------
/platform/config/development.json:
--------------------------------------------------------------------------------
1 | {
2 | "session": {
3 | "accessTokenExpiry": 31536000,
4 | "refreshTokenExpiry": 31536000,
5 | "refreshTokenDelete": 60
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/platform/config/errorCodes.js:
--------------------------------------------------------------------------------
1 | const ERROR_CODES = {
2 | // Platform specific error codes
3 | internalServerError: "internal_server_error",
4 | rateLimitExceeded: "rate_limit_exceeded",
5 | resourceNotFound: "resource_not_found",
6 | invalidRequestBody: "invalid_request_body",
7 | invalidContentType: "invalid_content_type",
8 | validationError: "validation_error",
9 | invalidCredentials: "invalid_credentials",
10 | missingAccessToken: "missing_access_token",
11 | invalidSession: "invalid_session",
12 | invalidAccessToken: "invalid_access_token",
13 | invalidUser: "invalid_user",
14 | notAllowed: "not_allowed",
15 | fileUploadError: "file_upload_error",
16 | unauthorized: "unauthorized",
17 | notFound: "not_found",
18 | fileSizeTooLarge: "file_size_too_large",
19 | badRequest: "bad_request",
20 | };
21 | export default ERROR_CODES;
22 |
--------------------------------------------------------------------------------
/platform/config/production.json:
--------------------------------------------------------------------------------
1 | {
2 | }
3 |
--------------------------------------------------------------------------------
/platform/controllers/container.js:
--------------------------------------------------------------------------------
1 | import BaseController from "./base.js";
2 | import { ContainerModel } from "../schemas/container.js";
3 |
4 | class ContainerController extends BaseController {
5 | constructor() {
6 | super(ContainerModel);
7 | }
8 | }
9 |
10 | export default new ContainerController();
11 |
--------------------------------------------------------------------------------
/platform/controllers/domain.js:
--------------------------------------------------------------------------------
1 | import BaseController from "./base.js";
2 | import { DomainModel } from "../schemas/domain.js";
3 |
4 | class DomainController extends BaseController {
5 | constructor() {
6 | super(DomainModel);
7 | }
8 | }
9 |
10 | export default new DomainController();
11 |
--------------------------------------------------------------------------------
/platform/controllers/environment.js:
--------------------------------------------------------------------------------
1 | import BaseController from "./base.js";
2 | import { ProjectEnvModel } from "../schemas/environment.js";
3 | import auditCtrl from "./audit.js";
4 | import cntrCtrl from "./container.js";
5 |
6 | class ProjectEnvController extends BaseController {
7 | constructor() {
8 | super(ProjectEnvModel);
9 | }
10 |
11 | /**
12 | * Delete all project environment related data
13 | * @param {Object} session The database session object
14 | * @param {Object} org The organization object
15 | * @param {Object} project The project object
16 | * @param {Object} environment The environment object that will be deleted
17 | */
18 | async deleteEnvironment(session, org, project, environment) {
19 | await this.deleteOneById(environment._id, {
20 | session,
21 | cacheKey: environment._id,
22 | });
23 |
24 | await cntrCtrl.deleteManyByQuery(
25 | {
26 | orgId: org._id,
27 | projectId: project._id,
28 | environmentId: environment._id,
29 | },
30 | { session }
31 | );
32 |
33 | await auditCtrl.deleteManyByQuery(
34 | {
35 | orgId: org._id,
36 | projectId: project._id,
37 | environmentId: environment._id,
38 | },
39 | { session }
40 | );
41 | }
42 | }
43 |
44 | export default new ProjectEnvController();
45 |
--------------------------------------------------------------------------------
/platform/controllers/gitProvider.js:
--------------------------------------------------------------------------------
1 | import BaseController from "./base.js";
2 | import { GitProviderModel } from "../schemas/gitProvider.js";
3 |
4 | class GitProviderController extends BaseController {
5 | constructor() {
6 | super(GitProviderModel);
7 | }
8 | }
9 |
10 | export default new GitProviderController();
11 |
--------------------------------------------------------------------------------
/platform/controllers/organizationInvitation.js:
--------------------------------------------------------------------------------
1 | import BaseController from "./base.js";
2 | import { OrgInvitationModel } from "../schemas/organizationInvitation.js";
3 |
4 | class OrgInvitationController extends BaseController {
5 | constructor() {
6 | super(OrgInvitationModel);
7 | }
8 |
9 | /**
10 | * Updates the matching host name in all organization invitaions
11 | * @param {string} userId The user identifier
12 | * @param {string} name The name of the user
13 | */
14 | async updateHostName(userId, name) {
15 | await this.updateMultiByQuery(
16 | { "host.userId": userId },
17 | { "host.name": name },
18 | {},
19 | { writeConcern: { w: 0 } }
20 | );
21 | }
22 |
23 | /**
24 | * Updates the matching host profile image in all organization invitaions
25 | * @param {string} userId The user identifier
26 | * @param {string} pictureUrl The url of the profile picture
27 | */
28 | async updateHostPicture(userId, pictureUrl) {
29 | await this.updateMultiByQuery(
30 | { "host.userId": userId },
31 | { "host.pictureUrl": pictureUrl },
32 | {},
33 | { writeConcern: { w: 0 } }
34 | );
35 | }
36 |
37 | /**
38 | * Removes the matching host profile image in all organization invitaions
39 | * @param {string} userId The user identifier
40 | */
41 | async removeHostPicture(userId) {
42 | await this.updateMultiByQuery(
43 | { "host.userId": userId },
44 | {},
45 | { "host.pictureUrl": 1 },
46 | { writeConcern: { w: 0 } }
47 | );
48 | }
49 | }
50 |
51 | export default new OrgInvitationController();
52 |
--------------------------------------------------------------------------------
/platform/controllers/organizationMember.js:
--------------------------------------------------------------------------------
1 | import BaseController from "./base.js";
2 | import { OrganizationMemberModel } from "../schemas/organizationMember.js";
3 |
4 | class OrganizationMemberController extends BaseController {
5 | constructor() {
6 | super(OrganizationMemberModel);
7 | }
8 | }
9 |
10 | export default new OrganizationMemberController();
11 |
--------------------------------------------------------------------------------
/platform/controllers/projectInvitation.js:
--------------------------------------------------------------------------------
1 | import BaseController from "./base.js";
2 | import { ProjectInvitationModel } from "../schemas/projectInvitation.js";
3 |
4 | class ProjectInvitationController extends BaseController {
5 | constructor() {
6 | super(ProjectInvitationModel);
7 | }
8 |
9 | /**
10 | * Updates the matching host name in all app invitaions
11 | * @param {string} userId The user identifier
12 | * @param {string} name The name of the user
13 | */
14 | async updateHostName(userId, name) {
15 | await this.updateMultiByQuery(
16 | { "host.userId": userId },
17 | { "host.name": name },
18 | {},
19 | { writeConcern: { w: 0 } }
20 | );
21 | }
22 |
23 | /**
24 | * Updates the matching host profile image in all app invitaions
25 | * @param {string} userId The user identifier
26 | * @param {string} pictureUrl The url of the profile picture
27 | */
28 | async updateHostPicture(userId, pictureUrl) {
29 | await this.updateMultiByQuery(
30 | { "host.userId": userId },
31 | { "host.pictureUrl": pictureUrl },
32 | {},
33 | { writeConcern: { w: 0 } }
34 | );
35 | }
36 |
37 | /**
38 | * Removes the matching host profile image in all app invitaions
39 | * @param {string} userId The user identifier
40 | */
41 | async removeHostPicture(userId) {
42 | await this.updateMultiByQuery(
43 | { "host.userId": userId },
44 | {},
45 | { "host.pictureUrl": 1 },
46 | { writeConcern: { w: 0 } }
47 | );
48 | }
49 | }
50 |
51 | export default new ProjectInvitationController();
52 |
--------------------------------------------------------------------------------
/platform/controllers/registry.js:
--------------------------------------------------------------------------------
1 | import BaseController from "./base.js";
2 | import { RegistryModel } from "../schemas/registry.js";
3 |
4 | class RegistryController extends BaseController {
5 | constructor() {
6 | super(RegistryModel);
7 | }
8 | }
9 |
10 | export default new RegistryController();
11 |
--------------------------------------------------------------------------------
/platform/controllers/tcpProxyPort.js:
--------------------------------------------------------------------------------
1 | import BaseController from "./base.js";
2 | import { TCPProxyPortModel } from "../schemas/tcpProxyPort.js";
3 |
4 | class TCPProxyPortController extends BaseController {
5 | constructor() {
6 | super(TCPProxyPortModel);
7 | }
8 | }
9 |
10 | export default new TCPProxyPortController();
11 |
--------------------------------------------------------------------------------
/platform/controllers/user.js:
--------------------------------------------------------------------------------
1 | import BaseController from "./base.js";
2 | import { UserModel } from "../schemas/user.js";
3 |
4 | class UserController extends BaseController {
5 | constructor() {
6 | super(UserModel);
7 | }
8 | }
9 |
10 | export default new UserController();
11 |
--------------------------------------------------------------------------------
/platform/eslint.config.js:
--------------------------------------------------------------------------------
1 | import globals from "globals";
2 | import pluginJs from "@eslint/js";
3 |
4 | export default [
5 | { languageOptions: { globals: { ...globals.browser, ...globals.node } } },
6 | pluginJs.configs.recommended,
7 | ];
8 |
--------------------------------------------------------------------------------
/platform/handlers/manifests/cronjob.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: batch/v1
2 | kind: CronJob
3 | metadata:
4 | name: dummy-cron-job
5 | namespace: dummy-namespace
6 | spec:
7 | schedule: "*/5 * * * *" # Runs every 5 minutes
8 | timezone: "Etc/UTC" # Set the timezone for the cron schedule
9 | concurrencyPolicy: Forbid # Prevent concurrent jobs from running
10 | suspend: false # Set to true to suspend the cron job
11 | successfulJobsHistoryLimit: 3 # Number of successful job history to keep
12 | failedJobsHistoryLimit: 1 # Number of failed job history to keep
13 | jobTemplate:
14 | spec:
15 | template:
16 | spec:
17 | restartPolicy: OnFailure
18 | containers:
19 | - name: dummy
20 | image: docker.io/alpine
21 | resources:
22 | requests:
23 | memory: "64Mi"
24 | cpu: "100m"
25 | limits:
26 | memory: "1Gi"
27 | cpu: "1"
28 | env:
29 | - name: DUMMY_KEY_1
30 | value: "dummy_value_1"
31 | - name: DUMMY_KEY_2
32 | value: "dummy_value_2"
33 | - name: DUMMY_KEY_3
34 | value: "dummy_value_3"
35 | volumeMounts:
36 | - name: dummy-volume
37 | mountPath: /dummy
38 | volumes:
39 | - name: data-volume
40 | persistentVolumeClaim:
41 | claimName: data-pvc
42 |
--------------------------------------------------------------------------------
/platform/handlers/manifests/hpa.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: autoscaling/v2
2 | kind: HorizontalPodAutoscaler
3 | metadata:
4 | name: example-hpa
5 | namespace: default
6 | spec:
7 | scaleTargetRef:
8 | apiVersion: apps/v1
9 | kind: Deployment
10 | name: example-deployment
11 | minReplicas: 1
12 | maxReplicas: 5
13 | metrics:
14 | - type: Resource
15 | resource:
16 | name: cpu
17 | target:
18 | type: Utilization
19 | averageUtilization: 50
20 | - type: Resource
21 | resource:
22 | name: memory
23 | target:
24 | type: AverageValue
25 | averageUtilization: 150Mi
26 |
--------------------------------------------------------------------------------
/platform/handlers/manifests/namespace.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Namespace
3 | metadata:
4 | name: my-namespace
--------------------------------------------------------------------------------
/platform/handlers/manifests/pvc.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: PersistentVolumeClaim
3 | metadata:
4 | name: data-pvc
5 | namespace: dummy-namespace
6 | spec:
7 | accessModes:
8 | - ReadWriteOnce
9 | resources:
10 | requests:
11 | storage: 1Gi
--------------------------------------------------------------------------------
/platform/handlers/manifests/service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: my-clusterip-service
5 | namespace: my-namespace
6 | spec:
7 | type: ClusterIP
8 | selector:
9 | app: myapp
10 | ports:
11 | - protocol: TCP
12 | port: 80
13 | targetPort: 9376
--------------------------------------------------------------------------------
/platform/init/db.js:
--------------------------------------------------------------------------------
1 | import config from "config";
2 | import mongoose from "mongoose";
3 |
4 | export const connectToDatabase = async () => {
5 | try {
6 | mongoose.set("strictQuery", false);
7 | await mongoose.connect(process.env.CLUSTER_DB_URI, {
8 | user: process.env.CLUSTER_DB_USER,
9 | pass: process.env.CLUSTER_DB_PWD,
10 | maxPoolSize: config.get("database.maxPoolSize"),
11 | });
12 |
13 | console.info(`Connected to the database @${process.env.CLUSTER_DB_URI}`);
14 | } catch (err) {
15 | console.error(`Cannot connect to the database`, { details: err });
16 | process.exit(1);
17 | }
18 | };
19 |
20 | export const disconnectFromDatabase = async () => {
21 | await mongoose.disconnect();
22 | console.info("Disconnected from the database");
23 | };
24 |
--------------------------------------------------------------------------------
/platform/init/sync.js:
--------------------------------------------------------------------------------
1 | import config from "config";
2 | import { io } from "socket.io-client";
3 | import helper from "../util/helper.js";
4 |
5 | var socket = null;
6 |
7 | export function initializeSyncClient() {
8 | let syncConfig = config.get("sync");
9 | socket = io(`${helper.getSyncUrl()}/${syncConfig.namespace}`, {
10 | reconnection: syncConfig.reconnection,
11 | reconnectionDelay: syncConfig.reconnectionDelay,
12 | transports: ["websocket", "polling"],
13 | path: syncConfig.path,
14 | });
15 |
16 | socket.on("connect", () => {
17 | console.info(
18 | `Connection established to synronization server @${helper.getSyncUrl()}`
19 | );
20 | });
21 |
22 | socket.io.on("reconnect", () => {
23 | console.info(
24 | `Connection re-established to synronization server @${helper.getSyncUrl()}`
25 | );
26 | });
27 | }
28 |
29 | export function disconnectSyncClient() {
30 | socket.close();
31 | }
32 |
33 | export function sendMessage(channel, message) {
34 | socket.emit("channel:message", { channel: channel.toString(), message });
35 | }
36 |
--------------------------------------------------------------------------------
/platform/middlewares/authMasterToken.js:
--------------------------------------------------------------------------------
1 | import ERROR_CODES from "../config/errorCodes.js";
2 |
3 | export const authMasterToken = async (req, res, next) => {
4 | // Get token
5 | let token = req.header("Authorization") ?? req.query.token;
6 |
7 | // Check if there is token
8 | if (!token) {
9 | return res.status(401).json({
10 | error: "Unauthorized",
11 | details: "No access token was found in 'Authorization' header.",
12 | code: ERROR_CODES.missingAccessToken,
13 | });
14 | }
15 |
16 | // Check if token is valid or not
17 | if (token !== process.env.MASTER_TOKEN) {
18 | return res.status(401).json({
19 | error: "Unauthorized",
20 | details: "The access token was not authorized or has expired.",
21 | code: ERROR_CODES.invalidAccessToken,
22 | });
23 | }
24 |
25 | next();
26 | };
27 |
--------------------------------------------------------------------------------
/platform/middlewares/checkClusterSetupStatus.js:
--------------------------------------------------------------------------------
1 | import userCtrl from "../controllers/user.js";
2 | import ERROR_CODES from "../config/errorCodes.js";
3 |
4 | export const checkClusterSetupStatus = async (req, res, next) => {
5 | // Get cluster owner
6 | let user = await userCtrl.getOneByQuery({ isClusterOwner: true });
7 |
8 | // Check if there is user
9 | if (user) {
10 | return res.status(401).json({
11 | error: "Not Allowed",
12 | details:
13 | "The cluster set up has already been initialized. You cannot reinitialize the set up.",
14 | code: ERROR_CODES.notAllowed,
15 | });
16 | }
17 |
18 | next();
19 | };
20 |
21 | export const hasClusterSetUpCompleted = async (req, res, next) => {
22 | // Get cluster owner
23 | let user = await userCtrl.getOneByQuery({ isClusterOwner: true });
24 |
25 | // Check if there is no cluster owner user
26 | if (!user) {
27 | return res.status(401).json({
28 | error: "Not Allowed",
29 | details: "The cluster set up has not been completed yet.",
30 | code: ERROR_CODES.notAllowed,
31 | });
32 | }
33 |
34 | next();
35 | };
36 |
--------------------------------------------------------------------------------
/platform/middlewares/contentType.js:
--------------------------------------------------------------------------------
1 | import config from "config";
2 | import express from "express";
3 | import ERROR_CODES from "../config/errorCodes.js";
4 |
5 | // Middleware to handle undefined paths or posts
6 | export const checkContentType = (req, res, next) => {
7 | // Check content type
8 | if (req.get("Content-Type") !== "application/json") {
9 | return res.status(415).json({
10 | error: "Unsupported Media Type",
11 | details:
12 | "The server does not accept the submitted content-type. The content-type should be 'application-json'.",
13 | code: ERROR_CODES.invalidContentType,
14 | });
15 | }
16 |
17 | // Parse content and build the req.body object
18 | express.json({ limit: config.get("server.maxBodySize") })(
19 | req,
20 | res,
21 | (error) => {
22 | if (error) {
23 | return res.status(400).json({
24 | error: "Invalid Request Body",
25 | details:
26 | "The server could not understand the request due to either invalid syntax of JSON document in request body or the request body payload is larger than the allowed limit.",
27 | code: ERROR_CODES.invalidRequestBody,
28 | });
29 | }
30 |
31 | next();
32 | }
33 | );
34 | };
35 |
--------------------------------------------------------------------------------
/platform/middlewares/handleFile.js:
--------------------------------------------------------------------------------
1 | import config from "config";
2 | import multer, { memoryStorage } from "multer";
3 | import ERROR_CODES from "../config/errorCodes.js";
4 |
5 | // Multer is required to process file uploads and make them available via req.files.
6 | const fileUpload = multer({
7 | storage: memoryStorage(),
8 | limits: {
9 | fileSize: config.get("general.maxImageSizeMB") * 1000 * 1000,
10 | },
11 | }).single("picture");
12 |
13 | export const fileUploadMiddleware = (req, res, next) => {
14 | fileUpload(req, res, function (err) {
15 | if (err instanceof multer.MulterError) {
16 | // Handle Multer-specific errors
17 | if (err.code === "LIMIT_FILE_SIZE") {
18 | return res.status(400).json({
19 | error: "File Too Large",
20 | code: ERROR_CODES.fileSizeTooLarge,
21 | details: `File size exceeds the limit of ${config.get(
22 | "general.maxImageSizeMB"
23 | )}MB.`,
24 | });
25 | }
26 | return res.status(500).json({
27 | error: "File Upload Error",
28 | code: ERROR_CODES.fileUploadError,
29 | details: `An error has occured when uploading the file. ${err.message}`,
30 | });
31 | } else if (err) {
32 | return res.status(500).json({
33 | error: "File Upload Error",
34 | code: ERROR_CODES.fileUploadError,
35 | details: `An error has occured when uploading the file. ${err.message}`,
36 | });
37 | }
38 | // If everything went fine, move to the next middleware
39 | next();
40 | });
41 | };
42 |
--------------------------------------------------------------------------------
/platform/middlewares/logRequest.js:
--------------------------------------------------------------------------------
1 | // Log requests to console
2 | export const logRequest = (req, res, time) => {
3 | if (req.originalUrl !== "/health")
4 | console.info(
5 | `${req.method} (${res.statusCode}) ${Math.round(time * 10) / 10}ms ${
6 | req.originalUrl
7 | }`
8 | );
9 | };
10 |
--------------------------------------------------------------------------------
/platform/middlewares/rateLimiter.js:
--------------------------------------------------------------------------------
1 | import { RateLimiterRedis } from "rate-limiter-flexible";
2 | import { getRedisClient } from "../init/cache.js";
3 | import helper from "../util/helper.js";
4 | import ERROR_CODES from "../config/errorCodes.js";
5 |
6 | // Apply rate limits to platform endpoints
7 | export const createRateLimiter = (rateLimitConfig) => {
8 | const rateLimiter = new RateLimiterRedis({
9 | storeClient: getRedisClient(),
10 | points: rateLimitConfig.rateLimitMaxHits, // Limit each unique identifier (IP or userId) to N requests per `window`
11 | duration: rateLimitConfig.rateLimitWindowSec, // Window duration in seconds
12 | });
13 |
14 | return (req, res, next) => {
15 | rateLimiter
16 | .consume(helper.getIP(req))
17 | .then(() => {
18 | next();
19 | })
20 | .catch(() => {
21 | return res.status(429).json({
22 | error: "Rate Limit Exceeded",
23 | details: "Too many requests, please try again later.",
24 | code: ERROR_CODES.rateLimitExceeded,
25 | });
26 | });
27 | };
28 | };
29 |
--------------------------------------------------------------------------------
/platform/middlewares/undefinedPaths.js:
--------------------------------------------------------------------------------
1 | import ERROR_CODES from "../config/errorCodes.js";
2 |
3 | // Middleware to handle undefined paths or posts
4 | export const handleUndefinedPaths = (req, res) => {
5 | return res.status(404).json({
6 | error: "Not Found",
7 | details: "The server can not find the requested resource.",
8 | code: ERROR_CODES.resourceNotFound,
9 | });
10 | };
11 |
--------------------------------------------------------------------------------
/platform/middlewares/validate.js:
--------------------------------------------------------------------------------
1 | import { validationResult } from "express-validator";
2 | import ERROR_CODES from "../config/errorCodes.js";
3 |
4 | // Middleare the create the error message for failed request input validations
5 | export const validate = (req, res, next) => {
6 | const errors = validationResult(req);
7 | if (!errors.isEmpty()) {
8 | return res.status(400).json({
9 | error: "Invalid Input",
10 | details:
11 | "The request parameters has failed to pass the validation rules.",
12 | code: ERROR_CODES.validationError,
13 | fields: errors.array(),
14 | });
15 | }
16 |
17 | next();
18 | };
19 |
--------------------------------------------------------------------------------
/platform/middlewares/validateCluster.js:
--------------------------------------------------------------------------------
1 | import userCtrl from "../controllers/user.js";
2 | import clsCtrl from "../controllers/cluster.js";
3 | import helper from "../util/helper.js";
4 |
5 | import ERROR_CODES from "../config/errorCodes.js";
6 |
7 | export const validateCluster = async (req, res, next) => {
8 | try {
9 | let user = await userCtrl.getOneByQuery({ isClusterOwner: true });
10 | if (!user) {
11 | return res.status(401).json({
12 | error: "Not Authorized",
13 | details: "Cluster set up has not been completed yet.",
14 | code: ERROR_CODES.unauthorized,
15 | });
16 | }
17 |
18 | // Get the cluster object
19 | const cluster = await clsCtrl.getOneByQuery(
20 | {
21 | clusterAccesssToken: process.env.CLUSTER_ACCESS_TOKEN,
22 | },
23 | {
24 | cacheKey: process.env.CLUSTER_ACCESS_TOKEN,
25 | }
26 | );
27 |
28 | // Assign cluster data
29 | req.cluster = cluster;
30 |
31 | next();
32 | } catch (err) {
33 | return helper.handleError(req, res, err);
34 | }
35 | };
36 |
--------------------------------------------------------------------------------
/platform/middlewares/validateContainer.js:
--------------------------------------------------------------------------------
1 | import cntrCtrl from "../controllers/container.js";
2 | import helper from "../util/helper.js";
3 |
4 | import ERROR_CODES from "../config/errorCodes.js";
5 |
6 | export const validateContainer = async (req, res, next) => {
7 | try {
8 | const { containerId } = req.params;
9 |
10 | // Get the container object
11 | let container = await cntrCtrl.getOneById(containerId, {
12 | cacheKey: containerId,
13 | });
14 |
15 | if (!container) {
16 | return res.status(404).json({
17 | error: "Not Found",
18 | details: `No such container with the provided id '${containerId}' exists.`,
19 | code: ERROR_CODES.notFound,
20 | });
21 | }
22 |
23 | if (container.environmentId.toString() !== req.environment._id.toString()) {
24 | return res.status(401).json({
25 | error: "Not Authorized",
26 | details: `Project environment does not have a container with the provided id '${containerId}'`,
27 | code: ERROR_CODES.unauthorized,
28 | });
29 | }
30 |
31 | // Assign container data
32 | req.container = container;
33 |
34 | next();
35 | } catch (err) {
36 | return helper.handleError(req, res, err);
37 | }
38 | };
39 |
--------------------------------------------------------------------------------
/platform/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "platform-core",
3 | "version": "v1.0.5",
4 | "description": "Platform core api server",
5 | "main": "server.js",
6 | "type": "module",
7 | "scripts": {
8 | "prod": "NODE_ENV=production node server",
9 | "dev": "NODE_ENV=development nodemon server"
10 | },
11 | "author": "Ümit Çakmak",
12 | "license": "ISC",
13 | "dependencies": {
14 | "@kubernetes/client-node": "0.18.1",
15 | "@octokit/core": "5.1.0",
16 | "axios": "1.3.3",
17 | "bcrypt": "5.1.0",
18 | "config": "3.3.9",
19 | "cors": "2.8.5",
20 | "cron-parser": "4.8.1",
21 | "crypto-js": "4.1.1",
22 | "express": "4.18.2",
23 | "express-validator": "6.14.3",
24 | "helmet": "6.0.1",
25 | "js-yaml": "4.1.0",
26 | "minio": "7.1.1",
27 | "mongodb": "5.1.0",
28 | "mongoose": "6.9.1",
29 | "multer": "1.4.5-lts.1",
30 | "nanoid": "4.0.1",
31 | "nocache": "3.0.4",
32 | "on-headers": "1.0.2",
33 | "psl": "1.9.0",
34 | "randomcolor": "0.6.2",
35 | "rate-limiter-flexible": "2.4.1",
36 | "redis": "3.1.2",
37 | "response-time": "2.3.2",
38 | "sharp": "0.31.3",
39 | "socket.io-client": "4.6.1",
40 | "ua-parser-js": "1.0.33",
41 | "winston": "3.8.2",
42 | "winston-transport": "4.5.0"
43 | },
44 | "devDependencies": {
45 | "@eslint/js": "^9.4.0",
46 | "eslint": "^9.4.0",
47 | "globals": "^15.3.0",
48 | "nodemon": "2.0.20"
49 | }
50 | }
--------------------------------------------------------------------------------
/platform/routes/registry.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import regCtrl from "../controllers/registry.js";
3 | import { authSession } from "../middlewares/authSession.js";
4 | import helper from "../util/helper.js";
5 |
6 | const router = express.Router({ mergeParams: true });
7 |
8 | /*
9 | @route /v1/registry
10 | @method GET
11 | @desc Get all registries for the cluster
12 | @access private
13 | */
14 | router.get("/", authSession, async (req, res) => {
15 | try {
16 | const registries = await regCtrl.getManyByQuery({});
17 | res.json(registries);
18 | } catch (err) {
19 | helper.handleError(req, res, err);
20 | }
21 | });
22 |
23 | export default router;
24 |
--------------------------------------------------------------------------------
/platform/routes/system.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 |
3 | const router = express.Router({ mergeParams: true });
4 |
5 | /*
6 | @route /
7 | @method GET
8 | @desc Checks liveliness of platform server
9 | @access public
10 | */
11 | router.get("/health", (req, res) => {
12 | res
13 | .status(200)
14 | .send(
15 | `${new Date().toISOString()} - Healthy platform server - ${
16 | process.env.RELEASE_NUMBER
17 | }`
18 | );
19 | });
20 |
21 | /*
22 | @route /ping
23 | @method GET
24 | @desc Checks liveliness of platform server
25 | @access public
26 | */
27 | router.get("/ping", (req, res) => {
28 | res.status(200).send(`${new Date().toISOString()} - Pong!`);
29 | });
30 |
31 | export default router;
32 |
--------------------------------------------------------------------------------
/platform/routes/types.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import { authSession } from "../middlewares/authSession.js";
3 | import {
4 | orgRoles,
5 | orgRoleDesc,
6 | projectRoles,
7 | projectRoleDesc,
8 | } from "../config/constants.js";
9 | import { timezones } from "../config/timezones.js";
10 |
11 | const router = express.Router({ mergeParams: true });
12 |
13 | /*
14 | @route /all
15 | @method GET
16 | @desc Returns all types used in the platform
17 | @access public
18 | */
19 | router.get("/all", authSession, (req, res) => {
20 | res.json({
21 | orgRoles,
22 | orgRoleDesc,
23 | projectRoles,
24 | projectRoleDesc,
25 | timezones,
26 | });
27 | });
28 |
29 | export default router;
30 |
--------------------------------------------------------------------------------
/platform/schemas/domain.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | /**
4 | * Keeps information about the custom domains added to the containers or cluster itself. This is used to prevent duplicate domain names and creation of conflicting ingress rules.
5 | */
6 | export const DomainModel = mongoose.model(
7 | "domain",
8 | new mongoose.Schema(
9 | {
10 | domain: {
11 | type: String,
12 | required: true,
13 | index: true,
14 | },
15 | __v: {
16 | type: Number,
17 | select: false,
18 | },
19 | },
20 | { timestamps: true }
21 | )
22 | );
23 |
--------------------------------------------------------------------------------
/platform/schemas/rules/cronJob.js:
--------------------------------------------------------------------------------
1 | import {
2 | checkName,
3 | checkRepoOrRegistry,
4 | checkRegistry,
5 | checkRepo,
6 | checkVariables,
7 | checkStorageConfig,
8 | checkPodConfig,
9 | checkCronJobConfig,
10 | } from "./checks.js";
11 |
12 | export default (actionType) => {
13 | switch (actionType) {
14 | case "create":
15 | case "update":
16 | return [
17 | ...checkName("cronjob", actionType),
18 | ...checkRepoOrRegistry("cronjob", actionType),
19 | ...checkRegistry("cronjob", actionType),
20 | ...checkRepo("cronjob", actionType),
21 | ...checkVariables("cronjob", actionType),
22 | ...checkStorageConfig("cronjob", actionType),
23 | ...checkPodConfig("cronjob", actionType),
24 | ...checkCronJobConfig("cronjob", actionType),
25 | ];
26 | default:
27 | return [];
28 | }
29 | };
30 |
--------------------------------------------------------------------------------
/platform/schemas/rules/deployment.js:
--------------------------------------------------------------------------------
1 | import {
2 | checkName,
3 | checkRepoOrRegistry,
4 | checkRegistry,
5 | checkRepo,
6 | checkVariables,
7 | checkNetworking,
8 | checkStorageConfig,
9 | checkPodConfig,
10 | checkDeploymentConfig,
11 | checkProbes,
12 | } from "./checks.js";
13 |
14 | export default (actionType) => {
15 | switch (actionType) {
16 | case "create":
17 | case "update":
18 | return [
19 | ...checkName("deployment", actionType),
20 | ...checkRepoOrRegistry("deployment", actionType),
21 | ...checkRegistry("deployment", actionType),
22 | ...checkRepo("deployment", actionType),
23 | ...checkVariables("deployment", actionType),
24 | ...checkStorageConfig("deployment", actionType),
25 | ...checkNetworking("deployment", actionType),
26 | ...checkPodConfig("deployment", actionType),
27 | ...checkDeploymentConfig("deployment", actionType),
28 | ...checkProbes("deployment", actionType),
29 | ];
30 | default:
31 | return [];
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/platform/schemas/rules/statefulSet.js:
--------------------------------------------------------------------------------
1 | import {
2 | checkName,
3 | checkRepoOrRegistry,
4 | checkRegistry,
5 | checkRepo,
6 | checkVariables,
7 | checkStorageConfig,
8 | checkNetworking,
9 | checkPodConfig,
10 | checkStatefulSetConfig,
11 | checkProbes,
12 | } from "./checks.js";
13 |
14 | export default (actionType) => {
15 | switch (actionType) {
16 | case "create":
17 | case "update":
18 | return [
19 | ...checkName("statefulset", actionType),
20 | ...checkRepoOrRegistry("statefulset", actionType),
21 | ...checkRegistry("statefulset", actionType),
22 | ...checkRepo("statefulset", actionType),
23 | ...checkVariables("statefulset", actionType),
24 | ...checkStorageConfig("statefulset", actionType),
25 | ...checkNetworking("statefulset", actionType),
26 | ...checkPodConfig("statefulset", actionType),
27 | ...checkStatefulSetConfig("statefulset", actionType),
28 | ...checkProbes("statefulset", actionType),
29 | ];
30 | default:
31 | return [];
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/platform/schemas/tcpProxyPort.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | /**
4 | * Keeps track of the used tcp proxy port numbers
5 | */
6 | export const TCPProxyPortModel = mongoose.model(
7 | "tcp_proxy_port",
8 | new mongoose.Schema(
9 | {
10 | port: {
11 | type: Number,
12 | index: true,
13 | },
14 | __v: {
15 | type: Number,
16 | select: false,
17 | },
18 | },
19 | { timestamps: true }
20 | )
21 | );
22 |
--------------------------------------------------------------------------------
/releases/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cloud-agnost/agnost-gitops/6c18a641b9dce8dea274b8797ee7a86ee474d8f2/releases/.gitkeep
--------------------------------------------------------------------------------
/releases/latest.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v1.0.10",
3 | "modules": {
4 | "monitor": "v1.0.0",
5 | "platform": "v1.0.5",
6 | "sync": "v1.0.0",
7 | "webhook": "v1.0.0",
8 | "studio": "v1.1.5"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.10.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.10",
3 | "modules": {
4 | "monitor": "v0.0.4",
5 | "platform": "v0.0.7",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.7"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.11.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.11",
3 | "modules": {
4 | "monitor": "v0.0.4",
5 | "platform": "v0.0.8",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.7"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.12.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.12",
3 | "modules": {
4 | "monitor": "v0.0.4",
5 | "platform": "v0.0.9",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.8"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.13.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.13",
3 | "modules": {
4 | "monitor": "v0.0.4",
5 | "platform": "v0.0.9",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.9"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.14.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.14",
3 | "modules": {
4 | "monitor": "v0.0.4",
5 | "platform": "v0.0.9",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.10"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.15.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.15",
3 | "modules": {
4 | "monitor": "v0.0.4",
5 | "platform": "v0.0.9",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.11"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.16.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.16",
3 | "modules": {
4 | "monitor": "v0.0.4",
5 | "platform": "v0.0.10",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.11"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.17.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.17",
3 | "modules": {
4 | "monitor": "v0.0.4",
5 | "platform": "v0.0.11",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.12"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.18.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.18",
3 | "modules": {
4 | "monitor": "v0.0.4",
5 | "platform": "v0.0.12",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.13"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.19.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.19",
3 | "modules": {
4 | "monitor": "v0.0.4",
5 | "platform": "v0.0.13",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.14"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.2.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.2",
3 | "modules": {
4 | "monitor": "v0.0.2",
5 | "platform": "v0.0.2",
6 | "sync": "v0.0.2",
7 | "webhook": "v0.0.2",
8 | "studio": "v0.0.1"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.20.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.20",
3 | "modules": {
4 | "monitor": "v0.0.4",
5 | "platform": "v0.0.13",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.15"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.21.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.21",
3 | "modules": {
4 | "monitor": "v0.0.4",
5 | "platform": "v0.0.14",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.16"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.22.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.22",
3 | "modules": {
4 | "monitor": "v0.0.4",
5 | "platform": "v0.0.15",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.17"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.23.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.23",
3 | "modules": {
4 | "monitor": "v0.0.4",
5 | "platform": "v0.0.16",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.17"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.24.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.24",
3 | "modules": {
4 | "monitor": "v0.0.4",
5 | "platform": "v0.0.17",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.17"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.25.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.25",
3 | "modules": {
4 | "monitor": "v0.0.4",
5 | "platform": "v0.0.18",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.17"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.26.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.26",
3 | "modules": {
4 | "monitor": "v0.0.4",
5 | "platform": "v0.0.19",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.17"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.27.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.27",
3 | "modules": {
4 | "monitor": "v0.0.4",
5 | "platform": "v0.0.20",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.17"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.28.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.28",
3 | "modules": {
4 | "monitor": "v0.0.4",
5 | "platform": "v0.0.21",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.17"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.29.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.29",
3 | "modules": {
4 | "monitor": "v0.0.4",
5 | "platform": "v0.0.22",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.18"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.3.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.3",
3 | "modules": {
4 | "monitor": "v0.0.3",
5 | "platform": "v0.0.3",
6 | "sync": "v0.0.3",
7 | "webhook": "v0.0.3",
8 | "studio": "v0.0.2"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.30.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.30",
3 | "modules": {
4 | "monitor": "v0.0.4",
5 | "platform": "v0.0.23",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.19"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.31.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.31",
3 | "modules": {
4 | "monitor": "v0.0.4",
5 | "platform": "v0.0.24",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.19"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.32.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.32",
3 | "modules": {
4 | "monitor": "v0.0.5",
5 | "platform": "v0.0.25",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.20"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.33.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.33",
3 | "modules": {
4 | "monitor": "v0.0.6",
5 | "platform": "v0.0.26",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.21"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.34.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.34",
3 | "modules": {
4 | "monitor": "v0.0.7",
5 | "platform": "v0.0.27",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.22"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.35.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.35",
3 | "modules": {
4 | "monitor": "v0.0.8",
5 | "platform": "v0.0.28",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.22"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.36.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.36",
3 | "modules": {
4 | "monitor": "v0.0.8",
5 | "platform": "v0.0.29",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.22"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.37.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.37",
3 | "modules": {
4 | "monitor": "v0.0.8",
5 | "platform": "v0.0.29",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.23"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.38.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.38",
3 | "modules": {
4 | "monitor": "v0.0.8",
5 | "platform": "v0.0.29",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.24"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.39.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.39",
3 | "modules": {
4 | "monitor": "v0.0.8",
5 | "platform": "v0.0.29",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.25"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.4.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.4",
3 | "modules": {
4 | "monitor": "v0.0.3",
5 | "platform": "v0.0.3",
6 | "sync": "v0.0.3",
7 | "webhook": "v0.0.3",
8 | "studio": "v0.0.3"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.40.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.40",
3 | "modules": {
4 | "monitor": "v0.0.8",
5 | "platform": "v0.0.30",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.25"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.41.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.41",
3 | "modules": {
4 | "monitor": "v0.0.8",
5 | "platform": "v0.0.30",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.26"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.42.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.42",
3 | "modules": {
4 | "monitor": "v0.0.8",
5 | "platform": "v0.0.30",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.27"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.43.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.43",
3 | "modules": {
4 | "monitor": "v0.0.8",
5 | "platform": "v0.0.30",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.28"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.44.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.44",
3 | "modules": {
4 | "monitor": "v0.0.8",
5 | "platform": "v0.0.30",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.29"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.45.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.45",
3 | "modules": {
4 | "monitor": "v0.0.8",
5 | "platform": "v0.0.31",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.29"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.46.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.46",
3 | "modules": {
4 | "monitor": "v0.0.8",
5 | "platform": "v0.0.32",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.29"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.47.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.47",
3 | "modules": {
4 | "monitor": "v0.0.8",
5 | "platform": "v0.0.33",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.29"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.48.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.48",
3 | "modules": {
4 | "monitor": "v0.0.8",
5 | "platform": "v0.0.34",
6 | "sync": "v0.0.4",
7 | "webhook": "v0.0.6",
8 | "studio": "v0.0.29"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.5.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.5",
3 | "modules": {
4 | "monitor": "v0.0.3",
5 | "platform": "v0.0.3",
6 | "sync": "v0.0.3",
7 | "webhook": "v0.0.3",
8 | "studio": "v0.0.4"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.6.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.6",
3 | "modules": {
4 | "monitor": "v0.0.3",
5 | "platform": "v0.0.3",
6 | "sync": "v0.0.3",
7 | "webhook": "v0.0.3",
8 | "studio": "v0.0.5"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.7.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.7",
3 | "modules": {
4 | "monitor": "v0.0.4",
5 | "platform": "v0.0.4",
6 | "sync": "v0.0.3",
7 | "webhook": "v0.0.3",
8 | "studio": "v0.0.6"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.8.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.8",
3 | "modules": {
4 | "monitor": "v0.0.4",
5 | "platform": "v0.0.5",
6 | "sync": "v0.0.3",
7 | "webhook": "v0.0.4",
8 | "studio": "v0.0.6"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v0.0.9.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v0.0.9",
3 | "modules": {
4 | "monitor": "v0.0.4",
5 | "platform": "v0.0.6",
6 | "sync": "v0.0.3",
7 | "webhook": "v0.0.5",
8 | "studio": "v0.0.6"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v1.0.0.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v1.0.0",
3 | "modules": {
4 | "monitor": "v1.0.0",
5 | "platform": "v1.0.0",
6 | "sync": "v1.0.0",
7 | "webhook": "v1.0.0",
8 | "studio": "v1.0.0"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v1.0.1.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v1.0.1",
3 | "modules": {
4 | "monitor": "v1.0.0",
5 | "platform": "v1.0.1",
6 | "sync": "v1.0.0",
7 | "webhook": "v1.0.0",
8 | "studio": "v1.0.0"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v1.0.10.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v1.0.10",
3 | "modules": {
4 | "monitor": "v1.0.0",
5 | "platform": "v1.0.5",
6 | "sync": "v1.0.0",
7 | "webhook": "v1.0.0",
8 | "studio": "v1.1.5"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v1.0.2.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v1.0.2",
3 | "modules": {
4 | "monitor": "v1.0.0",
5 | "platform": "v1.0.2",
6 | "sync": "v1.0.0",
7 | "webhook": "v1.0.0",
8 | "studio": "v1.0.0"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v1.0.3.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v1.0.3",
3 | "modules": {
4 | "monitor": "v1.0.0",
5 | "platform": "v1.0.2",
6 | "sync": "v1.0.0",
7 | "webhook": "v1.0.0",
8 | "studio": "v1.1.0"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v1.0.4.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v1.0.4",
3 | "modules": {
4 | "monitor": "v1.0.0",
5 | "platform": "v1.0.2",
6 | "sync": "v1.0.0",
7 | "webhook": "v1.0.0",
8 | "studio": "v1.1.1"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v1.0.5.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v1.0.5",
3 | "modules": {
4 | "monitor": "v1.0.0",
5 | "platform": "v1.0.2",
6 | "sync": "v1.0.0",
7 | "webhook": "v1.0.0",
8 | "studio": "v1.1.2"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v1.0.6.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v1.0.6",
3 | "modules": {
4 | "monitor": "v1.0.0",
5 | "platform": "v1.0.3",
6 | "sync": "v1.0.0",
7 | "webhook": "v1.0.0",
8 | "studio": "v1.1.2"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v1.0.7.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v1.0.7",
3 | "modules": {
4 | "monitor": "v1.0.0",
5 | "platform": "v1.0.3",
6 | "sync": "v1.0.0",
7 | "webhook": "v1.0.0",
8 | "studio": "v1.1.3"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v1.0.8.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v1.0.8",
3 | "modules": {
4 | "monitor": "v1.0.0",
5 | "platform": "v1.0.4",
6 | "sync": "v1.0.0",
7 | "webhook": "v1.0.0",
8 | "studio": "v1.1.4"
9 | }
10 | }
--------------------------------------------------------------------------------
/releases/v1.0.9.json:
--------------------------------------------------------------------------------
1 | {
2 | "release": "v1.0.9",
3 | "modules": {
4 | "monitor": "v1.0.0",
5 | "platform": "v1.0.5",
6 | "sync": "v1.0.0",
7 | "webhook": "v1.0.0",
8 | "studio": "v1.1.4"
9 | }
10 | }
--------------------------------------------------------------------------------
/studio/.Dockerignore:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/studio/.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 |
--------------------------------------------------------------------------------
/studio/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "tabWidth": 2,
4 | "printWidth": 100,
5 | "singleQuote": true,
6 | "trailingComma": "all",
7 | "jsxSingleQuote": true,
8 | "bracketSpacing": true,
9 | "useTabs": true
10 | }
11 |
--------------------------------------------------------------------------------
/studio/Dockerfile:
--------------------------------------------------------------------------------
1 | # Build Stage
2 | # Use a Node.js base image
3 | FROM node:20-alpine as build-stage
4 |
5 | # Set the working directory
6 | WORKDIR /app
7 |
8 | # Copy package.json and package-lock.json (or yarn.lock if you use yarn)
9 | COPY package*.json ./
10 |
11 | # Install dependencies
12 | RUN npm install
13 |
14 | # Copy the rest of your app's source code
15 | COPY . .
16 |
17 | # Build your app
18 | RUN npm run build
19 |
20 | # Run Stage
21 | # Use an Nginx base image
22 | FROM nginx:alpine
23 |
24 | # Copy the built app from the build stage to Nginx's serve directory
25 | COPY --from=build-stage /app/dist /usr/share/nginx/html
26 | COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf
27 |
28 | # Expose port 4000
29 | EXPOSE 4000
30 |
31 | # Start Nginx and keep it running
32 | CMD ["nginx", "-g", "daemon off;"]
33 |
--------------------------------------------------------------------------------
/studio/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | # Get the base node image from docker hub
2 | FROM node:20-alpine
3 | # Set the working directory in our docker image, anything that is copied from now on
4 | # from local machine to the image will be moved under '/app' directory in docker image
5 | WORKDIR '/app'
6 | # Copy package.json file from local machine to the image under '/app' directory
7 | COPY ./package.json ./
8 | # Run npm install to install dependent modules
9 | RUN npm install
10 | # Copy everything to the image under '/app' directory
11 | COPY ./ ./
12 | # This will be the start up command of the docker container
13 | CMD [ "npm", "run", "dev" ]
14 |
--------------------------------------------------------------------------------
/studio/nginx/default.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 4000;
3 |
4 | # Set a variable for the hostname, excluding port
5 | set $base_host $host;
6 | if ($host ~* "^(.+):(.+)$") {
7 | set $base_host $1;
8 | }
9 |
10 | location /studio {
11 | alias /usr/share/nginx/html/;
12 | index index.html index.htm;
13 |
14 | if ($uri = /studio) {
15 | return 301 $scheme://$base_host/studio/;
16 | }
17 |
18 | try_files $uri $uri/ /studio/index.html;
19 | }
20 | }
--------------------------------------------------------------------------------
/studio/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/studio/src/assets/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #70A9FF
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/studio/src/assets/images/agnost-dark-bg-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cloud-agnost/agnost-gitops/6c18a641b9dce8dea274b8797ee7a86ee474d8f2/studio/src/assets/images/agnost-dark-bg-logo.png
--------------------------------------------------------------------------------
/studio/src/assets/images/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cloud-agnost/agnost-gitops/6c18a641b9dce8dea274b8797ee7a86ee474d8f2/studio/src/assets/images/android-chrome-192x192.png
--------------------------------------------------------------------------------
/studio/src/assets/images/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cloud-agnost/agnost-gitops/6c18a641b9dce8dea274b8797ee7a86ee474d8f2/studio/src/assets/images/android-chrome-512x512.png
--------------------------------------------------------------------------------
/studio/src/assets/images/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cloud-agnost/agnost-gitops/6c18a641b9dce8dea274b8797ee7a86ee474d8f2/studio/src/assets/images/apple-touch-icon.png
--------------------------------------------------------------------------------
/studio/src/assets/images/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cloud-agnost/agnost-gitops/6c18a641b9dce8dea274b8797ee7a86ee474d8f2/studio/src/assets/images/favicon-16x16.png
--------------------------------------------------------------------------------
/studio/src/assets/images/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cloud-agnost/agnost-gitops/6c18a641b9dce8dea274b8797ee7a86ee474d8f2/studio/src/assets/images/favicon-32x32.png
--------------------------------------------------------------------------------
/studio/src/assets/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cloud-agnost/agnost-gitops/6c18a641b9dce8dea274b8797ee7a86ee474d8f2/studio/src/assets/images/favicon.ico
--------------------------------------------------------------------------------
/studio/src/assets/images/ms-icon-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cloud-agnost/agnost-gitops/6c18a641b9dce8dea274b8797ee7a86ee474d8f2/studio/src/assets/images/ms-icon-310x310.png
--------------------------------------------------------------------------------
/studio/src/assets/images/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cloud-agnost/agnost-gitops/6c18a641b9dce8dea274b8797ee7a86ee474d8f2/studio/src/assets/images/mstile-150x150.png
--------------------------------------------------------------------------------
/studio/src/assets/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Agnost",
3 | "icons": [
4 | {
5 | "src": "/android-icon-36x36.png",
6 | "sizes": "36x36",
7 | "type": "image/png",
8 | "density": "0.75"
9 | },
10 | {
11 | "src": "/android-icon-48x48.png",
12 | "sizes": "48x48",
13 | "type": "image/png",
14 | "density": "1.0"
15 | },
16 | {
17 | "src": "/android-icon-72x72.png",
18 | "sizes": "72x72",
19 | "type": "image/png",
20 | "density": "1.5"
21 | },
22 | {
23 | "src": "/android-icon-96x96.png",
24 | "sizes": "96x96",
25 | "type": "image/png",
26 | "density": "2.0"
27 | },
28 | {
29 | "src": "/android-icon-144x144.png",
30 | "sizes": "144x144",
31 | "type": "image/png",
32 | "density": "3.0"
33 | },
34 | {
35 | "src": "/android-icon-192x192.png",
36 | "sizes": "192x192",
37 | "type": "image/png",
38 | "density": "4.0"
39 | }
40 | ]
41 | }
42 |
--------------------------------------------------------------------------------
/studio/src/assets/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Agnost",
3 | "short_name": "Agnost",
4 | "icons": [
5 | {
6 | "src": "/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#70A9FF",
17 | "background_color": "#004DC9",
18 | "display": "standalone"
19 | }
20 |
--------------------------------------------------------------------------------
/studio/src/components/Accordion/index.ts:
--------------------------------------------------------------------------------
1 | export { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from './Accordion';
2 |
--------------------------------------------------------------------------------
/studio/src/components/ActionsCell/index.ts:
--------------------------------------------------------------------------------
1 | export { default as ActionsCell } from './ActionsCell';
2 |
--------------------------------------------------------------------------------
/studio/src/components/Alert/Feedback.tsx:
--------------------------------------------------------------------------------
1 | import { Error, SuccessCheck } from '@/components/icons';
2 | import { cn } from '@/utils';
3 | interface Props {
4 | success?: boolean;
5 | title: string;
6 | description: string;
7 | className?: string;
8 | }
9 | export default function Feedback({ success, title, description, className }: Props) {
10 | const Icon = success ? SuccessCheck : Error;
11 | return (
12 |
13 |
14 |
{title}
15 |
{description}
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/studio/src/components/Alert/alert.scss:
--------------------------------------------------------------------------------
1 | .alert {
2 | @apply relative border border-transparent flex items-center justify-center w-full;
3 | &-title {
4 | @apply text-default text-sm font-semibold [&_p]:leading-tight;
5 | }
6 | &-md {
7 | &.alert {
8 | @apply rounded-lg gap-6 p-4;
9 | }
10 | .alert-icon {
11 | @apply text-6xl;
12 | }
13 | .alert-title {
14 | @apply text-sm;
15 | }
16 | }
17 | &-sm {
18 | &.alert {
19 | @apply rounded-none gap-2 p-1.5;
20 | }
21 | .alert-icon {
22 | @apply text-lg;
23 | }
24 | .alert-title {
25 | @apply text-xs;
26 | }
27 | }
28 |
29 | &-success {
30 | @apply bg-surface-green;
31 | }
32 |
33 | &-error {
34 | @apply bg-surface-red;
35 | }
36 |
37 | &-warning {
38 | @apply bg-surface-yellow;
39 | }
40 |
41 | &-body {
42 | @apply flex-1;
43 | }
44 | &-icon {
45 | @apply rounded-full flex items-center justify-center;
46 | }
47 |
48 | &-description {
49 | @apply text-xs text-subtle [&_p]:leading-relaxed;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/studio/src/components/Alert/index.ts:
--------------------------------------------------------------------------------
1 | export { Alert, AlertDescription, AlertTitle } from './Alert';
2 | export { default as Feedback } from './Feedback';
3 |
--------------------------------------------------------------------------------
/studio/src/components/AuthUserAvatar/AuthUserAvatar.tsx:
--------------------------------------------------------------------------------
1 | import useAuthStore from '@/store/auth/authStore.ts';
2 | import { Avatar, AvatarFallback, AvatarImage, AvatarProps } from '@/components/Avatar';
3 |
4 | export default function AuthUserAvatar(props: AvatarProps) {
5 | const { user } = useAuthStore();
6 |
7 | return (
8 |
9 |
10 |
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/studio/src/components/AuthUserAvatar/index.ts:
--------------------------------------------------------------------------------
1 | export { default as AuthUserAvatar } from './AuthUserAvatar.tsx';
2 |
--------------------------------------------------------------------------------
/studio/src/components/Avatar/avatar.scss:
--------------------------------------------------------------------------------
1 | .avatar {
2 | @apply relative flex shrink-0 overflow-hidden rounded-full;
3 | &-image {
4 | @apply aspect-square h-full w-full;
5 | }
6 | &-fallback {
7 | @apply flex h-full w-full items-center justify-center;
8 | &-user {
9 | @apply text-default-reverse dark:text-default;
10 | }
11 | &-org {
12 | @apply text-default dark:text-default-reverse;
13 | }
14 | }
15 | &-xxs {
16 | @apply size-5 text-[10px];
17 | }
18 | &-xs {
19 | @apply size-6 text-xs;
20 | }
21 | &-sm {
22 | @apply size-7 text-xs;
23 | }
24 | &-md {
25 | @apply size-8 text-base;
26 | }
27 | &-lg {
28 | @apply size-12 text-lg;
29 | }
30 | &-xl {
31 | @apply size-14 text-2xl;
32 | }
33 | &-2xl {
34 | @apply size-16 text-2xl;
35 | }
36 | &-3xl {
37 | @apply size-20 text-3xl;
38 | }
39 | &-4xl {
40 | @apply size-32 text-6xl;
41 | }
42 | &-square {
43 | @apply rounded-sm;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/studio/src/components/Avatar/index.ts:
--------------------------------------------------------------------------------
1 | export { Avatar, AvatarFallback, AvatarImage } from './Avatar';
2 | export type { AvatarProps, AvatarFallbackProps } from './Avatar';
3 |
--------------------------------------------------------------------------------
/studio/src/components/Badge/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Badge } from './Badge';
2 |
--------------------------------------------------------------------------------
/studio/src/components/Button/ButtonGroup.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './Button.scss';
3 | export default function ButtonGroup({ children }: { children: React.ReactNode }) {
4 | return {children}
;
5 | }
6 |
--------------------------------------------------------------------------------
/studio/src/components/Button/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Button, buttonVariants } from './Button';
2 | export type { ButtonProps } from './Button';
3 | export { default as ButtonGroup } from './ButtonGroup';
4 |
--------------------------------------------------------------------------------
/studio/src/components/ChangeAvatar/changeAvatar.scss:
--------------------------------------------------------------------------------
1 | .avatar {
2 | &-actions {
3 | @apply inline-flex relative;
4 | &-button {
5 | @apply absolute bottom-0 left-1/2 bg-base/70 w-full hidden;
6 | @apply transform -translate-x-1/2 rounded-none space-x-2;
7 | }
8 | &:hover {
9 | .avatar-actions-button {
10 | @apply flex items-center justify-center;
11 | }
12 | }
13 | &-icon {
14 | @apply w-5 h-5;
15 | }
16 | &-loading {
17 | @apply text-icon-secondary;
18 | @apply absolute top-[20%] left-[20%];
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/studio/src/components/ChangeAvatar/index.ts:
--------------------------------------------------------------------------------
1 | export { default as ChangeAvatar } from './ChangeAvatar';
2 |
--------------------------------------------------------------------------------
/studio/src/components/ChangeNameForm/index.ts:
--------------------------------------------------------------------------------
1 | export { default as ChangeNameForm } from './ChangeNameForm';
2 |
--------------------------------------------------------------------------------
/studio/src/components/Checkbox/Checkbox.tsx:
--------------------------------------------------------------------------------
1 | import { Label } from '@/components/Label';
2 | import { cn } from '@/utils';
3 | import { Check } from '@phosphor-icons/react';
4 | import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
5 | import * as React from 'react';
6 | import './checkbox.scss';
7 |
8 | interface CheckboxProps extends React.ComponentPropsWithoutRef {
9 | className?: string;
10 | id?: string;
11 | label?: string;
12 | }
13 |
14 | const Checkbox = React.forwardRef, CheckboxProps>(
15 | ({ className, id, label, ...props }, ref) => (
16 |
17 |
23 |
24 |
25 |
26 |
27 | {label && (
28 |
31 | )}
32 |
33 | ),
34 | );
35 | Checkbox.displayName = CheckboxPrimitive.Root.displayName;
36 |
37 | export { Checkbox };
38 |
--------------------------------------------------------------------------------
/studio/src/components/Checkbox/checkbox.scss:
--------------------------------------------------------------------------------
1 | .checkbox {
2 | @apply h-4 w-4 shrink-0 rounded-sm;
3 | @apply border border-input-border ring-offset-wrapper-background-base;
4 | @apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-button-primary focus-visible:ring-offset-2;
5 | @apply disabled:cursor-not-allowed disabled:bg-input-disabled-border disabled:text-icon-disabled disabled:data-[state=checked]:opacity-50 disabled:pointer-events-none;
6 | @apply data-[state=checked]:bg-button-primary data-[state=checked]:text-white;
7 | @apply hover:data-[state=checked]:bg-button-primary-hover;
8 |
9 | &-wrapper {
10 | @apply flex items-center space-x-2;
11 | }
12 |
13 | &-indicator {
14 | @apply flex items-center justify-center text-current;
15 | }
16 | &-label {
17 | @apply cursor-pointer;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/studio/src/components/Checkbox/index.ts:
--------------------------------------------------------------------------------
1 | export { Checkbox } from './Checkbox.tsx';
2 |
--------------------------------------------------------------------------------
/studio/src/components/ConfirmationModal/confirmationModal.scss:
--------------------------------------------------------------------------------
1 | .confirmation-modal {
2 | @apply flex flex-col gap-y-4;
3 |
4 | &-actions {
5 | @apply flex justify-end items-center gap-4 self-end;
6 | }
7 | &-desc {
8 | @apply text-sm leading-6 text-subtle font-normal tracking-wide self-start;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/studio/src/components/ConfirmationModal/index.ts:
--------------------------------------------------------------------------------
1 | export { default as ConfirmationModal } from './ConfirmationModal.tsx';
2 |
--------------------------------------------------------------------------------
/studio/src/components/CopyButton/CopyButton.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from '@/components/Button';
2 | import { copyToClipboard } from '@/utils';
3 | import { Copy } from '@phosphor-icons/react';
4 |
5 | interface CopyButtonProps {
6 | text: string;
7 | className?: string;
8 | }
9 | export default function CopyButton({ text, className }: CopyButtonProps) {
10 | return (
11 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/studio/src/components/CopyButton/index.ts:
--------------------------------------------------------------------------------
1 | export { default as CopyButton } from './CopyButton.tsx';
2 |
--------------------------------------------------------------------------------
/studio/src/components/CopyInput/CopyInput.tsx:
--------------------------------------------------------------------------------
1 | import { Input } from '@/components/Input';
2 | import { cn, copyToClipboard } from '@/utils';
3 | import { Copy } from '@phosphor-icons/react';
4 | import * as React from 'react';
5 | import { useState } from 'react';
6 | import { Button } from '../Button';
7 | import './copyInput.scss';
8 | import { useUpdateEffect } from '@/hooks';
9 |
10 | interface CopyInputProps extends React.ComponentPropsWithoutRef<'input'> {
11 | hasError?: boolean;
12 | }
13 |
14 | const CopyInput = React.forwardRef(
15 | ({ className, value, readOnly, hasError, placeholder, ...props }, ref) => {
16 | const [inputValue, setInputValue] = useState(value as string);
17 |
18 | useUpdateEffect(() => {
19 | setInputValue(value as string);
20 | }, [value]);
21 |
22 | return (
23 |
24 | setInputValue(e.target.value)}
29 | placeholder={placeholder}
30 | className={cn('copy-input', hasError && 'input-error')}
31 | />
32 |
41 |
42 | );
43 | },
44 | );
45 | CopyInput.displayName = 'CopyInput';
46 |
47 | export { CopyInput };
48 |
--------------------------------------------------------------------------------
/studio/src/components/CopyInput/copyInput.scss:
--------------------------------------------------------------------------------
1 | .copy-input-wrapper {
2 | @apply relative;
3 | .copy-input {
4 | @apply pr-14 cursor-default;
5 |
6 | &-button {
7 | @apply absolute text-subtle right-1 top-0.5 hover:text-default;
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/studio/src/components/CopyInput/index.ts:
--------------------------------------------------------------------------------
1 | export { CopyInput } from './CopyInput';
2 |
--------------------------------------------------------------------------------
/studio/src/components/DataTable/index.ts:
--------------------------------------------------------------------------------
1 | export { default as DataTable } from './DataTable';
2 | export { SortButton } from './SortButton';
3 |
--------------------------------------------------------------------------------
/studio/src/components/DataTable/sortButton.scss:
--------------------------------------------------------------------------------
1 | @media (hover: hover) {
2 | .resizer {
3 | opacity: 0;
4 | }
5 |
6 | *:hover > .resizer {
7 | opacity: 1;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/studio/src/components/DateRangePicker/index.ts:
--------------------------------------------------------------------------------
1 | export { DateRangePicker } from './DateRangePicker';
2 |
--------------------------------------------------------------------------------
/studio/src/components/DateText/DateText.tsx:
--------------------------------------------------------------------------------
1 | import { ReactNode } from 'react';
2 | import { DATE_FORMAT_MONTH_DAY_YEAR, TIME_FORMAT, cn, formatDate } from '@/utils';
3 | import { Avatar, AvatarFallback, AvatarImage } from '../Avatar';
4 | import { OrganizationMember } from '@/types';
5 | interface DateTextProps {
6 | date: string | Date;
7 | children?: ReactNode;
8 | className?: string;
9 | user?: OrganizationMember;
10 | style?: React.CSSProperties;
11 | }
12 | export default function DateText({ date, children, className, user, ...props }: DateTextProps) {
13 | return (
14 |
15 | {children}
16 | {user && (
17 |
18 |
19 |
20 |
21 | )}
22 | {date ? (
23 |
24 |
25 | {formatDate(date, DATE_FORMAT_MONTH_DAY_YEAR)}
26 |
27 |
28 |
29 | ) : (
30 |
-
31 | )}
32 |
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/studio/src/components/DateText/index.ts:
--------------------------------------------------------------------------------
1 | export { default as DateText } from './DateText';
2 |
--------------------------------------------------------------------------------
/studio/src/components/Description/Description.scss:
--------------------------------------------------------------------------------
1 | .description {
2 | @apply space-y-2;
3 | &-title {
4 | @apply text-default text-sm leading-8 font-semibold;
5 | }
6 | &-content {
7 | @apply text-xs text-subtle font-normal leading-6;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/studio/src/components/Description/Description.tsx:
--------------------------------------------------------------------------------
1 | import { ReactNode } from 'react';
2 | import { cn } from '@/utils';
3 |
4 | import './Description.scss';
5 |
6 | type Props = {
7 | className?: string;
8 | children?: ReactNode;
9 | title?: string | null;
10 | };
11 |
12 | export default function Description({ children, title, className }: Props) {
13 | return (
14 |
15 | {title &&
{title}
}
16 | {children &&
{children}
}
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/studio/src/components/Description/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Description } from './Description.tsx';
2 |
--------------------------------------------------------------------------------
/studio/src/components/Dialog/dialog.scss:
--------------------------------------------------------------------------------
1 | .dialog {
2 | &-portal {
3 | @apply fixed inset-0 z-[999] flex items-start justify-center sm:items-center;
4 | }
5 | &-overlay {
6 | @apply fixed inset-0 z-50 bg-base/80 backdrop-blur-sm transition-all duration-100 data-[state=closed]:animate-out data-[state=closed]:fade-out data-[state=open]:fade-in;
7 | }
8 | &-content {
9 | @apply fixed z-50 grid w-full gap-4 rounded-b-lg bg-subtle p-6 shadow-lg animate-in data-[state=open]:fade-in-90 data-[state=open]:slide-in-from-bottom-10 sm:max-w-2xl sm:rounded-lg sm:zoom-in-90 data-[state=open]:sm:slide-in-from-bottom-0;
10 | }
11 | &-close {
12 | @apply absolute right-4 top-4 aspect-square w-10 h-10 text-icon-base hover:text-icon-secondary;
13 | }
14 | &-header {
15 | @apply flex flex-col space-y-1.5 text-center sm:text-left;
16 | }
17 | &-footer {
18 | @apply flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2;
19 | }
20 | &-title {
21 | @apply text-xl text-default font-semibold leading-8;
22 | }
23 | &-description {
24 | @apply text-sm text-subtle;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/studio/src/components/Dialog/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | Dialog,
3 | DialogTrigger,
4 | DialogContent,
5 | DialogHeader,
6 | DialogFooter,
7 | DialogTitle,
8 | DialogDescription,
9 | DialogClose,
10 | } from './Dialog';
11 |
--------------------------------------------------------------------------------
/studio/src/components/Drawer/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | Drawer,
3 | DrawerClose,
4 | DrawerContent,
5 | DrawerFooter,
6 | DrawerHeader,
7 | DrawerTitle,
8 | DrawerTrigger,
9 | } from './Drawer';
10 |
--------------------------------------------------------------------------------
/studio/src/components/Dropdown/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | DropdownMenu,
3 | DropdownMenuTrigger,
4 | DropdownMenuContent,
5 | DropdownMenuItem,
6 | DropdownMenuCheckboxItem,
7 | DropdownMenuRadioItem,
8 | DropdownMenuLabel,
9 | DropdownMenuSeparator,
10 | DropdownMenuShortcut,
11 | DropdownMenuGroup,
12 | DropdownMenuPortal,
13 | DropdownMenuSub,
14 | DropdownMenuSubContent,
15 | DropdownMenuSubTrigger,
16 | DropdownMenuRadioGroup,
17 | DropdownMenuItemContainer,
18 | } from './Dropdown.tsx';
19 |
--------------------------------------------------------------------------------
/studio/src/components/EmptyState/EmptyState.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '@/utils';
2 | import { Bell, Envelope, ShippingContainer, Users } from '@phosphor-icons/react';
3 | import { ProjectorScreenChart } from '@phosphor-icons/react/dist/ssr';
4 | import React, { ElementType } from 'react';
5 |
6 | export type Modules = 'org' | 'invitation' | 'project' | 'container' | 'notification';
7 | interface EmptyStateProps {
8 | title: string;
9 | children?: React.ReactNode;
10 | type: Modules;
11 | className?: string;
12 | }
13 |
14 | export default function EmptyState({ type, title, className, children }: EmptyStateProps) {
15 | const ICON_MAP: Record = {
16 | invitation: Envelope,
17 | org: Users,
18 | notification: Bell,
19 | project: ProjectorScreenChart,
20 | container: ShippingContainer,
21 | };
22 | const Icon = ICON_MAP[type];
23 |
24 | return (
25 |
26 |
27 | {}
28 |
29 |
{title}
30 | {children}
31 |
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/studio/src/components/EmptyState/index.ts:
--------------------------------------------------------------------------------
1 | export { default as EmptyState } from './EmptyState';
2 | export type { Modules } from './EmptyState';
3 |
--------------------------------------------------------------------------------
/studio/src/components/Error/NotFound.tsx:
--------------------------------------------------------------------------------
1 | import { ArrowLeft } from '@phosphor-icons/react';
2 | import { t } from 'i18next';
3 | import React from 'react';
4 | import { Button } from '../Button';
5 | import { Svg404 } from '../icons';
6 |
7 | export default function NotFound({ children }: { children?: React.ReactNode }) {
8 | return (
9 |
10 |
11 |
12 |
{t('general.pageNotFound')}
13 |
{t('general.errorPageDescription')}
14 |
{t('general.pageNotFoundDescription')}
15 |
16 |
17 | {children ?? (
18 |
22 | )}
23 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/studio/src/components/Error/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Error } from './Error';
2 | export { default as NotFound } from './NotFound';
3 |
--------------------------------------------------------------------------------
/studio/src/components/Form/form.scss:
--------------------------------------------------------------------------------
1 | .form {
2 | &-message {
3 | @apply text-error text-xs;
4 | }
5 |
6 | &-label-error {
7 | @apply text-error;
8 | }
9 |
10 | &-description {
11 | @apply text-subtle text-xs;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/studio/src/components/Form/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | useFormField,
3 | Form,
4 | FormItem,
5 | FormLabel,
6 | FormControl,
7 | FormDescription,
8 | FormMessage,
9 | FormField,
10 | FormFieldGroup,
11 | } from './Form';
12 |
--------------------------------------------------------------------------------
/studio/src/components/Header/header.scss:
--------------------------------------------------------------------------------
1 | .header-menu {
2 | --height: var(--header-menu-height);
3 | @apply bg-base flex justify-between items-center h-[var(--height)] py-1.5 px-3 border-b border-border;
4 | @apply sticky top-0 z-10;
5 | &-divider {
6 | @apply w-[1px] h-[40px] bg-border;
7 | }
8 |
9 | &-left {
10 | @apply flex gap-4 items-center;
11 |
12 | &-organization-select {
13 | @apply text-icon-base;
14 | }
15 | }
16 |
17 | &-right {
18 | @apply flex gap-3 items-center text-icon-base;
19 | &-nav {
20 | @apply hidden 2xl:flex gap-1 items-center;
21 | }
22 | &-actions {
23 | @apply flex items-center;
24 |
25 | .btn {
26 | @apply w-[32px] h-[32px] p-0 rounded-full;
27 | svg {
28 | @apply text-[17px];
29 | }
30 | }
31 |
32 | &-user {
33 | @apply flex items-center gap-2;
34 | &-avatar {
35 | @apply rounded-full overflow-hidden;
36 | }
37 | &-name {
38 | @apply leading-6 font-normal text-sm;
39 | }
40 | }
41 | }
42 |
43 | .header-menu-divider {
44 | @apply hidden 2xl:block;
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/studio/src/components/Header/index.ts:
--------------------------------------------------------------------------------
1 | export { Header } from './Header';
2 |
--------------------------------------------------------------------------------
/studio/src/components/InfoModal/index.ts:
--------------------------------------------------------------------------------
1 | export { default as InfoModal } from './InfoModal.tsx';
2 |
--------------------------------------------------------------------------------
/studio/src/components/InfoModal/infoModal.scss:
--------------------------------------------------------------------------------
1 | .info-modal {
2 | @apply flex flex-col items-center gap-y-6;
3 |
4 | &-actions {
5 | @apply flex justify-center items-center gap-4;
6 | }
7 | &-text {
8 | @apply space-y-1;
9 |
10 | &-title {
11 | @apply text-xl text-default leading-8 font-semibold text-center;
12 | }
13 |
14 | &-desc {
15 | @apply text-sm leading-6 text-subtle text-center font-normal tracking-wide;
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/studio/src/components/Input/Input.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '@/utils';
2 | import * as React from 'react';
3 | import './input.scss';
4 | import { VariantProps, cva } from 'class-variance-authority';
5 |
6 | const inputVariants = cva('input', {
7 | variants: {
8 | variant: {
9 | sm: 'input-sm',
10 | md: 'input-md',
11 | lg: 'input-lg',
12 | },
13 | },
14 | defaultVariants: {
15 | variant: 'md',
16 | },
17 | });
18 | export interface InputProps
19 | extends React.InputHTMLAttributes,
20 | VariantProps {
21 | type?: React.HTMLInputTypeAttribute;
22 | className?: string;
23 | error?: boolean;
24 | }
25 |
26 | const Input = React.forwardRef(
27 | ({ className, type, error, onChange, variant, ...props }, ref) => {
28 | const [value, setValue] = React.useState(props.value || '');
29 |
30 | const handleChange = (event: React.ChangeEvent) => {
31 | setValue(event.target.value);
32 | if (onChange) {
33 | onChange(event);
34 | }
35 | };
36 | return (
37 |
46 | );
47 | },
48 | );
49 | Input.displayName = 'Input';
50 |
51 | export { Input };
52 |
--------------------------------------------------------------------------------
/studio/src/components/Input/Textarea.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { cn } from '@/utils';
3 |
4 | export interface TextareaProps extends React.TextareaHTMLAttributes {
5 | showCount?: boolean;
6 | error?: boolean;
7 | className?: string;
8 | }
9 |
10 | const Textarea = React.forwardRef(
11 | ({ className, showCount, error, ...props }, ref) => {
12 | const [count, setCount] = useState(0);
13 | return (
14 |
15 |
32 | );
33 | },
34 | );
35 |
36 | Textarea.displayName = 'Textarea';
37 |
38 | export { Textarea };
39 |
--------------------------------------------------------------------------------
/studio/src/components/Input/index.ts:
--------------------------------------------------------------------------------
1 | export { Input } from './Input';
2 | export { Textarea } from './Textarea.tsx';
3 |
--------------------------------------------------------------------------------
/studio/src/components/Input/input.scss:
--------------------------------------------------------------------------------
1 | .input {
2 | @apply flex w-full px-3 py-2 text-sm text-default bg-input-background rounded appearance-none;
3 | @apply border border-input-border hover:border-input-hover dark:border-none;
4 | @apply file:border-0 file:bg-transparent file:text-sm file:font-medium;
5 | @apply placeholder:text-subtle placeholder:text-sm;
6 | @apply disabled:cursor-not-allowed disabled:opacity-50;
7 | @apply focus:border-input-focus focus:outline-none focus:ring-0;
8 | @apply disabled:bg-input-disabled-background disabled:border-input-disabled-border;
9 | &-error {
10 | @apply border-error;
11 | }
12 | &-sm {
13 | @apply h-7 text-xs placeholder:text-xs file:text-xs;
14 | }
15 | &-md {
16 | @apply h-9 text-sm placeholder:text-sm file:text-sm;
17 | }
18 | &-lg {
19 | @apply h-12;
20 | }
21 | }
22 |
23 | /* Chrome, Safari, Edge, Opera */
24 | .input::-webkit-outer-spin-button,
25 | .input::-webkit-inner-spin-button {
26 | -webkit-appearance: none;
27 | }
28 |
29 | /* Firefox */
30 | .input[type='number'] {
31 | -moz-appearance: textfield;
32 | }
33 |
34 | // for autofill
35 | input:-webkit-autofill,
36 | input:-webkit-autofill:hover,
37 | input:-webkit-autofill:focus,
38 | input:-webkit-autofill:active {
39 | transition: background-color 9999s ease-in-out 0s;
40 | -webkit-text-fill-color: rgb(var(--text-base)) !important;
41 | }
42 |
--------------------------------------------------------------------------------
/studio/src/components/InviteMemberForm/index.ts:
--------------------------------------------------------------------------------
1 | export { default as InviteMemberForm, InviteMemberSchema } from './InviteMemberForm';
2 |
--------------------------------------------------------------------------------
/studio/src/components/InviteMemberForm/inviteMemberForm.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cloud-agnost/agnost-gitops/6c18a641b9dce8dea274b8797ee7a86ee474d8f2/studio/src/components/InviteMemberForm/inviteMemberForm.scss
--------------------------------------------------------------------------------
/studio/src/components/Label/Label.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import * as React from 'react';
4 | import * as LabelPrimitive from '@radix-ui/react-label';
5 | import { cva, type VariantProps } from 'class-variance-authority';
6 | import { cn } from '@/utils';
7 | import './label.scss';
8 | const labelVariants = cva('label');
9 |
10 | const Label = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef & VariantProps
13 | >(({ className, ...props }, ref) => (
14 |
15 | ));
16 | Label.displayName = LabelPrimitive.Root.displayName;
17 |
18 | export { Label };
19 |
--------------------------------------------------------------------------------
/studio/src/components/Label/index.ts:
--------------------------------------------------------------------------------
1 | export { Label } from './Label';
2 |
--------------------------------------------------------------------------------
/studio/src/components/Label/label.scss:
--------------------------------------------------------------------------------
1 | .label {
2 | @apply text-xs text-default;
3 | @apply peer-disabled:cursor-not-allowed;
4 | }
5 |
--------------------------------------------------------------------------------
/studio/src/components/Loading/Loading.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '@/utils';
2 |
3 | interface LoadingProps {
4 | className?: string;
5 | loading?: boolean;
6 | }
7 | export default function Loading({ className, loading = true }: LoadingProps) {
8 | return (
9 |
16 | {loading && (
17 | <>
18 |
Loading...
19 |
20 |
21 |
22 | >
23 | )}
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/studio/src/components/Loading/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Loading } from './Loading';
2 |
--------------------------------------------------------------------------------
/studio/src/components/LogViewer/LogViewer.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '@/utils';
2 | import AnsiToHtml from 'ansi-to-html';
3 | import { useEffect, useState } from 'react';
4 |
5 | const LogViewer = ({ logs, className }: { logs: string[]; className?: string }) => {
6 | const [htmlLogs, setHtmlLogs] = useState('');
7 |
8 | useEffect(() => {
9 | const convert = new AnsiToHtml();
10 | const html = logs.map((log) => convert.toHtml(log)).join('
');
11 | setHtmlLogs(html);
12 | }, [logs]);
13 |
14 | return (
15 |
20 | );
21 | };
22 |
23 | export default LogViewer;
24 |
--------------------------------------------------------------------------------
/studio/src/components/LogViewer/index.ts:
--------------------------------------------------------------------------------
1 | export { default as LogViewer } from "./LogViewer";
2 |
--------------------------------------------------------------------------------
/studio/src/components/PasswordInput/index.ts:
--------------------------------------------------------------------------------
1 | export { PasswordInput } from './PasswordInput';
2 |
--------------------------------------------------------------------------------
/studio/src/components/PasswordInput/password-input.scss:
--------------------------------------------------------------------------------
1 | .password-input-wrapper {
2 | @apply relative;
3 |
4 | .password-input {
5 | @apply pr-20;
6 |
7 | &-button {
8 | @apply absolute right-2 top-2;
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/studio/src/components/Popover/Popover.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { cn } from '@/utils';
4 | import * as PopoverPrimitive from '@radix-ui/react-popover';
5 | import * as React from 'react';
6 | import './popover.scss';
7 |
8 | const Popover = PopoverPrimitive.Root;
9 |
10 | const PopoverTrigger = PopoverPrimitive.Trigger;
11 | const PopoverClose = PopoverPrimitive.Close;
12 |
13 | const PopoverContent = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef
16 | >(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
17 |
18 |
25 | {props.children}
26 |
27 |
28 |
29 | ));
30 | PopoverContent.displayName = PopoverPrimitive.Content.displayName;
31 |
32 | export { Popover, PopoverContent, PopoverTrigger, PopoverClose };
33 |
--------------------------------------------------------------------------------
/studio/src/components/Popover/index.ts:
--------------------------------------------------------------------------------
1 | export { Popover, PopoverClose, PopoverContent, PopoverTrigger } from './Popover';
2 |
--------------------------------------------------------------------------------
/studio/src/components/Popover/popover.scss:
--------------------------------------------------------------------------------
1 | .popover-content {
2 | @apply z-50 rounded-md border border-border bg-wrapper-background-light text-default shadow-md;
3 | @apply outline-none animate-in data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2;
4 | @apply data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2;
5 |
6 |
7 | .arrow {
8 | @apply fill-wrapper-background-light;
9 | }
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/studio/src/components/RadioGroup/RadioGroup.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
4 | import * as React from 'react';
5 |
6 | import { cn } from '@/utils';
7 |
8 | const RadioGroup = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => {
12 | return ;
13 | });
14 | RadioGroup.displayName = RadioGroupPrimitive.Root.displayName;
15 |
16 | const RadioGroupItem = React.forwardRef<
17 | React.ElementRef,
18 | React.ComponentPropsWithoutRef
19 | >(({ className, children, ...props }, ref) => {
20 | return (
21 |
29 |
30 |
31 |
32 |
33 | );
34 | });
35 | RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;
36 |
37 | export { RadioGroup, RadioGroupItem };
38 |
--------------------------------------------------------------------------------
/studio/src/components/RadioGroup/index.ts:
--------------------------------------------------------------------------------
1 | export { RadioGroup, RadioGroupItem } from './RadioGroup';
2 |
--------------------------------------------------------------------------------
/studio/src/components/RoleDropdown/RoleSelect.tsx:
--------------------------------------------------------------------------------
1 | import useTypeStore from '@/store/types/typeStore';
2 | import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/Select';
3 |
4 | interface RoleSelectProps {
5 | role: string;
6 | type: 'org' | 'project';
7 | onSelect: (role: string) => void;
8 | disabled?: boolean;
9 | }
10 | function RoleSelect({ role, type, disabled, onSelect }: RoleSelectProps) {
11 | const { projectRoles, orgRoles } = useTypeStore();
12 | const roles = type === 'org' ? orgRoles : projectRoles;
13 | return (
14 |
27 | );
28 | }
29 |
30 | export default RoleSelect;
31 |
--------------------------------------------------------------------------------
/studio/src/components/RoleDropdown/index.ts:
--------------------------------------------------------------------------------
1 | export { default as RoleDropdown } from './RoleDropdown';
2 | export { default as RoleSelect } from './RoleSelect';
3 |
--------------------------------------------------------------------------------
/studio/src/components/SearchInput/index.ts:
--------------------------------------------------------------------------------
1 | export { default as SearchInput } from './SearchInput';
2 |
--------------------------------------------------------------------------------
/studio/src/components/SearchInput/searchInput.scss:
--------------------------------------------------------------------------------
1 | .search-input-wrapper {
2 | @apply relative max-h-[36px];
3 | .search-input {
4 | @apply pr-14 pl-8;
5 |
6 | &-button {
7 | @apply absolute text-subtle right-2 top-1/2 hover:text-default;
8 | transform: translateY(-50%);
9 | }
10 | }
11 | }
12 | .search-input-icon {
13 | @apply absolute text-subtle top-1/2 ml-2;
14 | transform: translateY(-50%);
15 | }
16 |
--------------------------------------------------------------------------------
/studio/src/components/Select/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | Select,
3 | SelectGroup,
4 | SelectValue,
5 | SelectTrigger,
6 | SelectContent,
7 | SelectLabel,
8 | SelectItem,
9 | SelectSeparator,
10 | } from './Select';
11 |
--------------------------------------------------------------------------------
/studio/src/components/SelectionDropdown/index.ts:
--------------------------------------------------------------------------------
1 | export { default as SelectionDropdown } from './SelectionDropdown';
2 |
--------------------------------------------------------------------------------
/studio/src/components/Separator/Separator.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import * as React from 'react';
4 | import * as SeparatorPrimitive from '@radix-ui/react-separator';
5 | import { cn } from '@/utils';
6 |
7 | const Separator = React.forwardRef<
8 | React.ElementRef,
9 | React.ComponentPropsWithoutRef
10 | >(({ className, orientation = 'horizontal', decorative = true, ...props }, ref) => (
11 |
22 | ));
23 | Separator.displayName = SeparatorPrimitive.Root.displayName;
24 |
25 | export { Separator };
26 |
--------------------------------------------------------------------------------
/studio/src/components/Separator/index.ts:
--------------------------------------------------------------------------------
1 | export { Separator } from './Separator';
2 |
--------------------------------------------------------------------------------
/studio/src/components/SettingsFormItem/SettingsFormItem.tsx:
--------------------------------------------------------------------------------
1 | import { ElementType, ReactNode } from 'react';
2 | import { cn } from '@/utils';
3 |
4 | interface SettingsFormItemProps {
5 | title: string;
6 | description?: string | null;
7 | children?: ReactNode;
8 | className?: string;
9 | contentClassName?: string;
10 | twoColumns?: boolean;
11 | as?: ElementType;
12 | }
13 | export default function SettingsFormItem({
14 | title,
15 | description,
16 | children,
17 | contentClassName,
18 | className,
19 | twoColumns = false,
20 | as = 'div',
21 | }: SettingsFormItemProps) {
22 | const Component = as;
23 | return (
24 |
32 |
33 |
{title}
34 | {description && (
35 |
{description}
36 | )}
37 |
38 | {children}
39 |
40 | );
41 | }
42 |
--------------------------------------------------------------------------------
/studio/src/components/SettingsFormItem/index.ts:
--------------------------------------------------------------------------------
1 | export { default as SettingsFormItem } from './SettingsFormItem.tsx';
2 |
--------------------------------------------------------------------------------
/studio/src/components/SettingsFormItem/settingsFormItem.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cloud-agnost/agnost-gitops/6c18a641b9dce8dea274b8797ee7a86ee474d8f2/studio/src/components/SettingsFormItem/settingsFormItem.scss
--------------------------------------------------------------------------------
/studio/src/components/Switch/Switch.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as SwitchPrimitives from '@radix-ui/react-switch';
3 | import './switch.scss';
4 | import { cn } from '@/utils';
5 | import { Lock } from '@phosphor-icons/react';
6 |
7 | const Switch = React.forwardRef<
8 | React.ElementRef,
9 | React.ComponentPropsWithoutRef
10 | >(({ className, ...props }, ref) => (
11 |
12 |
13 |
14 |
15 | ));
16 | Switch.displayName = SwitchPrimitives.Root.displayName;
17 |
18 | export { Switch };
19 |
--------------------------------------------------------------------------------
/studio/src/components/Switch/index.ts:
--------------------------------------------------------------------------------
1 | export { Switch } from './Switch.tsx';
2 |
--------------------------------------------------------------------------------
/studio/src/components/Switch/switch.scss:
--------------------------------------------------------------------------------
1 | .switch-root {
2 | @apply inline-flex h-[24px] w-[48px] border-4 shrink-0 cursor-pointer items-center;
3 | @apply rounded-full border-transparent transition-colors relative;
4 |
5 | &:focus-visible {
6 | @apply outline-none;
7 | }
8 |
9 | .switch-thumb {
10 | @apply pointer-events-none bg-white block h-4 w-4 rounded-full shadow-lg transition-transform;
11 | }
12 |
13 | &[data-state='checked'] {
14 | @apply bg-success;
15 |
16 | .switch-thumb {
17 | @apply translate-x-6;
18 | }
19 | }
20 |
21 | &[data-state='unchecked'] {
22 | @apply bg-lighter;
23 |
24 | .switch-thumb {
25 | @apply translate-x-0;
26 | }
27 | }
28 |
29 | .switch-lock {
30 | @apply hidden;
31 | }
32 |
33 | &:disabled {
34 | @apply cursor-not-allowed dark:bg-border bg-border;
35 |
36 | .switch-lock {
37 | @apply block absolute dark:text-icon-base text-border;
38 | }
39 |
40 | .switch-thumb {
41 | @apply bg-icon-disabled;
42 | }
43 |
44 | &[data-state='checked'] {
45 | @apply bg-green-800;
46 | .switch-lock {
47 | @apply left-0;
48 | }
49 | }
50 | &[data-state='unchecked'] {
51 | .switch-lock {
52 | @apply right-0;
53 | }
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/studio/src/components/Table/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | Table,
3 | TableBody,
4 | TableCaption,
5 | TableCell,
6 | TableFooter,
7 | TableHead,
8 | TableHeader,
9 | TableRow,
10 | } from './Table';
11 |
12 | export { default as SelectedRowButton } from './SelectedRowButton';
13 | export { TableConfirmation } from './TableConfirmation';
14 |
--------------------------------------------------------------------------------
/studio/src/components/Table/table.scss:
--------------------------------------------------------------------------------
1 | .table {
2 | @apply w-full caption-bottom text-xs bg-base rounded-lg border-border;
3 | &-container {
4 | @apply w-full border border-border rounded-lg;
5 | }
6 | &-header {
7 | @apply [&_tr]:border-b;
8 | }
9 | &-footer {
10 | @apply bg-subtle font-medium text-subtle;
11 | }
12 | &-body {
13 | @apply [&_tr:last-child]:border-none bg-base;
14 | }
15 | &-row {
16 | @apply border-b transition-colors #{!important};
17 |
18 | &.head {
19 | @apply border-none;
20 | }
21 |
22 | &:hover {
23 | .table-cell.actions {
24 | * {
25 | @apply opacity-100;
26 | }
27 | }
28 | }
29 | }
30 | &-head {
31 | @apply h-10 px-4 text-left align-middle font-medium relative bg-subtle;
32 | @apply text-subtle [&:has([role=checkbox])]:pr-0 text-xs first:rounded-tl-lg last:rounded-tr-lg;
33 | &.sortable {
34 | @apply hover:bg-lighter;
35 | }
36 | }
37 | &-cell {
38 | @apply px-4 py-2 align-middle [&:has([role=checkbox])]:pr-0 text-default rounded-lg;
39 |
40 | &.actions {
41 | * {
42 | @apply opacity-0;
43 | }
44 | }
45 | }
46 | &-caption {
47 | @apply mt-4 text-xs text-subtle;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/studio/src/components/Toast/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | Toast,
3 | ToastClose,
4 | ToastProvider,
5 | ToastTitle,
6 | ToastViewport,
7 | type ToastProps,
8 | } from './Toast';
9 | export { Toaster } from './Toaster';
10 |
--------------------------------------------------------------------------------
/studio/src/components/Tooltip/Tooltip.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as TooltipPrimitive from '@radix-ui/react-tooltip';
3 | import { cn } from '@/utils';
4 |
5 | const TooltipProvider = TooltipPrimitive.Provider;
6 |
7 | type TooltipProps = React.ComponentPropsWithoutRef &
8 | React.ComponentPropsWithoutRef;
9 |
10 | function Tooltip({
11 | children,
12 | delayDuration,
13 | skipDelayDuration,
14 | disableHoverableContent,
15 | ...rest
16 | }: TooltipProps) {
17 | return (
18 |
23 | {children}
24 |
25 | );
26 | }
27 |
28 | const TooltipTrigger = TooltipPrimitive.Trigger;
29 |
30 | const TooltipContent = React.forwardRef<
31 | React.ElementRef,
32 | React.ComponentPropsWithoutRef
33 | >(({ className, sideOffset = 4, ...props }, ref) => (
34 |
43 | ));
44 | TooltipContent.displayName = TooltipPrimitive.Content.displayName;
45 |
46 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
47 |
--------------------------------------------------------------------------------
/studio/src/components/Tooltip/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Tooltip.tsx';
2 |
--------------------------------------------------------------------------------
/studio/src/components/TransferOwnership/index.ts:
--------------------------------------------------------------------------------
1 | export { default as TransferOwnership } from './TransferOwnership';
2 |
--------------------------------------------------------------------------------
/studio/src/components/icons/Bitbucket.tsx:
--------------------------------------------------------------------------------
1 | import { SVGProps } from 'react';
2 |
3 | export default function Bitbucket(props: SVGProps) {
4 | return (
5 |
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/studio/src/components/icons/Docker.tsx:
--------------------------------------------------------------------------------
1 | import { SVGProps } from 'react';
2 | const SvgDocker = (props: SVGProps) => (
3 |
16 | );
17 | export default SvgDocker;
18 |
--------------------------------------------------------------------------------
/studio/src/components/icons/Error.tsx:
--------------------------------------------------------------------------------
1 | import { SVGProps } from 'react';
2 | const SvgError = (props: SVGProps) => (
3 |
23 | );
24 | export default SvgError;
25 |
--------------------------------------------------------------------------------
/studio/src/components/icons/GitLab.tsx:
--------------------------------------------------------------------------------
1 | import { SVGProps } from 'react';
2 | const SvgGitlab = (props: SVGProps) => (
3 |
30 | );
31 | export default SvgGitlab;
32 |
--------------------------------------------------------------------------------
/studio/src/components/icons/RabbitMq.tsx:
--------------------------------------------------------------------------------
1 | import { SVGProps } from 'react';
2 | const SvgRabbitMq = (props: SVGProps) => (
3 |
16 | );
17 | export default SvgRabbitMq;
18 |
--------------------------------------------------------------------------------
/studio/src/components/icons/SocketIo.tsx:
--------------------------------------------------------------------------------
1 | import { SVGProps } from 'react';
2 | const SvgSocketIo = (props: SVGProps) => (
3 |
24 | );
25 | export default SvgSocketIo;
26 |
--------------------------------------------------------------------------------
/studio/src/components/icons/SuccessCheck.tsx:
--------------------------------------------------------------------------------
1 | import { SVGProps } from 'react';
2 | const SvgSuccessCheck = (props: SVGProps) => (
3 |
23 | );
24 | export default SvgSuccessCheck;
25 |
--------------------------------------------------------------------------------
/studio/src/components/icons/Warning.tsx:
--------------------------------------------------------------------------------
1 | import { SVGProps } from 'react';
2 | const SvgWarning = (props: SVGProps) => (
3 |
24 | );
25 | export default SvgWarning;
26 |
--------------------------------------------------------------------------------
/studio/src/components/icons/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Svg401 } from "./401";
2 | export { default as Svg404 } from "./404";
3 | export { default as Agnost } from "./Agnost";
4 | export { default as Awss3 } from "./Awss3";
5 | export { default as AzureBlobStorage } from "./AzureBlobStorage";
6 | export { default as Bitbucket } from "./Bitbucket";
7 | export { default as Docker } from "./Docker";
8 | export { default as Error } from "./Error";
9 | export { default as ErrorPage } from "./ErrorPage";
10 | export { default as GcpStorage } from "./GcpStorage";
11 | export { default as Github } from "./Github";
12 | export { default as Gitlab } from "./GitLab";
13 | export { default as Kafka } from "./Kafka";
14 | export { default as Kubernetes } from "./Kubernetes";
15 | export { default as Logo } from "./Logo";
16 | export { default as MinIo } from "./MinIo";
17 | export { default as MongoDb } from "./MongoDb";
18 | export { default as MySql } from "./MySql";
19 | export { default as Nodejs } from "./Nodejs";
20 | export { default as PostgreSql } from "./PostgreSql";
21 | export { default as RabbitMq } from "./RabbitMq";
22 | export { default as React } from "./React";
23 | export { default as Redis } from "./Redis";
24 | export { default as Resource } from "./Resource";
25 | export { default as SocketIo } from "./SocketIo";
26 | export { default as SuccessCheck } from "./SuccessCheck";
27 | export { default as Warning } from "./Warning";
28 | export { default as MariaDb } from "./MariaDb";
29 | export { default as Memcached } from "./Memcached";
30 |
--------------------------------------------------------------------------------
/studio/src/constants/index.ts:
--------------------------------------------------------------------------------
1 | export * from "@/constants/constants.ts";
2 |
--------------------------------------------------------------------------------
/studio/src/constants/stateList.ts:
--------------------------------------------------------------------------------
1 | // import useAuthStore from "@/store/auth/authStore";
2 | import useClusterStore from "@/store/cluster/clusterStore";
3 | import useContainerStore from "@/store/container/containerStore";
4 | import useEnvironmentStore from "@/store/environment/environmentStore";
5 | import useNotificationStore from "@/store/notification/notificationStore";
6 | import useOrganizationStore from "@/store/organization/organizationStore";
7 | import useProjectStore from "@/store/project/projectStore";
8 | import useThemeStore from "@/store/theme/themeStore";
9 | import useTypeStore from "@/store/types/typeStore";
10 | import { StoreApi, UseBoundStore } from "zustand";
11 | export const STATE_LIST: Record>> = {
12 | // auth: useAuthStore,
13 | cluster: useClusterStore,
14 | container: useContainerStore,
15 | environment: useEnvironmentStore,
16 | notification: useNotificationStore,
17 | organization: useOrganizationStore,
18 | project: useProjectStore,
19 | theme: useThemeStore,
20 | types: useTypeStore,
21 | };
22 |
--------------------------------------------------------------------------------
/studio/src/features/auth/AcceptInvitation/AcceptInvitation.tsx:
--------------------------------------------------------------------------------
1 | import { AuthLayout } from '@/layouts/AuthLayout';
2 | import { APIError } from '@/types';
3 | import { useLoaderData } from 'react-router-dom';
4 |
5 | export default function AcceptInvitation({ type }: { type: 'project' | 'org' }) {
6 | const error = useLoaderData() as APIError | undefined;
7 |
8 | return (
9 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/studio/src/features/auth/AcceptInvitation/index.ts:
--------------------------------------------------------------------------------
1 | export { default as AcceptInvitation } from "./AcceptInvitation";
2 |
--------------------------------------------------------------------------------
/studio/src/features/auth/AuthUserDropdown/index.ts:
--------------------------------------------------------------------------------
1 | export { default as AuthUserDropdown } from './AuthUserDropdown';
2 |
--------------------------------------------------------------------------------
/studio/src/features/auth/ChangeAvatar/ChangeAvatar.tsx:
--------------------------------------------------------------------------------
1 | import { ChangeAvatar } from '@/components/ChangeAvatar';
2 | import useAuthStore from '@/store/auth/authStore.ts';
3 | import { APIError } from '@/types';
4 | import { useState } from 'react';
5 | export default function ChangeUserAvatar() {
6 | const [loading, setLoading] = useState(false);
7 | const [error, setError] = useState(null);
8 | const { user, changeAvatar, removeAvatar } = useAuthStore();
9 |
10 | async function onChangeHandler(file: File) {
11 | try {
12 | setError(null);
13 | setLoading(true);
14 | await changeAvatar(file);
15 | } catch (error) {
16 | setError(error as APIError);
17 | } finally {
18 | setLoading(false);
19 | }
20 | }
21 |
22 | async function removeHandler() {
23 | try {
24 | setError(null);
25 | setLoading(true);
26 | await removeAvatar();
27 | } catch (error) {
28 | setError(error as APIError);
29 | } finally {
30 | setLoading(false);
31 | }
32 | }
33 |
34 | return (
35 |
49 | );
50 | }
51 |
--------------------------------------------------------------------------------
/studio/src/features/auth/ChangeAvatar/index.ts:
--------------------------------------------------------------------------------
1 | export { default as ChangeUserAvatar } from "./ChangeAvatar.tsx";
2 |
--------------------------------------------------------------------------------
/studio/src/features/auth/ChangeName/changeName.sass:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cloud-agnost/agnost-gitops/6c18a641b9dce8dea274b8797ee7a86ee474d8f2/studio/src/features/auth/ChangeName/changeName.sass
--------------------------------------------------------------------------------
/studio/src/features/auth/ChangeName/index.ts:
--------------------------------------------------------------------------------
1 | export { default as ChangeName } from './ChangeName.tsx';
2 |
--------------------------------------------------------------------------------
/studio/src/features/auth/DeleteAccount/deleteAccount.scss:
--------------------------------------------------------------------------------
1 | .delete-account-btn {
2 | @apply bg-transparent text-elements-red;
3 | }
4 |
--------------------------------------------------------------------------------
/studio/src/features/auth/DeleteAccount/index.ts:
--------------------------------------------------------------------------------
1 | export { default as DeleteAccount } from './DeleteAccount.tsx';
2 |
--------------------------------------------------------------------------------
/studio/src/features/auth/Notifications/Notification.tsx:
--------------------------------------------------------------------------------
1 | import { Avatar, AvatarFallback, AvatarImage } from '@/components/Avatar';
2 | import { Badge } from '@/components/Badge';
3 | import { DateText } from '@/components/DateText';
4 | import { BADGE_COLOR_MAP } from '@/constants';
5 | import { Notification } from '@/types';
6 | import _ from 'lodash';
7 |
8 | export default function MainNotification({ notification }: { notification: Notification }) {
9 | return (
10 |
11 |
12 |
13 |
14 |
20 |
21 |
22 |
{notification.actor.name}
23 |
{notification.actor.email}
24 |
25 |
26 |
{notification.description}
27 |
32 |
33 |
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/studio/src/features/auth/Notifications/NotificationItem.tsx:
--------------------------------------------------------------------------------
1 | import { Switch } from '@/components/Switch';
2 | import { useTranslation } from 'react-i18next';
3 |
4 | interface NotificationItemProps {
5 | notification: string;
6 | checked: boolean;
7 | onChange: (notification: string, status: boolean) => void;
8 | }
9 | export default function NotificationItem({
10 | notification,
11 | onChange,
12 | checked,
13 | }: NotificationItemProps) {
14 | const { t } = useTranslation();
15 |
16 | return (
17 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/studio/src/features/auth/Notifications/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Notifications } from "./Notifications.tsx";
2 | export { default as NotificationItem } from "./NotificationItem.tsx";
3 | export { default as NotificationFilter } from "./NotificationFilter.tsx";
4 | export { default as Notification } from "./Notification.tsx";
5 |
--------------------------------------------------------------------------------
/studio/src/features/auth/Notifications/notifications.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cloud-agnost/agnost-gitops/6c18a641b9dce8dea274b8797ee7a86ee474d8f2/studio/src/features/auth/Notifications/notifications.scss
--------------------------------------------------------------------------------
/studio/src/features/auth/ProfileSettings/index.ts:
--------------------------------------------------------------------------------
1 | export { default as ProfileSettings } from "./ProfileSettings.tsx";
2 |
--------------------------------------------------------------------------------
/studio/src/features/auth/UserProviders/index.ts:
--------------------------------------------------------------------------------
1 | export { default as UserProviders } from "./UserProviders";
2 |
--------------------------------------------------------------------------------
/studio/src/features/cluster/ReleaseColumns.tsx:
--------------------------------------------------------------------------------
1 | import { ClusterComponentReleaseInfo } from '@/types';
2 | import { cn } from '@/utils';
3 | import { ColumnDef } from '@tanstack/react-table';
4 | import { t } from 'i18next';
5 |
6 | const ReleaseColumns: ColumnDef[] = [
7 | {
8 | id: 'module',
9 | accessorKey: 'module',
10 | size: 200,
11 | enableResizing: false,
12 | header: () => {t('cluster.component')},
13 | },
14 | {
15 | id: 'current',
16 | accessorKey: 'version',
17 | size: 100,
18 | enableResizing: false,
19 | header: () => {t('cluster.current')},
20 | },
21 | {
22 | id: 'latest',
23 | accessorKey: 'latest',
24 | size: 100,
25 | enableResizing: false,
26 | header: () => {t('cluster.latest')},
27 | cell: ({ row }) => {
28 | const { latest, version } = row.original;
29 | return {latest};
30 | },
31 | },
32 | ];
33 |
34 | export default ReleaseColumns;
35 |
--------------------------------------------------------------------------------
/studio/src/features/cluster/ReleaseHistory.tsx:
--------------------------------------------------------------------------------
1 | import { DataTable } from '@/components/DataTable';
2 | import { Drawer, DrawerContent, DrawerHeader, DrawerTitle } from '@/components/Drawer';
3 | import { useTable } from '@/hooks';
4 | import useClusterStore from '@/store/cluster/clusterStore';
5 | import { useTranslation } from 'react-i18next';
6 | import ReleaseHistoryColumns from './ReleaseHistoryColumns';
7 |
8 | export default function ReleaseHistory() {
9 | const { t } = useTranslation();
10 | const { clusterReleaseInfo, isReleaseHistoryOpen, toggleReleaseHistory } = useClusterStore();
11 |
12 | const table = useTable({
13 | columns: ReleaseHistoryColumns,
14 | data: clusterReleaseInfo?.cluster?.releaseHistory ?? [],
15 | });
16 | return (
17 |
18 |
19 |
20 | {t('cluster.history')}
21 |
22 |
23 |
24 |
25 |
26 |
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/studio/src/features/cluster/ReleaseHistoryColumns.tsx:
--------------------------------------------------------------------------------
1 | import { DateText } from '@/components/DateText';
2 | import { ClusterReleaseHistory } from '@/types';
3 | import { ColumnDef } from '@tanstack/react-table';
4 | import { t } from 'i18next';
5 |
6 | const ReleaseHistoryColumns: ColumnDef[] = [
7 | {
8 | id: 'release',
9 | accessorKey: 'release',
10 | header: () => {t('cluster.release')},
11 | },
12 | {
13 | id: 'latest',
14 | accessorKey: 'latest',
15 | header: () => {t('cluster.deployed_at')},
16 | cell: ({ row }) => ,
17 | },
18 | ];
19 |
20 | export default ReleaseHistoryColumns;
21 |
--------------------------------------------------------------------------------
/studio/src/features/cluster/index.ts:
--------------------------------------------------------------------------------
1 | export { default as ReleaseDropdown } from './ReleaseDropdown';
2 |
--------------------------------------------------------------------------------
/studio/src/features/container/config/index.ts:
--------------------------------------------------------------------------------
1 | export { default as AutoScaleConfig } from "./AutoScaleConfig";
2 | export { default as Builds } from "./Builds";
3 | export { default as Events } from "./Events";
4 | export { default as Logs } from "./Logs";
5 | export { default as Networking } from "./Networking";
6 | export { default as PodConfiguration } from "./PodConfiguration";
7 | export { default as Pods } from "./Pods";
8 | export { default as Probes } from "./Probes";
9 | export { default as SourceConfig } from "./SourceConfig";
10 | export { default as StorageConfig } from "./StorageConfig";
11 | export { default as Variables } from "./Variables";
12 |
--------------------------------------------------------------------------------
/studio/src/features/organization/OrganizationCreateButton.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from '@/components/Button';
2 | import { Plus } from '@phosphor-icons/react';
3 | import './organization.scss';
4 | import { useTranslation } from 'react-i18next';
5 | function OrganizationCreateButton({ ...props }) {
6 | const { t } = useTranslation();
7 | return (
8 |
9 |
12 |
{t('organization.create')}
13 |
14 | );
15 | }
16 |
17 | export { OrganizationCreateButton };
18 |
--------------------------------------------------------------------------------
/studio/src/features/organization/Settings/ChangeOrganizationAvatar.tsx:
--------------------------------------------------------------------------------
1 | import { ChangeAvatar } from '@/components/ChangeAvatar';
2 | import useAuthorizeOrg from '@/hooks/useAuthorizeOrg';
3 | import useOrganizationStore from '@/store/organization/organizationStore';
4 | import { useMutation } from '@tanstack/react-query';
5 | import { useParams } from 'react-router-dom';
6 |
7 | export default function ChangeOrganizationAvatar() {
8 | const canUpdate = useAuthorizeOrg('update');
9 | const { organization, changeOrganizationAvatar, removeOrganizationAvatar } =
10 | useOrganizationStore();
11 | const { orgId } = useParams() as Record;
12 |
13 | const {
14 | mutate: changeAvatar,
15 | isPending: changeLoading,
16 | error: changeError,
17 | } = useMutation({
18 | mutationFn: (file: File) =>
19 | changeOrganizationAvatar({
20 | organizationId: orgId,
21 | picture: file,
22 | }),
23 | });
24 | const {
25 | mutate: remove,
26 | isPending: removeLoading,
27 | error: removeError,
28 | } = useMutation({
29 | mutationFn: removeOrganizationAvatar,
30 | });
31 |
32 | return (
33 |
46 | );
47 | }
48 |
--------------------------------------------------------------------------------
/studio/src/features/organization/Settings/Members/OrganizationInvitationDrawer.tsx:
--------------------------------------------------------------------------------
1 | import { Drawer, DrawerContent, DrawerHeader, DrawerTitle } from '@/components/Drawer';
2 | import { useTranslation } from 'react-i18next';
3 | import OrganizationInvitation from './InviteOrganization';
4 | interface OrganizationInvitationDrawerProps {
5 | open: boolean;
6 | onOpenChange: (open: boolean) => void;
7 | }
8 | export default function OrganizationInvitationDrawer({
9 | open,
10 | onOpenChange,
11 | }: OrganizationInvitationDrawerProps) {
12 | const { t } = useTranslation();
13 | return (
14 |
15 |
16 |
17 | {t('organization.settings.members.invite.title')}
18 |
19 |
20 |
21 |
22 |
23 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/studio/src/features/organization/Settings/TransferOrganization.tsx:
--------------------------------------------------------------------------------
1 | import { TransferOwnership } from '@/components/TransferOwnership';
2 | import useAuthStore from '@/store/auth/authStore';
3 | import useOrganizationStore from '@/store/organization/organizationStore';
4 |
5 | export default function TransferOrganization() {
6 | const { transferOrganization, organization } = useOrganizationStore();
7 | const user = useAuthStore((state) => state.user);
8 | return (
9 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/studio/src/features/organization/index.ts:
--------------------------------------------------------------------------------
1 | export { default as OrganizationMenuItem } from "./navbar/OrganizationMenuItem";
2 | export { OrganizationCreateButton } from "./OrganizationCreateButton";
3 | export { default as OrganizationCreateModal } from "./OrganizationCreateModal";
4 | export { default as ChangeOrganizationAvatar } from "./Settings/ChangeOrganizationAvatar";
5 | export { default as ChangeOrganizationName } from "./Settings/ChangeOrganizationName";
6 | export { default as DeleteOrganization } from "./Settings/DeleteOrganization";
7 | export { default as OrganizationInvitationDrawer } from "./Settings/Members/OrganizationInvitationDrawer";
8 | export { default as OrganizationInvitations } from "./Settings/Members/OrganizationInvitations";
9 | export { OrganizationInvitationsColumns } from "./Settings/Members/OrganizationInvitationsColumns";
10 | export { default as OrganizationMembers } from "./Settings/Members/OrganizationMembers";
11 | export { OrganizationMembersColumns } from "./Settings/Members/OrganizationMembersColumns";
12 | export { default as OrganizationMembersTableHeader } from "./Settings/Members/OrganizationMembersTableHeader";
13 | export { default as TransferOrganization } from "./Settings/TransferOrganization";
14 |
--------------------------------------------------------------------------------
/studio/src/features/organization/navbar/OrganizationMenuItem.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from '@/components/Button';
2 | import { cn } from '@/utils';
3 | import { useNavigate, useSearchParams } from 'react-router-dom';
4 | import './organizationMenu.scss';
5 | interface OrganizationMenuItemProps {
6 | onClick?: () => void;
7 | active?: boolean;
8 | item: {
9 | name: string;
10 | href: string;
11 | icon?: any;
12 | };
13 | isNavigate?: boolean;
14 | urlKey?: string;
15 | }
16 |
17 | export default function OrganizationMenuItem({
18 | item,
19 | active,
20 | onClick,
21 | urlKey,
22 | isNavigate = false,
23 | }: OrganizationMenuItemProps) {
24 | const [searchParams, setSearchParams] = useSearchParams();
25 | const navigate = useNavigate();
26 |
27 | function clickHandler() {
28 | onClick?.();
29 | if (isNavigate) {
30 | navigate(item.href);
31 | } else {
32 | searchParams.set(urlKey ?? 't', item.href);
33 | setSearchParams(searchParams);
34 | }
35 | }
36 | return (
37 |
38 |
42 |
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/studio/src/features/organization/navbar/organizationMenu.scss:
--------------------------------------------------------------------------------
1 | .org-menu {
2 | @apply flex items-end justify-center h-full mx-auto;
3 | &-container {
4 | @apply h-[72px] border-b border-border;
5 | @apply sticky top-0 z-50 bg-base;
6 | }
7 | &-item {
8 | @apply list-none;
9 | &-name {
10 | @apply text-default text-xs;
11 | }
12 | &.active {
13 | .org-menu-link {
14 | @apply border-b-brand-primary;
15 | }
16 | .org-menu-icon {
17 | @apply text-brand-primary;
18 | }
19 | }
20 | }
21 | &-link {
22 | @apply flex items-center justify-center gap-2 pb-4 px-8;
23 | @apply border-b-3 border-transparent;
24 | }
25 | &-icon {
26 | @apply text-icon-base;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/studio/src/features/organization/organization.scss:
--------------------------------------------------------------------------------
1 | .create-button {
2 | @apply w-32 h-32 rounded-3xl;
3 | &-container {
4 | @apply flex flex-col items-center gap-4;
5 | }
6 | &-icon {
7 | @apply text-icon-disabled;
8 | }
9 | &-label {
10 | @apply text-default text-xl font-semibold;
11 | }
12 | }
13 | .organization {
14 | &-form {
15 | @apply py-4;
16 | &-item {
17 | @apply space-y-1;
18 | }
19 | &-buttons {
20 | @apply flex justify-end gap-4 mt-2;
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/studio/src/features/projects/DeleteProject.tsx:
--------------------------------------------------------------------------------
1 | import { ConfirmationModal } from '@/components/ConfirmationModal';
2 | import useProjectStore from '@/store/project/projectStore';
3 | import { useMutation } from '@tanstack/react-query';
4 | import { Trans, useTranslation } from 'react-i18next';
5 | import { useParams } from 'react-router-dom';
6 | export default function DeleteProject() {
7 | const { t } = useTranslation();
8 | const { orgId } = useParams() as Record;
9 | const { deleteProject, toDeleteProject, closeDeleteModal, isDeleteModalOpen } = useProjectStore();
10 | const {
11 | mutateAsync: deleteMutate,
12 | isPending: deleteLoading,
13 | error: deleteError,
14 | } = useMutation({
15 | mutationFn: () => deleteProject(orgId, toDeleteProject?._id as string),
16 | onSuccess: closeDeleteModal,
17 | });
18 | return (
19 | ,
31 | }}
32 | />
33 | }
34 | confirmCode={toDeleteProject?.iid as string}
35 | onConfirm={deleteMutate}
36 | isOpen={isDeleteModalOpen}
37 | closeModal={closeDeleteModal}
38 | closable
39 | />
40 | );
41 | }
42 |
--------------------------------------------------------------------------------
/studio/src/features/projects/ProjectGeneralSettings.tsx:
--------------------------------------------------------------------------------
1 | import ChangeProjectAvatar from './Settings/ChangeProjectAvatar';
2 | import ChangeProjectName from './Settings/ChangeProjectName';
3 | import TransferProject from './Settings/TransferProject';
4 |
5 | export default function ProjectGeneralSettings() {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/studio/src/features/projects/ProjectTeam.tsx:
--------------------------------------------------------------------------------
1 | import { Avatar, AvatarFallback, AvatarImage } from '@/components/Avatar';
2 | import { ProjectTeam as Team } from '@/types';
3 |
4 | interface TeamProps {
5 | team: Team[];
6 | table?: boolean;
7 | }
8 | export default function ProjectTeam({ team, table = false }: TeamProps) {
9 | return (
10 |
11 | {team?.slice(0, 4)?.map((member) => (
12 |
13 |
14 |
15 |
16 | ))}
17 | {team?.length > 4 && (
18 |
19 |
20 |
21 | )}
22 |
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/studio/src/features/projects/Settings/TransferProject.tsx:
--------------------------------------------------------------------------------
1 | import { SettingsFormItem } from '@/components/SettingsFormItem';
2 | import { TransferOwnership } from '@/components/TransferOwnership';
3 | import useAuthStore from '@/store/auth/authStore';
4 | import useProjectStore from '@/store/project/projectStore';
5 | import { useTranslation } from 'react-i18next';
6 |
7 | export default function TransferProject() {
8 | const { t } = useTranslation();
9 | const user = useAuthStore((state) => state.user);
10 | const { transferProjectOwnership, project } = useProjectStore();
11 | return (
12 |
17 |
22 |
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/studio/src/helpers/componentLoader.ts:
--------------------------------------------------------------------------------
1 | export default function componentLoader(lazyComponent: any, attemptsLeft = 10) {
2 | window.sessionStorage.setItem('pageForceRefreshed', 'false');
3 | return new Promise((resolve, reject) => {
4 | lazyComponent()
5 | .then(resolve)
6 | .catch((error: any) => {
7 | setTimeout(() => {
8 | if (attemptsLeft === 1) {
9 | const pageForceRefreshed =
10 | window.sessionStorage.getItem('pageForceRefreshed') === 'true';
11 |
12 | if (!pageForceRefreshed) {
13 | window.sessionStorage.setItem('pageForceRefreshed', 'true');
14 | return window.location.reload();
15 | } else {
16 | reject(error);
17 | return;
18 | }
19 | }
20 | componentLoader(lazyComponent, attemptsLeft - 1).then(resolve, reject);
21 | }, 2000);
22 | });
23 | });
24 | }
25 |
--------------------------------------------------------------------------------
/studio/src/helpers/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | instance as axios,
3 | envInstance as http,
4 | testEndpointInstance as test,
5 | } from "./axios.ts";
6 | export { default as componentLoader } from "./componentLoader.ts";
7 | export { socket } from "./socket.ts";
8 |
--------------------------------------------------------------------------------
/studio/src/helpers/realtime/Cluster.ts:
--------------------------------------------------------------------------------
1 | import useClusterStore from "@/store/cluster/clusterStore";
2 | import { Cluster as ClusterType, RealtimeActionParams } from "@/types";
3 | import { RealtimeActions } from "./RealtimeActions";
4 |
5 | class Cluster implements RealtimeActions {
6 | update(param: RealtimeActionParams): void {
7 | useClusterStore.setState?.({ cluster: param.data });
8 | }
9 | telemetry(param: RealtimeActionParams): void {
10 | this.update(param);
11 | }
12 | }
13 |
14 | export default Cluster;
15 |
--------------------------------------------------------------------------------
/studio/src/helpers/realtime/Container.ts:
--------------------------------------------------------------------------------
1 | import useContainerStore from "@/store/container/containerStore";
2 | import { Container as ContainerType, RealtimeActionParams } from "@/types";
3 | import { RealtimeActions } from "./RealtimeActions";
4 | class Container implements RealtimeActions {
5 | delete(param: RealtimeActionParams): void {
6 | useContainerStore.setState?.((state) => ({
7 | ...state,
8 | containers: state.containers.filter(
9 | (container) => container._id !== param.identifiers.containerId
10 | ),
11 | }));
12 | }
13 | update(param: RealtimeActionParams): void {
14 | useContainerStore.setState?.((state) => ({
15 | ...state,
16 | containers: state.containers.map((container) => {
17 | if (container._id === param.data._id) {
18 | return param.data;
19 | }
20 | return container;
21 | }),
22 | }));
23 | }
24 | create(param: RealtimeActionParams): void {
25 | useContainerStore.setState?.((state) => ({
26 | ...state,
27 | containers: [
28 | ...state.containers.filter((en) => en._id !== param.data._id),
29 | param.data,
30 | ],
31 | }));
32 | }
33 | telemetry(param: RealtimeActionParams): void {
34 | this.update(param);
35 | }
36 | log(): void {
37 | return;
38 | }
39 | }
40 |
41 | export default Container;
42 |
--------------------------------------------------------------------------------
/studio/src/helpers/realtime/RealtimeActions.ts:
--------------------------------------------------------------------------------
1 | import { RealtimeActionParams } from '@/types';
2 |
3 | export interface RealtimeActions {
4 | delete?(param: RealtimeActionParams): void;
5 | update?(param: RealtimeActionParams): void;
6 | create?(param: RealtimeActionParams): void;
7 | telemetry?(param: RealtimeActionParams): void;
8 | log?(param: RealtimeActionParams): void;
9 | deploy?(param: RealtimeActionParams): void;
10 | redeploy?(param: RealtimeActionParams): void;
11 | accept?(param: RealtimeActionParams): void;
12 | }
13 |
--------------------------------------------------------------------------------
/studio/src/helpers/realtime/User.ts:
--------------------------------------------------------------------------------
1 | import useAuthStore from '@/store/auth/authStore';
2 | import { RealtimeActionParams, User as UserType } from '@/types';
3 | import { RealtimeActions } from './RealtimeActions';
4 | class User implements RealtimeActions {
5 | update({ data }: RealtimeActionParams) {
6 | useAuthStore.setState({ user: data });
7 | }
8 | }
9 |
10 | export default User;
11 |
--------------------------------------------------------------------------------
/studio/src/helpers/realtime/index.ts:
--------------------------------------------------------------------------------
1 | import { RealtimeObjectTypes } from "@/types";
2 | import Cluster from "./Cluster";
3 | import Container from "./Container";
4 | import Environment from "./Environment";
5 | import OrgMember from "./OrgMember";
6 | import Organization from "./Organization";
7 | import Project from "./Project";
8 | import ProjectTeam from "./ProjectTeam";
9 | import User from "./User";
10 | export function realtimeObjectMapper(type: RealtimeObjectTypes) {
11 | const keys = {
12 | user: User,
13 | org: Organization,
14 | cluster: Cluster,
15 | "org.project": Project,
16 | "org.project.environment": Environment,
17 | "org.project.environment.container": Container,
18 | "org.project.team": ProjectTeam,
19 | "org.member": OrgMember,
20 | };
21 | return new keys[type]();
22 | }
23 |
--------------------------------------------------------------------------------
/studio/src/helpers/socket.ts:
--------------------------------------------------------------------------------
1 | import { io } from "socket.io-client";
2 |
3 | export const socket = io(window.location.hostname, {
4 | reconnection: true,
5 | reconnectionDelay: 500,
6 | transports: ["websocket", "polling"],
7 | path: "/sync/sync/",
8 | });
9 |
10 | socket.on("connect", () => {});
11 |
12 | socket.on("connect_error", (error) => {
13 | console.error("Not Connected to socket.io server", error);
14 | });
15 |
--------------------------------------------------------------------------------
/studio/src/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export { default as useDebounce } from "./useDebounce.tsx";
2 | export { default as useRealtime } from "./useRealtime.tsx";
3 | export { default as useSearch } from "./useSearch.tsx";
4 | export { default as useTable } from "./useTable.tsx";
5 | export { useToast } from "./useToast.tsx";
6 | export { default as useUpdateEffect } from "./useUpdateEffect.tsx";
7 |
--------------------------------------------------------------------------------
/studio/src/hooks/useAuthorizeOrg.tsx:
--------------------------------------------------------------------------------
1 | import useOrganizationStore from '@/store/organization/organizationStore';
2 | import { getOrgPermission } from '@/utils';
3 | import React from 'react';
4 |
5 | export default function useAuthorizeOrg(key: string) {
6 | const org = useOrganizationStore((state) => state.organization);
7 | const hasPermission = React.useMemo(() => getOrgPermission(key), [key, org]);
8 |
9 | return hasPermission;
10 | }
11 |
--------------------------------------------------------------------------------
/studio/src/hooks/useAuthorizeProject.tsx:
--------------------------------------------------------------------------------
1 | import useProjectStore from '@/store/project/projectStore';
2 | import { getProjectPermission } from '@/utils';
3 | import React from 'react';
4 |
5 | export default function useAuthorizeProject(key: string) {
6 | const project = useProjectStore((state) => state.project);
7 | return React.useMemo(() => getProjectPermission(key), [project]);
8 | }
9 |
--------------------------------------------------------------------------------
/studio/src/hooks/useDebounce.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 |
3 | export default function useDebounce(value: string, delay: number) {
4 | const [debouncedValue, setDebouncedValue] = useState(value);
5 |
6 | useEffect(() => {
7 | const handler = setTimeout(() => {
8 | setDebouncedValue(value);
9 | }, delay);
10 |
11 | return () => {
12 | clearTimeout(handler);
13 | };
14 | }, [value, delay]);
15 |
16 | return debouncedValue;
17 | }
18 |
--------------------------------------------------------------------------------
/studio/src/hooks/useRealtime.tsx:
--------------------------------------------------------------------------------
1 | import { NOTIFICATION_ACTIONS } from '@/constants';
2 | import { realtimeObjectMapper } from '@/helpers/realtime';
3 | import useAuthStore from '@/store/auth/authStore';
4 | import { DATE_TIME_FORMAT_WITH_MS, formatDate, onChannelMessage } from '@/utils';
5 | import { useEffect } from 'react';
6 |
7 | export default function useRealtime() {
8 | const user = useAuthStore((state) => state.user);
9 | useEffect(() => {
10 | const cb = onChannelMessage('notification', (message) => {
11 | const { data, object, action, identifiers, timestamp, message: log, id, type } = message;
12 | if (message?.actor?.userId !== user?._id || action !== 'create') {
13 | const fn = realtimeObjectMapper(object);
14 |
15 | if (!(action in fn)) return;
16 | //@ts-expect-error - this is a valid call
17 | fn[action]({
18 | data,
19 | identifiers,
20 | timestamp: formatDate(timestamp, DATE_TIME_FORMAT_WITH_MS),
21 | message: log,
22 | ...(id && { id }),
23 | type,
24 | actor: message.actor,
25 | });
26 | if (NOTIFICATION_ACTIONS.includes(action)) {
27 | // add notf preview
28 | }
29 | }
30 | });
31 |
32 | return () => {
33 | cb();
34 | };
35 | }, []);
36 | }
37 |
--------------------------------------------------------------------------------
/studio/src/hooks/useSearch.tsx:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react';
2 | import { useSearchParams } from 'react-router-dom';
3 |
4 | export default function useSearch(data: any[]) {
5 | const [searchParams] = useSearchParams();
6 |
7 | const filteredData = useMemo(() => {
8 | if (searchParams.get('q')) {
9 | const query = new RegExp(searchParams.get('q') as string, 'i');
10 | return data.filter((item) => query.test(item.name));
11 | }
12 | return data;
13 | }, [searchParams.get('q'), data]);
14 |
15 | return filteredData;
16 | }
17 |
--------------------------------------------------------------------------------
/studio/src/hooks/useUpdateEffect.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from 'react';
2 |
3 | export default function useUpdateEffect(callback: () => void, dependencies: any[]) {
4 | const firstRenderRef = useRef(true);
5 | useEffect(() => {
6 | if (firstRenderRef.current) {
7 | firstRenderRef.current = false;
8 | } else {
9 | callback();
10 | }
11 | }, dependencies);
12 | }
13 |
--------------------------------------------------------------------------------
/studio/src/i18n/config.ts:
--------------------------------------------------------------------------------
1 | import i18next from "i18next";
2 | import { initReactI18next } from "react-i18next";
3 | import {
4 | cluster,
5 | container,
6 | forms,
7 | general,
8 | login,
9 | onboarding,
10 | organization,
11 | profileSettings,
12 | project,
13 | } from "./en";
14 |
15 | export const resources = {
16 | en: {
17 | translation: {
18 | login,
19 | forms,
20 | general,
21 | organization,
22 | profileSettings,
23 | onboarding,
24 | cluster,
25 | project,
26 | container,
27 | },
28 | },
29 | };
30 |
31 | i18next
32 | .use(initReactI18next)
33 | .init({
34 | lng: "en", // if you're using a language detector, do not define the lng option
35 | resources,
36 | defaultNS: "translation",
37 | })
38 | .catch(console.error);
39 |
40 | export const { t } = i18next;
41 |
42 | export default i18next;
43 |
--------------------------------------------------------------------------------
/studio/src/i18n/en/index.ts:
--------------------------------------------------------------------------------
1 | import cluster from "./cluster.json";
2 | import container from "./container.json";
3 | import forms from "./forms.json";
4 | import general from "./general.json";
5 | import login from "./login.json";
6 | import onboarding from "./onboarding.json";
7 | import organization from "./organization.json";
8 | import profileSettings from "./profileSettings.json";
9 | import project from "./project.json";
10 |
11 | export {
12 | cluster,
13 | container,
14 | forms,
15 | general,
16 | login,
17 | onboarding,
18 | organization,
19 | profileSettings,
20 | project,
21 | };
22 |
--------------------------------------------------------------------------------
/studio/src/i18n/en/onboarding.json:
--------------------------------------------------------------------------------
1 | {
2 | "welcome": "Welcome to Agnost",
3 | "account_info": "Account Information",
4 | "welcome_desc": "Through this onboarding flow, we will help you set up your cluster and create your organization and first project.",
5 | "next": "Next",
6 | "previous": "Previous",
7 | "skip_and_finish": "Skip & Finish",
8 | "finish": "Finish",
9 | "app": {
10 | "title": "Create Your First App",
11 | "desc": "In Agnost, you work on apps and their versions. An app is your workspace that packages all required design and configuration elements to run your backend app services."
12 | },
13 | "org": {
14 | "title": "Create Your Organization",
15 | "desc": "Organizations are the top level entities that are used to group your applications and manage organization specific resource (e.g., databases, cache, message brokers)"
16 | },
17 | "invite": {
18 | "title": "Invite Members To App Team",
19 | "stepper_title": "Invite Team Members",
20 | "desc": "You can invite team members to your application with different role profiles. These team members will also become organization members and can be easily added as member to other organization apps.",
21 | "emails_unique": "Emails must be unique"
22 | },
23 |
24 | "add_another_one": "Add Another One",
25 | "select_a_role": "Select a role",
26 | "role": "Role"
27 | }
28 |
--------------------------------------------------------------------------------
/studio/src/layouts/AuthLayout/AuthLayout.tsx:
--------------------------------------------------------------------------------
1 | import { Alert, AlertDescription, AlertTitle } from '@/components/Alert';
2 | import { Agnost } from '@/components/icons';
3 | import Providers from '@/features/auth/Providers/Providers';
4 | import { APIError } from '@/types';
5 | import { cn } from '@/utils';
6 |
7 | type AuthLayoutProps = {
8 | error?: APIError;
9 | className?: string;
10 | title: string;
11 | subtitle: string;
12 | };
13 |
14 | export default function AuthLayout({ className, error, title, subtitle }: AuthLayoutProps) {
15 | return (
16 |
22 |
23 |
24 |
25 |
{title}
26 |
{subtitle}
27 |
28 | {error?.error && (
29 |
30 | {error.error}
31 | {error.details}
32 |
33 | )}
34 |
35 |
36 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/studio/src/layouts/AuthLayout/index.ts:
--------------------------------------------------------------------------------
1 | export { default as AuthLayout } from './AuthLayout.tsx';
2 |
--------------------------------------------------------------------------------
/studio/src/layouts/Layout/Layout.tsx:
--------------------------------------------------------------------------------
1 | import { Header } from '@/components/Header';
2 | import React from 'react';
3 |
4 | export default function Layout({ children }: { children: React.ReactNode }) {
5 | return (
6 | <>
7 |
8 | {children}
9 | >
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/studio/src/layouts/Layout/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Layout } from './Layout';
2 |
--------------------------------------------------------------------------------
/studio/src/main.tsx:
--------------------------------------------------------------------------------
1 | import '@/i18n/config';
2 | import App from '@/App.tsx';
3 | import '@/index.scss';
4 | import ReactDOM from 'react-dom/client';
5 |
6 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render();
7 |
--------------------------------------------------------------------------------
/studio/src/pages/auth/Login.tsx:
--------------------------------------------------------------------------------
1 | import { AuthLayout } from '@/layouts/AuthLayout';
2 | import { APIError } from '@/types';
3 | import { useLoaderData } from 'react-router-dom';
4 |
5 | export default function Login() {
6 | const error = useLoaderData() as APIError | undefined;
7 |
8 | return (
9 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/studio/src/pages/auth/OrgAcceptInvitation.tsx:
--------------------------------------------------------------------------------
1 | import { AcceptInvitation } from '@/features/auth/AcceptInvitation';
2 | export default function OrgAcceptInvitation() {
3 | return ;
4 | }
5 |
--------------------------------------------------------------------------------
/studio/src/pages/auth/ProjectAcceptInvitation.tsx:
--------------------------------------------------------------------------------
1 | import { AcceptInvitation } from '@/features/auth/AcceptInvitation';
2 |
3 | export default function ProjectAcceptInvitation() {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/studio/src/pages/environment/Environment.tsx:
--------------------------------------------------------------------------------
1 | import useEnvironmentStore from '@/store/environment/environmentStore';
2 | import useProjectStore from '@/store/project/projectStore';
3 | import { joinChannel } from '@/utils';
4 | import _ from 'lodash';
5 | import { useEffect } from 'react';
6 | import { Outlet, useParams } from 'react-router-dom';
7 |
8 | export default function Environment() {
9 | const { projectId, orgId, envId } = useParams() as Record;
10 | const { project, getProjectById } = useProjectStore();
11 | const { getEnvironmentById } = useEnvironmentStore();
12 |
13 | useEffect(() => {
14 | if (_.isEmpty(project)) {
15 | getProjectById(orgId as string, projectId as string);
16 | } else {
17 | joinChannel(projectId as string);
18 | }
19 | }, [projectId]);
20 |
21 | useEffect(() => {
22 | getEnvironmentById({
23 | projectId: projectId as string,
24 | orgId: orgId as string,
25 | envId: envId as string,
26 | });
27 | }, [envId]);
28 | return (
29 |
30 |
31 |
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/studio/src/pages/errors/401.tsx:
--------------------------------------------------------------------------------
1 | import { Svg401 } from '@/components/icons';
2 | import { useTranslation } from 'react-i18next';
3 | import { Button } from '@/components/Button';
4 | import { ArrowLeft } from '@phosphor-icons/react';
5 | import useAuthStore from '@/store/auth/authStore';
6 | export default function UnauthorizedAccess() {
7 | const { t } = useTranslation();
8 | const { isAuthenticated } = useAuthStore();
9 | return (
10 |
11 |
12 |
13 |
14 |
{t('general.unauthorizedAccess')}
15 |
{t('general.unauthorizedAccessDescription')}
16 |
17 | {isAuthenticated() ? (
18 |
22 | ) : (
23 |
27 | )}
28 |
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/studio/src/pages/errors/404.tsx:
--------------------------------------------------------------------------------
1 | import { NotFound } from '@/components/Error';
2 | export default function NotFoundPage() {
3 | return ;
4 | }
5 |
--------------------------------------------------------------------------------
/studio/src/pages/errors/ErrorBoundary.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from '@/components/Button';
2 | import { Error } from '@/components/Error';
3 | import { ArrowLeft } from '@phosphor-icons/react';
4 | import { useTranslation } from 'react-i18next';
5 | export default function ErrorBoundary() {
6 | const { t } = useTranslation();
7 | return (
8 |
9 |
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/studio/src/pages/home/Health.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery } from '@tanstack/react-query';
2 | import { ClusterService } from '@/services';
3 | export default function Health() {
4 | const { data } = useQuery({
5 | queryKey: ['health'],
6 | queryFn: ClusterService.healthCheck,
7 | });
8 |
9 | return {data}
;
10 | }
11 |
--------------------------------------------------------------------------------
/studio/src/pages/home/Home.tsx:
--------------------------------------------------------------------------------
1 | export default function Home() {
2 | return Home
;
3 | }
4 |
--------------------------------------------------------------------------------
/studio/src/pages/onboarding/Onboarding.tsx:
--------------------------------------------------------------------------------
1 | import { Outlet } from 'react-router-dom';
2 |
3 | export default function Onboarding() {
4 | return (
5 |
6 |
7 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/studio/src/pages/onboarding/Register.tsx:
--------------------------------------------------------------------------------
1 | import { AuthLayout } from '@/layouts/AuthLayout';
2 | import { GuestOnly } from '@/router';
3 | import { APIError } from '@/types';
4 | import { useTranslation } from 'react-i18next';
5 | import { useLoaderData } from 'react-router-dom';
6 | export default function Register() {
7 | const { t } = useTranslation();
8 | const error = useLoaderData() as APIError | undefined;
9 |
10 | return (
11 |
12 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/studio/src/pages/organization/Organization.tsx:
--------------------------------------------------------------------------------
1 | import { OrganizationCreateModal } from '@/features/organization';
2 | import useOrganizationStore from '@/store/organization/organizationStore.ts';
3 | import useProjectStore from '@/store/project/projectStore';
4 | import { useEffect, useState } from 'react';
5 | import { Outlet, useParams } from 'react-router-dom';
6 |
7 | export default function Organization() {
8 | const [openOrgCreateModal, setOpenOrgCreateModal] = useState(false);
9 | const { orgId } = useParams();
10 | const { getOrgPermissions } = useOrganizationStore();
11 | const { getProjectPermissions } = useProjectStore();
12 | useEffect(() => {
13 | getProjectPermissions(orgId as string);
14 | getOrgPermissions();
15 | }, [orgId]);
16 |
17 | return (
18 | <>
19 | setOpenOrgCreateModal(false)}
23 | />
24 |
25 | setOpenOrgCreateModal(true),
28 | closeOrgCreateModal: () => setOpenOrgCreateModal(false),
29 | }}
30 | />
31 | >
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/studio/src/pages/organization/OrganizationDetails.tsx:
--------------------------------------------------------------------------------
1 | import { Layout } from '@/layouts/Layout';
2 | import { Outlet } from 'react-router-dom';
3 |
4 | export default function OrganizationDetails() {
5 | return (
6 |
7 |
8 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/studio/src/pages/organization/organization.scss:
--------------------------------------------------------------------------------
1 | .select-organization {
2 | &-container {
3 | @apply flex flex-col items-center justify-center gap-8 lg:gap-20 h-screen;
4 | }
5 | &-title {
6 | @apply text-default text-2xl font-semibold;
7 | }
8 | &-items {
9 | @apply flex flex-wrap justify-center gap-4 w-1/2;
10 | }
11 | &-item {
12 | @apply flex flex-col justify-center items-center gap-4;
13 | }
14 | &-button {
15 | @apply h-[13rem];
16 | }
17 | &-info {
18 | @apply space-y-2 text-center;
19 | }
20 | &-name {
21 | @apply text-default text-xl truncate max-w-[16ch];
22 | }
23 | &-role {
24 | @apply text-default text-sm;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/studio/src/pages/redirect-handle/RedirectHandle.tsx:
--------------------------------------------------------------------------------
1 | export default function RedirectHandle() {
2 | return null;
3 | }
4 |
--------------------------------------------------------------------------------
/studio/src/pages/root/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Root } from './Root.tsx';
2 |
--------------------------------------------------------------------------------
/studio/src/router/index.ts:
--------------------------------------------------------------------------------
1 | export { GuestOnly, default as router } from "./router.tsx";
2 |
--------------------------------------------------------------------------------
/studio/src/router/loader/HomeLoader.ts:
--------------------------------------------------------------------------------
1 | import useAuthStore from "@/store/auth/authStore";
2 | import useOrganizationStore from "@/store/organization/organizationStore";
3 | import { resetAfterOrgChange } from "@/utils";
4 | import { LoaderFunctionArgs, redirect } from "react-router-dom";
5 |
6 | const REDIRECT_URLS = {
7 | "project-invite": "/project-accept?token=:token",
8 | "org-invite": "/org-accept?token=:token",
9 | };
10 |
11 | function homeLoader() {
12 | if (useAuthStore.getState().isAuthenticated()) {
13 | return redirect("/organization");
14 | } else return redirect("/login");
15 | }
16 |
17 | function redirectHandleLoader(params: LoaderFunctionArgs) {
18 | const url = new URL(params.request.url);
19 | const token = url.searchParams.get("token");
20 | const type = url.searchParams.get("type");
21 |
22 | if (!type || !token) {
23 | return redirect("/login");
24 | }
25 |
26 | return redirect(
27 | REDIRECT_URLS[type as keyof typeof REDIRECT_URLS].replace(":token", token)
28 | );
29 | }
30 |
31 | function organizationSelectLoader() {
32 | resetAfterOrgChange();
33 | useOrganizationStore.getState().reset();
34 | return null;
35 | }
36 |
37 | function clusterManagementLoader() {
38 | const user = useAuthStore.getState().user;
39 |
40 | if (!user.isClusterOwner) {
41 | return redirect("/401");
42 | }
43 |
44 | return null;
45 | }
46 |
47 | export default {
48 | homeLoader,
49 | redirectHandleLoader,
50 | organizationSelectLoader,
51 | clusterManagementLoader,
52 | };
53 |
--------------------------------------------------------------------------------
/studio/src/router/loader/OnboardingLoader.ts:
--------------------------------------------------------------------------------
1 | async function onboardingLoader() {
2 | // const status = await useClusterStore.getState().checkClusterSetup();
3 | // const isAuthenticated = useAuthStore.getState().isAuthenticated();
4 |
5 | // // if (status) {
6 | // // return redirect(isAuthenticated ? "/organization" : "/login");
7 | // // }
8 |
9 | return null;
10 | }
11 |
12 | export default {
13 | onboardingLoader,
14 | };
15 |
--------------------------------------------------------------------------------
/studio/src/services/NotificationService.ts:
--------------------------------------------------------------------------------
1 | import { axios } from "@/helpers";
2 | import { GetAuditLogsRequest, GetDistinctActionsRequest } from "@/types";
3 |
4 | export default class NotificationService {
5 | static url = "/v1/log/org";
6 |
7 | static async getAuditLogs({ orgId, ...rest }: GetAuditLogsRequest) {
8 | return (
9 | await axios.get(`${this.url}/${orgId}`, {
10 | params: rest,
11 | })
12 | ).data;
13 | }
14 | static async getDistinctActions({
15 | orgId,
16 | ...rest
17 | }: GetDistinctActionsRequest) {
18 | return (
19 | await axios.get(`${this.url}/${orgId}/filters`, {
20 | params: rest,
21 | })
22 | ).data;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/studio/src/services/TypesService.ts:
--------------------------------------------------------------------------------
1 | import { axios } from '@/helpers';
2 | import { Types } from '@/types/type';
3 |
4 | export default class TypesService {
5 | static url = '/v1/types';
6 |
7 | static async getAllTypes(): Promise {
8 | return (await axios.get(`${this.url}/all`)).data;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/studio/src/services/index.ts:
--------------------------------------------------------------------------------
1 | export { default as AuthService } from "./AuthService.ts";
2 | export { default as ClusterService } from "./ClusterService.ts";
3 | export { default as UserService } from "./UserService.ts";
4 |
--------------------------------------------------------------------------------
/studio/src/store/theme/themeStore.ts:
--------------------------------------------------------------------------------
1 | import { create } from 'zustand';
2 | import { devtools, persist } from 'zustand/middleware';
3 |
4 | interface ThemeStore {
5 | theme: Record;
6 | setTheme: (theme: string, userId: string) => void;
7 | getTheme: (userId: string) => string;
8 | }
9 |
10 | const useThemeStore = create()(
11 | devtools(
12 | persist(
13 | (set, get) => ({
14 | theme: {},
15 | setTheme: (theme, userId) => {
16 | document.body.classList.remove(get().theme[userId]);
17 | document.body.classList.add(theme);
18 | set({
19 | theme: {
20 | [userId]: theme,
21 | },
22 | });
23 | },
24 | getTheme: (userId) => get().theme[userId] ?? 'dark',
25 | }),
26 |
27 | {
28 | name: 'theme-store',
29 | },
30 | ),
31 | ),
32 | );
33 |
34 | export default useThemeStore;
35 |
--------------------------------------------------------------------------------
/studio/src/store/types/typeStore.ts:
--------------------------------------------------------------------------------
1 | import TypesService from "@/services/TypesService";
2 | import { APIError, Types } from "@/types";
3 | import { create } from "zustand";
4 | import { devtools, persist } from "zustand/middleware";
5 | interface TypesStore {
6 | orgRoles: string[];
7 | projectRoles: string[];
8 | orgRoleDesc: Record;
9 | projectRoleDesc: Record;
10 | isTypesOk: boolean;
11 | timezones: {
12 | label: string;
13 | name: string;
14 | value: string;
15 | utc: string;
16 | }[];
17 | getAllTypes: () => Promise;
18 | }
19 |
20 | const useTypeStore = create()(
21 | devtools(
22 | persist(
23 | (set) => ({
24 | orgRoles: [],
25 | appRoles: [],
26 | projectRoles: [],
27 | orgRoleDesc: {},
28 | appRoleDesc: {},
29 | projectRoleDesc: {},
30 | isTypesOk: false,
31 | timezones: [],
32 | getAllTypes: async () => {
33 | try {
34 | const res = await TypesService.getAllTypes();
35 | set({
36 | ...res,
37 | isTypesOk: true,
38 | });
39 | return res;
40 | } catch (error) {
41 | throw error as APIError;
42 | }
43 | },
44 | }),
45 | {
46 | name: "type-store",
47 | }
48 | )
49 | )
50 | );
51 | export default useTypeStore;
52 |
--------------------------------------------------------------------------------
/studio/src/types/environment.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { UpdateProjectParams } from "./project";
3 | import { NameSchema } from "./schema";
4 |
5 | export interface Environment {
6 | orgId: string;
7 | projectId: string;
8 | iid: string;
9 | name: string;
10 | private: boolean;
11 | readOnly: boolean;
12 | createdBy: string;
13 | _id: string;
14 | createdAt: string;
15 | updatedAt: string;
16 | __v: number;
17 | }
18 |
19 | export type GetEnvironmentRequest = UpdateProjectParams;
20 |
21 | export interface GetEnvironmentByIdRequest {
22 | projectId: string;
23 | envId: string;
24 | orgId: string;
25 | }
26 |
27 | export const CreateNewEnvironmentSchema = z.object({
28 | name: NameSchema,
29 | private: z.boolean().default(false),
30 | readOnly: z.boolean().default(false),
31 | });
32 | export type CreateNewEnvironmentRequest = UpdateProjectParams &
33 | z.infer;
34 |
35 | export interface UpdateEnvironmentRequest extends CreateNewEnvironmentRequest {
36 | envId: string;
37 | }
38 |
39 | export interface DeleteEnvironmentRequest extends UpdateProjectParams {
40 | envId: string;
41 | }
42 |
--------------------------------------------------------------------------------
/studio/src/types/index.ts:
--------------------------------------------------------------------------------
1 | export type * from "./cluster.ts";
2 | export * from "./container.ts";
3 | export type * from "./environment.ts";
4 | export * from "./organization.ts";
5 | export type * from "./project.ts";
6 | export * from "./project.ts";
7 | export * from "./schema.ts";
8 | export type * from "./type.ts";
9 |
--------------------------------------------------------------------------------
/studio/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./redirect";
2 | export * from "./time";
3 | export * from "./utils";
4 |
--------------------------------------------------------------------------------
/studio/src/utils/redirect.ts:
--------------------------------------------------------------------------------
1 | import { NavigateFunction, Location } from 'react-router-dom';
2 |
3 | interface History {
4 | navigate: NavigateFunction | null;
5 | location: Location | null;
6 | }
7 | export const history: History = {
8 | navigate: null,
9 | location: null,
10 | };
11 |
--------------------------------------------------------------------------------
/studio/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/studio/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 | "jsx": "react-jsx",
16 |
17 | /* Linting */
18 | "strict": true,
19 | "strictNullChecks": true,
20 | "noUnusedLocals": true,
21 | "noUnusedParameters": true,
22 | "noFallthroughCasesInSwitch": true,
23 |
24 | /* Path Aliases */
25 | "baseUrl": ".",
26 | "paths": {
27 | "@/*": ["./src/*", "./dist/*", ""],
28 | "routes/*": ["./src/routes/*"],
29 | "services/*": ["./src/services/*"],
30 | "utils/*": ["./src/utils/*"],
31 | "components/*": ["./src/components/*"],
32 | "constants/*": ["./src/constants/*"],
33 | "˜/*": ["./src/assets/*"]
34 | }
35 | },
36 | "include": ["src", "src/features/auth/ChangeAvatar/ChangeAvatar.tsx"],
37 | "references": [{ "path": "./tsconfig.node.json" }]
38 | }
39 |
--------------------------------------------------------------------------------
/studio/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "include": ["vite.config.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/studio/vite.config.ts:
--------------------------------------------------------------------------------
1 | import react from '@vitejs/plugin-react';
2 | import path from 'path';
3 | import { defineConfig } from 'vite';
4 |
5 | // https://vitejs.dev/config/
6 | export default defineConfig({
7 | plugins: [react()],
8 | base: '/studio',
9 | resolve: {
10 | alias: {
11 | '@': path.resolve(__dirname, './src/'),
12 | routes: `${path.resolve(__dirname, './src/routes/')}`,
13 | services: `${path.resolve(__dirname, './src/services/')}`,
14 | utils: `${path.resolve(__dirname, './src/utils/')}`,
15 | components: `${path.resolve(__dirname, './src/components/')}`,
16 | constants: `${path.resolve(__dirname, './src/constants/')}`,
17 | '˜': `${path.resolve(__dirname, './src/assets/')}`,
18 | },
19 | },
20 | server: {
21 | port: 4000,
22 | host: true,
23 | },
24 | });
25 |
--------------------------------------------------------------------------------
/sync/.dockerignore:
--------------------------------------------------------------------------------
1 | Dockerfile
2 | Dockerfile.dev
3 | package-lock.json
4 | eslint.config.js
5 | node_modules
--------------------------------------------------------------------------------
/sync/Dockerfile:
--------------------------------------------------------------------------------
1 | # Get the base node image from docker hub
2 | FROM node:20-alpine
3 | # Set the working directory in our docker image, anything that is copied from now on
4 | # from local machine to the image will be moved under '/app' directory in docker image
5 | WORKDIR '/app'
6 | # Copy package.json file from local machine to the image under '/app' directory
7 | COPY ./package.json ./
8 | # Set the node environment variable
9 | ENV NODE_ENV production
10 | # Run npm install to install dependent modules
11 | RUN npm install --omit=dev
12 | # Copy everything to the image under '/app' directory
13 | COPY ./ ./
14 | # This will be the start up command of the docker container, run the development react server
15 | CMD ["node", "--expose-gc", "server"]
16 |
--------------------------------------------------------------------------------
/sync/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | # Get the base node image from docker hub
2 | FROM node:20-alpine
3 | # Set the working directory in our docker image, anything that is copied from now on
4 | # from local machine to the image will be moved under '/app' directory in docker image
5 | WORKDIR '/app'
6 | # Copy package.json file from local machine to the image under '/app' directory
7 | COPY ./package.json ./
8 | # Set the node environment variable
9 | ENV NODE_ENV development
10 | # Run npm install to install dependent modules
11 | RUN npm install -g nodemon
12 | RUN npm install --omit=dev
13 | # Copy everything to the image under '/app' directory
14 | COPY ./ ./
15 | # This will be the start up command of the docker container, run the development react server
16 | CMD ["nodemon", "--expose-gc", "server"]
17 |
--------------------------------------------------------------------------------
/sync/config/default.json:
--------------------------------------------------------------------------------
1 | {
2 | "server": {
3 | "host": "localhost",
4 | "port": 4000,
5 | "timeout": 3600000
6 | },
7 | "rateLimiters": [
8 | {
9 | "rateLimitWindowSec": 30,
10 | "rateLimitMaxHits": 1000
11 | }
12 | ],
13 | "sync": {
14 | "pingTimeout": 30000,
15 | "pingInterval": 10000,
16 | "upgradeTimeout": 10000,
17 | "serveClient": false,
18 | "path": "/sync/"
19 | },
20 | "cache": {
21 | "port": 6379
22 | },
23 | "general": {
24 | "gcSeconds": 30
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/sync/config/development.json:
--------------------------------------------------------------------------------
1 | {
2 | }
3 |
--------------------------------------------------------------------------------
/sync/config/errorCodes.js:
--------------------------------------------------------------------------------
1 | const ERROR_CODES = {
2 | // Sync specific error codes
3 | internalServerError: "internal_server_error",
4 | rateLimitExceeded: "rate_limit_exceeded",
5 | resourceNotFound: "resource_not_found",
6 | };
7 | export default ERROR_CODES;
8 |
--------------------------------------------------------------------------------
/sync/config/production.json:
--------------------------------------------------------------------------------
1 | {
2 | }
3 |
--------------------------------------------------------------------------------
/sync/eslint.config.js:
--------------------------------------------------------------------------------
1 | import globals from "globals";
2 | import pluginJs from "@eslint/js";
3 |
4 | export default [
5 | { languageOptions: { globals: { ...globals.browser, ...globals.node } } },
6 | pluginJs.configs.recommended,
7 | ];
8 |
--------------------------------------------------------------------------------
/sync/middlewares/logRequest.js:
--------------------------------------------------------------------------------
1 | // Log requests to console
2 | export const logRequest = (req, res, time) => {
3 | if (req.originalUrl !== "/health")
4 | console.info(
5 | `${req.method} (${res.statusCode}) ${Math.round(time * 10) / 10}ms ${
6 | req.originalUrl
7 | }`
8 | );
9 | };
10 |
--------------------------------------------------------------------------------
/sync/middlewares/rateLimiter.js:
--------------------------------------------------------------------------------
1 | import { RateLimiterRedis } from "rate-limiter-flexible";
2 | import { getRedisClient } from "../init/cache.js";
3 | import helper from "../util/helper.js";
4 | import ERROR_CODES from "../config/errorCodes.js";
5 |
6 | // Apply rate limits to engine internal endpoints
7 | export const createRateLimiter = (rateLimitConfig) => {
8 | const rateLimiter = new RateLimiterRedis({
9 | storeClient: getRedisClient(),
10 | points: rateLimitConfig.rateLimitMaxHits, // Limit each unique identifier (IP or userId) to N requests per `window`
11 | duration: rateLimitConfig.rateLimitWindowSec, // Window duration in seconds
12 | });
13 |
14 | return (req, res, next) => {
15 | rateLimiter
16 | .consume(helper.getIP(req))
17 | .then(() => {
18 | next();
19 | })
20 | .catch(() => {
21 | return res.status(429).json({
22 | error: "Rate Limit Exceeded",
23 | details: "Too many requests, please try again later.",
24 | code: ERROR_CODES.rateLimitExceeded,
25 | });
26 | });
27 | };
28 | };
29 |
--------------------------------------------------------------------------------
/sync/middlewares/undefinedPaths.js:
--------------------------------------------------------------------------------
1 | import ERROR_CODES from "../config/errorCodes.js";
2 |
3 | // Middleware to handle undefined paths or posts
4 | export const handleUndefinedPaths = (req, res) => {
5 | return res.status(404).json({
6 | error: "Not Found",
7 | details: "The server can not find the requested resource.",
8 | code: ERROR_CODES.resourceNotFound,
9 | });
10 | };
11 |
--------------------------------------------------------------------------------
/sync/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "platform-worker",
3 | "version": "v1.0.0",
4 | "description": "Platform worker api server",
5 | "main": "server.js",
6 | "type": "module",
7 | "scripts": {
8 | "prod": "NODE_ENV=production node server",
9 | "dev": "NODE_ENV=development nodemon server"
10 | },
11 | "author": "Ümit Çakmak",
12 | "license": "ISC",
13 | "dependencies": {
14 | "@socket.io/redis-adapter": "8.1.0",
15 | "axios": "1.3.3",
16 | "config": "3.3.9",
17 | "cors": "2.8.5",
18 | "express": "4.18.2",
19 | "helmet": "6.0.1",
20 | "nocache": "3.0.4",
21 | "rate-limiter-flexible": "2.4.1",
22 | "redis": "3.1.2",
23 | "response-time": "2.3.2",
24 | "socket.io": "4.6.1",
25 | "winston": "3.8.2",
26 | "winston-transport": "4.5.0"
27 | },
28 | "devDependencies": {
29 | "@eslint/js": "^9.4.0",
30 | "eslint": "^9.4.0",
31 | "globals": "^15.3.0",
32 | "nodemon": "2.0.20"
33 | }
34 | }
--------------------------------------------------------------------------------
/sync/routes/system.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 |
3 | const router = express.Router({ mergeParams: true });
4 |
5 | /*
6 | @route /
7 | @method GET
8 | @desc Checks liveliness of sync server
9 | @access public
10 | */
11 | router.get("/health", (req, res) => {
12 | res
13 | .status(200)
14 | .send(
15 | new Date().toISOString() +
16 | " - Healthy sync server" +
17 | " - " +
18 | process.env.RELEASE_NUMBER
19 | );
20 | });
21 |
22 | /*
23 | @route /ping
24 | @method GET
25 | @desc Checks liveliness of sync server
26 | @access public
27 | */
28 | router.get("/ping", (req, res) => {
29 | res.status(200).send(new Date().toISOString() + " - Pong!");
30 | });
31 |
32 | export default router;
33 |
--------------------------------------------------------------------------------
/sync/util/helper.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Get the IP number of requesting client
3 | * @param {object} req HTTP request object
4 | */
5 | function getIP(req) {
6 | try {
7 | var ip;
8 | if (req.headers["x-forwarded-for"]) {
9 | ip = req.headers["x-forwarded-for"].split(",")[0];
10 | } else if (req.connection && req.connection.remoteAddress) {
11 | ip = req.connection.remoteAddress;
12 | } else {
13 | ip = req.ip;
14 | }
15 |
16 | return ip;
17 | } catch {
18 | return req.ip ?? null;
19 | }
20 | }
21 |
22 | export default {
23 | getIP,
24 | };
25 |
--------------------------------------------------------------------------------
/webhook/Dockerfile:
--------------------------------------------------------------------------------
1 | # build stage
2 | FROM golang:1.21 AS build
3 |
4 | WORKDIR /workspace
5 |
6 | COPY . .
7 |
8 | RUN go mod download
9 |
10 | RUN CGO_ENABLED=0 go build -o webhook -ldflags '-w -extldflags "-static"' .
11 |
12 | # final stage
13 | FROM alpine:3.18
14 |
15 | RUN apk add --no-cache ca-certificates
16 |
17 | COPY --from=build /workspace/webhook /usr/local/bin/webhook
18 |
19 | ENTRYPOINT ["webhook"]
20 |
--------------------------------------------------------------------------------
/webhook/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | FROM golang:1.21-alpine3.18 AS build_deps
2 |
3 | RUN apk add --no-cache git
4 |
5 | WORKDIR /workspace
6 |
7 | COPY go.mod .
8 | COPY go.sum .
9 |
10 | RUN go mod download
11 |
12 | FROM build_deps AS build
13 |
14 | COPY . .
15 |
16 | RUN CGO_ENABLED=0 go build -o webhook -ldflags '-w -extldflags "-static"' .
17 |
18 | FROM alpine:3.18
19 |
20 | RUN apk add --no-cache ca-certificates
21 |
22 | COPY --from=build /workspace/webhook /usr/local/bin/webhook
23 |
24 | ENTRYPOINT ["webhook"]
25 |
--------------------------------------------------------------------------------
/webhook/agnost.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "io"
7 | "log"
8 | "net/http"
9 | "net/url"
10 | )
11 |
12 | func (c *agnostDNSProviderSolver) makeAgnostRequest(method, baseURL string, body []byte) ([]byte, error) {
13 | // Parse the base URL
14 | u, err := url.Parse(baseURL)
15 | if err != nil {
16 | log.Printf("Error parsing base URL: %v", err)
17 | return nil, err
18 | }
19 |
20 | req, err := http.NewRequest(method, u.String(), bytes.NewReader(body))
21 | if err != nil {
22 | log.Printf("Error creating HTTP request: %v", err)
23 | return nil, err
24 | }
25 |
26 | req.Header.Set("Content-Type", "application/json")
27 |
28 | client := &http.Client{}
29 | resp, err := client.Do(req)
30 | if err != nil {
31 | log.Printf("Error sending HTTP request: %v", err)
32 | return nil, err
33 | }
34 | defer resp.Body.Close()
35 |
36 | respBody, err := io.ReadAll(resp.Body)
37 | if err != nil {
38 | log.Printf("Error reading response body: %v", err)
39 | return nil, err
40 | }
41 |
42 | if resp.StatusCode >= 400 {
43 | log.Printf("Error from Agnost API: %s", respBody)
44 | return nil, fmt.Errorf("error from Agnost API: %s", respBody)
45 | }
46 |
47 | return respBody, nil
48 | }
49 |
50 |
51 |
--------------------------------------------------------------------------------
/webhook/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webhook",
3 | "version": "v1.0.0",
4 | "description": "ACME issuer webhook solver"
5 | }
--------------------------------------------------------------------------------